diff options
author | Mark Haines <mark.haines@matrix.org> | 2014-11-17 12:55:24 +0000 |
---|---|---|
committer | Mark Haines <mark.haines@matrix.org> | 2014-11-17 12:55:24 +0000 |
commit | 5d273a0c76e12e3125dbf06a8f0caa03de01d4a9 (patch) | |
tree | c968f7c7586778b3e7b3053b9c07b4336ba727e8 | |
parent | SYWEB-152: Remove room join logic from RoomController and put it in eventHand... (diff) | |
download | synapse-5d273a0c76e12e3125dbf06a8f0caa03de01d4a9.tar.xz |
Remove syweb directory. pull in syweb as a dependency from github
92 files changed, 4 insertions, 50658 deletions
diff --git a/setup.py b/setup.py index 7f46ce990f..68b2655d6b 100755 --- a/setup.py +++ b/setup.py @@ -26,12 +26,13 @@ def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() setup( - name="SynapseHomeServer", - version="0.0.1", + name="synapse", + version=read("VERSION"), packages=find_packages(exclude=["tests", "tests.*"]), description="Reference Synapse Home Server", install_requires=[ "syutil==0.0.2", + "syweb==0.0.1", "Twisted>=14.0.0", "service_identity>=1.0.0", "pyopenssl>=0.14", @@ -44,6 +45,7 @@ setup( dependency_links=[ "https://github.com/matrix-org/syutil/tarball/v0.0.2#egg=syutil-0.0.2", "https://github.com/pyca/pynacl/tarball/52dbe2dc33f1#egg=pynacl-0.3.0", + "https://github.com/matrix-org/matrix-angular-sdk/tarball/master/#egg=syweb-0.0.1", ], setup_requires=[ "setuptools_trial", diff --git a/syweb/__init__.py b/syweb/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 --- a/syweb/__init__.py +++ /dev/null diff --git a/syweb/webclient/CAPTCHA_SETUP b/syweb/webclient/CAPTCHA_SETUP deleted file mode 100644 index ebc8a5f3b0..0000000000 --- a/syweb/webclient/CAPTCHA_SETUP +++ /dev/null @@ -1,46 +0,0 @@ -Captcha can be enabled for this web client / home server. This file explains how to do that. -The captcha mechanism used is Google's ReCaptcha. This requires API keys from Google. - -Getting keys ------------- -Requires a public/private key pair from: - -https://developers.google.com/recaptcha/ - - -Setting Private ReCaptcha Key ------------------------------ -The private key is a config option on the home server config. If it is not -visible, you can generate it via --generate-config. Set the following value: - - recaptcha_private_key: YOUR_PRIVATE_KEY - -In addition, you MUST enable captchas via: - - enable_registration_captcha: true - -Setting Public ReCaptcha Key ----------------------------- -The web client will look for the global variable webClientConfig for config -options. You should put your ReCaptcha public key there like so: - -webClientConfig = { - useCaptcha: true, - recaptcha_public_key: "YOUR_PUBLIC_KEY" -} - -This should be put in webclient/config.js which is already .gitignored, rather -than in the web client source files. You MUST set useCaptcha to true else a -ReCaptcha widget will not be generated. - -Configuring IP used for auth ----------------------------- -The ReCaptcha API requires that the IP address of the user who solved the -captcha is sent. If the client is connecting through a proxy or load balancer, -it may be required to use the X-Forwarded-For (XFF) header instead of the origin -IP address. This can be configured as an option on the home server like so: - - captcha_ip_origin_is_x_forwarded: true - - - diff --git a/syweb/webclient/README b/syweb/webclient/README deleted file mode 100644 index ef79b25708..0000000000 --- a/syweb/webclient/README +++ /dev/null @@ -1,13 +0,0 @@ -Basic Usage ------------ - -The web client should automatically run when running the home server. -Alternatively, you can run it stand-alone: - - $ python -m SimpleHTTPServer - -Then, open this URL in a WEB browser:: - - http://127.0.0.1:8000/ - - diff --git a/syweb/webclient/app-controller.js b/syweb/webclient/app-controller.js deleted file mode 100644 index 46890c2287..0000000000 --- a/syweb/webclient/app-controller.js +++ /dev/null @@ -1,216 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/* - * Main controller - */ - -'use strict'; - -angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'eventStreamService']) -.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', '$timeout', 'matrixService', 'mPresence', 'eventStreamService', 'eventHandlerService', 'matrixPhoneService', 'modelService', - function($scope, $location, $rootScope, $timeout, matrixService, mPresence, eventStreamService, eventHandlerService, matrixPhoneService, modelService) { - - // Check current URL to avoid to display the logout button on the login page - $scope.location = $location.path(); - - // Update the location state when the ng location changed - $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { - $scope.location = $location.path(); - }); - - if (matrixService.isUserLoggedIn()) { - eventStreamService.resume(); - mPresence.start(); - } - - $scope.user_id; - var config = matrixService.config(); - if (config) { - $scope.user_id = matrixService.config().user_id; - } - - /** - * Open a given page. - * @param {String} url url of the page - */ - $rootScope.goToPage = function(url) { - $location.url(url); - }; - - // Open the given user profile page - $scope.goToUserPage = function(user_id) { - if (user_id === $scope.user_id) { - $location.url("/settings"); - } - else { - $location.url("/user/" + user_id); - } - }; - - $scope.leave = function(room_id) { - matrixService.leave(room_id).then( - function(response) { - console.log("Left room " + room_id); - }, - function(error) { - console.log("Failed to leave room " + room_id + ": " + error.data.error); - }); - }; - - // Logs the user out - $scope.logout = function() { - - // kill the event stream - eventStreamService.stop(); - - // Do not update presence anymore - mPresence.stop(); - - // Clean permanent data - matrixService.setConfig({}); - matrixService.saveConfig(); - - // Reset cached data - modelService.clearRooms(); - eventHandlerService.reset(); - - // And go to the login page - $location.url("login"); - }; - - // Listen to the event indicating that the access token is no longer valid. - // In this case, the user needs to log in again. - $scope.$on("M_UNKNOWN_TOKEN", function() { - console.log("Invalid access token -> log user out"); - $scope.logout(); - }); - - $rootScope.updateHeader = function() { - $scope.user_id = matrixService.config().user_id; - }; - - $rootScope.$watch('currentCall', function(newVal, oldVal) { - if (!$rootScope.currentCall) { - // This causes the still frame to be flushed out of the video elements, - // avoiding a flash of the last frame of the previous call when starting the next - if (angular.element('#localVideo')[0].load) angular.element('#localVideo')[0].load(); - if (angular.element('#remoteVideo')[0].load) angular.element('#remoteVideo')[0].load(); - return; - } - - var roomMembers = angular.copy(modelService.getRoom($rootScope.currentCall.room_id).current_room_state.members); - delete roomMembers[matrixService.config().user_id]; - - $rootScope.currentCall.user_id = Object.keys(roomMembers)[0]; - - // set it to the user ID until we fetch the display name - $rootScope.currentCall.userProfile = { displayname: $rootScope.currentCall.user_id }; - - matrixService.getProfile($rootScope.currentCall.user_id).then( - function(response) { - if (response.data.displayname) $rootScope.currentCall.userProfile.displayname = response.data.displayname; - if (response.data.avatar_url) $rootScope.currentCall.userProfile.avatar_url = response.data.avatar_url; - }, - function(error) { - $scope.feedback = "Can't load user profile"; - } - ); - }); - $rootScope.$watch('currentCall.state', function(newVal, oldVal) { - if (newVal == 'ringing') { - angular.element('#ringbackAudio')[0].pause(); - angular.element('#ringAudio')[0].load(); - angular.element('#ringAudio')[0].play(); - } else if (newVal == 'invite_sent') { - angular.element('#ringAudio')[0].pause(); - angular.element('#ringbackAudio')[0].load(); - angular.element('#ringbackAudio')[0].play(); - } else if (newVal == 'ended' && oldVal == 'connected') { - angular.element('#ringAudio')[0].pause(); - angular.element('#ringbackAudio')[0].pause(); - angular.element('#callendAudio')[0].play(); - $scope.videoMode = undefined; - } else if (newVal == 'ended' && oldVal == 'invite_sent' && $rootScope.currentCall.hangupParty == 'remote') { - angular.element('#ringAudio')[0].pause(); - angular.element('#ringbackAudio')[0].pause(); - angular.element('#busyAudio')[0].play(); - } else if (newVal == 'ended' && oldVal == 'invite_sent' && $rootScope.currentCall.hangupParty == 'local' && $rootScope.currentCall.hangupReason == 'invite_timeout') { - angular.element('#ringAudio')[0].pause(); - angular.element('#ringbackAudio')[0].pause(); - angular.element('#busyAudio')[0].play(); - } else if (oldVal == 'invite_sent') { - angular.element('#ringbackAudio')[0].pause(); - } else if (oldVal == 'ringing') { - angular.element('#ringAudio')[0].pause(); - } else if (newVal == 'connected') { - $timeout(function() { - if ($scope.currentCall.type == 'video') $scope.videoMode = 'large'; - }, 500); - } - - if ($rootScope.currentCall && $rootScope.currentCall.type == 'video' && $rootScope.currentCall.state != 'connected') { - $scope.videoMode = 'mini'; - } - }); - $rootScope.$watch('currentCall.type', function(newVal, oldVal) { - // need to listen for this too as the type of the call won't be know when it's created - if ($rootScope.currentCall && $rootScope.currentCall.type == 'video' && $rootScope.currentCall.state != 'connected') { - $scope.videoMode = 'mini'; - } - }); - - $rootScope.$on(matrixPhoneService.INCOMING_CALL_EVENT, function(ngEvent, call) { - console.log("incoming call"); - if ($rootScope.currentCall && $rootScope.currentCall.state != 'ended') { - console.log("rejecting call because we're already in a call"); - call.hangup(); - return; - } - call.onError = $scope.onCallError; - call.onHangup = $scope.onCallHangup; - call.localVideoSelector = '#localVideo'; - call.remoteVideoSelector = '#remoteVideo'; - $rootScope.currentCall = call; - }); - - $rootScope.$on(matrixPhoneService.REPLACED_CALL_EVENT, function(ngEvent, oldCall, newCall) { - console.log("call ID "+oldCall.call_id+" has been replaced by call ID "+newCall.call_id+"!"); - newCall.onError = $scope.onCallError; - newCall.onHangup = $scope.onCallHangup; - $rootScope.currentCall = newCall; - }); - - $scope.answerCall = function() { - $rootScope.currentCall.answer(); - }; - - $scope.hangupCall = function() { - $rootScope.currentCall.hangup(); - }; - - $rootScope.onCallError = function(errStr) { - $scope.feedback = errStr; - }; - - $rootScope.onCallHangup = function(call) { - if (call == $rootScope.currentCall) { - $timeout(function(){ - if (call == $rootScope.currentCall) $rootScope.currentCall = undefined; - }, 4070); - } - }; -}]); diff --git a/syweb/webclient/app-directive.js b/syweb/webclient/app-directive.js deleted file mode 100644 index c1ba0af3a9..0000000000 --- a/syweb/webclient/app-directive.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -'use strict'; - -angular.module('matrixWebClient') -.directive('ngEnter', function () { - return function (scope, element, attrs) { - element.bind("keydown keypress", function (event) { - if(event.which === 13) { - scope.$apply(function () { - scope.$eval(attrs.ngEnter); - }); - event.preventDefault(); - } - }); - }; -}) -.directive('ngFocus', ['$timeout', function($timeout) { - return { - link: function(scope, element, attr) { - // XXX: slightly evil hack to disable autofocus on iOS, as in general - // it causes more problems than it fixes, by bouncing the page - // around - if (!/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) { - $timeout(function() { element[0].focus(); }, 0); - } - } - }; -}]) -.directive('asjson', function() { - return { - restrict: 'A', - require: 'ngModel', - link: function (scope, element, attrs, ngModelCtrl) { - function isValidJson(model) { - var flag = true; - try { - angular.fromJson(model); - } catch (err) { - flag = false; - } - return flag; - }; - - function string2JSON(text) { - try { - var j = angular.fromJson(text); - ngModelCtrl.$setValidity('json', true); - return j; - } catch (err) { - //returning undefined results in a parser error as of angular-1.3-rc.0, and will not go through $validators - //return undefined - ngModelCtrl.$setValidity('json', false); - return text; - } - }; - - function JSON2String(object) { - return angular.toJson(object, true); - }; - - //$validators is an object, where key is the error - //ngModelCtrl.$validators.json = isValidJson; - - //array pipelines - ngModelCtrl.$parsers.push(string2JSON); - ngModelCtrl.$formatters.push(JSON2String); - } - } -}); diff --git a/syweb/webclient/app-filter.js b/syweb/webclient/app-filter.js deleted file mode 100644 index 003e6d3f11..0000000000 --- a/syweb/webclient/app-filter.js +++ /dev/null @@ -1,138 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -'use strict'; - -angular.module('matrixWebClient') -.filter('duration', function() { - return function(time) { - if (!time) return; - var t = parseInt(time / 1000); - var s = t % 60; - var m = parseInt(t / 60) % 60; - var h = parseInt(t / (60 * 60)) % 24; - var d = parseInt(t / (60 * 60 * 24)); - if (t < 60) { - if (t < 0) { - return "0s"; - } - return s + "s"; - } - if (t < 60 * 60) { - return m + "m"; // + s + "s"; - } - if (t < 24 * 60 * 60) { - return h + "h"; // + m + "m"; - } - return d + "d "; // + h + "h"; - }; -}) -.filter('orderMembersList', function($sce) { - return function(members) { - var filtered = []; - - var displayNames = {}; - angular.forEach(members, function(value, key) { - value["id"] = key; - filtered.push( value ); - }); - - filtered.sort(function (a, b) { - // Sort members on their last_active absolute time - a = a.user; - b = b.user; - - var aLastActiveTS = 0, bLastActiveTS = 0; - if (a && a.event && a.event.content && a.event.content.last_active_ago !== undefined) { - aLastActiveTS = a.last_updated - a.event.content.last_active_ago; - } - if (b && b.event && b.event.content && b.event.content.last_active_ago !== undefined) { - bLastActiveTS = b.last_updated - b.event.content.last_active_ago; - } - if (aLastActiveTS || bLastActiveTS) { - return bLastActiveTS - aLastActiveTS; - } - else { - // If they do not have last_active_ago, sort them according to their presence state - // Online users go first amongs members who do not have last_active_ago - var presenceLevels = { - offline: 1, - unavailable: 2, - online: 4, - free_for_chat: 3 - }; - var aPresence = (a && a.event && a.event.content.presence in presenceLevels) ? presenceLevels[a.event.content.presence] : 0; - var bPresence = (b && b.event && b.event.content.presence in presenceLevels) ? presenceLevels[b.event.content.presence] : 0; - return bPresence - aPresence; - } - }); - return filtered; - }; -}) -.filter('unsafe', ['$sce', function($sce) { - return function(text) { - return $sce.trustAsHtml(text); - }; -}]) -// Exactly the same as ngSanitize's linky but instead of pushing sanitized -// text in the addText function, we just push the raw text. -.filter('unsanitizedLinky', ['$sanitize', function($sanitize) { - var LINKY_URL_REGEXP = - /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/, - MAILTO_REGEXP = /^mailto:/; - - return function(text, target) { - if (!text) return text; - var match; - var raw = text; - var html = []; - var url; - var i; - while ((match = raw.match(LINKY_URL_REGEXP))) { - // We can not end in these as they are sometimes found at the end of the sentence - url = match[0]; - // if we did not match ftp/http/mailto then assume mailto - if (match[2] == match[3]) url = 'mailto:' + url; - i = match.index; - addText(raw.substr(0, i)); - addLink(url, match[0].replace(MAILTO_REGEXP, '')); - raw = raw.substring(i + match[0].length); - } - addText(raw); - return $sanitize(html.join('')); - - function addText(text) { - if (!text) { - return; - } - html.push(text); - } - - function addLink(url, text) { - html.push('<a '); - if (angular.isDefined(target)) { - html.push('target="'); - html.push(target); - html.push('" '); - } - html.push('href="'); - html.push(url); - html.push('">'); - addText(text); - html.push('</a>'); - } - }; -}]); diff --git a/syweb/webclient/app.css b/syweb/webclient/app.css deleted file mode 100755 index 1264ac787c..0000000000 --- a/syweb/webclient/app.css +++ /dev/null @@ -1,893 +0,0 @@ -/** Common layout **/ - -html { - height: 100%; -} - -body { - height: 100%; - font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif; - font-size: 12pt; - margin: 0px; -} - -h1 { - font-size: 20pt; -} - -a:link { color: #666; } -a:visited { color: #666; } -a:hover { color: #000; } -a:active { color: #000; } - -textarea, input { - font-family: inherit; - font-size: inherit; -} - -.page { - min-height: 100%; - margin-bottom: -32px; /* to make room for the footer */ -} - -#wrapper { - margin: auto; - max-width: 1280px; - padding-top: 40px; - padding-bottom: 40px; - padding-left: 20px; - padding-right: 20px; -} - -#unsupportedBrowser { - padding-top: 240px; - text-align: center; -} - -#header -{ - position: absolute; - z-index: 2; - top: 0px; - width: 100%; - background-color: #333; - height: 32px; -} - -#callBar { - float: left; - height: 32px; - margin: auto; - text-align: right; - line-height: 16px; -} - -.callIcon { - margin-left: 4px; - margin-right: 4px; - margin-top: 8px; - transition: transform linear 0.5s; - transition: -webkit-transform linear 0.5s; -} - -.callIcon.ended { - transform: rotateZ(45deg); - -webkit-transform: rotateZ(45deg); - filter: hue-rotate(-90deg); - -webkit-filter: hue-rotate(-90deg); -} - -#callPeerImage { - width: 32px; - height: 32px; - border: none; - float: left; -} - -#callPeerNameAndState { - float: left; - margin-left: 4px; -} - -#callState { - font-size: 60%; -} - -#callPeerName { - font-size: 80%; -} - -#videoBackground { - position: absolute; - height: 100%; - width: 100%; - top: 0px; - left: 0px; - z-index: 1; - background-color: rgba(0,0,0,0.0); - pointer-events: none; - transition: background-color linear 500ms; -} - -#videoBackground.large { - background-color: rgba(0,0,0,0.85); - pointer-events: auto; -} - -#videoContainer { - position: relative; - top: 32px; - max-width: 1280px; - margin: auto; -} - -#videoContainerPadding { - width: 1280px; -} - -#localVideo { - position: absolute; - width: 128px; - height: 72px; - z-index: 1; - transition: left linear 500ms, top linear 500ms, width linear 500ms, height linear 500ms; -} - -.mini #localVideo { - top: 0px; - left: 130px; -} - -.large #localVideo { - top: 70px; - left: 20px; -} - -.ended #localVideo { - -webkit-filter: grayscale(1); - filter: grayscale(1); -} - -#remoteVideo { - position: relative; - height: auto; - transition: left linear 500ms, top linear 500ms, width linear 500ms, height linear 500ms; -} - -.mini #remoteVideo { - left: 260px; - top: 0px; - width: 128px; -} - -.large #remoteVideo { - left: 0px; - top: 50px; - width: 100%; -} - -.ended #remoteVideo { - -webkit-filter: grayscale(1); - filter: grayscale(1); -} - -#headerContent { - color: #ccc; - max-width: 1280px; - margin: auto; - text-align: right; - height: 32px; - line-height: 32px; - position: relative; -} - -#headerContent a:link, -#headerContent a:visited, -#headerContent a:hover, -#headerContent a:active { - color: #fff; -} - -#footer -{ - width: 100%; - border-top: #666 1px solid; - background-color: #aaa; - height: 32px; -} - -#footerContent -{ - font-size: 8pt; - color: #fff; - max-width: 1280px; - margin: auto; - text-align: center; - height: 32px; - line-height: 32px; -} - -#genericHeading -{ - margin-top: 13px; -} - -#feedback { - color: #800; -} - -.mouse-pointer { - cursor: pointer; -} - -.invited { - opacity: 0.2; -} - -/*** Login Pages ***/ - -.loginWrapper { - text-align: center; -} - -#recaptcha_area { - margin: auto -} - -#loginForm { - text-align: left; - padding: 1em; - margin-bottom: 40px; - display: inline-block; - - -webkit-border-radius: 10px; - -moz-border-radius: 10px; - border-radius: 10px; - - -webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15); - -moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15); - box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15); - - background-color: #f8f8f8; - border: 1px #ccc solid; -} - -#loginForm input[type='radio'] { - margin-right: 1em; -} - -#serverConfig { - text-align: center; -} - -#serverConfig, -#serverConfig input, -#serverConfig button -{ - font-size: 10pt ! important; -} - -.smallPrint { - color: #888; - font-size: 9pt ! important; - font-style: italic ! important; -} - -#serverConfig label { - display: inline-block; - text-align: right; - margin-right: 0.5em; - width: 7em; -} - -#loginForm, -#loginForm input, -#loginForm button, -#loginForm select { - font-size: 18px; -} - -/*** Room page ***/ - -#roomPage { - position: absolute; - top: 120px; - bottom: 120px; - left: 20px; - right: 20px; -} - -#roomWrapper { - margin: auto; - max-width: 1280px; - height: 100%; -} - -#roomHeader { - margin: auto; - padding-left: 20px; - padding-right: 20px; - padding-top: 53px; - max-width: 1280px; -} - -#controlPanel { - position: absolute; - bottom: 0px; - width: 100%; - height: 70px; - background-color: #f8f8f8; - border-top: #aaa 1px solid; -} - -#controls { - max-width: 1280px; - padding: 12px; - padding-right: 42px; - margin: auto; - position: relative; -} - -#buttonsCell { - width: 150px; -} - -#inputBarTable { - width: 100%; -} - -#inputBarTable tr td { - padding: 1px 4px; -} - -#mainInput { - width: 100%; - padding: 5px; - resize: vertical; -} - -#attachButton { - position: absolute; - cursor: pointer; - margin-top: 3px; - right: 0px; - background: url('img/attach.png'); - width: 25px; - height: 25px; - border: 0px; -} - -.blink { - background-color: #faa; -} - -.roomHighlight { - font-weight: bold; -} - -.publicTable { - max-width: 480px; - width: 100%; - border-collapse: collapse; -} -.publicTable tr { - width: 100%; -} -.publicTable td { - vertical-align: text-top; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.publicRoomEntry { - max-width: 430px; -} - -.publicRoomJoinedUsers { - width: 5em; - text-align: right; - font-size: 12px; - color: #888; -} - -.publicRoomTopic { - color: #888; - font-size: 12px; - overflow: hidden; - padding-bottom: 5px; - border-bottom: 1px #ddd solid; -} - -#roomName { - font-size: 16px; - text-align: right; -} - -#roomTopic { - font-size: 13px; - text-align: right; -} - -.roomNameInput, .roomTopicInput { - width: 100%; -} - -.roomNameSection, .roomTopicSection { - text-align: right; - float: right; - width: 100%; -} - -.roomNameSetNew, .roomTopicSetNew { - float: right; -} - -.roomHeaderInfo { - text-align: right; - float: right; - margin-top: 0px; - margin-right: 30px; -} - -/*** Room Info Dialog ***/ - -.room-info { - border-collapse: collapse; - width: 100%; -} - -.room-info-event { - border-bottom: 1pt solid black; -} - -.room-info-event-meta { - padding-top: 1em; - padding-bottom: 1em; -} - -.room-info-event-content { - padding-top: 1em; - padding-bottom: 1em; -} - -.monospace { - font-family: monospace; -} - -.redact-button { - float: left -} - -.room-info-textarea-content { - height: auto; - width: 100%; - resize: vertical; -} - -/*** Control Buttons ***/ -#controlButtons { - float: right; - margin-right: -4px; - padding-bottom: 6px; -} - -.controlButton { - cursor: pointer; - border: 0px; - width: 30px; - height: 30px; - margin-left: 3px; - margin-right: 3px; -} - -/*** Participant list ***/ - -#usersTableWrapper { - float: right; - clear: right; - width: 101px; - height: 100%; - overflow-y: auto; -} - -/* -#usersTable { - width: 100%; - border-collapse: collapse; -} - -#usersTable td { - padding: 0px; -} - -.userAvatar { - width: 80px; - height: 100px; - position: relative; - background-color: #000; -} -*/ - -.userAvatar { -} - -.userAvatarFrame { - border-radius: 46px; - width: 80px; - margin: auto; - position: relative; - border: 3px solid #aaa; - background-color: #aaa; -} - -.userAvatarImage { - border-radius: 40px; - text-align: center; - object-fit: cover; - display: block; -} - -/* -.userAvatar .userAvatarGradient { - position: absolute; - bottom: 20px; - width: 100%; -} -*/ - -.userName { - margin-top: 3px; - margin-bottom: 6px; - text-align: center; - font-size: 12px; - word-wrap: break-word; -} - -.userPowerLevel { - position: absolute; - bottom: -1px; - height: 1px; - background-color: #f00; -} - -.userPowerLevelBar { - display: inline; - position: absolute; - width: 2px; - height: 10px; -/* border: 1px solid #000; -*/ background-color: #aaa; -} - -.userPowerLevelMeter { - position: relative; - bottom: 0px; - background-color: #f00; -} - -/* -.userPresence { - text-align: center; - font-size: 12px; - color: #fff; - background-color: #aaa; - border-bottom: 1px #ddd solid; -} -*/ - -.online { - border-color: #38AF00; - background-color: #38AF00; -} - -.unavailable { - border-color: #FFCC00; - background-color: #FFCC00; -} - -/*** Message table ***/ - -#messageTableWrapper { - height: 100%; - margin-right: 140px; - overflow-y: auto; - width: auto; -} - -#messageTable { - margin: auto; - max-width: 1280px; - width: 100%; - border-collapse: collapse; - table-layout: fixed; -} - -#messageTable td { - padding: 0px; -/* border: 1px solid #888; */ -} - -.leftBlock { - width: 7em; - word-wrap: break-word; - vertical-align: top; - background-color: #fff; - color: #aaa; - font-weight: medium; - font-size: 12px; - text-align: right; -/* - border-top: 1px #ddd solid; -*/ -} - -.rightBlock { - width: 32px; - color: #888; - line-height: 0; - vertical-align: top; -} - -.sender, .timestamp { -/* padding-top: 3px; -*/} - -.timestamp { - font-size: 10px; - color: #ccc; - height: 13px; - margin-top: 4px; - transition-property: opacity; - transition-duration: 0.3s; -} - -.sender { - font-size: 12px; -/* - margin-top: 5px; - margin-bottom: -9px; -*/ -} - -.avatar { - width: 48px; - text-align: right; - vertical-align: top; - line-height: 0; -} - -.avatarImage { - position: relative; - top: 5px; - object-fit: cover; - border-radius: 32px; - margin-top: 4px; -} - -.emote { - background-color: transparent ! important; - border: 0px ! important; -} - -.membership { - background-color: transparent ! important; - border: 0px ! important; -} - -.image { - border: 1px solid #888; - display: block; - max-width:320px; - max-height:320px; - width: auto; - height: auto; -} - -.text { - vertical-align: top; -} - -.bubble { -/* - background-color: #eee; - border: 1px solid #d8d8d8; - margin-bottom: -1px; - padding-top: 7px; - padding-bottom: 5px; - -webkit-text-size-adjust:100% - vertical-align: middle; -*/ - display: inline-block; - max-width: 80%; - padding-left: 1em; - padding-right: 1em; - padding-top: 2px; - padding-bottom: 2px; - font-size: 14px; - word-wrap: break-word; -} - -.bubble img { - max-width: 100%; - max-height: auto; -} - -.differentUser .msg { - padding-top: 14px ! important; -} - -.mine { - text-align: right; -} - -/* -.text.emote .bubble, -.text.membership .bubble, -.mine .text.emote .bubble, -.mine .text.membership .bubble -{ - background-color: transparent ! important; - border: 0px ! important; -} -*/ - -.mine .text .bubble { -/* - background-color: #f8f8ff ! important; -*/ - text-align: left ! important; -} - -.bubble .message { - /* Wrap words and break lines on CR+LF */ - white-space: pre-wrap; -} -.bubble .messagePending { - opacity: 0.3 -} -.messageUnSent { - color: #F00; -} - -.messageBing { - color: #00F; -} - -#room-fullscreen-image { - position: absolute; - top: 0px; - height: 0px; - width: 100%; - height: 100%; -} - -#room-fullscreen-image img { - max-width: 90%; - max-height: 90%; - bottom: 0; - left: 0; - margin: auto; - overflow: auto; - position: fixed; - right: 0; - top: 0; - - -webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.75); - -moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.75); - box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.75); -} - -/*** Recents ***/ - -.recentsTable { - max-width: 480px; - width: 100%; - border-collapse: collapse; - table-layout: fixed; -} - -.recentsTable tr { - width: 100%; -} -.recentsTable td { - vertical-align: text-top; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - padding-left: 0.5em; - padding-right: 0.5em; -} - -.recentsRoom { - cursor: pointer; -} - -.recentsRoom:hover { - background-color: #f8f8ff; -} - -.recentsRoomSelected { - background-color: #eee; -} - -.recentsRoomUnread { - background-color: #fee; -} - -.recentsRoomBing { - background-color: #eef; -} - -.recentsRoomName { - font-size: 16px; - padding-top: 7px; - width: auto; -} - -.recentsPublicRoom { - font-weight: bold; -} - -.recentsRoomSummaryUsersCount, .recentsRoomSummaryTS { - color: #888; - font-size: 12px; - width: 7em; - text-align: right; -} - -.recentsRoomSummary { - color: #888; - font-size: 12px; - padding-bottom: 5px; -} - -/* Do not show users count in the recents fragment displayed on the room page */ -#roomPage .recentsRoomSummaryUsersCount { - width: 0em; -} - -/*** Recents in the room page ***/ - -#roomRecentsTableWrapper { - float: left; - max-width: 320px; - padding-right: 10px; - margin-right: 10px; - height: 100%; -/* border-right: 1px solid #ddd; */ - overflow-y: auto; -} - -/*** Profile ***/ - -.profile-avatar { - width: 160px; - height: 160px; - display: table-cell; - vertical-align: middle; - text-align: center; -} - -.profile-avatar img { - width: 100%; - height: 100%; - object-fit: cover; -} - -/*** User profile page ***/ - -#user-displayname { - font-size: 24px; -} - -#user-displayname-input { - width: 160px; - max-width: 155px; -} - -#user-save-button { - width: 160px; - font-size: 14px; -} - diff --git a/syweb/webclient/app.js b/syweb/webclient/app.js deleted file mode 100644 index 9e5b85820d..0000000000 --- a/syweb/webclient/app.js +++ /dev/null @@ -1,115 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -var matrixWebClient = angular.module('matrixWebClient', [ - 'ngRoute', - 'MatrixWebClientController', - 'LoginController', - 'RegisterController', - 'RoomController', - 'HomeController', - 'RecentsController', - 'SettingsController', - 'UserController', - 'matrixService', - 'matrixPhoneService', - 'MatrixCall', - 'eventStreamService', - 'eventHandlerService', - 'notificationService', - 'recentsService', - 'modelService', - 'commandsService', - 'infinite-scroll', - 'ui.bootstrap', - 'monospaced.elastic' -]); - -matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider', - function($routeProvider, $provide, $httpProvider) { - $routeProvider. - when('/login', { - templateUrl: 'login/login.html' - }). - when('/register', { - templateUrl: 'login/register.html' - }). - when('/room/:room_id_or_alias', { - templateUrl: 'room/room.html' - }). - when('/room/', { // room URL with room alias in it (ex: http://127.0.0.1:8000/#/room/#public:localhost:8080) will come here. - // The reason is that 2nd hash key breaks routeProvider parameters cutting so that the URL will not match with - // the previous '/room/:room_id_or_alias' URL rule - templateUrl: 'room/room.html' - }). - when('/', { - templateUrl: 'home/home.html' - }). - when('/settings', { - templateUrl: 'settings/settings.html' - }). - when('/user/:user_matrix_id', { - templateUrl: 'user/user.html' - }). - otherwise({ - redirectTo: '/' - }); - - $provide.factory('AccessTokenInterceptor', ['$q', '$rootScope', - function ($q, $rootScope) { - return { - responseError: function(rejection) { - if (rejection.status === 403 && "data" in rejection && - "errcode" in rejection.data && - rejection.data.errcode === "M_UNKNOWN_TOKEN") { - console.log("Got a 403 with an unknown token. Logging out.") - $rootScope.$broadcast("M_UNKNOWN_TOKEN"); - } - return $q.reject(rejection); - } - }; - }]); - $httpProvider.interceptors.push('AccessTokenInterceptor'); - }]); - -matrixWebClient.run(['$location', '$rootScope', 'matrixService', function($location, $rootScope, matrixService) { - - // Check browser support - // Support IE from 9.0. AngularJS needs some tricks to run on IE8 and below - var version = parseFloat($.browser.version); - if ($.browser.msie && version < 9.0) { - $rootScope.unsupportedBrowser = { - browser: navigator.userAgent, - reason: "Internet Explorer is supported from version 9" - }; - } - // The app requires localStorage - if(typeof(Storage) === "undefined") { - $rootScope.unsupportedBrowser = { - browser: navigator.userAgent, - reason: "It does not support HTML local storage" - }; - } - - // If user auth details are not in cache, go to the login page - if (!matrixService.isUserLoggedIn() && - $location.path() !== "/login" && - $location.path() !== "/register") - { - $location.path("login"); - } - -}]); diff --git a/syweb/webclient/bootstrap.css b/syweb/webclient/bootstrap.css deleted file mode 100644 index 7ebcb2a007..0000000000 --- a/syweb/webclient/bootstrap.css +++ /dev/null @@ -1,5081 +0,0 @@ -/*! - * Bootstrap v3.2.0 (http://getbootstrap.com) - * Copyright 2011-2014 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ - -/*! normalize.css v3.0.1 | MIT License | git.io/normalize */ -.img-responsive, -.thumbnail > img, -.thumbnail a > img, -.carousel-inner > .item > img, -.carousel-inner > .item > a > img { - display: block; - width: 100% \9; - max-width: 100%; - height: auto; -} -.img-rounded { - border-radius: 6px; -} -.img-thumbnail { - display: inline-block; - width: 100% \9; - max-width: 100%; - height: auto; - padding: 4px; - line-height: 1.42857143; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 4px; - -webkit-transition: all .2s ease-in-out; - -o-transition: all .2s ease-in-out; - transition: all .2s ease-in-out; -} -.img-circle { - border-radius: 50%; -} -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - border: 0; -} -.sr-only-focusable:active, -.sr-only-focusable:focus { - position: static; - width: auto; - height: auto; - margin: 0; - overflow: visible; - clip: auto; -} -.lead { - margin-bottom: 20px; - font-size: 16px; - font-weight: 300; - line-height: 1.4; -} -.bg-primary { - color: #fff; - background-color: #428bca; -} -a.bg-primary:hover { - background-color: #3071a9; -} -.bg-success { - background-color: #dff0d8; -} -a.bg-success:hover { - background-color: #c1e2b3; -} -.bg-info { - background-color: #d9edf7; -} -a.bg-info:hover { - background-color: #afd9ee; -} -.bg-warning { - background-color: #fcf8e3; -} -a.bg-warning:hover { - background-color: #f7ecb5; -} -.bg-danger { - background-color: #f2dede; -} -a.bg-danger:hover { - background-color: #e4b9b9; -} -.page-header { - padding-bottom: 9px; - margin: 40px 0 20px; - border-bottom: 1px solid #eee; -} -ul, -ol { - margin-top: 0; - margin-bottom: 10px; -} -ul ul, -ol ul, -ul ol, -ol ol { - margin-bottom: 0; -} -.list-unstyled { - padding-left: 0; - list-style: none; -} -.list-inline { - padding-left: 0; - margin-left: -5px; - list-style: none; -} -.list-inline > li { - display: inline-block; - padding-right: 5px; - padding-left: 5px; -} -dl { - margin-top: 0; - margin-bottom: 20px; -} -dt, -dd { - line-height: 1.42857143; -} -dt { - font-weight: bold; -} -dd { - margin-left: 0; -} -@media (min-width: 768px) { - .dl-horizontal dt { - float: left; - width: 160px; - overflow: hidden; - clear: left; - text-align: right; - text-overflow: ellipsis; - white-space: nowrap; - } - .dl-horizontal dd { - margin-left: 180px; - } -} -abbr[title], -abbr[data-original-title] { - cursor: help; - border-bottom: 1px dotted #777; -} -.initialism { - font-size: 90%; - text-transform: uppercase; -} -blockquote { - padding: 10px 20px; - margin: 0 0 20px; - font-size: 17.5px; - border-left: 5px solid #eee; -} -blockquote p:last-child, -blockquote ul:last-child, -blockquote ol:last-child { - margin-bottom: 0; -} -blockquote footer, -blockquote small, -blockquote .small { - display: block; - font-size: 80%; - line-height: 1.42857143; - color: #777; -} -blockquote footer:before, -blockquote small:before, -blockquote .small:before { - content: '\2014 \00A0'; -} -.blockquote-reverse, -blockquote.pull-right { - padding-right: 15px; - padding-left: 0; - text-align: right; - border-right: 5px solid #eee; - border-left: 0; -} -.blockquote-reverse footer:before, -blockquote.pull-right footer:before, -.blockquote-reverse small:before, -blockquote.pull-right small:before, -.blockquote-reverse .small:before, -blockquote.pull-right .small:before { - content: ''; -} -.blockquote-reverse footer:after, -blockquote.pull-right footer:after, -.blockquote-reverse small:after, -blockquote.pull-right small:after, -.blockquote-reverse .small:after, -blockquote.pull-right .small:after { - content: '\00A0 \2014'; -} -blockquote:before, -blockquote:after { - content: ""; -} -address { - margin-bottom: 20px; - font-style: normal; - line-height: 1.42857143; -} -code, -kbd, -pre, -samp { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; -} -code { - padding: 2px 4px; - font-size: 90%; - color: #c7254e; - background-color: #f9f2f4; - border-radius: 4px; -} -kbd { - padding: 2px 4px; - font-size: 90%; - color: #fff; - background-color: #333; - border-radius: 3px; - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); -} -kbd kbd { - padding: 0; - font-size: 100%; - -webkit-box-shadow: none; - box-shadow: none; -} -pre { - display: block; - padding: 9.5px; - margin: 0 0 10px; - font-size: 13px; - line-height: 1.42857143; - color: #333; - word-break: break-all; - word-wrap: break-word; - background-color: #f5f5f5; - border: 1px solid #ccc; - border-radius: 4px; -} -pre code { - padding: 0; - font-size: inherit; - color: inherit; - white-space: pre-wrap; - background-color: transparent; - border-radius: 0; -} -.pre-scrollable { - max-height: 340px; - overflow-y: scroll; -} -.container { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} -@media (min-width: 768px) { - .container { - width: 750px; - } -} -@media (min-width: 992px) { - .container { - width: 970px; - } -} -@media (min-width: 1200px) { - .container { - width: 1170px; - } -} -.container-fluid { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} -.row { - margin-right: -15px; - margin-left: -15px; -} -.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { - position: relative; - min-height: 1px; - padding-right: 15px; - padding-left: 15px; -} -.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { - float: left; -} -.col-xs-12 { - width: 100%; -} -.col-xs-11 { - width: 91.66666667%; -} -.col-xs-10 { - width: 83.33333333%; -} -.col-xs-9 { - width: 75%; -} -.col-xs-8 { - width: 66.66666667%; -} -.col-xs-7 { - width: 58.33333333%; -} -.col-xs-6 { - width: 50%; -} -.col-xs-5 { - width: 41.66666667%; -} -.col-xs-4 { - width: 33.33333333%; -} -.col-xs-3 { - width: 25%; -} -.col-xs-2 { - width: 16.66666667%; -} -.col-xs-1 { - width: 8.33333333%; -} -.col-xs-pull-12 { - right: 100%; -} -.col-xs-pull-11 { - right: 91.66666667%; -} -.col-xs-pull-10 { - right: 83.33333333%; -} -.col-xs-pull-9 { - right: 75%; -} -.col-xs-pull-8 { - right: 66.66666667%; -} -.col-xs-pull-7 { - right: 58.33333333%; -} -.col-xs-pull-6 { - right: 50%; -} -.col-xs-pull-5 { - right: 41.66666667%; -} -.col-xs-pull-4 { - right: 33.33333333%; -} -.col-xs-pull-3 { - right: 25%; -} -.col-xs-pull-2 { - right: 16.66666667%; -} -.col-xs-pull-1 { - right: 8.33333333%; -} -.col-xs-pull-0 { - right: auto; -} -.col-xs-push-12 { - left: 100%; -} -.col-xs-push-11 { - left: 91.66666667%; -} -.col-xs-push-10 { - left: 83.33333333%; -} -.col-xs-push-9 { - left: 75%; -} -.col-xs-push-8 { - left: 66.66666667%; -} -.col-xs-push-7 { - left: 58.33333333%; -} -.col-xs-push-6 { - left: 50%; -} -.col-xs-push-5 { - left: 41.66666667%; -} -.col-xs-push-4 { - left: 33.33333333%; -} -.col-xs-push-3 { - left: 25%; -} -.col-xs-push-2 { - left: 16.66666667%; -} -.col-xs-push-1 { - left: 8.33333333%; -} -.col-xs-push-0 { - left: auto; -} -.col-xs-offset-12 { - margin-left: 100%; -} -.col-xs-offset-11 { - margin-left: 91.66666667%; -} -.col-xs-offset-10 { - margin-left: 83.33333333%; -} -.col-xs-offset-9 { - margin-left: 75%; -} -.col-xs-offset-8 { - margin-left: 66.66666667%; -} -.col-xs-offset-7 { - margin-left: 58.33333333%; -} -.col-xs-offset-6 { - margin-left: 50%; -} -.col-xs-offset-5 { - margin-left: 41.66666667%; -} -.col-xs-offset-4 { - margin-left: 33.33333333%; -} -.col-xs-offset-3 { - margin-left: 25%; -} -.col-xs-offset-2 { - margin-left: 16.66666667%; -} -.col-xs-offset-1 { - margin-left: 8.33333333%; -} -.col-xs-offset-0 { - margin-left: 0; -} -@media (min-width: 768px) { - .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { - float: left; - } - .col-sm-12 { - width: 100%; - } - .col-sm-11 { - width: 91.66666667%; - } - .col-sm-10 { - width: 83.33333333%; - } - .col-sm-9 { - width: 75%; - } - .col-sm-8 { - width: 66.66666667%; - } - .col-sm-7 { - width: 58.33333333%; - } - .col-sm-6 { - width: 50%; - } - .col-sm-5 { - width: 41.66666667%; - } - .col-sm-4 { - width: 33.33333333%; - } - .col-sm-3 { - width: 25%; - } - .col-sm-2 { - width: 16.66666667%; - } - .col-sm-1 { - width: 8.33333333%; - } - .col-sm-pull-12 { - right: 100%; - } - .col-sm-pull-11 { - right: 91.66666667%; - } - .col-sm-pull-10 { - right: 83.33333333%; - } - .col-sm-pull-9 { - right: 75%; - } - .col-sm-pull-8 { - right: 66.66666667%; - } - .col-sm-pull-7 { - right: 58.33333333%; - } - .col-sm-pull-6 { - right: 50%; - } - .col-sm-pull-5 { - right: 41.66666667%; - } - .col-sm-pull-4 { - right: 33.33333333%; - } - .col-sm-pull-3 { - right: 25%; - } - .col-sm-pull-2 { - right: 16.66666667%; - } - .col-sm-pull-1 { - right: 8.33333333%; - } - .col-sm-pull-0 { - right: auto; - } - .col-sm-push-12 { - left: 100%; - } - .col-sm-push-11 { - left: 91.66666667%; - } - .col-sm-push-10 { - left: 83.33333333%; - } - .col-sm-push-9 { - left: 75%; - } - .col-sm-push-8 { - left: 66.66666667%; - } - .col-sm-push-7 { - left: 58.33333333%; - } - .col-sm-push-6 { - left: 50%; - } - .col-sm-push-5 { - left: 41.66666667%; - } - .col-sm-push-4 { - left: 33.33333333%; - } - .col-sm-push-3 { - left: 25%; - } - .col-sm-push-2 { - left: 16.66666667%; - } - .col-sm-push-1 { - left: 8.33333333%; - } - .col-sm-push-0 { - left: auto; - } - .col-sm-offset-12 { - margin-left: 100%; - } - .col-sm-offset-11 { - margin-left: 91.66666667%; - } - .col-sm-offset-10 { - margin-left: 83.33333333%; - } - .col-sm-offset-9 { - margin-left: 75%; - } - .col-sm-offset-8 { - margin-left: 66.66666667%; - } - .col-sm-offset-7 { - margin-left: 58.33333333%; - } - .col-sm-offset-6 { - margin-left: 50%; - } - .col-sm-offset-5 { - margin-left: 41.66666667%; - } - .col-sm-offset-4 { - margin-left: 33.33333333%; - } - .col-sm-offset-3 { - margin-left: 25%; - } - .col-sm-offset-2 { - margin-left: 16.66666667%; - } - .col-sm-offset-1 { - margin-left: 8.33333333%; - } - .col-sm-offset-0 { - margin-left: 0; - } -} -@media (min-width: 992px) { - .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { - float: left; - } - .col-md-12 { - width: 100%; - } - .col-md-11 { - width: 91.66666667%; - } - .col-md-10 { - width: 83.33333333%; - } - .col-md-9 { - width: 75%; - } - .col-md-8 { - width: 66.66666667%; - } - .col-md-7 { - width: 58.33333333%; - } - .col-md-6 { - width: 50%; - } - .col-md-5 { - width: 41.66666667%; - } - .col-md-4 { - width: 33.33333333%; - } - .col-md-3 { - width: 25%; - } - .col-md-2 { - width: 16.66666667%; - } - .col-md-1 { - width: 8.33333333%; - } - .col-md-pull-12 { - right: 100%; - } - .col-md-pull-11 { - right: 91.66666667%; - } - .col-md-pull-10 { - right: 83.33333333%; - } - .col-md-pull-9 { - right: 75%; - } - .col-md-pull-8 { - right: 66.66666667%; - } - .col-md-pull-7 { - right: 58.33333333%; - } - .col-md-pull-6 { - right: 50%; - } - .col-md-pull-5 { - right: 41.66666667%; - } - .col-md-pull-4 { - right: 33.33333333%; - } - .col-md-pull-3 { - right: 25%; - } - .col-md-pull-2 { - right: 16.66666667%; - } - .col-md-pull-1 { - right: 8.33333333%; - } - .col-md-pull-0 { - right: auto; - } - .col-md-push-12 { - left: 100%; - } - .col-md-push-11 { - left: 91.66666667%; - } - .col-md-push-10 { - left: 83.33333333%; - } - .col-md-push-9 { - left: 75%; - } - .col-md-push-8 { - left: 66.66666667%; - } - .col-md-push-7 { - left: 58.33333333%; - } - .col-md-push-6 { - left: 50%; - } - .col-md-push-5 { - left: 41.66666667%; - } - .col-md-push-4 { - left: 33.33333333%; - } - .col-md-push-3 { - left: 25%; - } - .col-md-push-2 { - left: 16.66666667%; - } - .col-md-push-1 { - left: 8.33333333%; - } - .col-md-push-0 { - left: auto; - } - .col-md-offset-12 { - margin-left: 100%; - } - .col-md-offset-11 { - margin-left: 91.66666667%; - } - .col-md-offset-10 { - margin-left: 83.33333333%; - } - .col-md-offset-9 { - margin-left: 75%; - } - .col-md-offset-8 { - margin-left: 66.66666667%; - } - .col-md-offset-7 { - margin-left: 58.33333333%; - } - .col-md-offset-6 { - margin-left: 50%; - } - .col-md-offset-5 { - margin-left: 41.66666667%; - } - .col-md-offset-4 { - margin-left: 33.33333333%; - } - .col-md-offset-3 { - margin-left: 25%; - } - .col-md-offset-2 { - margin-left: 16.66666667%; - } - .col-md-offset-1 { - margin-left: 8.33333333%; - } - .col-md-offset-0 { - margin-left: 0; - } -} -@media (min-width: 1200px) { - .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { - float: left; - } - .col-lg-12 { - width: 100%; - } - .col-lg-11 { - width: 91.66666667%; - } - .col-lg-10 { - width: 83.33333333%; - } - .col-lg-9 { - width: 75%; - } - .col-lg-8 { - width: 66.66666667%; - } - .col-lg-7 { - width: 58.33333333%; - } - .col-lg-6 { - width: 50%; - } - .col-lg-5 { - width: 41.66666667%; - } - .col-lg-4 { - width: 33.33333333%; - } - .col-lg-3 { - width: 25%; - } - .col-lg-2 { - width: 16.66666667%; - } - .col-lg-1 { - width: 8.33333333%; - } - .col-lg-pull-12 { - right: 100%; - } - .col-lg-pull-11 { - right: 91.66666667%; - } - .col-lg-pull-10 { - right: 83.33333333%; - } - .col-lg-pull-9 { - right: 75%; - } - .col-lg-pull-8 { - right: 66.66666667%; - } - .col-lg-pull-7 { - right: 58.33333333%; - } - .col-lg-pull-6 { - right: 50%; - } - .col-lg-pull-5 { - right: 41.66666667%; - } - .col-lg-pull-4 { - right: 33.33333333%; - } - .col-lg-pull-3 { - right: 25%; - } - .col-lg-pull-2 { - right: 16.66666667%; - } - .col-lg-pull-1 { - right: 8.33333333%; - } - .col-lg-pull-0 { - right: auto; - } - .col-lg-push-12 { - left: 100%; - } - .col-lg-push-11 { - left: 91.66666667%; - } - .col-lg-push-10 { - left: 83.33333333%; - } - .col-lg-push-9 { - left: 75%; - } - .col-lg-push-8 { - left: 66.66666667%; - } - .col-lg-push-7 { - left: 58.33333333%; - } - .col-lg-push-6 { - left: 50%; - } - .col-lg-push-5 { - left: 41.66666667%; - } - .col-lg-push-4 { - left: 33.33333333%; - } - .col-lg-push-3 { - left: 25%; - } - .col-lg-push-2 { - left: 16.66666667%; - } - .col-lg-push-1 { - left: 8.33333333%; - } - .col-lg-push-0 { - left: auto; - } - .col-lg-offset-12 { - margin-left: 100%; - } - .col-lg-offset-11 { - margin-left: 91.66666667%; - } - .col-lg-offset-10 { - margin-left: 83.33333333%; - } - .col-lg-offset-9 { - margin-left: 75%; - } - .col-lg-offset-8 { - margin-left: 66.66666667%; - } - .col-lg-offset-7 { - margin-left: 58.33333333%; - } - .col-lg-offset-6 { - margin-left: 50%; - } - .col-lg-offset-5 { - margin-left: 41.66666667%; - } - .col-lg-offset-4 { - margin-left: 33.33333333%; - } - .col-lg-offset-3 { - margin-left: 25%; - } - .col-lg-offset-2 { - margin-left: 16.66666667%; - } - .col-lg-offset-1 { - margin-left: 8.33333333%; - } - .col-lg-offset-0 { - margin-left: 0; - } -} -table { - background-color: transparent; -} -th { - text-align: left; -} -.table { - width: 100%; - max-width: 100%; - margin-bottom: 20px; -} -.table > thead > tr > th, -.table > tbody > tr > th, -.table > tfoot > tr > th, -.table > thead > tr > td, -.table > tbody > tr > td, -.table > tfoot > tr > td { - padding: 8px; - line-height: 1.42857143; - vertical-align: top; - border-top: 1px solid #ddd; -} -.table > thead > tr > th { - vertical-align: bottom; - border-bottom: 2px solid #ddd; -} -.table > caption + thead > tr:first-child > th, -.table > colgroup + thead > tr:first-child > th, -.table > thead:first-child > tr:first-child > th, -.table > caption + thead > tr:first-child > td, -.table > colgroup + thead > tr:first-child > td, -.table > thead:first-child > tr:first-child > td { - border-top: 0; -} -.table > tbody + tbody { - border-top: 2px solid #ddd; -} -.table .table { - background-color: #fff; -} -.table-condensed > thead > tr > th, -.table-condensed > tbody > tr > th, -.table-condensed > tfoot > tr > th, -.table-condensed > thead > tr > td, -.table-condensed > tbody > tr > td, -.table-condensed > tfoot > tr > td { - padding: 5px; -} -.table-bordered { - border: 1px solid #ddd; -} -.table-bordered > thead > tr > th, -.table-bordered > tbody > tr > th, -.table-bordered > tfoot > tr > th, -.table-bordered > thead > tr > td, -.table-bordered > tbody > tr > td, -.table-bordered > tfoot > tr > td { - border: 1px solid #ddd; -} -.table-bordered > thead > tr > th, -.table-bordered > thead > tr > td { - border-bottom-width: 2px; -} -.table-striped > tbody > tr:nth-child(odd) > td, -.table-striped > tbody > tr:nth-child(odd) > th { - background-color: #f9f9f9; -} -.table-hover > tbody > tr:hover > td, -.table-hover > tbody > tr:hover > th { - background-color: #f5f5f5; -} -table col[class*="col-"] { - position: static; - display: table-column; - float: none; -} -table td[class*="col-"], -table th[class*="col-"] { - position: static; - display: table-cell; - float: none; -} -.table > thead > tr > td.active, -.table > tbody > tr > td.active, -.table > tfoot > tr > td.active, -.table > thead > tr > th.active, -.table > tbody > tr > th.active, -.table > tfoot > tr > th.active, -.table > thead > tr.active > td, -.table > tbody > tr.active > td, -.table > tfoot > tr.active > td, -.table > thead > tr.active > th, -.table > tbody > tr.active > th, -.table > tfoot > tr.active > th { - background-color: #f5f5f5; -} -.table-hover > tbody > tr > td.active:hover, -.table-hover > tbody > tr > th.active:hover, -.table-hover > tbody > tr.active:hover > td, -.table-hover > tbody > tr:hover > .active, -.table-hover > tbody > tr.active:hover > th { - background-color: #e8e8e8; -} -.table > thead > tr > td.success, -.table > tbody > tr > td.success, -.table > tfoot > tr > td.success, -.table > thead > tr > th.success, -.table > tbody > tr > th.success, -.table > tfoot > tr > th.success, -.table > thead > tr.success > td, -.table > tbody > tr.success > td, -.table > tfoot > tr.success > td, -.table > thead > tr.success > th, -.table > tbody > tr.success > th, -.table > tfoot > tr.success > th { - background-color: #dff0d8; -} -.table-hover > tbody > tr > td.success:hover, -.table-hover > tbody > tr > th.success:hover, -.table-hover > tbody > tr.success:hover > td, -.table-hover > tbody > tr:hover > .success, -.table-hover > tbody > tr.success:hover > th { - background-color: #d0e9c6; -} -.table > thead > tr > td.info, -.table > tbody > tr > td.info, -.table > tfoot > tr > td.info, -.table > thead > tr > th.info, -.table > tbody > tr > th.info, -.table > tfoot > tr > th.info, -.table > thead > tr.info > td, -.table > tbody > tr.info > td, -.table > tfoot > tr.info > td, -.table > thead > tr.info > th, -.table > tbody > tr.info > th, -.table > tfoot > tr.info > th { - background-color: #d9edf7; -} -.table-hover > tbody > tr > td.info:hover, -.table-hover > tbody > tr > th.info:hover, -.table-hover > tbody > tr.info:hover > td, -.table-hover > tbody > tr:hover > .info, -.table-hover > tbody > tr.info:hover > th { - background-color: #c4e3f3; -} -.table > thead > tr > td.warning, -.table > tbody > tr > td.warning, -.table > tfoot > tr > td.warning, -.table > thead > tr > th.warning, -.table > tbody > tr > th.warning, -.table > tfoot > tr > th.warning, -.table > thead > tr.warning > td, -.table > tbody > tr.warning > td, -.table > tfoot > tr.warning > td, -.table > thead > tr.warning > th, -.table > tbody > tr.warning > th, -.table > tfoot > tr.warning > th { - background-color: #fcf8e3; -} -.table-hover > tbody > tr > td.warning:hover, -.table-hover > tbody > tr > th.warning:hover, -.table-hover > tbody > tr.warning:hover > td, -.table-hover > tbody > tr:hover > .warning, -.table-hover > tbody > tr.warning:hover > th { - background-color: #faf2cc; -} -.table > thead > tr > td.danger, -.table > tbody > tr > td.danger, -.table > tfoot > tr > td.danger, -.table > thead > tr > th.danger, -.table > tbody > tr > th.danger, -.table > tfoot > tr > th.danger, -.table > thead > tr.danger > td, -.table > tbody > tr.danger > td, -.table > tfoot > tr.danger > td, -.table > thead > tr.danger > th, -.table > tbody > tr.danger > th, -.table > tfoot > tr.danger > th { - background-color: #f2dede; -} -.table-hover > tbody > tr > td.danger:hover, -.table-hover > tbody > tr > th.danger:hover, -.table-hover > tbody > tr.danger:hover > td, -.table-hover > tbody > tr:hover > .danger, -.table-hover > tbody > tr.danger:hover > th { - background-color: #ebcccc; -} -@media screen and (max-width: 767px) { - .table-responsive { - width: 100%; - margin-bottom: 15px; - overflow-x: auto; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - border: 1px solid #ddd; - } - .table-responsive > .table { - margin-bottom: 0; - } - .table-responsive > .table > thead > tr > th, - .table-responsive > .table > tbody > tr > th, - .table-responsive > .table > tfoot > tr > th, - .table-responsive > .table > thead > tr > td, - .table-responsive > .table > tbody > tr > td, - .table-responsive > .table > tfoot > tr > td { - white-space: nowrap; - } - .table-responsive > .table-bordered { - border: 0; - } - .table-responsive > .table-bordered > thead > tr > th:first-child, - .table-responsive > .table-bordered > tbody > tr > th:first-child, - .table-responsive > .table-bordered > tfoot > tr > th:first-child, - .table-responsive > .table-bordered > thead > tr > td:first-child, - .table-responsive > .table-bordered > tbody > tr > td:first-child, - .table-responsive > .table-bordered > tfoot > tr > td:first-child { - border-left: 0; - } - .table-responsive > .table-bordered > thead > tr > th:last-child, - .table-responsive > .table-bordered > tbody > tr > th:last-child, - .table-responsive > .table-bordered > tfoot > tr > th:last-child, - .table-responsive > .table-bordered > thead > tr > td:last-child, - .table-responsive > .table-bordered > tbody > tr > td:last-child, - .table-responsive > .table-bordered > tfoot > tr > td:last-child { - border-right: 0; - } - .table-responsive > .table-bordered > tbody > tr:last-child > th, - .table-responsive > .table-bordered > tfoot > tr:last-child > th, - .table-responsive > .table-bordered > tbody > tr:last-child > td, - .table-responsive > .table-bordered > tfoot > tr:last-child > td { - border-bottom: 0; - } -} -fieldset { - min-width: 0; - padding: 0; - margin: 0; - border: 0; -} -legend { - display: block; - width: 100%; - padding: 0; - margin-bottom: 20px; - font-size: 21px; - line-height: inherit; - color: #333; - border: 0; - border-bottom: 1px solid #e5e5e5; -} -label { - display: inline-block; - max-width: 100%; - margin-bottom: 5px; - font-weight: bold; -} -input[type="search"] { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -input[type="radio"], -input[type="checkbox"] { - margin: 4px 0 0; - margin-top: 1px \9; - line-height: normal; -} -input[type="file"] { - display: block; -} -input[type="range"] { - display: block; - width: 100%; -} -select[multiple], -select[size] { - height: auto; -} -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - outline: thin dotted; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} -output { - display: block; - padding-top: 7px; - font-size: 14px; - line-height: 1.42857143; - color: #555; -} -.form-control { - display: block; - width: 100%; - height: 34px; - padding: 6px 12px; - font-size: 14px; - line-height: 1.42857143; - color: #555; - background-color: #fff; - background-image: none; - border: 1px solid #ccc; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; - -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; - transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; -} -.form-control:focus { - border-color: #66afe9; - outline: 0; - -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); - box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); -} -.form-control::-moz-placeholder { - color: #777; - opacity: 1; -} -.form-control:-ms-input-placeholder { - color: #777; -} -.form-control::-webkit-input-placeholder { - color: #777; -} -.form-control[disabled], -.form-control[readonly], -fieldset[disabled] .form-control { - cursor: not-allowed; - background-color: #eee; - opacity: 1; -} -textarea.form-control { - height: auto; -} -input[type="search"] { - -webkit-appearance: none; -} -input[type="date"], -input[type="time"], -input[type="datetime-local"], -input[type="month"] { - line-height: 34px; - line-height: 1.42857143 \0; -} -input[type="date"].input-sm, -input[type="time"].input-sm, -input[type="datetime-local"].input-sm, -input[type="month"].input-sm { - line-height: 30px; -} -input[type="date"].input-lg, -input[type="time"].input-lg, -input[type="datetime-local"].input-lg, -input[type="month"].input-lg { - line-height: 46px; -} -.form-group { - margin-bottom: 15px; -} -.radio, -.checkbox { - position: relative; - display: block; - min-height: 20px; - margin-top: 10px; - margin-bottom: 10px; -} -.radio label, -.checkbox label { - padding-left: 20px; - margin-bottom: 0; - font-weight: normal; - cursor: pointer; -} -.radio input[type="radio"], -.radio-inline input[type="radio"], -.checkbox input[type="checkbox"], -.checkbox-inline input[type="checkbox"] { - position: absolute; - margin-top: 4px \9; - margin-left: -20px; -} -.radio + .radio, -.checkbox + .checkbox { - margin-top: -5px; -} -.radio-inline, -.checkbox-inline { - display: inline-block; - padding-left: 20px; - margin-bottom: 0; - font-weight: normal; - vertical-align: middle; - cursor: pointer; -} -.radio-inline + .radio-inline, -.checkbox-inline + .checkbox-inline { - margin-top: 0; - margin-left: 10px; -} -input[type="radio"][disabled], -input[type="checkbox"][disabled], -input[type="radio"].disabled, -input[type="checkbox"].disabled, -fieldset[disabled] input[type="radio"], -fieldset[disabled] input[type="checkbox"] { - cursor: not-allowed; -} -.radio-inline.disabled, -.checkbox-inline.disabled, -fieldset[disabled] .radio-inline, -fieldset[disabled] .checkbox-inline { - cursor: not-allowed; -} -.radio.disabled label, -.checkbox.disabled label, -fieldset[disabled] .radio label, -fieldset[disabled] .checkbox label { - cursor: not-allowed; -} -.form-control-static { - padding-top: 7px; - padding-bottom: 7px; - margin-bottom: 0; -} -.form-control-static.input-lg, -.form-control-static.input-sm { - padding-right: 0; - padding-left: 0; -} -.input-sm, -.form-horizontal .form-group-sm .form-control { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -select.input-sm { - height: 30px; - line-height: 30px; -} -textarea.input-sm, -select[multiple].input-sm { - height: auto; -} -.input-lg, -.form-horizontal .form-group-lg .form-control { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.33; - border-radius: 6px; -} -select.input-lg { - height: 46px; - line-height: 46px; -} -textarea.input-lg, -select[multiple].input-lg { - height: auto; -} -.has-feedback { - position: relative; -} -.has-feedback .form-control { - padding-right: 42.5px; -} -.form-control-feedback { - position: absolute; - top: 25px; - right: 0; - z-index: 2; - display: block; - width: 34px; - height: 34px; - line-height: 34px; - text-align: center; -} -.input-lg + .form-control-feedback { - width: 46px; - height: 46px; - line-height: 46px; -} -.input-sm + .form-control-feedback { - width: 30px; - height: 30px; - line-height: 30px; -} -.has-success .help-block, -.has-success .control-label, -.has-success .radio, -.has-success .checkbox, -.has-success .radio-inline, -.has-success .checkbox-inline { - color: #3c763d; -} -.has-success .form-control { - border-color: #3c763d; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} -.has-success .form-control:focus { - border-color: #2b542c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; -} -.has-success .input-group-addon { - color: #3c763d; - background-color: #dff0d8; - border-color: #3c763d; -} -.has-success .form-control-feedback { - color: #3c763d; -} -.has-warning .help-block, -.has-warning .control-label, -.has-warning .radio, -.has-warning .checkbox, -.has-warning .radio-inline, -.has-warning .checkbox-inline { - color: #8a6d3b; -} -.has-warning .form-control { - border-color: #8a6d3b; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} -.has-warning .form-control:focus { - border-color: #66512c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; -} -.has-warning .input-group-addon { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #8a6d3b; -} -.has-warning .form-control-feedback { - color: #8a6d3b; -} -.has-error .help-block, -.has-error .control-label, -.has-error .radio, -.has-error .checkbox, -.has-error .radio-inline, -.has-error .checkbox-inline { - color: #a94442; -} -.has-error .form-control { - border-color: #a94442; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} -.has-error .form-control:focus { - border-color: #843534; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; -} -.has-error .input-group-addon { - color: #a94442; - background-color: #f2dede; - border-color: #a94442; -} -.has-error .form-control-feedback { - color: #a94442; -} -.has-feedback label.sr-only ~ .form-control-feedback { - top: 0; -} -.help-block { - display: block; - margin-top: 5px; - margin-bottom: 10px; - color: #737373; -} -@media (min-width: 768px) { - .form-inline .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .form-control { - display: inline-block; - width: auto; - vertical-align: middle; - } - .form-inline .input-group { - display: inline-table; - vertical-align: middle; - } - .form-inline .input-group .input-group-addon, - .form-inline .input-group .input-group-btn, - .form-inline .input-group .form-control { - width: auto; - } - .form-inline .input-group > .form-control { - width: 100%; - } - .form-inline .control-label { - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .radio, - .form-inline .checkbox { - display: inline-block; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .radio label, - .form-inline .checkbox label { - padding-left: 0; - } - .form-inline .radio input[type="radio"], - .form-inline .checkbox input[type="checkbox"] { - position: relative; - margin-left: 0; - } - .form-inline .has-feedback .form-control-feedback { - top: 0; - } -} -.form-horizontal .radio, -.form-horizontal .checkbox, -.form-horizontal .radio-inline, -.form-horizontal .checkbox-inline { - padding-top: 7px; - margin-top: 0; - margin-bottom: 0; -} -.form-horizontal .radio, -.form-horizontal .checkbox { - min-height: 27px; -} -.form-horizontal .form-group { - margin-right: -15px; - margin-left: -15px; -} -@media (min-width: 768px) { - .form-horizontal .control-label { - padding-top: 7px; - margin-bottom: 0; - text-align: right; - } -} -.form-horizontal .has-feedback .form-control-feedback { - top: 0; - right: 15px; -} -@media (min-width: 768px) { - .form-horizontal .form-group-lg .control-label { - padding-top: 14.3px; - } -} -@media (min-width: 768px) { - .form-horizontal .form-group-sm .control-label { - padding-top: 6px; - } -} -.btn { - display: inline-block; - padding: 6px 12px; - margin-bottom: 0; - font-size: 14px; - font-weight: normal; - line-height: 1.42857143; - text-align: center; - white-space: nowrap; - vertical-align: middle; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-image: none; - border: 1px solid transparent; - border-radius: 4px; -} -.btn:focus, -.btn:active:focus, -.btn.active:focus { - outline: thin dotted; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} -.btn:hover, -.btn:focus { - color: #333; - text-decoration: none; -} -.btn:active, -.btn.active { - background-image: none; - outline: 0; - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); -} -.btn.disabled, -.btn[disabled], -fieldset[disabled] .btn { - pointer-events: none; - cursor: not-allowed; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - box-shadow: none; - opacity: .65; -} -.btn-default { - color: #333; - background-color: #fff; - border-color: #ccc; -} -.btn-default:hover, -.btn-default:focus, -.btn-default:active, -.btn-default.active, -.open > .dropdown-toggle.btn-default { - color: #333; - background-color: #e6e6e6; - border-color: #adadad; -} -.btn-default:active, -.btn-default.active, -.open > .dropdown-toggle.btn-default { - background-image: none; -} -.btn-default.disabled, -.btn-default[disabled], -fieldset[disabled] .btn-default, -.btn-default.disabled:hover, -.btn-default[disabled]:hover, -fieldset[disabled] .btn-default:hover, -.btn-default.disabled:focus, -.btn-default[disabled]:focus, -fieldset[disabled] .btn-default:focus, -.btn-default.disabled:active, -.btn-default[disabled]:active, -fieldset[disabled] .btn-default:active, -.btn-default.disabled.active, -.btn-default[disabled].active, -fieldset[disabled] .btn-default.active { - background-color: #fff; - border-color: #ccc; -} -.btn-default .badge { - color: #fff; - background-color: #333; -} -.btn-primary { - color: #fff; - background-color: #428bca; - border-color: #357ebd; -} -.btn-primary:hover, -.btn-primary:focus, -.btn-primary:active, -.btn-primary.active, -.open > .dropdown-toggle.btn-primary { - color: #fff; - background-color: #3071a9; - border-color: #285e8e; -} -.btn-primary:active, -.btn-primary.active, -.open > .dropdown-toggle.btn-primary { - background-image: none; -} -.btn-primary.disabled, -.btn-primary[disabled], -fieldset[disabled] .btn-primary, -.btn-primary.disabled:hover, -.btn-primary[disabled]:hover, -fieldset[disabled] .btn-primary:hover, -.btn-primary.disabled:focus, -.btn-primary[disabled]:focus, -fieldset[disabled] .btn-primary:focus, -.btn-primary.disabled:active, -.btn-primary[disabled]:active, -fieldset[disabled] .btn-primary:active, -.btn-primary.disabled.active, -.btn-primary[disabled].active, -fieldset[disabled] .btn-primary.active { - background-color: #428bca; - border-color: #357ebd; -} -.btn-primary .badge { - color: #428bca; - background-color: #fff; -} -.btn-success { - color: #fff; - background-color: #5cb85c; - border-color: #4cae4c; -} -.btn-success:hover, -.btn-success:focus, -.btn-success:active, -.btn-success.active, -.open > .dropdown-toggle.btn-success { - color: #fff; - background-color: #449d44; - border-color: #398439; -} -.btn-success:active, -.btn-success.active, -.open > .dropdown-toggle.btn-success { - background-image: none; -} -.btn-success.disabled, -.btn-success[disabled], -fieldset[disabled] .btn-success, -.btn-success.disabled:hover, -.btn-success[disabled]:hover, -fieldset[disabled] .btn-success:hover, -.btn-success.disabled:focus, -.btn-success[disabled]:focus, -fieldset[disabled] .btn-success:focus, -.btn-success.disabled:active, -.btn-success[disabled]:active, -fieldset[disabled] .btn-success:active, -.btn-success.disabled.active, -.btn-success[disabled].active, -fieldset[disabled] .btn-success.active { - background-color: #5cb85c; - border-color: #4cae4c; -} -.btn-success .badge { - color: #5cb85c; - background-color: #fff; -} -.btn-info { - color: #fff; - background-color: #5bc0de; - border-color: #46b8da; -} -.btn-info:hover, -.btn-info:focus, -.btn-info:active, -.btn-info.active, -.open > .dropdown-toggle.btn-info { - color: #fff; - background-color: #31b0d5; - border-color: #269abc; -} -.btn-info:active, -.btn-info.active, -.open > .dropdown-toggle.btn-info { - background-image: none; -} -.btn-info.disabled, -.btn-info[disabled], -fieldset[disabled] .btn-info, -.btn-info.disabled:hover, -.btn-info[disabled]:hover, -fieldset[disabled] .btn-info:hover, -.btn-info.disabled:focus, -.btn-info[disabled]:focus, -fieldset[disabled] .btn-info:focus, -.btn-info.disabled:active, -.btn-info[disabled]:active, -fieldset[disabled] .btn-info:active, -.btn-info.disabled.active, -.btn-info[disabled].active, -fieldset[disabled] .btn-info.active { - background-color: #5bc0de; - border-color: #46b8da; -} -.btn-info .badge { - color: #5bc0de; - background-color: #fff; -} -.btn-warning { - color: #fff; - background-color: #f0ad4e; - border-color: #eea236; -} -.btn-warning:hover, -.btn-warning:focus, -.btn-warning:active, -.btn-warning.active, -.open > .dropdown-toggle.btn-warning { - color: #fff; - background-color: #ec971f; - border-color: #d58512; -} -.btn-warning:active, -.btn-warning.active, -.open > .dropdown-toggle.btn-warning { - background-image: none; -} -.btn-warning.disabled, -.btn-warning[disabled], -fieldset[disabled] .btn-warning, -.btn-warning.disabled:hover, -.btn-warning[disabled]:hover, -fieldset[disabled] .btn-warning:hover, -.btn-warning.disabled:focus, -.btn-warning[disabled]:focus, -fieldset[disabled] .btn-warning:focus, -.btn-warning.disabled:active, -.btn-warning[disabled]:active, -fieldset[disabled] .btn-warning:active, -.btn-warning.disabled.active, -.btn-warning[disabled].active, -fieldset[disabled] .btn-warning.active { - background-color: #f0ad4e; - border-color: #eea236; -} -.btn-warning .badge { - color: #f0ad4e; - background-color: #fff; -} -.btn-danger { - color: #fff; - background-color: #d9534f; - border-color: #d43f3a; -} -.btn-danger:hover, -.btn-danger:focus, -.btn-danger:active, -.btn-danger.active, -.open > .dropdown-toggle.btn-danger { - color: #fff; - background-color: #c9302c; - border-color: #ac2925; -} -.btn-danger:active, -.btn-danger.active, -.open > .dropdown-toggle.btn-danger { - background-image: none; -} -.btn-danger.disabled, -.btn-danger[disabled], -fieldset[disabled] .btn-danger, -.btn-danger.disabled:hover, -.btn-danger[disabled]:hover, -fieldset[disabled] .btn-danger:hover, -.btn-danger.disabled:focus, -.btn-danger[disabled]:focus, -fieldset[disabled] .btn-danger:focus, -.btn-danger.disabled:active, -.btn-danger[disabled]:active, -fieldset[disabled] .btn-danger:active, -.btn-danger.disabled.active, -.btn-danger[disabled].active, -fieldset[disabled] .btn-danger.active { - background-color: #d9534f; - border-color: #d43f3a; -} -.btn-danger .badge { - color: #d9534f; - background-color: #fff; -} -.btn-link { - font-weight: normal; - color: #428bca; - cursor: pointer; - border-radius: 0; -} -.btn-link, -.btn-link:active, -.btn-link[disabled], -fieldset[disabled] .btn-link { - background-color: transparent; - -webkit-box-shadow: none; - box-shadow: none; -} -.btn-link, -.btn-link:hover, -.btn-link:focus, -.btn-link:active { - border-color: transparent; -} -.btn-link:hover, -.btn-link:focus { - color: #2a6496; - text-decoration: underline; - background-color: transparent; -} -.btn-link[disabled]:hover, -fieldset[disabled] .btn-link:hover, -.btn-link[disabled]:focus, -fieldset[disabled] .btn-link:focus { - color: #777; - text-decoration: none; -} -.btn-lg, -.btn-group-lg > .btn { - padding: 10px 16px; - font-size: 18px; - line-height: 1.33; - border-radius: 6px; -} -.btn-sm, -.btn-group-sm > .btn { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -.btn-xs, -.btn-group-xs > .btn { - padding: 1px 5px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -.btn-block { - display: block; - width: 100%; -} -.btn-block + .btn-block { - margin-top: 5px; -} -input[type="submit"].btn-block, -input[type="reset"].btn-block, -input[type="button"].btn-block { - width: 100%; -} -.fade { - opacity: 0; - -webkit-transition: opacity .15s linear; - -o-transition: opacity .15s linear; - transition: opacity .15s linear; -} -.fade.in { - opacity: 1; -} -.collapse { - display: none; -} -.collapse.in { - display: block; -} -tr.collapse.in { - display: table-row; -} -tbody.collapse.in { - display: table-row-group; -} -.collapsing { - position: relative; - height: 0; - overflow: hidden; - -webkit-transition: height .35s ease; - -o-transition: height .35s ease; - transition: height .35s ease; -} -.caret { - display: inline-block; - width: 0; - height: 0; - margin-left: 2px; - vertical-align: middle; - border-top: 4px solid; - border-right: 4px solid transparent; - border-left: 4px solid transparent; -} -.dropdown { - position: relative; -} -.dropdown-toggle:focus { - outline: 0; -} -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: 1000; - display: none; - float: left; - min-width: 160px; - padding: 5px 0; - margin: 2px 0 0; - font-size: 14px; - text-align: left; - list-style: none; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, .15); - border-radius: 4px; - -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); - box-shadow: 0 6px 12px rgba(0, 0, 0, .175); -} -.dropdown-menu.pull-right { - right: 0; - left: auto; -} -.dropdown-menu .divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5; -} -.dropdown-menu > li > a { - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: 1.42857143; - color: #333; - white-space: nowrap; -} -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus { - color: #262626; - text-decoration: none; - background-color: #f5f5f5; -} -.dropdown-menu > .active > a, -.dropdown-menu > .active > a:hover, -.dropdown-menu > .active > a:focus { - color: #fff; - text-decoration: none; - background-color: #428bca; - outline: 0; -} -.dropdown-menu > .disabled > a, -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - color: #777; -} -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - text-decoration: none; - cursor: not-allowed; - background-color: transparent; - background-image: none; - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); -} -.open > .dropdown-menu { - display: block; -} -.open > a { - outline: 0; -} -.dropdown-menu-right { - right: 0; - left: auto; -} -.dropdown-menu-left { - right: auto; - left: 0; -} -.dropdown-header { - display: block; - padding: 3px 20px; - font-size: 12px; - line-height: 1.42857143; - color: #777; - white-space: nowrap; -} -.dropdown-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 990; -} -.pull-right > .dropdown-menu { - right: 0; - left: auto; -} -.dropup .caret, -.navbar-fixed-bottom .dropdown .caret { - content: ""; - border-top: 0; - border-bottom: 4px solid; -} -.dropup .dropdown-menu, -.navbar-fixed-bottom .dropdown .dropdown-menu { - top: auto; - bottom: 100%; - margin-bottom: 1px; -} -@media (min-width: 768px) { - .navbar-right .dropdown-menu { - right: 0; - left: auto; - } - .navbar-right .dropdown-menu-left { - right: auto; - left: 0; - } -} -.btn-group, -.btn-group-vertical { - position: relative; - display: inline-block; - vertical-align: middle; -} -.btn-group > .btn, -.btn-group-vertical > .btn { - position: relative; - float: left; -} -.btn-group > .btn:hover, -.btn-group-vertical > .btn:hover, -.btn-group > .btn:focus, -.btn-group-vertical > .btn:focus, -.btn-group > .btn:active, -.btn-group-vertical > .btn:active, -.btn-group > .btn.active, -.btn-group-vertical > .btn.active { - z-index: 2; -} -.btn-group > .btn:focus, -.btn-group-vertical > .btn:focus { - outline: 0; -} -.btn-group .btn + .btn, -.btn-group .btn + .btn-group, -.btn-group .btn-group + .btn, -.btn-group .btn-group + .btn-group { - margin-left: -1px; -} -.btn-toolbar { - margin-left: -5px; -} -.btn-toolbar .btn-group, -.btn-toolbar .input-group { - float: left; -} -.btn-toolbar > .btn, -.btn-toolbar > .btn-group, -.btn-toolbar > .input-group { - margin-left: 5px; -} -.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { - border-radius: 0; -} -.btn-group > .btn:first-child { - margin-left: 0; -} -.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.btn-group > .btn:last-child:not(:first-child), -.btn-group > .dropdown-toggle:not(:first-child) { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group > .btn-group { - float: left; -} -.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0; -} -.btn-group > .btn-group:first-child > .btn:last-child, -.btn-group > .btn-group:first-child > .dropdown-toggle { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.btn-group > .btn-group:last-child > .btn:first-child { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group .dropdown-toggle:active, -.btn-group.open .dropdown-toggle { - outline: 0; -} -.btn-group > .btn + .dropdown-toggle { - padding-right: 8px; - padding-left: 8px; -} -.btn-group > .btn-lg + .dropdown-toggle { - padding-right: 12px; - padding-left: 12px; -} -.btn-group.open .dropdown-toggle { - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); -} -.btn-group.open .dropdown-toggle.btn-link { - -webkit-box-shadow: none; - box-shadow: none; -} -.btn .caret { - margin-left: 0; -} -.btn-lg .caret { - border-width: 5px 5px 0; - border-bottom-width: 0; -} -.dropup .btn-lg .caret { - border-width: 0 5px 5px; -} -.btn-group-vertical > .btn, -.btn-group-vertical > .btn-group, -.btn-group-vertical > .btn-group > .btn { - display: block; - float: none; - width: 100%; - max-width: 100%; -} -.btn-group-vertical > .btn-group > .btn { - float: none; -} -.btn-group-vertical > .btn + .btn, -.btn-group-vertical > .btn + .btn-group, -.btn-group-vertical > .btn-group + .btn, -.btn-group-vertical > .btn-group + .btn-group { - margin-top: -1px; - margin-left: 0; -} -.btn-group-vertical > .btn:not(:first-child):not(:last-child) { - border-radius: 0; -} -.btn-group-vertical > .btn:first-child:not(:last-child) { - border-top-right-radius: 4px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group-vertical > .btn:last-child:not(:first-child) { - border-top-left-radius: 0; - border-top-right-radius: 0; - border-bottom-left-radius: 4px; -} -.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0; -} -.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, -.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.btn-group-justified { - display: table; - width: 100%; - table-layout: fixed; - border-collapse: separate; -} -.btn-group-justified > .btn, -.btn-group-justified > .btn-group { - display: table-cell; - float: none; - width: 1%; -} -.btn-group-justified > .btn-group .btn { - width: 100%; -} -.btn-group-justified > .btn-group .dropdown-menu { - left: auto; -} -[data-toggle="buttons"] > .btn > input[type="radio"], -[data-toggle="buttons"] > .btn > input[type="checkbox"] { - position: absolute; - z-index: -1; - filter: alpha(opacity=0); - opacity: 0; -} -.input-group { - position: relative; - display: table; - border-collapse: separate; -} -.input-group[class*="col-"] { - float: none; - padding-right: 0; - padding-left: 0; -} -.input-group .form-control { - position: relative; - z-index: 2; - float: left; - width: 100%; - margin-bottom: 0; -} -.input-group-lg > .form-control, -.input-group-lg > .input-group-addon, -.input-group-lg > .input-group-btn > .btn { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.33; - border-radius: 6px; -} -select.input-group-lg > .form-control, -select.input-group-lg > .input-group-addon, -select.input-group-lg > .input-group-btn > .btn { - height: 46px; - line-height: 46px; -} -textarea.input-group-lg > .form-control, -textarea.input-group-lg > .input-group-addon, -textarea.input-group-lg > .input-group-btn > .btn, -select[multiple].input-group-lg > .form-control, -select[multiple].input-group-lg > .input-group-addon, -select[multiple].input-group-lg > .input-group-btn > .btn { - height: auto; -} -.input-group-sm > .form-control, -.input-group-sm > .input-group-addon, -.input-group-sm > .input-group-btn > .btn { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -select.input-group-sm > .form-control, -select.input-group-sm > .input-group-addon, -select.input-group-sm > .input-group-btn > .btn { - height: 30px; - line-height: 30px; -} -textarea.input-group-sm > .form-control, -textarea.input-group-sm > .input-group-addon, -textarea.input-group-sm > .input-group-btn > .btn, -select[multiple].input-group-sm > .form-control, -select[multiple].input-group-sm > .input-group-addon, -select[multiple].input-group-sm > .input-group-btn > .btn { - height: auto; -} -.input-group-addon, -.input-group-btn, -.input-group .form-control { - display: table-cell; -} -.input-group-addon:not(:first-child):not(:last-child), -.input-group-btn:not(:first-child):not(:last-child), -.input-group .form-control:not(:first-child):not(:last-child) { - border-radius: 0; -} -.input-group-addon, -.input-group-btn { - width: 1%; - white-space: nowrap; - vertical-align: middle; -} -.input-group-addon { - padding: 6px 12px; - font-size: 14px; - font-weight: normal; - line-height: 1; - color: #555; - text-align: center; - background-color: #eee; - border: 1px solid #ccc; - border-radius: 4px; -} -.input-group-addon.input-sm { - padding: 5px 10px; - font-size: 12px; - border-radius: 3px; -} -.input-group-addon.input-lg { - padding: 10px 16px; - font-size: 18px; - border-radius: 6px; -} -.input-group-addon input[type="radio"], -.input-group-addon input[type="checkbox"] { - margin-top: 0; -} -.input-group .form-control:first-child, -.input-group-addon:first-child, -.input-group-btn:first-child > .btn, -.input-group-btn:first-child > .btn-group > .btn, -.input-group-btn:first-child > .dropdown-toggle, -.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), -.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.input-group-addon:first-child { - border-right: 0; -} -.input-group .form-control:last-child, -.input-group-addon:last-child, -.input-group-btn:last-child > .btn, -.input-group-btn:last-child > .btn-group > .btn, -.input-group-btn:last-child > .dropdown-toggle, -.input-group-btn:first-child > .btn:not(:first-child), -.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} -.input-group-addon:last-child { - border-left: 0; -} -.input-group-btn { - position: relative; - font-size: 0; - white-space: nowrap; -} -.input-group-btn > .btn { - position: relative; -} -.input-group-btn > .btn + .btn { - margin-left: -1px; -} -.input-group-btn > .btn:hover, -.input-group-btn > .btn:focus, -.input-group-btn > .btn:active { - z-index: 2; -} -.input-group-btn:first-child > .btn, -.input-group-btn:first-child > .btn-group { - margin-right: -1px; -} -.input-group-btn:last-child > .btn, -.input-group-btn:last-child > .btn-group { - margin-left: -1px; -} -.nav { - padding-left: 0; - margin-bottom: 0; - list-style: none; -} -.nav > li { - position: relative; - display: block; -} -.nav > li > a { - position: relative; - display: block; - padding: 10px 15px; -} -.nav > li > a:hover, -.nav > li > a:focus { - text-decoration: none; - background-color: #eee; -} -.nav > li.disabled > a { - color: #777; -} -.nav > li.disabled > a:hover, -.nav > li.disabled > a:focus { - color: #777; - text-decoration: none; - cursor: not-allowed; - background-color: transparent; -} -.nav .open > a, -.nav .open > a:hover, -.nav .open > a:focus { - background-color: #eee; - border-color: #428bca; -} -.nav .nav-divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5; -} -.nav > li > a > img { - max-width: none; -} -.nav-tabs { - border-bottom: 1px solid #ddd; -} -.nav-tabs > li { - float: left; - margin-bottom: -1px; -} -.nav-tabs > li > a { - margin-right: 2px; - line-height: 1.42857143; - border: 1px solid transparent; - border-radius: 4px 4px 0 0; -} -.nav-tabs > li > a:hover { - border-color: #eee #eee #ddd; -} -.nav-tabs > li.active > a, -.nav-tabs > li.active > a:hover, -.nav-tabs > li.active > a:focus { - color: #555; - cursor: default; - background-color: #fff; - border: 1px solid #ddd; - border-bottom-color: transparent; -} -.nav-tabs.nav-justified { - width: 100%; - border-bottom: 0; -} -.nav-tabs.nav-justified > li { - float: none; -} -.nav-tabs.nav-justified > li > a { - margin-bottom: 5px; - text-align: center; -} -.nav-tabs.nav-justified > .dropdown .dropdown-menu { - top: auto; - left: auto; -} -@media (min-width: 768px) { - .nav-tabs.nav-justified > li { - display: table-cell; - width: 1%; - } - .nav-tabs.nav-justified > li > a { - margin-bottom: 0; - } -} -.nav-tabs.nav-justified > li > a { - margin-right: 0; - border-radius: 4px; -} -.nav-tabs.nav-justified > .active > a, -.nav-tabs.nav-justified > .active > a:hover, -.nav-tabs.nav-justified > .active > a:focus { - border: 1px solid #ddd; -} -@media (min-width: 768px) { - .nav-tabs.nav-justified > li > a { - border-bottom: 1px solid #ddd; - border-radius: 4px 4px 0 0; - } - .nav-tabs.nav-justified > .active > a, - .nav-tabs.nav-justified > .active > a:hover, - .nav-tabs.nav-justified > .active > a:focus { - border-bottom-color: #fff; - } -} -.nav-pills > li { - float: left; -} -.nav-pills > li > a { - border-radius: 4px; -} -.nav-pills > li + li { - margin-left: 2px; -} -.nav-pills > li.active > a, -.nav-pills > li.active > a:hover, -.nav-pills > li.active > a:focus { - color: #fff; - background-color: #428bca; -} -.nav-stacked > li { - float: none; -} -.nav-stacked > li + li { - margin-top: 2px; - margin-left: 0; -} -.nav-justified { - width: 100%; -} -.nav-justified > li { - float: none; -} -.nav-justified > li > a { - margin-bottom: 5px; - text-align: center; -} -.nav-justified > .dropdown .dropdown-menu { - top: auto; - left: auto; -} -@media (min-width: 768px) { - .nav-justified > li { - display: table-cell; - width: 1%; - } - .nav-justified > li > a { - margin-bottom: 0; - } -} -.nav-tabs-justified { - border-bottom: 0; -} -.nav-tabs-justified > li > a { - margin-right: 0; - border-radius: 4px; -} -.nav-tabs-justified > .active > a, -.nav-tabs-justified > .active > a:hover, -.nav-tabs-justified > .active > a:focus { - border: 1px solid #ddd; -} -@media (min-width: 768px) { - .nav-tabs-justified > li > a { - border-bottom: 1px solid #ddd; - border-radius: 4px 4px 0 0; - } - .nav-tabs-justified > .active > a, - .nav-tabs-justified > .active > a:hover, - .nav-tabs-justified > .active > a:focus { - border-bottom-color: #fff; - } -} -.tab-content > .tab-pane { - display: none; -} -.tab-content > .active { - display: block; -} -.nav-tabs .dropdown-menu { - margin-top: -1px; - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.navbar { - position: relative; - min-height: 50px; - margin-bottom: 20px; - border: 1px solid transparent; -} -@media (min-width: 768px) { - .navbar { - border-radius: 4px; - } -} -@media (min-width: 768px) { - .navbar-header { - float: left; - } -} -.navbar-collapse { - padding-right: 15px; - padding-left: 15px; - overflow-x: visible; - -webkit-overflow-scrolling: touch; - border-top: 1px solid transparent; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); -} -.navbar-collapse.in { - overflow-y: auto; -} -@media (min-width: 768px) { - .navbar-collapse { - width: auto; - border-top: 0; - -webkit-box-shadow: none; - box-shadow: none; - } - .navbar-collapse.collapse { - display: block !important; - height: auto !important; - padding-bottom: 0; - overflow: visible !important; - } - .navbar-collapse.in { - overflow-y: visible; - } - .navbar-fixed-top .navbar-collapse, - .navbar-static-top .navbar-collapse, - .navbar-fixed-bottom .navbar-collapse { - padding-right: 0; - padding-left: 0; - } -} -.navbar-fixed-top .navbar-collapse, -.navbar-fixed-bottom .navbar-collapse { - max-height: 340px; -} -@media (max-width: 480px) and (orientation: landscape) { - .navbar-fixed-top .navbar-collapse, - .navbar-fixed-bottom .navbar-collapse { - max-height: 200px; - } -} -.container > .navbar-header, -.container-fluid > .navbar-header, -.container > .navbar-collapse, -.container-fluid > .navbar-collapse { - margin-right: -15px; - margin-left: -15px; -} -@media (min-width: 768px) { - .container > .navbar-header, - .container-fluid > .navbar-header, - .container > .navbar-collapse, - .container-fluid > .navbar-collapse { - margin-right: 0; - margin-left: 0; - } -} -.navbar-static-top { - z-index: 1000; - border-width: 0 0 1px; -} -@media (min-width: 768px) { - .navbar-static-top { - border-radius: 0; - } -} -.navbar-fixed-top, -.navbar-fixed-bottom { - position: fixed; - right: 0; - left: 0; - z-index: 1030; - -webkit-transform: translate3d(0, 0, 0); - -o-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); -} -@media (min-width: 768px) { - .navbar-fixed-top, - .navbar-fixed-bottom { - border-radius: 0; - } -} -.navbar-fixed-top { - top: 0; - border-width: 0 0 1px; -} -.navbar-fixed-bottom { - bottom: 0; - margin-bottom: 0; - border-width: 1px 0 0; -} -.navbar-brand { - float: left; - height: 50px; - padding: 15px 15px; - font-size: 18px; - line-height: 20px; -} -.navbar-brand:hover, -.navbar-brand:focus { - text-decoration: none; -} -@media (min-width: 768px) { - .navbar > .container .navbar-brand, - .navbar > .container-fluid .navbar-brand { - margin-left: -15px; - } -} -.navbar-toggle { - position: relative; - float: right; - padding: 9px 10px; - margin-top: 8px; - margin-right: 15px; - margin-bottom: 8px; - background-color: transparent; - background-image: none; - border: 1px solid transparent; - border-radius: 4px; -} -.navbar-toggle:focus { - outline: 0; -} -.navbar-toggle .icon-bar { - display: block; - width: 22px; - height: 2px; - border-radius: 1px; -} -.navbar-toggle .icon-bar + .icon-bar { - margin-top: 4px; -} -@media (min-width: 768px) { - .navbar-toggle { - display: none; - } -} -.navbar-nav { - margin: 7.5px -15px; -} -.navbar-nav > li > a { - padding-top: 10px; - padding-bottom: 10px; - line-height: 20px; -} -@media (max-width: 767px) { - .navbar-nav .open .dropdown-menu { - position: static; - float: none; - width: auto; - margin-top: 0; - background-color: transparent; - border: 0; - -webkit-box-shadow: none; - box-shadow: none; - } - .navbar-nav .open .dropdown-menu > li > a, - .navbar-nav .open .dropdown-menu .dropdown-header { - padding: 5px 15px 5px 25px; - } - .navbar-nav .open .dropdown-menu > li > a { - line-height: 20px; - } - .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-nav .open .dropdown-menu > li > a:focus { - background-image: none; - } -} -@media (min-width: 768px) { - .navbar-nav { - float: left; - margin: 0; - } - .navbar-nav > li { - float: left; - } - .navbar-nav > li > a { - padding-top: 15px; - padding-bottom: 15px; - } - .navbar-nav.navbar-right:last-child { - margin-right: -15px; - } -} -@media (min-width: 768px) { - .navbar-left { - float: left !important; - } - .navbar-right { - float: right !important; - } -} -.navbar-form { - padding: 10px 15px; - margin-top: 8px; - margin-right: -15px; - margin-bottom: 8px; - margin-left: -15px; - border-top: 1px solid transparent; - border-bottom: 1px solid transparent; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); -} -@media (min-width: 768px) { - .navbar-form .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; - } - .navbar-form .form-control { - display: inline-block; - width: auto; - vertical-align: middle; - } - .navbar-form .input-group { - display: inline-table; - vertical-align: middle; - } - .navbar-form .input-group .input-group-addon, - .navbar-form .input-group .input-group-btn, - .navbar-form .input-group .form-control { - width: auto; - } - .navbar-form .input-group > .form-control { - width: 100%; - } - .navbar-form .control-label { - margin-bottom: 0; - vertical-align: middle; - } - .navbar-form .radio, - .navbar-form .checkbox { - display: inline-block; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle; - } - .navbar-form .radio label, - .navbar-form .checkbox label { - padding-left: 0; - } - .navbar-form .radio input[type="radio"], - .navbar-form .checkbox input[type="checkbox"] { - position: relative; - margin-left: 0; - } - .navbar-form .has-feedback .form-control-feedback { - top: 0; - } -} -@media (max-width: 767px) { - .navbar-form .form-group { - margin-bottom: 5px; - } -} -@media (min-width: 768px) { - .navbar-form { - width: auto; - padding-top: 0; - padding-bottom: 0; - margin-right: 0; - margin-left: 0; - border: 0; - -webkit-box-shadow: none; - box-shadow: none; - } - .navbar-form.navbar-right:last-child { - margin-right: -15px; - } -} -.navbar-nav > li > .dropdown-menu { - margin-top: 0; - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.navbar-btn { - margin-top: 8px; - margin-bottom: 8px; -} -.navbar-btn.btn-sm { - margin-top: 10px; - margin-bottom: 10px; -} -.navbar-btn.btn-xs { - margin-top: 14px; - margin-bottom: 14px; -} -.navbar-text { - margin-top: 15px; - margin-bottom: 15px; -} -@media (min-width: 768px) { - .navbar-text { - float: left; - margin-right: 15px; - margin-left: 15px; - } - .navbar-text.navbar-right:last-child { - margin-right: 0; - } -} -.navbar-default { - background-color: #f8f8f8; - border-color: #e7e7e7; -} -.navbar-default .navbar-brand { - color: #777; -} -.navbar-default .navbar-brand:hover, -.navbar-default .navbar-brand:focus { - color: #5e5e5e; - background-color: transparent; -} -.navbar-default .navbar-text { - color: #777; -} -.navbar-default .navbar-nav > li > a { - color: #777; -} -.navbar-default .navbar-nav > li > a:hover, -.navbar-default .navbar-nav > li > a:focus { - color: #333; - background-color: transparent; -} -.navbar-default .navbar-nav > .active > a, -.navbar-default .navbar-nav > .active > a:hover, -.navbar-default .navbar-nav > .active > a:focus { - color: #555; - background-color: #e7e7e7; -} -.navbar-default .navbar-nav > .disabled > a, -.navbar-default .navbar-nav > .disabled > a:hover, -.navbar-default .navbar-nav > .disabled > a:focus { - color: #ccc; - background-color: transparent; -} -.navbar-default .navbar-toggle { - border-color: #ddd; -} -.navbar-default .navbar-toggle:hover, -.navbar-default .navbar-toggle:focus { - background-color: #ddd; -} -.navbar-default .navbar-toggle .icon-bar { - background-color: #888; -} -.navbar-default .navbar-collapse, -.navbar-default .navbar-form { - border-color: #e7e7e7; -} -.navbar-default .navbar-nav > .open > a, -.navbar-default .navbar-nav > .open > a:hover, -.navbar-default .navbar-nav > .open > a:focus { - color: #555; - background-color: #e7e7e7; -} -@media (max-width: 767px) { - .navbar-default .navbar-nav .open .dropdown-menu > li > a { - color: #777; - } - .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { - color: #333; - background-color: transparent; - } - .navbar-default .navbar-nav .open .dropdown-menu > .active > a, - .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #555; - background-color: #e7e7e7; - } - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { - color: #ccc; - background-color: transparent; - } -} -.navbar-default .navbar-link { - color: #777; -} -.navbar-default .navbar-link:hover { - color: #333; -} -.navbar-default .btn-link { - color: #777; -} -.navbar-default .btn-link:hover, -.navbar-default .btn-link:focus { - color: #333; -} -.navbar-default .btn-link[disabled]:hover, -fieldset[disabled] .navbar-default .btn-link:hover, -.navbar-default .btn-link[disabled]:focus, -fieldset[disabled] .navbar-default .btn-link:focus { - color: #ccc; -} -.navbar-inverse { - background-color: #222; - border-color: #080808; -} -.navbar-inverse .navbar-brand { - color: #777; -} -.navbar-inverse .navbar-brand:hover, -.navbar-inverse .navbar-brand:focus { - color: #fff; - background-color: transparent; -} -.navbar-inverse .navbar-text { - color: #777; -} -.navbar-inverse .navbar-nav > li > a { - color: #777; -} -.navbar-inverse .navbar-nav > li > a:hover, -.navbar-inverse .navbar-nav > li > a:focus { - color: #fff; - background-color: transparent; -} -.navbar-inverse .navbar-nav > .active > a, -.navbar-inverse .navbar-nav > .active > a:hover, -.navbar-inverse .navbar-nav > .active > a:focus { - color: #fff; - background-color: #080808; -} -.navbar-inverse .navbar-nav > .disabled > a, -.navbar-inverse .navbar-nav > .disabled > a:hover, -.navbar-inverse .navbar-nav > .disabled > a:focus { - color: #444; - background-color: transparent; -} -.navbar-inverse .navbar-toggle { - border-color: #333; -} -.navbar-inverse .navbar-toggle:hover, -.navbar-inverse .navbar-toggle:focus { - background-color: #333; -} -.navbar-inverse .navbar-toggle .icon-bar { - background-color: #fff; -} -.navbar-inverse .navbar-collapse, -.navbar-inverse .navbar-form { - border-color: #101010; -} -.navbar-inverse .navbar-nav > .open > a, -.navbar-inverse .navbar-nav > .open > a:hover, -.navbar-inverse .navbar-nav > .open > a:focus { - color: #fff; - background-color: #080808; -} -@media (max-width: 767px) { - .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { - border-color: #080808; - } - .navbar-inverse .navbar-nav .open .dropdown-menu .divider { - background-color: #080808; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { - color: #777; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { - color: #fff; - background-color: transparent; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #fff; - background-color: #080808; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { - color: #444; - background-color: transparent; - } -} -.navbar-inverse .navbar-link { - color: #777; -} -.navbar-inverse .navbar-link:hover { - color: #fff; -} -.navbar-inverse .btn-link { - color: #777; -} -.navbar-inverse .btn-link:hover, -.navbar-inverse .btn-link:focus { - color: #fff; -} -.navbar-inverse .btn-link[disabled]:hover, -fieldset[disabled] .navbar-inverse .btn-link:hover, -.navbar-inverse .btn-link[disabled]:focus, -fieldset[disabled] .navbar-inverse .btn-link:focus { - color: #444; -} -.breadcrumb { - padding: 8px 15px; - margin-bottom: 20px; - list-style: none; - background-color: #f5f5f5; - border-radius: 4px; -} -.breadcrumb > li { - display: inline-block; -} -.breadcrumb > li + li:before { - padding: 0 5px; - color: #ccc; - content: "/\00a0"; -} -.breadcrumb > .active { - color: #777; -} -.pagination { - display: inline-block; - padding-left: 0; - margin: 20px 0; - border-radius: 4px; -} -.pagination > li { - display: inline; -} -.pagination > li > a, -.pagination > li > span { - position: relative; - float: left; - padding: 6px 12px; - margin-left: -1px; - line-height: 1.42857143; - color: #428bca; - text-decoration: none; - background-color: #fff; - border: 1px solid #ddd; -} -.pagination > li:first-child > a, -.pagination > li:first-child > span { - margin-left: 0; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; -} -.pagination > li:last-child > a, -.pagination > li:last-child > span { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; -} -.pagination > li > a:hover, -.pagination > li > span:hover, -.pagination > li > a:focus, -.pagination > li > span:focus { - color: #2a6496; - background-color: #eee; - border-color: #ddd; -} -.pagination > .active > a, -.pagination > .active > span, -.pagination > .active > a:hover, -.pagination > .active > span:hover, -.pagination > .active > a:focus, -.pagination > .active > span:focus { - z-index: 2; - color: #fff; - cursor: default; - background-color: #428bca; - border-color: #428bca; -} -.pagination > .disabled > span, -.pagination > .disabled > span:hover, -.pagination > .disabled > span:focus, -.pagination > .disabled > a, -.pagination > .disabled > a:hover, -.pagination > .disabled > a:focus { - color: #777; - cursor: not-allowed; - background-color: #fff; - border-color: #ddd; -} -.pagination-lg > li > a, -.pagination-lg > li > span { - padding: 10px 16px; - font-size: 18px; -} -.pagination-lg > li:first-child > a, -.pagination-lg > li:first-child > span { - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; -} -.pagination-lg > li:last-child > a, -.pagination-lg > li:last-child > span { - border-top-right-radius: 6px; - border-bottom-right-radius: 6px; -} -.pagination-sm > li > a, -.pagination-sm > li > span { - padding: 5px 10px; - font-size: 12px; -} -.pagination-sm > li:first-child > a, -.pagination-sm > li:first-child > span { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; -} -.pagination-sm > li:last-child > a, -.pagination-sm > li:last-child > span { - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; -} -.pager { - padding-left: 0; - margin: 20px 0; - text-align: center; - list-style: none; -} -.pager li { - display: inline; -} -.pager li > a, -.pager li > span { - display: inline-block; - padding: 5px 14px; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 15px; -} -.pager li > a:hover, -.pager li > a:focus { - text-decoration: none; - background-color: #eee; -} -.pager .next > a, -.pager .next > span { - float: right; -} -.pager .previous > a, -.pager .previous > span { - float: left; -} -.pager .disabled > a, -.pager .disabled > a:hover, -.pager .disabled > a:focus, -.pager .disabled > span { - color: #777; - cursor: not-allowed; - background-color: #fff; -} -.label { - display: inline; - padding: .2em .6em .3em; - font-size: 75%; - font-weight: bold; - line-height: 1; - color: #fff; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: .25em; -} -a.label:hover, -a.label:focus { - color: #fff; - text-decoration: none; - cursor: pointer; -} -.label:empty { - display: none; -} -.btn .label { - position: relative; - top: -1px; -} -.label-default { - background-color: #777; -} -.label-default[href]:hover, -.label-default[href]:focus { - background-color: #5e5e5e; -} -.label-primary { - background-color: #428bca; -} -.label-primary[href]:hover, -.label-primary[href]:focus { - background-color: #3071a9; -} -.label-success { - background-color: #5cb85c; -} -.label-success[href]:hover, -.label-success[href]:focus { - background-color: #449d44; -} -.label-info { - background-color: #5bc0de; -} -.label-info[href]:hover, -.label-info[href]:focus { - background-color: #31b0d5; -} -.label-warning { - background-color: #f0ad4e; -} -.label-warning[href]:hover, -.label-warning[href]:focus { - background-color: #ec971f; -} -.label-danger { - background-color: #d9534f; -} -.label-danger[href]:hover, -.label-danger[href]:focus { - background-color: #c9302c; -} -.badge { - display: inline-block; - min-width: 10px; - padding: 3px 7px; - font-size: 12px; - font-weight: bold; - line-height: 1; - color: #fff; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - background-color: #777; - border-radius: 10px; -} -.badge:empty { - display: none; -} -.btn .badge { - position: relative; - top: -1px; -} -.btn-xs .badge { - top: 0; - padding: 1px 5px; -} -a.badge:hover, -a.badge:focus { - color: #fff; - text-decoration: none; - cursor: pointer; -} -a.list-group-item.active > .badge, -.nav-pills > .active > a > .badge { - color: #428bca; - background-color: #fff; -} -.nav-pills > li > a > .badge { - margin-left: 3px; -} -.jumbotron { - padding: 30px; - margin-bottom: 30px; - color: inherit; - background-color: #eee; -} -.jumbotron h1, -.jumbotron .h1 { - color: inherit; -} -.jumbotron p { - margin-bottom: 15px; - font-size: 21px; - font-weight: 200; -} -.jumbotron > hr { - border-top-color: #d5d5d5; -} -.container .jumbotron { - border-radius: 6px; -} -.jumbotron .container { - max-width: 100%; -} -@media screen and (min-width: 768px) { - .jumbotron { - padding-top: 48px; - padding-bottom: 48px; - } - .container .jumbotron { - padding-right: 60px; - padding-left: 60px; - } - .jumbotron h1, - .jumbotron .h1 { - font-size: 63px; - } -} -.thumbnail { - display: block; - padding: 4px; - margin-bottom: 20px; - line-height: 1.42857143; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 4px; - -webkit-transition: all .2s ease-in-out; - -o-transition: all .2s ease-in-out; - transition: all .2s ease-in-out; -} -.thumbnail > img, -.thumbnail a > img { - margin-right: auto; - margin-left: auto; -} -a.thumbnail:hover, -a.thumbnail:focus, -a.thumbnail.active { - border-color: #428bca; -} -.thumbnail .caption { - padding: 9px; - color: #333; -} -.alert { - padding: 15px; - margin-bottom: 20px; - border: 1px solid transparent; - border-radius: 4px; -} -.alert h4 { - margin-top: 0; - color: inherit; -} -.alert .alert-link { - font-weight: bold; -} -.alert > p, -.alert > ul { - margin-bottom: 0; -} -.alert > p + p { - margin-top: 5px; -} -.alert-dismissable, -.alert-dismissible { - padding-right: 35px; -} -.alert-dismissable .close, -.alert-dismissible .close { - position: relative; - top: -2px; - right: -21px; - color: inherit; -} -.alert-success { - color: #3c763d; - background-color: #dff0d8; - border-color: #d6e9c6; -} -.alert-success hr { - border-top-color: #c9e2b3; -} -.alert-success .alert-link { - color: #2b542c; -} -.alert-info { - color: #31708f; - background-color: #d9edf7; - border-color: #bce8f1; -} -.alert-info hr { - border-top-color: #a6e1ec; -} -.alert-info .alert-link { - color: #245269; -} -.alert-warning { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #faebcc; -} -.alert-warning hr { - border-top-color: #f7e1b5; -} -.alert-warning .alert-link { - color: #66512c; -} -.alert-danger { - color: #a94442; - background-color: #f2dede; - border-color: #ebccd1; -} -.alert-danger hr { - border-top-color: #e4b9c0; -} -.alert-danger .alert-link { - color: #843534; -} -@-webkit-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} -@-o-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} -@keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} -.progress { - height: 20px; - margin-bottom: 20px; - overflow: hidden; - background-color: #f5f5f5; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); -} -.progress-bar { - float: left; - width: 0; - height: 100%; - font-size: 12px; - line-height: 20px; - color: #fff; - text-align: center; - background-color: #428bca; - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); - -webkit-transition: width .6s ease; - -o-transition: width .6s ease; - transition: width .6s ease; -} -.progress-striped .progress-bar, -.progress-bar-striped { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - -webkit-background-size: 40px 40px; - background-size: 40px 40px; -} -.progress.active .progress-bar, -.progress-bar.active { - -webkit-animation: progress-bar-stripes 2s linear infinite; - -o-animation: progress-bar-stripes 2s linear infinite; - animation: progress-bar-stripes 2s linear infinite; -} -.progress-bar[aria-valuenow="1"], -.progress-bar[aria-valuenow="2"] { - min-width: 30px; -} -.progress-bar[aria-valuenow="0"] { - min-width: 30px; - color: #777; - background-color: transparent; - background-image: none; - -webkit-box-shadow: none; - box-shadow: none; -} -.progress-bar-success { - background-color: #5cb85c; -} -.progress-striped .progress-bar-success { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} -.progress-bar-info { - background-color: #5bc0de; -} -.progress-striped .progress-bar-info { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} -.progress-bar-warning { - background-color: #f0ad4e; -} -.progress-striped .progress-bar-warning { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} -.progress-bar-danger { - background-color: #d9534f; -} -.progress-striped .progress-bar-danger { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} -.media, -.media-body { - overflow: hidden; - zoom: 1; -} -.media, -.media .media { - margin-top: 15px; -} -.media:first-child { - margin-top: 0; -} -.media-object { - display: block; -} -.media-heading { - margin: 0 0 5px; -} -.media > .pull-left { - margin-right: 10px; -} -.media > .pull-right { - margin-left: 10px; -} -.media-list { - padding-left: 0; - list-style: none; -} -.list-group { - padding-left: 0; - margin-bottom: 20px; -} -.list-group-item { - position: relative; - display: block; - padding: 10px 15px; - margin-bottom: -1px; - background-color: #fff; - border: 1px solid #ddd; -} -.list-group-item:first-child { - border-top-left-radius: 4px; - border-top-right-radius: 4px; -} -.list-group-item:last-child { - margin-bottom: 0; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; -} -.list-group-item > .badge { - float: right; -} -.list-group-item > .badge + .badge { - margin-right: 5px; -} -a.list-group-item { - color: #555; -} -a.list-group-item .list-group-item-heading { - color: #333; -} -a.list-group-item:hover, -a.list-group-item:focus { - color: #555; - text-decoration: none; - background-color: #f5f5f5; -} -.list-group-item.disabled, -.list-group-item.disabled:hover, -.list-group-item.disabled:focus { - color: #777; - background-color: #eee; -} -.list-group-item.disabled .list-group-item-heading, -.list-group-item.disabled:hover .list-group-item-heading, -.list-group-item.disabled:focus .list-group-item-heading { - color: inherit; -} -.list-group-item.disabled .list-group-item-text, -.list-group-item.disabled:hover .list-group-item-text, -.list-group-item.disabled:focus .list-group-item-text { - color: #777; -} -.list-group-item.active, -.list-group-item.active:hover, -.list-group-item.active:focus { - z-index: 2; - color: #fff; - background-color: #428bca; - border-color: #428bca; -} -.list-group-item.active .list-group-item-heading, -.list-group-item.active:hover .list-group-item-heading, -.list-group-item.active:focus .list-group-item-heading, -.list-group-item.active .list-group-item-heading > small, -.list-group-item.active:hover .list-group-item-heading > small, -.list-group-item.active:focus .list-group-item-heading > small, -.list-group-item.active .list-group-item-heading > .small, -.list-group-item.active:hover .list-group-item-heading > .small, -.list-group-item.active:focus .list-group-item-heading > .small { - color: inherit; -} -.list-group-item.active .list-group-item-text, -.list-group-item.active:hover .list-group-item-text, -.list-group-item.active:focus .list-group-item-text { - color: #e1edf7; -} -.list-group-item-success { - color: #3c763d; - background-color: #dff0d8; -} -a.list-group-item-success { - color: #3c763d; -} -a.list-group-item-success .list-group-item-heading { - color: inherit; -} -a.list-group-item-success:hover, -a.list-group-item-success:focus { - color: #3c763d; - background-color: #d0e9c6; -} -a.list-group-item-success.active, -a.list-group-item-success.active:hover, -a.list-group-item-success.active:focus { - color: #fff; - background-color: #3c763d; - border-color: #3c763d; -} -.list-group-item-info { - color: #31708f; - background-color: #d9edf7; -} -a.list-group-item-info { - color: #31708f; -} -a.list-group-item-info .list-group-item-heading { - color: inherit; -} -a.list-group-item-info:hover, -a.list-group-item-info:focus { - color: #31708f; - background-color: #c4e3f3; -} -a.list-group-item-info.active, -a.list-group-item-info.active:hover, -a.list-group-item-info.active:focus { - color: #fff; - background-color: #31708f; - border-color: #31708f; -} -.list-group-item-warning { - color: #8a6d3b; - background-color: #fcf8e3; -} -a.list-group-item-warning { - color: #8a6d3b; -} -a.list-group-item-warning .list-group-item-heading { - color: inherit; -} -a.list-group-item-warning:hover, -a.list-group-item-warning:focus { - color: #8a6d3b; - background-color: #faf2cc; -} -a.list-group-item-warning.active, -a.list-group-item-warning.active:hover, -a.list-group-item-warning.active:focus { - color: #fff; - background-color: #8a6d3b; - border-color: #8a6d3b; -} -.list-group-item-danger { - color: #a94442; - background-color: #f2dede; -} -a.list-group-item-danger { - color: #a94442; -} -a.list-group-item-danger .list-group-item-heading { - color: inherit; -} -a.list-group-item-danger:hover, -a.list-group-item-danger:focus { - color: #a94442; - background-color: #ebcccc; -} -a.list-group-item-danger.active, -a.list-group-item-danger.active:hover, -a.list-group-item-danger.active:focus { - color: #fff; - background-color: #a94442; - border-color: #a94442; -} -.list-group-item-heading { - margin-top: 0; - margin-bottom: 5px; -} -.list-group-item-text { - margin-bottom: 0; - line-height: 1.3; -} -.panel { - margin-bottom: 20px; - background-color: #fff; - border: 1px solid transparent; - border-radius: 4px; - -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); - box-shadow: 0 1px 1px rgba(0, 0, 0, .05); -} -.panel-body { - padding: 15px; -} -.panel-heading { - padding: 10px 15px; - border-bottom: 1px solid transparent; - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel-heading > .dropdown .dropdown-toggle { - color: inherit; -} -.panel-title { - margin-top: 0; - margin-bottom: 0; - font-size: 16px; - color: inherit; -} -.panel-title > a { - color: inherit; -} -.panel-footer { - padding: 10px 15px; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} -.panel > .list-group { - margin-bottom: 0; -} -.panel > .list-group .list-group-item { - border-width: 1px 0; - border-radius: 0; -} -.panel > .list-group:first-child .list-group-item:first-child { - border-top: 0; - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel > .list-group:last-child .list-group-item:last-child { - border-bottom: 0; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} -.panel-heading + .list-group .list-group-item:first-child { - border-top-width: 0; -} -.list-group + .panel-footer { - border-top-width: 0; -} -.panel > .table, -.panel > .table-responsive > .table, -.panel > .panel-collapse > .table { - margin-bottom: 0; -} -.panel > .table:first-child, -.panel > .table-responsive:first-child > .table:first-child { - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, -.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, -.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, -.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { - border-top-left-radius: 3px; -} -.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, -.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, -.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, -.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { - border-top-right-radius: 3px; -} -.panel > .table:last-child, -.panel > .table-responsive:last-child > .table:last-child { - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} -.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, -.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { - border-bottom-left-radius: 3px; -} -.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, -.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { - border-bottom-right-radius: 3px; -} -.panel > .panel-body + .table, -.panel > .panel-body + .table-responsive { - border-top: 1px solid #ddd; -} -.panel > .table > tbody:first-child > tr:first-child th, -.panel > .table > tbody:first-child > tr:first-child td { - border-top: 0; -} -.panel > .table-bordered, -.panel > .table-responsive > .table-bordered { - border: 0; -} -.panel > .table-bordered > thead > tr > th:first-child, -.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, -.panel > .table-bordered > tbody > tr > th:first-child, -.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, -.panel > .table-bordered > tfoot > tr > th:first-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, -.panel > .table-bordered > thead > tr > td:first-child, -.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, -.panel > .table-bordered > tbody > tr > td:first-child, -.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, -.panel > .table-bordered > tfoot > tr > td:first-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { - border-left: 0; -} -.panel > .table-bordered > thead > tr > th:last-child, -.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, -.panel > .table-bordered > tbody > tr > th:last-child, -.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, -.panel > .table-bordered > tfoot > tr > th:last-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, -.panel > .table-bordered > thead > tr > td:last-child, -.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, -.panel > .table-bordered > tbody > tr > td:last-child, -.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, -.panel > .table-bordered > tfoot > tr > td:last-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { - border-right: 0; -} -.panel > .table-bordered > thead > tr:first-child > td, -.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, -.panel > .table-bordered > tbody > tr:first-child > td, -.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, -.panel > .table-bordered > thead > tr:first-child > th, -.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, -.panel > .table-bordered > tbody > tr:first-child > th, -.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { - border-bottom: 0; -} -.panel > .table-bordered > tbody > tr:last-child > td, -.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, -.panel > .table-bordered > tfoot > tr:last-child > td, -.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, -.panel > .table-bordered > tbody > tr:last-child > th, -.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, -.panel > .table-bordered > tfoot > tr:last-child > th, -.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { - border-bottom: 0; -} -.panel > .table-responsive { - margin-bottom: 0; - border: 0; -} -.panel-group { - margin-bottom: 20px; -} -.panel-group .panel { - margin-bottom: 0; - border-radius: 4px; -} -.panel-group .panel + .panel { - margin-top: 5px; -} -.panel-group .panel-heading { - border-bottom: 0; -} -.panel-group .panel-heading + .panel-collapse > .panel-body { - border-top: 1px solid #ddd; -} -.panel-group .panel-footer { - border-top: 0; -} -.panel-group .panel-footer + .panel-collapse .panel-body { - border-bottom: 1px solid #ddd; -} -.panel-default { - border-color: #ddd; -} -.panel-default > .panel-heading { - color: #333; - background-color: #f5f5f5; - border-color: #ddd; -} -.panel-default > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #ddd; -} -.panel-default > .panel-heading .badge { - color: #f5f5f5; - background-color: #333; -} -.panel-default > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #ddd; -} -.panel-primary { - border-color: #428bca; -} -.panel-primary > .panel-heading { - color: #fff; - background-color: #428bca; - border-color: #428bca; -} -.panel-primary > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #428bca; -} -.panel-primary > .panel-heading .badge { - color: #428bca; - background-color: #fff; -} -.panel-primary > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #428bca; -} -.panel-success { - border-color: #d6e9c6; -} -.panel-success > .panel-heading { - color: #3c763d; - background-color: #dff0d8; - border-color: #d6e9c6; -} -.panel-success > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #d6e9c6; -} -.panel-success > .panel-heading .badge { - color: #dff0d8; - background-color: #3c763d; -} -.panel-success > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #d6e9c6; -} -.panel-info { - border-color: #bce8f1; -} -.panel-info > .panel-heading { - color: #31708f; - background-color: #d9edf7; - border-color: #bce8f1; -} -.panel-info > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #bce8f1; -} -.panel-info > .panel-heading .badge { - color: #d9edf7; - background-color: #31708f; -} -.panel-info > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #bce8f1; -} -.panel-warning { - border-color: #faebcc; -} -.panel-warning > .panel-heading { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #faebcc; -} -.panel-warning > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #faebcc; -} -.panel-warning > .panel-heading .badge { - color: #fcf8e3; - background-color: #8a6d3b; -} -.panel-warning > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #faebcc; -} -.panel-danger { - border-color: #ebccd1; -} -.panel-danger > .panel-heading { - color: #a94442; - background-color: #f2dede; - border-color: #ebccd1; -} -.panel-danger > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #ebccd1; -} -.panel-danger > .panel-heading .badge { - color: #f2dede; - background-color: #a94442; -} -.panel-danger > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #ebccd1; -} -.embed-responsive { - position: relative; - display: block; - height: 0; - padding: 0; - overflow: hidden; -} -.embed-responsive .embed-responsive-item, -.embed-responsive iframe, -.embed-responsive embed, -.embed-responsive object { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - border: 0; -} -.embed-responsive.embed-responsive-16by9 { - padding-bottom: 56.25%; -} -.embed-responsive.embed-responsive-4by3 { - padding-bottom: 75%; -} -.well { - min-height: 20px; - padding: 19px; - margin-bottom: 20px; - background-color: #f5f5f5; - border: 1px solid #e3e3e3; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); -} -.well blockquote { - border-color: #ddd; - border-color: rgba(0, 0, 0, .15); -} -.well-lg { - padding: 24px; - border-radius: 6px; -} -.well-sm { - padding: 9px; - border-radius: 3px; -} -.close { - float: right; - font-size: 21px; - font-weight: bold; - line-height: 1; - color: #000; - text-shadow: 0 1px 0 #fff; - filter: alpha(opacity=20); - opacity: .2; -} -.close:hover, -.close:focus { - color: #000; - text-decoration: none; - cursor: pointer; - filter: alpha(opacity=50); - opacity: .5; -} -button.close { - -webkit-appearance: none; - padding: 0; - cursor: pointer; - background: transparent; - border: 0; -} -.modal-open { - overflow: hidden; -} -.modal { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1050; - display: none; - overflow: hidden; - -webkit-overflow-scrolling: touch; - outline: 0; -} -.modal.fade .modal-dialog { - -webkit-transition: -webkit-transform .3s ease-out; - -o-transition: -o-transform .3s ease-out; - transition: transform .3s ease-out; - -webkit-transform: translate3d(0, -25%, 0); - -o-transform: translate3d(0, -25%, 0); - transform: translate3d(0, -25%, 0); -} -.modal.in .modal-dialog { - -webkit-transform: translate3d(0, 0, 0); - -o-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); -} -.modal-open .modal { - overflow-x: hidden; - overflow-y: auto; -} -.modal-dialog { - position: relative; - width: auto; - margin: 10px; -} -.modal-content { - position: relative; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #999; - border: 1px solid rgba(0, 0, 0, .2); - border-radius: 6px; - outline: 0; - -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); - box-shadow: 0 3px 9px rgba(0, 0, 0, .5); -} -.modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1040; - background-color: #000; -} -.modal-backdrop.fade { - filter: alpha(opacity=0); - opacity: 0; -} -.modal-backdrop.in { - filter: alpha(opacity=50); - opacity: .5; -} -.modal-header { - min-height: 16.42857143px; - padding: 15px; - border-bottom: 1px solid #e5e5e5; -} -.modal-header .close { - margin-top: -2px; -} -.modal-title { - margin: 0; - line-height: 1.42857143; -} -.modal-body { - position: relative; - padding: 15px; -} -.modal-footer { - padding: 15px; - text-align: right; - border-top: 1px solid #e5e5e5; -} -.modal-footer .btn + .btn { - margin-bottom: 0; - margin-left: 5px; -} -.modal-footer .btn-group .btn + .btn { - margin-left: -1px; -} -.modal-footer .btn-block + .btn-block { - margin-left: 0; -} -.modal-scrollbar-measure { - position: absolute; - top: -9999px; - width: 50px; - height: 50px; - overflow: scroll; -} -@media (min-width: 768px) { - .modal-dialog { - width: 600px; - margin: 30px auto; - } - .modal-content { - -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); - box-shadow: 0 5px 15px rgba(0, 0, 0, .5); - } - .modal-sm { - width: 300px; - } -} -@media (min-width: 992px) { - .modal-lg { - width: 900px; - } -} -.tooltip { - position: absolute; - z-index: 1070; - display: block; - font-size: 12px; - line-height: 1.4; - visibility: visible; - filter: alpha(opacity=0); - opacity: 0; -} -.tooltip.in { - filter: alpha(opacity=90); - opacity: .9; -} -.tooltip.top { - padding: 5px 0; - margin-top: -3px; -} -.tooltip.right { - padding: 0 5px; - margin-left: 3px; -} -.tooltip.bottom { - padding: 5px 0; - margin-top: 3px; -} -.tooltip.left { - padding: 0 5px; - margin-left: -3px; -} -.tooltip-inner { - max-width: 200px; - padding: 3px 8px; - color: #fff; - text-align: center; - text-decoration: none; - background-color: #000; - border-radius: 4px; -} -.tooltip-arrow { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} -.tooltip.top .tooltip-arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-width: 5px 5px 0; - border-top-color: #000; -} -.tooltip.top-left .tooltip-arrow { - bottom: 0; - left: 5px; - border-width: 5px 5px 0; - border-top-color: #000; -} -.tooltip.top-right .tooltip-arrow { - right: 5px; - bottom: 0; - border-width: 5px 5px 0; - border-top-color: #000; -} -.tooltip.right .tooltip-arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-width: 5px 5px 5px 0; - border-right-color: #000; -} -.tooltip.left .tooltip-arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-width: 5px 0 5px 5px; - border-left-color: #000; -} -.tooltip.bottom .tooltip-arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} -.tooltip.bottom-left .tooltip-arrow { - top: 0; - left: 5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} -.tooltip.bottom-right .tooltip-arrow { - top: 0; - right: 5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} -.popover { - position: absolute; - top: 0; - left: 0; - z-index: 1060; - display: none; - max-width: 276px; - padding: 1px; - text-align: left; - white-space: normal; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, .2); - border-radius: 6px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); - box-shadow: 0 5px 10px rgba(0, 0, 0, .2); -} -.popover.top { - margin-top: -10px; -} -.popover.right { - margin-left: 10px; -} -.popover.bottom { - margin-top: 10px; -} -.popover.left { - margin-left: -10px; -} -.popover-title { - padding: 8px 14px; - margin: 0; - font-size: 14px; - font-weight: normal; - line-height: 18px; - background-color: #f7f7f7; - border-bottom: 1px solid #ebebeb; - border-radius: 5px 5px 0 0; -} -.popover-content { - padding: 9px 14px; -} -.popover > .arrow, -.popover > .arrow:after { - position: absolute; - display: block; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} -.popover > .arrow { - border-width: 11px; -} -.popover > .arrow:after { - content: ""; - border-width: 10px; -} -.popover.top > .arrow { - bottom: -11px; - left: 50%; - margin-left: -11px; - border-top-color: #999; - border-top-color: rgba(0, 0, 0, .25); - border-bottom-width: 0; -} -.popover.top > .arrow:after { - bottom: 1px; - margin-left: -10px; - content: " "; - border-top-color: #fff; - border-bottom-width: 0; -} -.popover.right > .arrow { - top: 50%; - left: -11px; - margin-top: -11px; - border-right-color: #999; - border-right-color: rgba(0, 0, 0, .25); - border-left-width: 0; -} -.popover.right > .arrow:after { - bottom: -10px; - left: 1px; - content: " "; - border-right-color: #fff; - border-left-width: 0; -} -.popover.bottom > .arrow { - top: -11px; - left: 50%; - margin-left: -11px; - border-top-width: 0; - border-bottom-color: #999; - border-bottom-color: rgba(0, 0, 0, .25); -} -.popover.bottom > .arrow:after { - top: 1px; - margin-left: -10px; - content: " "; - border-top-width: 0; - border-bottom-color: #fff; -} -.popover.left > .arrow { - top: 50%; - right: -11px; - margin-top: -11px; - border-right-width: 0; - border-left-color: #999; - border-left-color: rgba(0, 0, 0, .25); -} -.popover.left > .arrow:after { - right: 1px; - bottom: -10px; - content: " "; - border-right-width: 0; - border-left-color: #fff; -} -.carousel { - position: relative; -} -.carousel-inner { - position: relative; - width: 100%; - overflow: hidden; -} -.carousel-inner > .item { - position: relative; - display: none; - -webkit-transition: .6s ease-in-out left; - -o-transition: .6s ease-in-out left; - transition: .6s ease-in-out left; -} -.carousel-inner > .item > img, -.carousel-inner > .item > a > img { - line-height: 1; -} -.carousel-inner > .active, -.carousel-inner > .next, -.carousel-inner > .prev { - display: block; -} -.carousel-inner > .active { - left: 0; -} -.carousel-inner > .next, -.carousel-inner > .prev { - position: absolute; - top: 0; - width: 100%; -} -.carousel-inner > .next { - left: 100%; -} -.carousel-inner > .prev { - left: -100%; -} -.carousel-inner > .next.left, -.carousel-inner > .prev.right { - left: 0; -} -.carousel-inner > .active.left { - left: -100%; -} -.carousel-inner > .active.right { - left: 100%; -} -.carousel-control { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 15%; - font-size: 20px; - color: #fff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, .6); - filter: alpha(opacity=50); - opacity: .5; -} -.carousel-control.left { - background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); - background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); - background-repeat: repeat-x; -} -.carousel-control.right { - right: 0; - left: auto; - background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); - background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); - background-repeat: repeat-x; -} -.carousel-control:hover, -.carousel-control:focus { - color: #fff; - text-decoration: none; - filter: alpha(opacity=90); - outline: 0; - opacity: .9; -} -.carousel-control .icon-prev, -.carousel-control .icon-next, -.carousel-control .glyphicon-chevron-left, -.carousel-control .glyphicon-chevron-right { - position: absolute; - top: 50%; - z-index: 5; - display: inline-block; -} -.carousel-control .icon-prev, -.carousel-control .glyphicon-chevron-left { - left: 50%; - margin-left: -10px; -} -.carousel-control .icon-next, -.carousel-control .glyphicon-chevron-right { - right: 50%; - margin-right: -10px; -} -.carousel-control .icon-prev, -.carousel-control .icon-next { - width: 20px; - height: 20px; - margin-top: -10px; - font-family: serif; -} -.carousel-control .icon-prev:before { - content: '\2039'; -} -.carousel-control .icon-next:before { - content: '\203a'; -} -.carousel-indicators { - position: absolute; - bottom: 10px; - left: 50%; - z-index: 15; - width: 60%; - padding-left: 0; - margin-left: -30%; - text-align: center; - list-style: none; -} -.carousel-indicators li { - display: inline-block; - width: 10px; - height: 10px; - margin: 1px; - text-indent: -999px; - cursor: pointer; - background-color: #000 \9; - background-color: rgba(0, 0, 0, 0); - border: 1px solid #fff; - border-radius: 10px; -} -.carousel-indicators .active { - width: 12px; - height: 12px; - margin: 0; - background-color: #fff; -} -.carousel-caption { - position: absolute; - right: 15%; - bottom: 20px; - left: 15%; - z-index: 10; - padding-top: 20px; - padding-bottom: 20px; - color: #fff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, .6); -} -.carousel-caption .btn { - text-shadow: none; -} -@media screen and (min-width: 768px) { - .carousel-control .glyphicon-chevron-left, - .carousel-control .glyphicon-chevron-right, - .carousel-control .icon-prev, - .carousel-control .icon-next { - width: 30px; - height: 30px; - margin-top: -15px; - font-size: 30px; - } - .carousel-control .glyphicon-chevron-left, - .carousel-control .icon-prev { - margin-left: -15px; - } - .carousel-control .glyphicon-chevron-right, - .carousel-control .icon-next { - margin-right: -15px; - } - .carousel-caption { - right: 20%; - left: 20%; - padding-bottom: 30px; - } - .carousel-indicators { - bottom: 20px; - } -} -.clearfix:before, -.clearfix:after, -.dl-horizontal dd:before, -.dl-horizontal dd:after, -.container:before, -.container:after, -.container-fluid:before, -.container-fluid:after, -.row:before, -.row:after, -.form-horizontal .form-group:before, -.form-horizontal .form-group:after, -.btn-toolbar:before, -.btn-toolbar:after, -.btn-group-vertical > .btn-group:before, -.btn-group-vertical > .btn-group:after, -.nav:before, -.nav:after, -.navbar:before, -.navbar:after, -.navbar-header:before, -.navbar-header:after, -.navbar-collapse:before, -.navbar-collapse:after, -.pager:before, -.pager:after, -.panel-body:before, -.panel-body:after, -.modal-footer:before, -.modal-footer:after { - display: table; - content: " "; -} -.clearfix:after, -.dl-horizontal dd:after, -.container:after, -.container-fluid:after, -.row:after, -.form-horizontal .form-group:after, -.btn-toolbar:after, -.btn-group-vertical > .btn-group:after, -.nav:after, -.navbar:after, -.navbar-header:after, -.navbar-collapse:after, -.pager:after, -.panel-body:after, -.modal-footer:after { - clear: both; -} -.center-block { - display: block; - margin-right: auto; - margin-left: auto; -} -.pull-right { - float: right !important; -} -.pull-left { - float: left !important; -} -.hide { - display: none !important; -} -.show { - display: block !important; -} -.invisible { - visibility: hidden; -} -.text-hide { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0; -} -.hidden { - display: none !important; - visibility: hidden !important; -} -.affix { - position: fixed; - -webkit-transform: translate3d(0, 0, 0); - -o-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); -} -@-ms-viewport { - width: device-width; -} -.visible-xs, -.visible-sm, -.visible-md, -.visible-lg { - display: none !important; -} -.visible-xs-block, -.visible-xs-inline, -.visible-xs-inline-block, -.visible-sm-block, -.visible-sm-inline, -.visible-sm-inline-block, -.visible-md-block, -.visible-md-inline, -.visible-md-inline-block, -.visible-lg-block, -.visible-lg-inline, -.visible-lg-inline-block { - display: none !important; -} -@media (max-width: 767px) { - .visible-xs { - display: block !important; - } - table.visible-xs { - display: table; - } - tr.visible-xs { - display: table-row !important; - } - th.visible-xs, - td.visible-xs { - display: table-cell !important; - } -} -@media (max-width: 767px) { - .visible-xs-block { - display: block !important; - } -} -@media (max-width: 767px) { - .visible-xs-inline { - display: inline !important; - } -} -@media (max-width: 767px) { - .visible-xs-inline-block { - display: inline-block !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm { - display: block !important; - } - table.visible-sm { - display: table; - } - tr.visible-sm { - display: table-row !important; - } - th.visible-sm, - td.visible-sm { - display: table-cell !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-block { - display: block !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-inline { - display: inline !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-inline-block { - display: inline-block !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md { - display: block !important; - } - table.visible-md { - display: table; - } - tr.visible-md { - display: table-row !important; - } - th.visible-md, - td.visible-md { - display: table-cell !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-block { - display: block !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-inline { - display: inline !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-inline-block { - display: inline-block !important; - } -} -@media (min-width: 1200px) { - .visible-lg { - display: block !important; - } - table.visible-lg { - display: table; - } - tr.visible-lg { - display: table-row !important; - } - th.visible-lg, - td.visible-lg { - display: table-cell !important; - } -} -@media (min-width: 1200px) { - .visible-lg-block { - display: block !important; - } -} -@media (min-width: 1200px) { - .visible-lg-inline { - display: inline !important; - } -} -@media (min-width: 1200px) { - .visible-lg-inline-block { - display: inline-block !important; - } -} -@media (max-width: 767px) { - .hidden-xs { - display: none !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .hidden-sm { - display: none !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .hidden-md { - display: none !important; - } -} -@media (min-width: 1200px) { - .hidden-lg { - display: none !important; - } -} -.visible-print { - display: none !important; -} -@media print { - .visible-print { - display: block !important; - } - table.visible-print { - display: table; - } - tr.visible-print { - display: table-row !important; - } - th.visible-print, - td.visible-print { - display: table-cell !important; - } -} -.visible-print-block { - display: none !important; -} -@media print { - .visible-print-block { - display: block !important; - } -} -.visible-print-inline { - display: none !important; -} -@media print { - .visible-print-inline { - display: inline !important; - } -} -.visible-print-inline-block { - display: none !important; -} -@media print { - .visible-print-inline-block { - display: inline-block !important; - } -} -@media print { - .hidden-print { - display: none !important; - } -} -/*# sourceMappingURL=bootstrap.css.map */ diff --git a/syweb/webclient/components/fileInput/file-input-directive.js b/syweb/webclient/components/fileInput/file-input-directive.js deleted file mode 100644 index e530c06085..0000000000 --- a/syweb/webclient/components/fileInput/file-input-directive.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -'use strict'; - -/* - * Transform an element into an image file input button. - * Watch to the passed variable change. It will contain the selected HTML5 file object. - */ -angular.module('mFileInput', []) -.directive('mFileInput', function() { - return { - restrict: 'A', - transclude: 'true', - // FIXME: add back in accept="image/*" when needed - e.g. for avatars - template: '<div ng-transclude></div><input ng-hide="true" type="file"/>', - scope: { - selectedFile: '=mFileInput' - }, - - link: function(scope, element, attrs, ctrl) { - - // Check if HTML5 file selection is supported - if (window.FileList) { - element.bind("click", function() { - element.find("input")[0].click(); - element.find("input").bind("change", function(e) { - scope.selectedFile = this.files[0]; - scope.$apply(); - }); - }); - } - else { - setTimeout(function() { - element.attr("disabled", true); - element.attr("title", "The app uses the HTML5 File API to send files. Your browser does not support it."); - }, 1); - } - - // Change the mouse icon on mouseover on this element - element.css("cursor", "pointer"); - } - }; -}); \ No newline at end of file diff --git a/syweb/webclient/components/fileUpload/file-upload-service.js b/syweb/webclient/components/fileUpload/file-upload-service.js deleted file mode 100644 index 9a67096c5f..0000000000 --- a/syweb/webclient/components/fileUpload/file-upload-service.js +++ /dev/null @@ -1,208 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -'use strict'; - -// TODO determine if this is really required as a separate service to matrixService. -/* - * Upload an HTML5 file to a server - */ -angular.module('mFileUpload', ['matrixService', 'mUtilities']) -.service('mFileUpload', ['$q', 'matrixService', 'mUtilities', function ($q, matrixService, mUtilities) { - - /* - * Upload an HTML5 file or blob to a server and returned a promise - * that will provide the URL of the uploaded file. - * @param {File|Blob} file the file data to send - */ - this.uploadFile = function(file) { - var deferred = $q.defer(); - console.log("Uploading " + file.name + "... to /_matrix/content"); - matrixService.uploadContent(file).then( - function(response) { - var content_url = response.data.content_token; - console.log(" -> Successfully uploaded! Available at " + content_url); - deferred.resolve(content_url); - }, - function(error) { - console.log(" -> Failed to upload " + file.name); - deferred.reject(error); - } - ); - - return deferred.promise; - }; - - /* - * Upload an file plus generate a thumbnail of it (if possible) and upload it so that - * we will have all information to fulfill an file/image message request - * @param {File} file the file to send - * @param {Integer} thumbnailSize the max side size of the thumbnail to create - * @returns {promise} A promise that will be resolved by a message object - * ready to be send with the Matrix API - */ - this.uploadFileAndThumbnail = function(file, thumbnailSize) { - var self = this; - var deferred = $q.defer(); - - console.log("uploadFileAndThumbnail " + file.name + " - thumbnailSize: " + thumbnailSize); - - // The message structure that will be returned in the promise will look something like: - var message = { -/* - msgtype: "m.image", - url: undefined, - body: "Image", - info: { - size: undefined, - w: undefined, - h: undefined, - mimetype: undefined - }, - thumbnail_url: undefined, - thumbnail_info: { - size: undefined, - w: undefined, - h: undefined, - mimetype: undefined - } -*/ - }; - - if (file.type.indexOf("image/") === 0) { - // it's an image - try to do clientside thumbnailing. - mUtilities.getImageSize(file).then( - function(size) { - console.log("image size: " + JSON.stringify(size)); - - // The final operation: send file - var uploadImage = function() { - self.uploadFile(file).then( - function(url) { - // Update message metadata - message.url = url; - message.msgtype = "m.image"; - message.body = file.name; - message.info = { - size: file.size, - w: size.width, - h: size.height, - mimetype: file.type - }; - - // If there is no thumbnail (because the original image is smaller than thumbnailSize), - // reuse the original image info for thumbnail data - if (!message.thumbnail_url) { - message.thumbnail_url = message.url; - message.thumbnail_info = message.info; - } - - // We are done - deferred.resolve(message); - }, - function(error) { - console.log(" -> Can't upload image"); - deferred.reject(error); - } - ); - }; - - // Create a thumbnail if the image size exceeds thumbnailSize - if (Math.max(size.width, size.height) > thumbnailSize) { - console.log(" Creating thumbnail..."); - mUtilities.resizeImage(file, thumbnailSize).then( - function(thumbnailBlob) { - - // Get its size - mUtilities.getImageSize(thumbnailBlob).then( - function(thumbnailSize) { - console.log(" -> Thumbnail size: " + JSON.stringify(thumbnailSize)); - - // Upload it to the server - self.uploadFile(thumbnailBlob).then( - function(thumbnailUrl) { - - // Update image message data - message.thumbnail_url = thumbnailUrl; - message.thumbnail_info = { - size: thumbnailBlob.size, - w: thumbnailSize.width, - h: thumbnailSize.height, - mimetype: thumbnailBlob.type - }; - - // Then, upload the original image - uploadImage(); - }, - function(error) { - console.log(" -> Can't upload thumbnail"); - deferred.reject(error); - } - ); - }, - function(error) { - console.log(" -> Failed to get thumbnail size"); - deferred.reject(error); - } - ); - - }, - function(error) { - console.log(" -> Failed to create thumbnail: " + error); - deferred.reject(error); - } - ); - } - else { - // No need of thumbnail - console.log(" Thumbnail is not required"); - uploadImage(); - } - - }, - function(error) { - console.log(" -> Failed to get image size"); - deferred.reject(error); - } - ); - } - else { - // it's a random file - just upload it. - self.uploadFile(file).then( - function(url) { - // Update message metadata - message.url = url; - message.msgtype = "m.file"; - message.body = file.name; - message.info = { - size: file.size, - mimetype: file.type - }; - - // We are done - deferred.resolve(message); - }, - function(error) { - console.log(" -> Can't upload file"); - deferred.reject(error); - } - ); - } - - return deferred.promise; - }; - -}]); diff --git a/syweb/webclient/components/matrix/commands-service.js b/syweb/webclient/components/matrix/commands-service.js deleted file mode 100644 index 3c516ad1e4..0000000000 --- a/syweb/webclient/components/matrix/commands-service.js +++ /dev/null @@ -1,164 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -/* -This service contains logic for parsing and performing IRC style commands. -*/ -angular.module('commandsService', []) -.factory('commandsService', ['$q', '$location', 'matrixService', 'modelService', function($q, $location, matrixService, modelService) { - - // create a rejected promise with the given message - var reject = function(msg) { - var deferred = $q.defer(); - deferred.reject({ - data: { - error: msg - } - }); - return deferred.promise; - }; - - // Change your nickname - var doNick = function(room_id, args) { - if (args) { - return matrixService.setDisplayName(args); - } - return reject("Usage: /nick <display_name>"); - }; - - // Join a room - var doJoin = function(room_id, args) { - if (args) { - var matches = args.match(/^(\S+)$/); - if (matches) { - var room_alias = matches[1]; - $location.url("room/" + room_alias); - // NB: We don't need to actually do the join, since that happens - // automatically if we are not joined onto a room already when - // the page loads. - return reject("Joining "+room_alias); - } - } - return reject("Usage: /join <room_alias>"); - }; - - // Kick a user from the room with an optional reason - var doKick = function(room_id, args) { - if (args) { - var matches = args.match(/^(\S+?)( +(.*))?$/); - if (matches) { - return matrixService.kick(room_id, matches[1], matches[3]); - } - } - return reject("Usage: /kick <userId> [<reason>]"); - }; - - // Ban a user from the room with an optional reason - var doBan = function(room_id, args) { - if (args) { - var matches = args.match(/^(\S+?)( +(.*))?$/); - if (matches) { - return matrixService.ban(room_id, matches[1], matches[3]); - } - } - return reject("Usage: /ban <userId> [<reason>]"); - }; - - // Unban a user from the room - var doUnban = function(room_id, args) { - if (args) { - var matches = args.match(/^(\S+)$/); - if (matches) { - // Reset the user membership to "leave" to unban him - return matrixService.unban(room_id, matches[1]); - } - } - return reject("Usage: /unban <userId>"); - }; - - // Define the power level of a user - var doOp = function(room_id, args) { - if (args) { - var matches = args.match(/^(\S+?)( +(\d+))?$/); - var powerLevel = 50; // default power level for op - if (matches) { - var user_id = matches[1]; - if (matches.length === 4 && undefined !== matches[3]) { - powerLevel = parseInt(matches[3]); - } - if (powerLevel !== NaN) { - var powerLevelEvent = modelService.getRoom(room_id).current_room_state.state("m.room.power_levels"); - return matrixService.setUserPowerLevel(room_id, user_id, powerLevel, powerLevelEvent); - } - } - } - return reject("Usage: /op <userId> [<power level>]"); - }; - - // Reset the power level of a user - var doDeop = function(room_id, args) { - if (args) { - var matches = args.match(/^(\S+)$/); - if (matches) { - var powerLevelEvent = modelService.getRoom(room_id).current_room_state.state("m.room.power_levels"); - return matrixService.setUserPowerLevel(room_id, args, undefined, powerLevelEvent); - } - } - return reject("Usage: /deop <userId>"); - }; - - - var commands = { - "nick": doNick, - "join": doJoin, - "kick": doKick, - "ban": doBan, - "unban": doUnban, - "op": doOp, - "deop": doDeop - }; - - return { - - /** - * Process the given text for commands and perform them. - * @param {String} roomId The room in which the input was performed. - * @param {String} input The raw text input by the user. - * @return {Promise} A promise of the pending command, or null if the - * input is not a command. - */ - processInput: function(roomId, input) { - // trim any trailing whitespace, as it can confuse the parser for - // IRC-style commands - input = input.replace(/\s+$/, ""); - if (input[0] === "/" && input[1] !== "/") { - var bits = input.match(/^(\S+?)( +(.*))?$/); - var cmd = bits[1].substring(1); - var args = bits[3]; - if (commands[cmd]) { - return commands[cmd](roomId, args); - } - return reject("Unrecognised IRC-style command: " + cmd); - } - return null; // not a command - } - - }; - -}]); - diff --git a/syweb/webclient/components/matrix/event-handler-service.js b/syweb/webclient/components/matrix/event-handler-service.js deleted file mode 100644 index de0436d209..0000000000 --- a/syweb/webclient/components/matrix/event-handler-service.js +++ /dev/null @@ -1,557 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -/* -This service handles what should happen when you get an event. This service does -not care where the event came from, it only needs enough context to be able to -process them. Events may be coming from the event stream, the REST API (via -direct GETs or via a pagination stream API), etc. - -Typically, this service will store events and broadcast them to any listeners -(e.g. controllers) via $broadcast. -*/ -angular.module('eventHandlerService', []) -.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', '$timeout', '$filter', 'mPresence', 'notificationService', 'modelService', -function(matrixService, $rootScope, $q, $timeout, $filter, mPresence, notificationService, modelService) { - var ROOM_CREATE_EVENT = "ROOM_CREATE_EVENT"; - var MSG_EVENT = "MSG_EVENT"; - var MEMBER_EVENT = "MEMBER_EVENT"; - var PRESENCE_EVENT = "PRESENCE_EVENT"; - var POWERLEVEL_EVENT = "POWERLEVEL_EVENT"; - var CALL_EVENT = "CALL_EVENT"; - var NAME_EVENT = "NAME_EVENT"; - var TOPIC_EVENT = "TOPIC_EVENT"; - var RESET_EVENT = "RESET_EVENT"; // eventHandlerService has been resetted - - // used for dedupping events - could be expanded in future... - // FIXME: means that we leak memory over time (along with lots of the rest - // of the app, given we never try to reap memory yet) - var eventMap = {}; - - var initialSyncDeferred; - - var reset = function() { - initialSyncDeferred = $q.defer(); - eventMap = {}; - }; - reset(); - - // Generic method to handle events data - var handleRoomStateEvent = function(event, isLiveEvent, addToRoomMessages) { - var room = modelService.getRoom(event.room_id); - if (addToRoomMessages) { - // some state events are displayed as messages, so add them. - room.addMessageEvent(event, !isLiveEvent); - } - - if (isLiveEvent) { - // update the current room state with the latest state - room.current_room_state.storeStateEvent(event); - } - else { - var eventTs = event.origin_server_ts; - var storedEvent = room.current_room_state.getStateEvent(event.type, event.state_key); - if (storedEvent) { - if (storedEvent.origin_server_ts < eventTs) { - // the incoming event is newer, use it. - room.current_room_state.storeStateEvent(event); - } - } - } - // TODO: handle old_room_state - }; - - var handleRoomCreate = function(event, isLiveEvent) { - $rootScope.$broadcast(ROOM_CREATE_EVENT, event, isLiveEvent); - }; - - var handleRoomAliases = function(event, isLiveEvent) { - modelService.createRoomIdToAliasMapping(event.room_id, event.content.aliases[0]); - }; - - var containsBingWord = function(event) { - if (!event.content || !event.content.body) { - return false; - } - - return notificationService.containsBingWord( - matrixService.config().user_id, - matrixService.config().display_name, - matrixService.config().bingWords, - event.content.body - ); - }; - - var displayNotification = function(event) { - if (window.Notification && event.user_id != matrixService.config().user_id) { - var member = modelService.getMember(event.room_id, event.user_id); - var displayname = $filter("mUserDisplayName")(event.user_id, event.room_id); - var message; - var shouldBing = false; - - if (event.type === "m.room.message") { - shouldBing = containsBingWord(event); - message = event.content.body; - if (event.content.msgtype === "m.emote") { - message = "* " + displayname + " " + message; - } - else if (event.content.msgtype === "m.image") { - message = displayname + " sent an image."; - } - } - else if (event.type == "m.room.member") { - // Notify when another user joins only - if (event.state_key !== matrixService.config().user_id && "join" === event.content.membership) { - member = modelService.getMember(event.room_id, event.state_key); - displayname = $filter("mUserDisplayName")(event.state_key, event.room_id); - message = displayname + " joined"; - shouldBing = true; - } - else { - return; - } - } - - // Ideally we would notify only when the window is hidden (i.e. document.hidden = true). - // - // However, Chrome on Linux and OSX currently returns document.hidden = false unless the window is - // explicitly showing a different tab. So we need another metric to determine hiddenness - we - // simply use idle time. If the user has been idle enough that their presence goes to idle, then - // we also display notifs when things happen. - // - // This is far far better than notifying whenever anything happens anyway, otherwise you get spammed - // to death with notifications when the window is in the foreground, which is horrible UX (especially - // if you have not defined any bingers and so get notified for everything). - var isIdle = (document.hidden || matrixService.presence.unavailable === mPresence.getState()); - - // We need a way to let people get notifications for everything, if they so desire. The way to do this - // is to specify zero bingwords. - var bingWords = matrixService.config().bingWords; - if (bingWords === undefined || bingWords.length === 0) { - shouldBing = true; - } - - if (shouldBing && isIdle) { - console.log("Displaying notification for "+JSON.stringify(event)); - - var roomTitle = $filter("mRoomName")(event.room_id); - - notificationService.showNotification( - displayname + " (" + roomTitle + ")", - message, - member ? member.event.content.avatar_url : undefined, - function() { - console.log("notification.onclick() room=" + event.room_id); - $rootScope.goToPage('room/' + event.room_id); - } - ); - } - } - }; - - var handleMessage = function(event, isLiveEvent) { - // Check for empty event content - var hasContent = false; - for (var prop in event.content) { - hasContent = true; - break; - } - if (!hasContent) { - // empty json object is a redacted event, so ignore. - return; - } - - // ======================= - - var room = modelService.getRoom(event.room_id); - - if (event.user_id !== matrixService.config().user_id) { - room.addMessageEvent(event, !isLiveEvent); - if (isLiveEvent) { - displayNotification(event); - } - } - else { - // we may have locally echoed this, so we should replace the event - // instead of just adding. - room.addOrReplaceMessageEvent(event, !isLiveEvent); - } - - // TODO send delivery receipt if isLiveEvent - - $rootScope.$broadcast(MSG_EVENT, event, isLiveEvent); - }; - - var handleRoomMember = function(event, isLiveEvent, isStateEvent) { - var room = modelService.getRoom(event.room_id); - - // did something change? - var memberChanges = undefined; - if (!isStateEvent) { - // could be a membership change, display name change, etc. - // Find out which one. - if ((event.prev_content === undefined && event.content.membership) || (event.prev_content && (event.prev_content.membership !== event.content.membership))) { - memberChanges = "membership"; - } - else if (event.prev_content && (event.prev_content.displayname !== event.content.displayname)) { - memberChanges = "displayname"; - } - // mark the key which changed - event.changedKey = memberChanges; - } - - - // modify state before adding the message so it points to the right thing. - // The events are copied to avoid referencing the same event when adding - // the message (circular json structures) - if (isStateEvent || isLiveEvent) { - var newEvent = angular.copy(event); - newEvent.cnt = event.content; - room.current_room_state.storeStateEvent(newEvent); - } - else if (!isLiveEvent) { - // mutate the old room state - var oldEvent = angular.copy(event); - oldEvent.cnt = event.content; - if (event.prev_content) { - // the m.room.member event we are handling is the NEW event. When - // we keep going back in time, we want the PREVIOUS value for displaying - // names/etc, hence the clobber here. - oldEvent.cnt = event.prev_content; - } - - if (event.changedKey === "membership" && event.content.membership === "join") { - // join has a prev_content but it doesn't contain all the info unlike the join, so use that. - oldEvent.cnt = event.content; - } - - room.old_room_state.storeStateEvent(oldEvent); - } - - // If there was a change we want to display, dump it in the message - // list. This has to be done after room state is updated. - if (memberChanges) { - room.addMessageEvent(event, !isLiveEvent); - - if (memberChanges === "membership" && isLiveEvent) { - displayNotification(event); - } - } - - - - $rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent, isStateEvent); - }; - - var handlePresence = function(event, isLiveEvent) { - // presence is always current, so clobber. - modelService.setUser(event); - $rootScope.$broadcast(PRESENCE_EVENT, event, isLiveEvent); - }; - - var handlePowerLevels = function(event, isLiveEvent) { - handleRoomStateEvent(event, isLiveEvent); - $rootScope.$broadcast(POWERLEVEL_EVENT, event, isLiveEvent); - }; - - var handleRoomName = function(event, isLiveEvent, isStateEvent) { - console.log("handleRoomName room_id: " + event.room_id + " - isLiveEvent: " + isLiveEvent + " - name: " + event.content.name); - handleRoomStateEvent(event, isLiveEvent, !isStateEvent); - $rootScope.$broadcast(NAME_EVENT, event, isLiveEvent); - }; - - - var handleRoomTopic = function(event, isLiveEvent, isStateEvent) { - console.log("handleRoomTopic room_id: " + event.room_id + " - isLiveEvent: " + isLiveEvent + " - topic: " + event.content.topic); - handleRoomStateEvent(event, isLiveEvent, !isStateEvent); - $rootScope.$broadcast(TOPIC_EVENT, event, isLiveEvent); - }; - - var handleCallEvent = function(event, isLiveEvent) { - $rootScope.$broadcast(CALL_EVENT, event, isLiveEvent); - if (event.type === 'm.call.invite') { - var room = modelService.getRoom(event.room_id); - room.addMessageEvent(event, !isLiveEvent); - } - }; - - var handleRedaction = function(event, isLiveEvent) { - if (!isLiveEvent) { - // we have nothing to remove, so just ignore it. - console.log("Received redacted event: "+JSON.stringify(event)); - return; - } - - // we need to remove something possibly: do we know the redacted - // event ID? - if (eventMap[event.redacts]) { - var room = modelService.getRoom(event.room_id); - // remove event from list of messages in this room. - var eventList = room.events; - for (var i=0; i<eventList.length; i++) { - if (eventList[i].event_id === event.redacts) { - console.log("Removing event " + event.redacts); - eventList.splice(i, 1); - break; - } - } - - console.log("Redacted an event."); - } - }; - - // resolves a room ID or alias, returning a deferred. - var resolveRoomIdentifier = function(roomIdOrAlias) { - var defer = $q.defer(); - if ('#' === roomIdOrAlias[0]) { - matrixService.resolveRoomAlias(roomIdOrAlias).then(function(response) { - defer.resolve(response.data.room_id); - console.log("resolveRoomIdentifier: "+roomIdOrAlias+" -> " + response.data.room_id); - }, - function(err) { - console.error("resolveRoomIdentifier: lookup failed. "+JSON.stringify(err.data)); - defer.reject(err.data); - }); - } - else if ('!' === roomIdOrAlias[0]) { - defer.resolve(roomIdOrAlias); - } - else { - console.error("resolveRoomIdentifier: Unknown roomIdOrAlias => "+roomIdOrAlias); - defer.reject("Bad room identifier: "+roomIdOrAlias); - } - return defer.promise; - }; - - return { - ROOM_CREATE_EVENT: ROOM_CREATE_EVENT, - MSG_EVENT: MSG_EVENT, - MEMBER_EVENT: MEMBER_EVENT, - PRESENCE_EVENT: PRESENCE_EVENT, - POWERLEVEL_EVENT: POWERLEVEL_EVENT, - CALL_EVENT: CALL_EVENT, - NAME_EVENT: NAME_EVENT, - TOPIC_EVENT: TOPIC_EVENT, - RESET_EVENT: RESET_EVENT, - - reset: function() { - reset(); - $rootScope.$broadcast(RESET_EVENT); - }, - - handleEvent: function(event, isLiveEvent, isStateEvent) { - // Avoid duplicated events - // Needed for rooms where initialSync has not been done. - // In this case, we do not know where to start pagination. So, it starts from the END - // and we can have the same event (ex: joined, invitation) coming from the pagination - // AND from the event stream. - // FIXME: This workaround should be no more required when /initialSync on a particular room - // will be available (as opposite to the global /initialSync done at startup) - if (!isStateEvent) { // Do not consider state events - if (event.event_id && eventMap[event.event_id]) { - console.log("discarding duplicate event: " + JSON.stringify(event, undefined, 4)); - return; - } - else { - eventMap[event.event_id] = 1; - } - } - - if (event.type.indexOf('m.call.') === 0) { - handleCallEvent(event, isLiveEvent); - } - else { - switch(event.type) { - case "m.room.create": - handleRoomCreate(event, isLiveEvent); - break; - case "m.room.aliases": - handleRoomAliases(event, isLiveEvent); - break; - case "m.room.message": - handleMessage(event, isLiveEvent); - break; - case "m.room.member": - handleRoomMember(event, isLiveEvent, isStateEvent); - break; - case "m.presence": - handlePresence(event, isLiveEvent); - break; - case 'm.room.ops_levels': - case 'm.room.send_event_level': - case 'm.room.add_state_level': - case 'm.room.join_rules': - case 'm.room.power_levels': - handlePowerLevels(event, isLiveEvent); - break; - case 'm.room.name': - handleRoomName(event, isLiveEvent, isStateEvent); - break; - case 'm.room.topic': - handleRoomTopic(event, isLiveEvent, isStateEvent); - break; - case 'm.room.redaction': - handleRedaction(event, isLiveEvent); - break; - default: - // if it is a state event, then just add it in so it - // displays on the Room Info screen. - if (typeof(event.state_key) === "string") { // incls. 0-len strings - if (event.room_id) { - handleRoomStateEvent(event, isLiveEvent, false); - } - } - console.log("Unable to handle event type " + event.type); - // console.log(JSON.stringify(event, undefined, 4)); - break; - } - } - }, - - // isLiveEvents determines whether notifications should be shown, whether - // messages get appended to the start/end of lists, etc. - handleEvents: function(events, isLiveEvents, isStateEvents) { - for (var i=0; i<events.length; i++) { - this.handleEvent(events[i], isLiveEvents, isStateEvents); - } - }, - - // Handle messages from /initialSync or /messages - handleRoomMessages: function(room_id, messages, isLiveEvents, dir) { - var events = messages.chunk; - - // Handles messages according to their time order - if (dir && 'b' === dir) { - // paginateBackMessages requests messages to be in reverse chronological order - for (var i=0; i<events.length; i++) { - this.handleEvent(events[i], isLiveEvents, isLiveEvents); - } - - // Store how far back we've paginated - var room = modelService.getRoom(room_id); - room.old_room_state.pagination_token = messages.end; - - } - else { - // InitialSync returns messages in chronological order, so invert - // it to get most recent > oldest - for (var i=events.length - 1; i>=0; i--) { - this.handleEvent(events[i], isLiveEvents, isLiveEvents); - } - // Store where to start pagination - var room = modelService.getRoom(room_id); - room.old_room_state.pagination_token = messages.start; - } - }, - - handleInitialSyncDone: function(response) { - console.log("# handleInitialSyncDone"); - - var rooms = response.data.rooms; - for (var i = 0; i < rooms.length; ++i) { - var room = rooms[i]; - - // FIXME: This is ming: the HS should be sending down the m.room.member - // event for the invite in .state but it isn't, so fudge it for now. - if (room.inviter && room.membership === "invite") { - var me = matrixService.config().user_id; - var fakeEvent = { - event_id: "__FAKE__" + room.room_id, - user_id: room.inviter, - origin_server_ts: 0, - room_id: room.room_id, - state_key: me, - type: "m.room.member", - content: { - membership: "invite" - } - }; - if (!room.state) { - room.state = []; - } - room.state.push(fakeEvent); - console.log("RECV /initialSync invite >> "+room.room_id); - } - - var newRoom = modelService.getRoom(room.room_id); - newRoom.current_room_state.storeStateEvents(room.state); - newRoom.old_room_state.storeStateEvents(room.state); - - // this should be done AFTER storing state events since these - // messages may make the old_room_state diverge. - if ("messages" in room) { - this.handleRoomMessages(room.room_id, room.messages, false); - newRoom.current_room_state.pagination_token = room.messages.end; - newRoom.old_room_state.pagination_token = room.messages.start; - } - } - var presence = response.data.presence; - this.handleEvents(presence, false); - - initialSyncDeferred.resolve(response); - }, - - // joins a room and handles the requests which need to be done e.g. getting room state - joinRoom: function(roomIdOrAlias) { - var defer = $q.defer(); - var eventHandlerService = this; - - var errorFunc = function(error) { - console.error("joinRoom: " + JSON.stringify(error)); - defer.reject(error); - }; - - resolveRoomIdentifier(roomIdOrAlias).then(function(roomId) { - // check if you are joined already - eventHandlerService.waitForInitialSyncCompletion().then(function() { - var members = modelService.getRoom(roomId).current_room_state.members; - var me = matrixService.config().user_id; - if (me in members) { - if ("join" === members[me].event.content.membership) { - console.log("joinRoom: Already joined room "+roomId); - defer.resolve(roomId); - return; - } - } - // join the room and get current room state - matrixService.join(roomId).then(function() { - matrixService.roomState(roomId).then(function(response) { - var room = modelService.getRoom(roomId); - room.current_room_state.storeStateEvents(response.data); - room.old_room_state.storeStateEvents(response.data); - console.log("joinRoom: Joined room "+roomId); - defer.resolve(roomId); - }, errorFunc); - }, errorFunc); - }, errorFunc); - }, errorFunc); - - return defer.promise; - }, - - // Returns a promise that resolves when the initialSync request has been processed - waitForInitialSyncCompletion: function() { - return initialSyncDeferred.promise; - }, - - eventContainsBingWord: function(event) { - return containsBingWord(event); - } - - }; -}]); diff --git a/syweb/webclient/components/matrix/event-stream-service.js b/syweb/webclient/components/matrix/event-stream-service.js deleted file mode 100644 index be60de7472..0000000000 --- a/syweb/webclient/components/matrix/event-stream-service.js +++ /dev/null @@ -1,164 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -/* -This service manages where in the event stream the web client currently is, -repolling the event stream, and provides methods to resume/pause/stop the event -stream. This service is not responsible for parsing event data. For that, see -the eventHandlerService. -*/ -angular.module('eventStreamService', []) -.factory('eventStreamService', ['$q', '$timeout', 'matrixService', 'eventHandlerService', function($q, $timeout, matrixService, eventHandlerService) { - var END = "END"; - var SERVER_TIMEOUT_MS = 30000; - var CLIENT_TIMEOUT_MS = 40000; - var ERR_TIMEOUT_MS = 5000; - - var settings = { - from: "END", - to: undefined, - limit: undefined, - shouldPoll: true, - isActive: false - }; - - // interrupts the stream. Only valid if there is a stream conneciton - // open. - var interrupt = function(shouldPoll) { - console.log("[EventStream] interrupt("+shouldPoll+") "+ - JSON.stringify(settings)); - settings.shouldPoll = shouldPoll; - settings.isActive = false; - }; - - var saveStreamSettings = function() { - localStorage.setItem("streamSettings", JSON.stringify(settings)); - }; - - var doEventStream = function(deferred) { - settings.shouldPoll = true; - settings.isActive = true; - deferred = deferred || $q.defer(); - - // run the stream from the latest token - matrixService.getEventStream(settings.from, SERVER_TIMEOUT_MS, CLIENT_TIMEOUT_MS).then( - function(response) { - if (!settings.isActive) { - console.log("[EventStream] Got response but now inactive. Dropping data."); - return; - } - - settings.from = response.data.end; - - console.log( - "[EventStream] Got response from "+settings.from+ - " to "+response.data.end - ); - eventHandlerService.handleEvents(response.data.chunk, true); - - deferred.resolve(response); - - if (settings.shouldPoll) { - $timeout(doEventStream, 0); - } - else { - console.log("[EventStream] Stopping poll."); - } - }, - function(error) { - if (error.status === 403) { - settings.shouldPoll = false; - } - - deferred.reject(error); - - if (settings.shouldPoll) { - $timeout(doEventStream, ERR_TIMEOUT_MS); - } - else { - console.log("[EventStream] Stopping polling."); - } - } - ); - - return deferred.promise; - }; - - var startEventStream = function() { - settings.shouldPoll = true; - settings.isActive = true; - var deferred = $q.defer(); - - // Initial sync: get all information and the last 30 messages of all rooms of the user - // 30 messages should be enough to display a full page of messages in a room - // without requiring to make an additional request - matrixService.initialSync(30, false).then( - function(response) { - eventHandlerService.handleInitialSyncDone(response); - - // Start event streaming from that point - settings.from = response.data.end; - doEventStream(deferred); - }, - function(error) { - $scope.feedback = "Failure: " + error.data; - } - ); - - return deferred.promise; - }; - - return { - // expose these values for testing - SERVER_TIMEOUT: SERVER_TIMEOUT_MS, - CLIENT_TIMEOUT: CLIENT_TIMEOUT_MS, - - // resume the stream from whereever it last got up to. Typically used - // when the page is opened. - resume: function() { - if (settings.isActive) { - console.log("[EventStream] Already active, ignoring resume()"); - return; - } - - console.log("[EventStream] resume "+JSON.stringify(settings)); - return startEventStream(); - }, - - // pause the stream. Resuming it will continue from the current position - pause: function() { - console.log("[EventStream] pause "+JSON.stringify(settings)); - // kill any running stream - interrupt(false); - // save the latest token - saveStreamSettings(); - }, - - // stop the stream and wipe the position in the stream. Typically used - // when logging out / logged out. - stop: function() { - console.log("[EventStream] stop "+JSON.stringify(settings)); - // kill any running stream - interrupt(false); - // clear the latest token - settings.from = END; - saveStreamSettings(); - } - }; - -}]); diff --git a/syweb/webclient/components/matrix/matrix-call.js b/syweb/webclient/components/matrix/matrix-call.js deleted file mode 100644 index 56431817d9..0000000000 --- a/syweb/webclient/components/matrix/matrix-call.js +++ /dev/null @@ -1,659 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -var forAllVideoTracksOnStream = function(s, f) { - var tracks = s.getVideoTracks(); - for (var i = 0; i < tracks.length; i++) { - f(tracks[i]); - } -} - -var forAllAudioTracksOnStream = function(s, f) { - var tracks = s.getAudioTracks(); - for (var i = 0; i < tracks.length; i++) { - f(tracks[i]); - } -} - -var forAllTracksOnStream = function(s, f) { - forAllVideoTracksOnStream(s, f); - forAllAudioTracksOnStream(s, f); -} - -angular.module('MatrixCall', []) -.factory('MatrixCall', ['matrixService', 'matrixPhoneService', 'modelService', '$rootScope', '$timeout', function MatrixCallFactory(matrixService, matrixPhoneService, modelService, $rootScope, $timeout) { - $rootScope.isWebRTCSupported = function () { - navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; - window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection; // but not mozRTCPeerConnection because its interface is not compatible - window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription; - window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate; - - return !!(navigator.getUserMedia || window.RTCPeerConnection || window.RTCSessionDescription || window.RTCIceCandidate); - }; - - var MatrixCall = function(room_id) { - this.room_id = room_id; - this.call_id = "c" + new Date().getTime(); - this.state = 'fledgling'; - this.didConnect = false; - - // a queue for candidates waiting to go out. We try to amalgamate candidates into a single candidate message where possible - this.candidateSendQueue = []; - this.candidateSendTries = 0; - - var self = this; - $rootScope.$watch(this.getRemoteVideoElement(), function (oldValue, newValue) { - self.tryPlayRemoteStream(); - }); - - } - - MatrixCall.getTurnServer = function() { - matrixService.getTurnServer().then(function(response) { - if (response.data.uris) { - console.log("Got TURN URIs: "+response.data.uris); - MatrixCall.turnServer = response.data; - $rootScope.haveTurn = true; - // re-fetch when we're about to reach the TTL - $timeout(MatrixCall.getTurnServer, MatrixCall.turnServer.ttl * 1000 * 0.9); - } else { - console.log("Got no TURN URIs from HS"); - $rootScope.haveTurn = false; - } - }, function(error) { - console.log("Failed to get TURN URIs"); - MatrixCall.turnServer = {}; - $timeout(MatrixCall.getTurnServer, 60000); - }); - } - - // FIXME: we should prevent any calls from being placed or accepted before this has finished - MatrixCall.getTurnServer(); - - MatrixCall.CALL_TIMEOUT = 60000; - MatrixCall.FALLBACK_STUN_SERVER = 'stun:stun.l.google.com:19302'; - - MatrixCall.prototype.createPeerConnection = function() { - var pc; - if (window.mozRTCPeerConnection) { - var iceServers = []; - // https://github.com/EricssonResearch/openwebrtc/issues/85 - if (MatrixCall.turnServer /*&& !this.isOpenWebRTC()*/) { - if (MatrixCall.turnServer.uris) { - for (var i = 0; i < MatrixCall.turnServer.uris.length; i++) { - iceServers.push({ - 'url': MatrixCall.turnServer.uris[i], - 'username': MatrixCall.turnServer.username, - 'credential': MatrixCall.turnServer.password, - }); - } - } else { - console.log("No TURN server: using fallback STUN server"); - iceServers.push({ 'url' : MatrixCall.FALLBACK_STUN_SERVER }); - } - } - - pc = new window.mozRTCPeerConnection({"iceServers":iceServers}); - } else { - var iceServers = []; - // https://github.com/EricssonResearch/openwebrtc/issues/85 - if (MatrixCall.turnServer && !this.isOpenWebRTC()) { - if (MatrixCall.turnServer.uris) { - iceServers.push({ - 'urls': MatrixCall.turnServer.uris, - 'username': MatrixCall.turnServer.username, - 'credential': MatrixCall.turnServer.password, - }); - } else { - console.log("No TURN server: using fallback STUN server"); - iceServers.push({ 'urls' : MatrixCall.FALLBACK_STUN_SERVER }); - } - } - - pc = new window.RTCPeerConnection({"iceServers":iceServers}); - } - var self = this; - pc.oniceconnectionstatechange = function() { self.onIceConnectionStateChanged(); }; - pc.onsignalingstatechange = function() { self.onSignallingStateChanged(); }; - pc.onicecandidate = function(c) { self.gotLocalIceCandidate(c); }; - pc.onaddstream = function(s) { self.onAddStream(s); }; - return pc; - } - - MatrixCall.prototype.getUserMediaVideoContraints = function(callType) { - switch (callType) { - case 'voice': - return ({audio: true, video: false}); - case 'video': - return ({audio: true, video: { - mandatory: { - minWidth: 640, - maxWidth: 640, - minHeight: 360, - maxHeight: 360, - } - }}); - } - }; - - MatrixCall.prototype.placeVoiceCall = function() { - this.placeCallWithConstraints(this.getUserMediaVideoContraints('voice')); - this.type = 'voice'; - }; - - MatrixCall.prototype.placeVideoCall = function(config) { - this.placeCallWithConstraints(this.getUserMediaVideoContraints('video')); - this.type = 'video'; - }; - - MatrixCall.prototype.placeCallWithConstraints = function(constraints) { - var self = this; - matrixPhoneService.callPlaced(this); - navigator.getUserMedia(constraints, function(s) { self.gotUserMediaForInvite(s); }, function(e) { self.getUserMediaFailed(e); }); - this.state = 'wait_local_media'; - this.direction = 'outbound'; - this.config = constraints; - }; - - MatrixCall.prototype.initWithInvite = function(event) { - this.msg = event.content; - this.peerConn = this.createPeerConnection(); - this.peerConn.setRemoteDescription(new RTCSessionDescription(this.msg.offer), this.onSetRemoteDescriptionSuccess, this.onSetRemoteDescriptionError); - this.state = 'ringing'; - this.direction = 'inbound'; - - // This also applied to the Safari OpenWebRTC extension so let's just do this all the time at least for now - //if (window.mozRTCPeerConnection) { - // firefox's RTCPeerConnection doesn't add streams until it starts getting media on them - // so we need to figure out whether a video channel has been offered by ourselves. - if (this.msg.offer.sdp.indexOf('m=video') > -1) { - this.type = 'video'; - } else { - this.type = 'voice'; - } - //} - - var self = this; - $timeout(function() { - if (self.state == 'ringing') { - self.state = 'ended'; - self.hangupParty = 'remote'; // effectively - self.stopAllMedia(); - if (self.peerConn.signalingState != 'closed') self.peerConn.close(); - if (self.onHangup) self.onHangup(self); - } - }, this.msg.lifetime - event.age); - }; - - // perverse as it may seem, sometimes we want to instantiate a call with a hangup message - // (because when getting the state of the room on load, events come in reverse order and - // we want to remember that a call has been hung up) - MatrixCall.prototype.initWithHangup = function(event) { - this.msg = event.content; - this.state = 'ended'; - }; - - MatrixCall.prototype.answer = function() { - console.log("Answering call "+this.call_id); - - var self = this; - - var roomMembers = modelService.getRoom(this.room_id).current_room_state.members; - if (roomMembers[matrixService.config().user_id].event.content.membership != 'join') { - console.log("We need to join the room before we can accept this call"); - matrixService.join(this.room_id).then(function() { - self.answer(); - }, function() { - console.log("Failed to join room: can't answer call!"); - self.onError("Unable to join room to answer call!"); - self.hangup(); - }); - return; - } - - if (!this.localAVStream && !this.waitForLocalAVStream) { - navigator.getUserMedia(this.getUserMediaVideoContraints(this.type), function(s) { self.gotUserMediaForAnswer(s); }, function(e) { self.getUserMediaFailed(e); }); - this.state = 'wait_local_media'; - } else if (this.localAVStream) { - this.gotUserMediaForAnswer(this.localAVStream); - } else if (this.waitForLocalAVStream) { - this.state = 'wait_local_media'; - } - }; - - MatrixCall.prototype.stopAllMedia = function() { - if (this.localAVStream) { - forAllTracksOnStream(this.localAVStream, function(t) { - if (t.stop) t.stop(); - }); - } - if (this.remoteAVStream) { - forAllTracksOnStream(this.remoteAVStream, function(t) { - if (t.stop) t.stop(); - }); - } - }; - - MatrixCall.prototype.hangup = function(reason, suppressEvent) { - console.log("Ending call "+this.call_id); - - // pausing now keeps the last frame (ish) of the video call in the video element - // rather than it just turning black straight away - if (this.getRemoteVideoElement() && this.getRemoteVideoElement().pause) this.getRemoteVideoElement().pause(); - if (this.getLocalVideoElement() && this.getLocalVideoElement().pause) this.getLocalVideoElement().pause(); - - this.stopAllMedia(); - if (this.peerConn) this.peerConn.close(); - - this.hangupParty = 'local'; - this.hangupReason = reason; - - var content = { - version: 0, - call_id: this.call_id, - reason: reason - }; - this.sendEventWithRetry('m.call.hangup', content); - this.state = 'ended'; - if (this.onHangup && !suppressEvent) this.onHangup(this); - }; - - MatrixCall.prototype.gotUserMediaForInvite = function(stream) { - if (this.successor) { - this.successor.gotUserMediaForAnswer(stream); - return; - } - if (this.state == 'ended') return; - - var videoEl = this.getLocalVideoElement(); - - if (videoEl && this.type == 'video') { - var vidTrack = stream.getVideoTracks()[0]; - videoEl.autoplay = true; - videoEl.src = URL.createObjectURL(stream); - videoEl.muted = true; - var self = this; - $timeout(function() { - var vel = self.getLocalVideoElement(); - if (vel.play) vel.play(); - }); - } - - this.localAVStream = stream; - var audioTracks = stream.getAudioTracks(); - for (var i = 0; i < audioTracks.length; i++) { - audioTracks[i].enabled = true; - } - this.peerConn = this.createPeerConnection(); - this.peerConn.addStream(stream); - var self = this; - this.peerConn.createOffer(function(d) { - self.gotLocalOffer(d); - }, function(e) { - self.getLocalOfferFailed(e); - }); - $rootScope.$apply(function() { - self.state = 'create_offer'; - }); - }; - - MatrixCall.prototype.gotUserMediaForAnswer = function(stream) { - if (this.state == 'ended') return; - - var localVidEl = this.getLocalVideoElement(); - - if (localVidEl && this.type == 'video') { - localVidEl.autoplay = true; - var vidTrack = stream.getVideoTracks()[0]; - localVidEl.src = URL.createObjectURL(stream); - localVidEl.muted = true; - var self = this; - $timeout(function() { - var vel = self.getLocalVideoElement(); - if (vel.play) vel.play(); - }); - } - - this.localAVStream = stream; - var audioTracks = stream.getAudioTracks(); - for (var i = 0; i < audioTracks.length; i++) { - audioTracks[i].enabled = true; - } - this.peerConn.addStream(stream); - var self = this; - var constraints = { - 'mandatory': { - 'OfferToReceiveAudio': true, - 'OfferToReceiveVideo': this.type == 'video' - }, - }; - this.peerConn.createAnswer(function(d) { self.createdAnswer(d); }, function(e) {}, constraints); - // This can't be in an apply() because it's called by a predecessor call under glare conditions :( - self.state = 'create_answer'; - }; - - MatrixCall.prototype.gotLocalIceCandidate = function(event) { - if (event.candidate) { - console.log("Got local ICE "+event.candidate.sdpMid+" candidate: "+event.candidate.candidate); - this.sendCandidate(event.candidate); - } - } - - MatrixCall.prototype.gotRemoteIceCandidate = function(cand) { - if (this.state == 'ended') { - //console.log("Ignoring remote ICE candidate because call has ended"); - return; - } - console.log("Got remote ICE "+cand.sdpMid+" candidate: "+cand.candidate); - this.peerConn.addIceCandidate(new RTCIceCandidate(cand), function() {}, function(e) {}); - }; - - MatrixCall.prototype.receivedAnswer = function(msg) { - if (this.state == 'ended') return; - - this.peerConn.setRemoteDescription(new RTCSessionDescription(msg.answer), this.onSetRemoteDescriptionSuccess, this.onSetRemoteDescriptionError); - this.state = 'connecting'; - }; - - - MatrixCall.prototype.gotLocalOffer = function(description) { - console.log("Created offer: "+description); - - if (this.state == 'ended') { - console.log("Ignoring newly created offer on call ID "+this.call_id+" because the call has ended"); - return; - } - - var self = this; - this.peerConn.setLocalDescription(description, function() { - var content = { - version: 0, - call_id: self.call_id, - // OpenWebRTC appears to add extra stuff (like the DTLS fingerprint) to the description - // when setting it on the peerconnection. According to the spec it should only add ICE - // candidates. Any ICE candidates that have already been generated at this point will - // probably be sent both in the offer and separately. Ho hum. - offer: self.peerConn.localDescription, - lifetime: MatrixCall.CALL_TIMEOUT - }; - self.sendEventWithRetry('m.call.invite', content); - - $timeout(function() { - if (self.state == 'invite_sent') { - self.hangup('invite_timeout'); - } - }, MatrixCall.CALL_TIMEOUT); - - $rootScope.$apply(function() { - self.state = 'invite_sent'; - }); - }, function() { console.log("Error setting local description!"); }); - }; - - MatrixCall.prototype.createdAnswer = function(description) { - console.log("Created answer: "+description); - var self = this; - this.peerConn.setLocalDescription(description, function() { - var content = { - version: 0, - call_id: self.call_id, - answer: self.peerConn.localDescription - }; - self.sendEventWithRetry('m.call.answer', content); - $rootScope.$apply(function() { - self.state = 'connecting'; - }); - }, function() { console.log("Error setting local description!"); } ); - }; - - MatrixCall.prototype.getLocalOfferFailed = function(error) { - this.onError("Failed to start audio for call!"); - }; - - MatrixCall.prototype.getUserMediaFailed = function() { - this.onError("Couldn't start capturing! Is your microphone set up?"); - this.hangup(); - }; - - MatrixCall.prototype.onIceConnectionStateChanged = function() { - if (this.state == 'ended') return; // because ICE can still complete as we're ending the call - console.log("Ice connection state changed to: "+this.peerConn.iceConnectionState); - // ideally we'd consider the call to be connected when we get media but chrome doesn't implement nay of the 'onstarted' events yet - if (this.peerConn.iceConnectionState == 'completed' || this.peerConn.iceConnectionState == 'connected') { - var self = this; - $rootScope.$apply(function() { - self.state = 'connected'; - self.didConnect = true; - }); - } else if (this.peerConn.iceConnectionState == 'failed') { - this.hangup('ice_failed'); - } - }; - - MatrixCall.prototype.onSignallingStateChanged = function() { - console.log("call "+this.call_id+": Signalling state changed to: "+this.peerConn.signalingState); - }; - - MatrixCall.prototype.onSetRemoteDescriptionSuccess = function() { - console.log("Set remote description"); - }; - - MatrixCall.prototype.onSetRemoteDescriptionError = function(e) { - console.log("Failed to set remote description"+e); - }; - - MatrixCall.prototype.onAddStream = function(event) { - console.log("Stream added"+event); - - var s = event.stream; - - this.remoteAVStream = s; - - if (this.direction == 'inbound') { - if (s.getVideoTracks().length > 0) { - this.type = 'video'; - } else { - this.type = 'voice'; - } - } - - var self = this; - forAllTracksOnStream(s, function(t) { - // not currently implemented in chrome - t.onstarted = self.onRemoteStreamTrackStarted; - }); - - event.stream.onended = function(e) { self.onRemoteStreamEnded(e); }; - // not currently implemented in chrome - event.stream.onstarted = function(e) { self.onRemoteStreamStarted(e); }; - - this.tryPlayRemoteStream(); - }; - - MatrixCall.prototype.tryPlayRemoteStream = function(event) { - if (this.getRemoteVideoElement() && this.remoteAVStream) { - var player = this.getRemoteVideoElement(); - player.autoplay = true; - player.src = URL.createObjectURL(this.remoteAVStream); - var self = this; - $timeout(function() { - var vel = self.getRemoteVideoElement(); - if (vel.play) vel.play(); - // OpenWebRTC does not support oniceconnectionstatechange yet - if (self.isOpenWebRTC()) self.state = 'connected'; - }); - } - }; - - MatrixCall.prototype.onRemoteStreamStarted = function(event) { - var self = this; - $rootScope.$apply(function() { - self.state = 'connected'; - }); - }; - - MatrixCall.prototype.onRemoteStreamEnded = function(event) { - console.log("Remote stream ended"); - var self = this; - $rootScope.$apply(function() { - self.state = 'ended'; - self.hangupParty = 'remote'; - self.stopAllMedia(); - if (self.peerConn.signalingState != 'closed') self.peerConn.close(); - if (self.onHangup) self.onHangup(self); - }); - }; - - MatrixCall.prototype.onRemoteStreamTrackStarted = function(event) { - var self = this; - $rootScope.$apply(function() { - self.state = 'connected'; - }); - }; - - MatrixCall.prototype.onHangupReceived = function(msg) { - console.log("Hangup received"); - if (this.getRemoteVideoElement() && this.getRemoteVideoElement().pause) this.getRemoteVideoElement().pause(); - if (this.getLocalVideoElement() && this.getLocalVideoElement().pause) this.getLocalVideoElement().pause(); - this.state = 'ended'; - this.hangupParty = 'remote'; - this.hangupReason = msg.reason; - this.stopAllMedia(); - if (this.peerConn && this.peerConn.signalingState != 'closed') this.peerConn.close(); - if (this.onHangup) this.onHangup(this); - }; - - MatrixCall.prototype.replacedBy = function(newCall) { - console.log(this.call_id+" being replaced by "+newCall.call_id); - if (this.state == 'wait_local_media') { - console.log("Telling new call to wait for local media"); - newCall.waitForLocalAVStream = true; - } else if (this.state == 'create_offer') { - console.log("Handing local stream to new call"); - newCall.gotUserMediaForAnswer(this.localAVStream); - delete(this.localAVStream); - } else if (this.state == 'invite_sent') { - console.log("Handing local stream to new call"); - newCall.gotUserMediaForAnswer(this.localAVStream); - delete(this.localAVStream); - } - newCall.localVideoSelector = this.localVideoSelector; - newCall.remoteVideoSelector = this.remoteVideoSelector; - this.successor = newCall; - this.hangup(true); - }; - - MatrixCall.prototype.sendEventWithRetry = function(evType, content) { - var ev = { type:evType, content:content, tries:1 }; - var self = this; - matrixService.sendEvent(this.room_id, evType, undefined, content).then(this.eventSent, function(error) { self.eventSendFailed(ev, error); } ); - }; - - MatrixCall.prototype.eventSent = function() { - }; - - MatrixCall.prototype.eventSendFailed = function(ev, error) { - if (ev.tries > 5) { - console.log("Failed to send event of type "+ev.type+" on attempt "+ev.tries+". Giving up."); - return; - } - var delayMs = 500 * Math.pow(2, ev.tries); - console.log("Failed to send event of type "+ev.type+". Retrying in "+delayMs+"ms"); - ++ev.tries; - var self = this; - $timeout(function() { - matrixService.sendEvent(self.room_id, ev.type, undefined, ev.content).then(self.eventSent, function(error) { self.eventSendFailed(ev, error); } ); - }, delayMs); - }; - - // Sends candidates with are sent in a special way because we try to amalgamate them into one message - MatrixCall.prototype.sendCandidate = function(content) { - this.candidateSendQueue.push(content); - var self = this; - if (this.candidateSendTries == 0) $timeout(function() { self.sendCandidateQueue(); }, 100); - }; - - MatrixCall.prototype.sendCandidateQueue = function(content) { - if (this.candidateSendQueue.length == 0) return; - - var cands = this.candidateSendQueue; - this.candidateSendQueue = []; - ++this.candidateSendTries; - var content = { - version: 0, - call_id: this.call_id, - candidates: cands - }; - var self = this; - console.log("Attempting to send "+cands.length+" candidates"); - matrixService.sendEvent(self.room_id, 'm.call.candidates', undefined, content).then(function() { self.candsSent(); }, function(error) { self.candsSendFailed(cands, error); } ); - }; - - MatrixCall.prototype.candsSent = function() { - this.candidateSendTries = 0; - this.sendCandidateQueue(); - }; - - MatrixCall.prototype.candsSendFailed = function(cands, error) { - for (var i = 0; i < cands.length; ++i) { - this.candidateSendQueue.push(cands[i]); - } - - if (this.candidateSendTries > 5) { - console.log("Failed to send candidates on attempt "+this.candidateSendTries+". Giving up for now."); - this.candidateSendTries = 0; - return; - } - - var delayMs = 500 * Math.pow(2, this.candidateSendTries); - ++this.candidateSendTries; - console.log("Failed to send candidates. Retrying in "+delayMs+"ms"); - var self = this; - $timeout(function() { - self.sendCandidateQueue(); - }, delayMs); - }; - - MatrixCall.prototype.getLocalVideoElement = function() { - if (this.localVideoSelector) { - var t = angular.element(this.localVideoSelector); - if (t.length) return t[0]; - } - return null; - }; - - MatrixCall.prototype.getRemoteVideoElement = function() { - if (this.remoteVideoSelector) { - var t = angular.element(this.remoteVideoSelector); - if (t.length) return t[0]; - } - return null; - }; - - MatrixCall.prototype.isOpenWebRTC = function() { - var scripts = angular.element('script'); - for (var i = 0; i < scripts.length; i++) { - if (scripts[i].src.indexOf("owr.js") > -1) { - return true; - } - } - return false; - }; - - return MatrixCall; -}]); diff --git a/syweb/webclient/components/matrix/matrix-filter.js b/syweb/webclient/components/matrix/matrix-filter.js deleted file mode 100644 index cef9235891..0000000000 --- a/syweb/webclient/components/matrix/matrix-filter.js +++ /dev/null @@ -1,172 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -'use strict'; - -angular.module('matrixFilter', []) - -// Compute the room name according to information we have -// TODO: It would be nice if this was stateless and had no dependencies. That would -// make the business logic here a lot easier to see. -.filter('mRoomName', ['$rootScope', 'matrixService', 'modelService', 'mUserDisplayNameFilter', -function($rootScope, matrixService, modelService, mUserDisplayNameFilter) { - return function(room_id) { - var roomName; - - // If there is an alias, use it - // TODO: only one alias is managed for now - var alias = modelService.getRoomIdToAliasMapping(room_id); - var room = modelService.getRoom(room_id).current_room_state; - - var room_name_event = room.state("m.room.name"); - - // Determine if it is a public room - var isPublicRoom = false; - if (room.state("m.room.join_rules") && room.state("m.room.join_rules").content) { - isPublicRoom = ("public" === room.state("m.room.join_rules").content.join_rule); - } - - if (room_name_event) { - roomName = room_name_event.content.name; - } - else if (alias) { - roomName = alias; - } - else if (Object.keys(room.members).length > 0 && !isPublicRoom) { // Do not rename public room - var user_id = matrixService.config().user_id; - - // this is a "one to one" room and should have the name of the other user. - if (Object.keys(room.members).length === 2) { - for (var i in room.members) { - if (!room.members.hasOwnProperty(i)) continue; - - var member = room.members[i].event; - if (member.state_key !== user_id) { - roomName = mUserDisplayNameFilter(member.state_key, room_id); - if (!roomName) { - roomName = member.state_key; - } - break; - } - } - } - else if (Object.keys(room.members).length === 1) { - // this could be just us (self-chat) or could be the other person - // in a room if they have invited us to the room. Find out which. - var otherUserId = Object.keys(room.members)[0]; - if (otherUserId === user_id) { - // it's us, we may have been invited to this room or it could - // be a self chat. - if (room.members[otherUserId].event.content.membership === "invite") { - // someone invited us, use the right ID. - roomName = mUserDisplayNameFilter(room.members[otherUserId].event.user_id, room_id); - if (!roomName) { - roomName = room.members[otherUserId].event.user_id; - } - } - else { - roomName = mUserDisplayNameFilter(otherUserId, room_id); - if (!roomName) { - roomName = user_id; - } - } - } - else { // it isn't us, so use their name if we know it. - roomName = mUserDisplayNameFilter(otherUserId, room_id); - if (!roomName) { - roomName = otherUserId; - } - } - } - else if (Object.keys(room.members).length === 0) { - // this shouldn't be possible - console.error("0 members in room >> " + room_id); - } - } - - - // Always show the alias in the room displayed name - if (roomName && alias && alias !== roomName) { - roomName += " (" + alias + ")"; - } - - if (undefined === roomName) { - // By default, use the room ID - roomName = room_id; - } - - return roomName; - }; -}]) - -// Return the user display name -.filter('mUserDisplayName', ['modelService', 'matrixService', function(modelService, matrixService) { - /** - * Return the display name of an user acccording to data already downloaded - * @param {String} user_id the id of the user - * @param {String} room_id the room id - * @param {boolean} wrap whether to insert whitespace into the userid (if displayname not available) to help it wrap - * @returns {String} A suitable display name for the user. - */ - return function(user_id, room_id, wrap) { - var displayName; - - // Get the user display name from the member list of the room - var member = modelService.getMember(room_id, user_id); - if (member) { - member = member.event; - } - if (member && member.content.displayname) { // Do not consider null displayname - displayName = member.content.displayname; - - // Disambiguate users who have the same displayname in the room - if (user_id !== matrixService.config().user_id) { - var room = modelService.getRoom(room_id); - - for (var member_id in room.current_room_state.members) { - if (room.current_room_state.members.hasOwnProperty(member_id) && member_id !== user_id) { - var member2 = room.current_room_state.members[member_id].event; - if (member2.content.displayname && member2.content.displayname === displayName) { - displayName = displayName + " (" + user_id + ")"; - break; - } - } - } - } - } - - // The user may not have joined the room yet. So try to resolve display name from presence data - // Note: This data may not be available - if (undefined === displayName) { - var usr = modelService.getUser(user_id); - if (usr) { - displayName = usr.event.content.displayname; - } - } - - if (undefined === displayName) { - // By default, use the user ID - if (wrap && user_id.indexOf(':') >= 0) { - displayName = user_id.substr(0, user_id.indexOf(':')) + " " + user_id.substr(user_id.indexOf(':')); - } - else { - displayName = user_id; - } - } - - return displayName; - }; -}]); diff --git a/syweb/webclient/components/matrix/matrix-phone-service.js b/syweb/webclient/components/matrix/matrix-phone-service.js deleted file mode 100644 index 55dbbf522e..0000000000 --- a/syweb/webclient/components/matrix/matrix-phone-service.js +++ /dev/null @@ -1,155 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -angular.module('matrixPhoneService', []) -.factory('matrixPhoneService', ['$rootScope', '$injector', 'matrixService', 'eventHandlerService', function MatrixPhoneService($rootScope, $injector, matrixService, eventHandlerService) { - var matrixPhoneService = function() { - }; - - matrixPhoneService.INCOMING_CALL_EVENT = "INCOMING_CALL_EVENT"; - matrixPhoneService.REPLACED_CALL_EVENT = "REPLACED_CALL_EVENT"; - matrixPhoneService.allCalls = {}; - // a place to save candidates that come in for calls we haven't got invites for yet (when paginating backwards) - matrixPhoneService.candidatesByCall = {}; - - matrixPhoneService.callPlaced = function(call) { - matrixPhoneService.allCalls[call.call_id] = call; - }; - - $rootScope.$on(eventHandlerService.CALL_EVENT, function(ngEvent, event, isLive) { - if (event.user_id == matrixService.config().user_id) return; - - var msg = event.content; - - if (event.type == 'm.call.invite') { - if (event.age == undefined || msg.lifetime == undefined) { - // if the event doesn't have either an age (the HS is too old) or a lifetime - // (the sending client was too old when it sent it) then fall back to old behaviour - if (!isLive) return; // until matrix supports expiring messages - } - - if (event.age > msg.lifetime) { - console.log("Ignoring expired call event of type "+event.type); - return; - } - - var call = undefined; - if (!isLive) { - // if this event wasn't live then this call may already be over - call = matrixPhoneService.allCalls[msg.call_id]; - if (call && call.state == 'ended') { - return; - } - } - - var MatrixCall = $injector.get('MatrixCall'); - var call = new MatrixCall(event.room_id); - - if (!$rootScope.isWebRTCSupported()) { - console.log("Incoming call ID "+msg.call_id+" but this browser doesn't support WebRTC"); - // don't hang up the call: there could be other clients connected that do support WebRTC and declining the - // the call on their behalf would be really annoying. - // instead, we broadcast a fake call event with a non-functional call object - $rootScope.$broadcast(matrixPhoneService.INCOMING_CALL_EVENT, call); - return; - } - - call.call_id = msg.call_id; - call.initWithInvite(event); - matrixPhoneService.allCalls[call.call_id] = call; - - // if we stashed candidate events for that call ID, play them back now - if (!isLive && matrixPhoneService.candidatesByCall[call.call_id] != undefined) { - for (var i = 0; i < matrixPhoneService.candidatesByCall[call.call_id].length; ++i) { - call.gotRemoteIceCandidate(matrixPhoneService.candidatesByCall[call.call_id][i]); - } - } - - // Were we trying to call that user (room)? - var existingCall; - var callIds = Object.keys(matrixPhoneService.allCalls); - for (var i = 0; i < callIds.length; ++i) { - var thisCallId = callIds[i]; - var thisCall = matrixPhoneService.allCalls[thisCallId]; - - if (call.room_id == thisCall.room_id && thisCall.direction == 'outbound' - && (thisCall.state == 'wait_local_media' || thisCall.state == 'create_offer' || thisCall.state == 'invite_sent')) { - existingCall = thisCall; - break; - } - } - - if (existingCall) { - // If we've only got to wait_local_media or create_offer and we've got an invite, - // pick the incoming call because we know we haven't sent our invite yet - // otherwise, pick whichever call has the lowest call ID (by string comparison) - if (existingCall.state == 'wait_local_media' || existingCall.state == 'create_offer' || existingCall.call_id > call.call_id) { - console.log("Glare detected: answering incoming call "+call.call_id+" and canceling outgoing call "+existingCall.call_id); - existingCall.replacedBy(call); - call.answer(); - $rootScope.$broadcast(matrixPhoneService.REPLACED_CALL_EVENT, existingCall, call); - } else { - console.log("Glare detected: rejecting incoming call "+call.call_id+" and keeping outgoing call "+existingCall.call_id); - call.hangup(); - } - } else { - $rootScope.$broadcast(matrixPhoneService.INCOMING_CALL_EVENT, call); - } - } else if (event.type == 'm.call.answer') { - var call = matrixPhoneService.allCalls[msg.call_id]; - if (!call) { - console.log("Got answer for unknown call ID "+msg.call_id); - return; - } - call.receivedAnswer(msg); - } else if (event.type == 'm.call.candidates') { - var call = matrixPhoneService.allCalls[msg.call_id]; - if (!call && isLive) { - console.log("Got candidates for unknown call ID "+msg.call_id); - return; - } else if (!call) { - if (matrixPhoneService.candidatesByCall[msg.call_id] == undefined) { - matrixPhoneService.candidatesByCall[msg.call_id] = []; - } - matrixPhoneService.candidatesByCall[msg.call_id] = matrixPhoneService.candidatesByCall[msg.call_id].concat(msg.candidates); - } else { - for (var i = 0; i < msg.candidates.length; ++i) { - call.gotRemoteIceCandidate(msg.candidates[i]); - } - } - } else if (event.type == 'm.call.hangup') { - var call = matrixPhoneService.allCalls[msg.call_id]; - if (!call && isLive) { - console.log("Got hangup for unknown call ID "+msg.call_id); - } else if (!call) { - // if not live, store the fact that the call has ended because we're probably getting events backwards so - // the hangup will come before the invite - var MatrixCall = $injector.get('MatrixCall'); - var call = new MatrixCall(event.room_id); - call.call_id = msg.call_id; - call.initWithHangup(event); - matrixPhoneService.allCalls[msg.call_id] = call; - } else { - call.onHangupReceived(msg); - delete(matrixPhoneService.allCalls[msg.call_id]); - } - } - }); - - return matrixPhoneService; -}]); diff --git a/syweb/webclient/components/matrix/matrix-service.js b/syweb/webclient/components/matrix/matrix-service.js deleted file mode 100644 index cfe8691f85..0000000000 --- a/syweb/webclient/components/matrix/matrix-service.js +++ /dev/null @@ -1,701 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -/* -This service wraps up Matrix API calls. - -This serves to isolate the caller from changes to the underlying url paths, as -well as attach common params (e.g. access_token) to requests. -*/ -angular.module('matrixService', []) -.factory('matrixService', ['$http', '$q', function($http, $q) { - - /* - * Permanent storage of user information - * The config contains: - * - homeserver url - * - Identity server url - * - user_id - * - access_token - * - version: the version of this cache - */ - var config; - - // Current version of permanent storage - var configVersion = 0; - var prefixPath = "/_matrix/client/api/v1"; - - var doRequest = function(method, path, params, data, $httpParams) { - if (!config) { - console.warn("No config exists. Cannot perform request to "+path); - return; - } - - // Inject the access token - if (!params) { - params = {}; - } - - params.access_token = config.access_token; - - if (path.indexOf(prefixPath) !== 0) { - path = prefixPath + path; - } - - return doBaseRequest(config.homeserver, method, path, params, data, undefined, $httpParams); - }; - - var doBaseRequest = function(baseUrl, method, path, params, data, headers, $httpParams) { - - var request = { - method: method, - url: baseUrl + path, - params: params, - data: data, - headers: headers - }; - - // Add additional $http parameters - if ($httpParams) { - angular.extend(request, $httpParams); - } - - return $http(request); - }; - - var doRegisterLogin = function(path, loginType, sessionId, userName, password, threepidCreds) { - var data = {}; - if (loginType === "m.login.recaptcha") { - var challengeToken = Recaptcha.get_challenge(); - var captchaEntry = Recaptcha.get_response(); - data = { - type: "m.login.recaptcha", - challenge: challengeToken, - response: captchaEntry - }; - } - else if (loginType === "m.login.email.identity") { - data = { - threepidCreds: threepidCreds - }; - } - else if (loginType === "m.login.password") { - data = { - user: userName, - password: password - }; - } - - if (sessionId) { - data.session = sessionId; - } - data.type = loginType; - console.log("doRegisterLogin >>> " + loginType); - return doRequest("POST", path, undefined, data); - }; - - return { - /****** Home server API ******/ - prefix: prefixPath, - - // Register an user - register: function(user_name, password, threepidCreds, useCaptcha) { - // registration is composed of multiple requests, to check you can - // register, then to actually register. This deferred will fire when - // all the requests are done, along with the final response. - var deferred = $q.defer(); - var path = "/register"; - - // check we can actually register with this HS. - doRequest("GET", path, undefined, undefined).then( - function(response) { - console.log("/register [1] : "+JSON.stringify(response)); - var flows = response.data.flows; - var knownTypes = [ - "m.login.password", - "m.login.recaptcha", - "m.login.email.identity" - ]; - // if they entered 3pid creds, we want to use a flow which uses it. - var useThreePidFlow = threepidCreds != undefined; - var flowIndex = 0; - var firstRegType = undefined; - - for (var i=0; i<flows.length; i++) { - var isThreePidFlow = false; - if (flows[i].stages) { - for (var j=0; j<flows[i].stages.length; j++) { - var regType = flows[i].stages[j]; - if (knownTypes.indexOf(regType) === -1) { - deferred.reject("Unknown type: "+regType); - return; - } - if (regType == "m.login.email.identity") { - isThreePidFlow = true; - } - if (!useCaptcha && regType == "m.login.recaptcha") { - console.error("Web client setup to not use captcha, but HS demands a captcha."); - deferred.reject({ - data: { - errcode: "M_CAPTCHA_NEEDED", - error: "Home server requires a captcha." - } - }); - return; - } - } - } - - if ( (isThreePidFlow && useThreePidFlow) || (!isThreePidFlow && !useThreePidFlow) ) { - flowIndex = i; - } - - if (knownTypes.indexOf(flows[i].type) == -1) { - deferred.reject("Unknown type: "+flows[i].type); - return; - } - } - - // looks like we can register fine, go ahead and do it. - console.log("Using flow " + JSON.stringify(flows[flowIndex])); - firstRegType = flows[flowIndex].type; - var sessionId = undefined; - - // generic response processor so it can loop as many times as required - var loginResponseFunc = function(response) { - if (response.data.session) { - sessionId = response.data.session; - } - console.log("login response: " + JSON.stringify(response.data)); - if (response.data.access_token) { - deferred.resolve(response); - } - else if (response.data.next) { - var nextType = response.data.next; - if (response.data.next instanceof Array) { - for (var i=0; i<response.data.next.length; i++) { - if (useThreePidFlow && response.data.next[i] == "m.login.email.identity") { - nextType = response.data.next[i]; - break; - } - else if (!useThreePidFlow && response.data.next[i] != "m.login.email.identity") { - nextType = response.data.next[i]; - break; - } - } - } - return doRegisterLogin(path, nextType, sessionId, user_name, password, threepidCreds).then( - loginResponseFunc, - function(err) { - deferred.reject(err); - } - ); - } - else { - deferred.reject("Unknown continuation: "+JSON.stringify(response)); - } - }; - - // set the ball rolling - doRegisterLogin(path, firstRegType, undefined, user_name, password, threepidCreds).then( - loginResponseFunc, - function(err) { - deferred.reject(err); - } - ); - - }, - function(err) { - deferred.reject(err); - } - ); - - return deferred.promise; - }, - - // Create a room - create: function(room_alias, visibility) { - // The REST path spec - var path = "/createRoom"; - - var req = { - "visibility": visibility - }; - if (room_alias) { - req.room_alias_name = room_alias; - } - - return doRequest("POST", path, undefined, req); - }, - - // Get the user's current state: his presence, the list of his rooms with - // the last {limit} events - initialSync: function(limit, feedback) { - // The REST path spec - - var path = "/initialSync"; - - var params = {}; - if (limit) { - params.limit = limit; - } - if (feedback) { - params.feedback = feedback; - } - - return doRequest("GET", path, params); - }, - - // get room state for a specific room - roomState: function(room_id) { - var path = "/rooms/" + encodeURIComponent(room_id) + "/state"; - return doRequest("GET", path); - }, - - // Joins a room - join: function(room_id) { - return this.membershipChange(room_id, undefined, "join"); - }, - - joinAlias: function(room_alias) { - var path = "/join/$room_alias"; - room_alias = encodeURIComponent(room_alias); - - path = path.replace("$room_alias", room_alias); - - // TODO: PUT with txn ID - return doRequest("POST", path, undefined, {}); - }, - - // Invite a user to a room - invite: function(room_id, user_id) { - return this.membershipChange(room_id, user_id, "invite"); - }, - - // Leaves a room - leave: function(room_id) { - return this.membershipChange(room_id, undefined, "leave"); - }, - - membershipChange: function(room_id, user_id, membershipValue) { - // The REST path spec - var path = "/rooms/$room_id/$membership"; - path = path.replace("$room_id", encodeURIComponent(room_id)); - path = path.replace("$membership", encodeURIComponent(membershipValue)); - - var data = {}; - if (user_id !== undefined) { - data = { user_id: user_id }; - } - - // TODO: Use PUT with transaction IDs - return doRequest("POST", path, undefined, data); - }, - - // Change the membership of an another user - setMembership: function(room_id, user_id, membershipValue, reason) { - - // The REST path spec - var path = "/rooms/$room_id/state/m.room.member/$user_id"; - path = path.replace("$room_id", encodeURIComponent(room_id)); - path = path.replace("$user_id", user_id); - - return doRequest("PUT", path, undefined, { - membership : membershipValue, - reason: reason - }); - }, - - // Bans a user from a room - ban: function(room_id, user_id, reason) { - var path = "/rooms/$room_id/ban"; - path = path.replace("$room_id", encodeURIComponent(room_id)); - - return doRequest("POST", path, undefined, { - user_id: user_id, - reason: reason - }); - }, - - // Unbans a user in a room - unban: function(room_id, user_id) { - // FIXME: To update when there will be homeserver API for unban - // For now, do an unban by resetting the user membership to "leave" - return this.setMembership(room_id, user_id, "leave"); - }, - - // Kicks a user from a room - kick: function(room_id, user_id, reason) { - // Set the user membership to "leave" to kick him - return this.setMembership(room_id, user_id, "leave", reason); - }, - - // Retrieves the room ID corresponding to a room alias - resolveRoomAlias:function(room_alias) { - var path = "/_matrix/client/api/v1/directory/room/$room_alias"; - room_alias = encodeURIComponent(room_alias); - - path = path.replace("$room_alias", room_alias); - - return doRequest("GET", path, undefined, {}); - }, - - setName: function(room_id, name) { - var data = { - name: name - }; - return this.sendStateEvent(room_id, "m.room.name", data); - }, - - setTopic: function(room_id, topic) { - var data = { - topic: topic - }; - return this.sendStateEvent(room_id, "m.room.topic", data); - }, - - - sendStateEvent: function(room_id, eventType, content, state_key) { - var path = "/rooms/$room_id/state/"+ eventType; - // TODO: uncomment this when matrix.org is updated, else all state events 500. - // var path = "/rooms/$room_id/state/"+ encodeURIComponent(eventType); - if (state_key !== undefined) { - path += "/" + encodeURIComponent(state_key); - } - room_id = encodeURIComponent(room_id); - path = path.replace("$room_id", room_id); - - return doRequest("PUT", path, undefined, content); - }, - - sendEvent: function(room_id, eventType, txn_id, content) { - // The REST path spec - var path = "/rooms/$room_id/send/"+eventType+"/$txn_id"; - - if (!txn_id) { - txn_id = "m" + new Date().getTime(); - } - - // Like the cmd client, escape room ids - room_id = encodeURIComponent(room_id); - - // Customize it - path = path.replace("$room_id", room_id); - path = path.replace("$txn_id", txn_id); - - return doRequest("PUT", path, undefined, content); - }, - - sendMessage: function(room_id, txn_id, content) { - return this.sendEvent(room_id, 'm.room.message', txn_id, content); - }, - - // Send a text message - sendTextMessage: function(room_id, body, msg_id) { - var content = { - msgtype: "m.text", - body: body - }; - - return this.sendMessage(room_id, msg_id, content); - }, - - // Send an image message - sendImageMessage: function(room_id, image_url, image_body, msg_id) { - var content = { - msgtype: "m.image", - url: image_url, - info: image_body, - body: "Image" - }; - - return this.sendMessage(room_id, msg_id, content); - }, - - // Send an emote message - sendEmoteMessage: function(room_id, body, msg_id) { - var content = { - msgtype: "m.emote", - body: body - }; - - return this.sendMessage(room_id, msg_id, content); - }, - - redactEvent: function(room_id, event_id) { - var path = "/rooms/$room_id/redact/$event_id"; - path = path.replace("$room_id", encodeURIComponent(room_id)); - // TODO: encodeURIComponent when HS updated. - path = path.replace("$event_id", event_id); - var content = {}; - return doRequest("POST", path, undefined, content); - }, - - // get a snapshot of the members in a room. - getMemberList: function(room_id) { - // Like the cmd client, escape room ids - room_id = encodeURIComponent(room_id); - - var path = "/rooms/$room_id/members"; - path = path.replace("$room_id", room_id); - return doRequest("GET", path); - }, - - paginateBackMessages: function(room_id, from_token, limit) { - var path = "/rooms/$room_id/messages"; - path = path.replace("$room_id", encodeURIComponent(room_id)); - var params = { - from: from_token, - limit: limit, - dir: 'b' - }; - return doRequest("GET", path, params); - }, - - // get a list of public rooms on your home server - publicRooms: function() { - var path = "/publicRooms"; - return doRequest("GET", path); - }, - - // get a user's profile - getProfile: function(userId) { - return this.getProfileInfo(userId); - }, - - // get a display name for this user ID - getDisplayName: function(userId) { - return this.getProfileInfo(userId, "displayname"); - }, - - // get the profile picture url for this user ID - getProfilePictureUrl: function(userId) { - return this.getProfileInfo(userId, "avatar_url"); - }, - - // update your display name - setDisplayName: function(newName) { - var content = { - displayname: newName - }; - return this.setProfileInfo(content, "displayname"); - }, - - // update your profile picture url - setProfilePictureUrl: function(newUrl) { - var content = { - avatar_url: newUrl - }; - return this.setProfileInfo(content, "avatar_url"); - }, - - setProfileInfo: function(data, info_segment) { - var path = "/profile/$user/" + info_segment; - path = path.replace("$user", encodeURIComponent(config.user_id)); - return doRequest("PUT", path, undefined, data); - }, - - getProfileInfo: function(userId, info_segment) { - var path = "/profile/"+encodeURIComponent(userId); - if (info_segment) path += '/' + info_segment; - return doRequest("GET", path); - }, - - login: function(userId, password) { - // TODO We should be checking to make sure the client can support - // logging in to this HS, else use the fallback. - var path = "/login"; - var data = { - "type": "m.login.password", - "user": userId, - "password": password - }; - return doRequest("POST", path, undefined, data); - }, - - // hit the Identity Server for a 3PID request. - linkEmail: function(email, clientSecret, sendAttempt) { - var path = "/_matrix/identity/api/v1/validate/email/requestToken"; - var data = "clientSecret="+clientSecret+"&email=" + encodeURIComponent(email)+"&sendAttempt="+sendAttempt; - var headers = {}; - headers["Content-Type"] = "application/x-www-form-urlencoded"; - return doBaseRequest(config.identityServer, "POST", path, {}, data, headers); - }, - - authEmail: function(clientSecret, sid, code) { - var path = "/_matrix/identity/api/v1/validate/email/submitToken"; - var data = "token="+code+"&sid="+sid+"&clientSecret="+clientSecret; - var headers = {}; - headers["Content-Type"] = "application/x-www-form-urlencoded"; - return doBaseRequest(config.identityServer, "POST", path, {}, data, headers); - }, - - bindEmail: function(userId, tokenId, clientSecret) { - var path = "/_matrix/identity/api/v1/3pid/bind"; - var data = "mxid="+encodeURIComponent(userId)+"&sid="+tokenId+"&clientSecret="+clientSecret; - var headers = {}; - headers["Content-Type"] = "application/x-www-form-urlencoded"; - return doBaseRequest(config.identityServer, "POST", path, {}, data, headers); - }, - - lookup3pid: function(medium, address) { - var path = "/_matrix/identity/api/v1/lookup?medium="+encodeURIComponent(medium)+"&address="+encodeURIComponent(address); - return doBaseRequest(config.identityServer, "GET", path, {}, undefined, {}); - }, - - uploadContent: function(file) { - var path = "/_matrix/content"; - var headers = { - "Content-Type": undefined // undefined means angular will figure it out - }; - var params = { - access_token: config.access_token - }; - - // If the file is actually a Blob object, prevent $http from JSON-stringified it before sending - // (Equivalent to jQuery ajax processData = false) - var $httpParams; - if (file instanceof Blob) { - $httpParams = { - transformRequest: angular.identity - }; - } - - return doBaseRequest(config.homeserver, "POST", path, params, file, headers, $httpParams); - }, - - /** - * Start listening on /events - * @param {String} from the token from which to listen events to - * @param {Integer} serverTimeout the time in ms the server will hold open the connection - * @param {Integer} clientTimeout the timeout in ms used at the client HTTP request level - * @returns a promise - */ - getEventStream: function(from, serverTimeout, clientTimeout) { - var path = "/events"; - var params = { - from: from, - timeout: serverTimeout - }; - - var $httpParams; - if (clientTimeout) { - // If the Internet connection is lost, this timeout is used to be able to - // cancel the current request and notify the client so that it can retry with a new request. - $httpParams = { - timeout: clientTimeout - }; - } - - return doRequest("GET", path, params, undefined, $httpParams); - }, - - // Indicates if user authentications details are stored in cache - isUserLoggedIn: function() { - var config = this.config(); - - // User is considered logged in if his cache is not empty and contains - // an access token - if (config && config.access_token) { - return true; - } - else { - return false; - } - }, - - // Enum of presence state - presence: { - offline: "offline", - unavailable: "unavailable", - online: "online", - free_for_chat: "free_for_chat" - }, - - // Set the logged in user presence state - setUserPresence: function(presence) { - var path = "/presence/$user_id/status"; - path = path.replace("$user_id", encodeURIComponent(config.user_id)); - return doRequest("PUT", path, undefined, { - presence: presence - }); - }, - - - /****** Permanent storage of user information ******/ - - // Returns the current config - config: function() { - if (!config) { - config = localStorage.getItem("config"); - if (config) { - config = JSON.parse(config); - - // Reset the cache if the version loaded is not the expected one - if (configVersion !== config.version) { - config = undefined; - this.saveConfig(); - } - } - } - return config; - }, - - // Set a new config (Use saveConfig to actually store it permanently) - setConfig: function(newConfig) { - config = newConfig; - console.log("new IS: "+config.identityServer); - }, - - // Commits config into permanent storage - saveConfig: function() { - config.version = configVersion; - localStorage.setItem("config", JSON.stringify(config)); - }, - - /** - * Change or reset the power level of a user - * @param {String} room_id the room id - * @param {String} user_id the user id - * @param {Number} powerLevel The desired power level. - * If undefined, the user power level will be reset, ie he will use the default room user power level - * @param event The existing m.room.power_levels event if one exists. - * @returns {promise} an $http promise - */ - setUserPowerLevel: function(room_id, user_id, powerLevel, event) { - var content = {}; - if (event) { - // if there is an existing event, copy the content as it contains - // the power level values for other members which we do not want - // to modify. - content = angular.copy(event.content); - } - content[user_id] = powerLevel; - - var path = "/rooms/$room_id/state/m.room.power_levels"; - path = path.replace("$room_id", encodeURIComponent(room_id)); - - return doRequest("PUT", path, undefined, content); - }, - - getTurnServer: function() { - return doRequest("GET", "/voip/turnServer"); - } - - }; -}]); diff --git a/syweb/webclient/components/matrix/model-service.js b/syweb/webclient/components/matrix/model-service.js deleted file mode 100644 index 0ec919b637..0000000000 --- a/syweb/webclient/components/matrix/model-service.js +++ /dev/null @@ -1,349 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -/* -This service serves as the entry point for all models in the app. If access to -underlying data in a room is required, then this service should be used as the -dependency. -*/ -// NB: This is more explicit than linking top-level models to $rootScope -// in that by adding this service as a dep you are clearly saying "this X -// needs access to the underlying data store", rather than polluting the -// $rootScope. -angular.module('modelService', []) -.factory('modelService', ['matrixService', function(matrixService) { - - // alias / id lookups - var roomIdToAlias, aliasToRoomId; - var setRoomIdToAliasMapping = function(roomId, alias) { - roomIdToAlias[roomId] = alias; - aliasToRoomId[alias] = roomId; - }; - - // user > room member lookups - var userIdToRoomMember; - - // main store - var rooms, users; - - var init = function() { - roomIdToAlias = {}; - aliasToRoomId = {}; - userIdToRoomMember = { - // user_id: [RoomMember, RoomMember, ...] - }; - - // rooms are stored here when they come in. - rooms = { - // roomid: <Room> - }; - - users = { - // user_id: <User> - }; - console.log("Models inited."); - }; - - init(); - - /***** Room Object *****/ - var Room = function Room(room_id) { - this.room_id = room_id; - this.old_room_state = new RoomState(); - this.current_room_state = new RoomState(); - this.now = this.current_room_state; // makes html access shorter - this.events = []; // events which can be displayed on the UI. TODO move? - }; - Room.prototype = { - addMessageEvents: function addMessageEvents(events, toFront) { - for (var i=0; i<events.length; i++) { - this.addMessageEvent(events[i], toFront); - } - }, - - addMessageEvent: function addMessageEvent(event, toFront) { - // every message must reference the RoomMember which made it *at - // that time* so things like display names display correctly. - var stateAtTheTime = toFront ? this.old_room_state : this.current_room_state; - event.__room_member = stateAtTheTime.getStateEvent("m.room.member", event.user_id); - if (event.type === "m.room.member" && event.content.membership === "invite") { - // give information on both the inviter and invitee - event.__target_room_member = stateAtTheTime.getStateEvent("m.room.member", event.state_key); - } - - if (toFront) { - this.events.unshift(event); - } - else { - this.events.push(event); - } - }, - - addOrReplaceMessageEvent: function addOrReplaceMessageEvent(event, toFront) { - // Start looking from the tail since the first goal of this function - // is to find a message among the latest ones - for (var i = this.events.length - 1; i >= 0; i--) { - var storedEvent = this.events[i]; - if (storedEvent.event_id === event.event_id) { - // It's clobbering time! - this.events[i] = event; - return; - } - } - this.addMessageEvent(event, toFront); - }, - - leave: function leave() { - return matrixService.leave(this.room_id); - } - }; - - /***** Room State Object *****/ - var RoomState = function RoomState() { - // list of RoomMember - this.members = {}; - // state events, the key is a compound of event type + state_key - this.state_events = {}; - this.pagination_token = ""; - }; - RoomState.prototype = { - // get a state event for this room from this.state_events. State events - // are unique per type+state_key tuple, with a lot of events using 0-len - // state keys. To make it not Really Annoying to access, this method is - // provided which can just be given the type and it will return the - // 0-len event by default. - state: function state(type, state_key) { - if (!type) { - return undefined; // event type MUST be specified - } - if (!state_key) { - return this.state_events[type]; // treat as 0-len state key - } - return this.state_events[type + state_key]; - }, - - storeStateEvent: function storeState(event) { - var keyIndex = event.state_key === undefined ? event.type : event.type + event.state_key; - this.state_events[keyIndex] = event; - if (event.type === "m.room.member") { - var userId = event.state_key; - var rm = new RoomMember(); - rm.event = event; - rm.user = users[userId]; - this.members[userId] = rm; - - // add to lookup so new m.presence events update the user - if (!userIdToRoomMember[userId]) { - userIdToRoomMember[userId] = []; - } - userIdToRoomMember[userId].push(rm); - } - else if (event.type === "m.room.aliases") { - setRoomIdToAliasMapping(event.room_id, event.content.aliases[0]); - } - else if (event.type === "m.room.power_levels") { - // normalise power levels: find the max first. - var maxPowerLevel = 0; - for (var user_id in event.content) { - if (!event.content.hasOwnProperty(user_id) || user_id === "hsob_ts") continue; // XXX hsob_ts on some old rooms :( - maxPowerLevel = Math.max(maxPowerLevel, event.content[user_id]); - } - // set power level f.e room member - var defaultPowerLevel = event.content.default === undefined ? 0 : event.content.default; - for (var user_id in this.members) { - if (!this.members.hasOwnProperty(user_id)) continue; - var rm = this.members[user_id]; - if (!rm) { - continue; - } - rm.power_level = event.content[user_id] === undefined ? defaultPowerLevel : event.content[user_id]; - rm.power_level_norm = (rm.power_level * 100) / maxPowerLevel; - } - } - }, - - storeStateEvents: function storeState(events) { - if (!events) { - return; - } - for (var i=0; i<events.length; i++) { - this.storeStateEvent(events[i]); - } - }, - - getStateEvent: function getStateEvent(event_type, state_key) { - return this.state_events[event_type + state_key]; - } - }; - - /***** Room Member Object *****/ - var RoomMember = function RoomMember() { - this.event = {}; // the m.room.member event representing the RoomMember. - this.power_level_norm = 0; - this.power_level = 0; - this.user = undefined; // the User - }; - - /***** User Object *****/ - var User = function User() { - this.event = {}; // the m.presence event representing the User. - this.last_updated = 0; // used with last_active_ago to work out last seen times - }; - - return { - - getRoom: function(roomId) { - if(!rooms[roomId]) { - rooms[roomId] = new Room(roomId); - } - return rooms[roomId]; - }, - - getRooms: function() { - return rooms; - }, - - /** - * Get the member object of a room member - * @param {String} room_id the room id - * @param {String} user_id the id of the user - * @returns {undefined | Object} the member object of this user in this room if he is part of the room - */ - getMember: function(room_id, user_id) { - var room = this.getRoom(room_id); - return room.current_room_state.members[user_id]; - }, - - createRoomIdToAliasMapping: function(roomId, alias) { - setRoomIdToAliasMapping(roomId, alias); - }, - - getRoomIdToAliasMapping: function(roomId) { - var alias = roomIdToAlias[roomId]; - //console.log("looking for alias for " + roomId + "; found: " + alias); - return alias; - }, - - getAliasToRoomIdMapping: function(alias) { - var roomId = aliasToRoomId[alias]; - //console.log("looking for roomId for " + alias + "; found: " + roomId); - return roomId; - }, - - getUser: function(user_id) { - return users[user_id]; - }, - - setUser: function(event) { - var usr = new User(); - usr.event = event; - - // migrate old data but clobber matching keys - if (users[event.content.user_id] && users[event.content.user_id].event) { - angular.extend(users[event.content.user_id].event, event); - usr = users[event.content.user_id]; - } - else { - users[event.content.user_id] = usr; - } - - usr.last_updated = new Date().getTime(); - - // update room members - var roomMembers = userIdToRoomMember[event.content.user_id]; - if (roomMembers) { - for (var i=0; i<roomMembers.length; i++) { - var rm = roomMembers[i]; - rm.user = usr; - } - } - }, - - /** - * Return the power level of an user in a particular room - * @param {String} room_id the room id - * @param {String} user_id the user id - * @returns {Number} - */ - getUserPowerLevel: function(room_id, user_id) { - var powerLevel = 0; - var room = this.getRoom(room_id).current_room_state; - if (room.state("m.room.power_levels")) { - if (user_id in room.state("m.room.power_levels").content) { - powerLevel = room.state("m.room.power_levels").content[user_id]; - } - else { - // Use the room default user power - powerLevel = room.state("m.room.power_levels").content["default"]; - } - } - return powerLevel; - }, - - /** - * Compute the room users number, ie the number of members who has joined the room. - * @param {String} room_id the room id - * @returns {undefined | Number} the room users number if available - */ - getUserCountInRoom: function(room_id) { - var memberCount; - - var room = this.getRoom(room_id); - memberCount = 0; - for (var i in room.current_room_state.members) { - if (!room.current_room_state.members.hasOwnProperty(i)) continue; - - var member = room.current_room_state.members[i].event; - - if ("join" === member.content.membership) { - memberCount = memberCount + 1; - } - } - - return memberCount; - }, - - /** - * Return the last message event of a room - * @param {String} room_id the room id - * @param {Boolean} filterFake true to not take into account fake messages - * @returns {undefined | Event} the last message event if available - */ - getLastMessage: function(room_id, filterEcho) { - var lastMessage; - - var events = this.getRoom(room_id).events; - for (var i = events.length - 1; i >= 0; i--) { - var message = events[i]; - - // TODO: define a better marker than echo_msg_state - if (!filterEcho || undefined === message.echo_msg_state) { - lastMessage = message; - break; - } - } - - return lastMessage; - }, - - clearRooms: function() { - init(); - } - - }; -}]); diff --git a/syweb/webclient/components/matrix/notification-service.js b/syweb/webclient/components/matrix/notification-service.js deleted file mode 100644 index 9a911413c3..0000000000 --- a/syweb/webclient/components/matrix/notification-service.js +++ /dev/null @@ -1,104 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -/* -This service manages notifications: enabling, creating and showing them. This -also contains 'bing word' logic. -*/ -angular.module('notificationService', []) -.factory('notificationService', ['$timeout', function($timeout) { - - var getLocalPartFromUserId = function(user_id) { - if (!user_id) { - return null; - } - var localpartRegex = /@(.*):\w+/i - var results = localpartRegex.exec(user_id); - if (results && results.length == 2) { - return results[1]; - } - return null; - }; - - return { - - containsBingWord: function(userId, displayName, bingWords, content) { - // case-insensitive name check for user_id OR display_name if they exist - var userRegex = ""; - if (userId) { - var localpart = getLocalPartFromUserId(userId); - if (localpart) { - localpart = localpart.toLocaleLowerCase(); - userRegex += "\\b" + localpart + "\\b"; - } - } - if (displayName) { - displayName = displayName.toLocaleLowerCase(); - if (userRegex.length > 0) { - userRegex += "|"; - } - userRegex += "\\b" + displayName + "\\b"; - } - - var regexList = [new RegExp(userRegex, 'i')]; - - // bing word list check - if (bingWords && bingWords.length > 0) { - for (var i=0; i<bingWords.length; i++) { - var re = RegExp(bingWords[i], 'i'); - regexList.push(re); - } - } - return this.hasMatch(regexList, content); - }, - - hasMatch: function(regExps, content) { - if (!content || $.type(content) != "string") { - return false; - } - - if (regExps && regExps.length > 0) { - for (var i=0; i<regExps.length; i++) { - if (content.search(regExps[i]) != -1) { - return true; - } - } - } - return false; - }, - - showNotification: function(title, body, icon, onclick) { - var notification = new window.Notification( - title, - { - "body": body, - "icon": icon - } - ); - - if (onclick) { - notification.onclick = onclick; - } - - $timeout(function() { - notification.close(); - }, 5 * 1000); - } - }; - -}]); diff --git a/syweb/webclient/components/matrix/presence-service.js b/syweb/webclient/components/matrix/presence-service.js deleted file mode 100644 index b487e3d3bd..0000000000 --- a/syweb/webclient/components/matrix/presence-service.js +++ /dev/null @@ -1,113 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -'use strict'; - -/* - * This service tracks user activity on the page to determine his presence state. - * Any state change will be sent to the Home Server. - */ -angular.module('mPresence', []) -.service('mPresence', ['$timeout', 'matrixService', function ($timeout, matrixService) { - - // Time in ms after that a user is considered as unavailable/away - var UNAVAILABLE_TIME = 3 * 60000; // 3 mins - - // The current presence state - var state = undefined; - - var self =this; - var timer; - - /** - * Start listening the user activity to evaluate his presence state. - * Any state change will be sent to the Home Server. - */ - this.start = function() { - if (undefined === state) { - // The user is online if he moves the mouser or press a key - document.onmousemove = resetTimer; - document.onkeypress = resetTimer; - - resetTimer(); - } - }; - - /** - * Stop tracking user activity - */ - this.stop = function() { - if (timer) { - $timeout.cancel(timer); - timer = undefined; - } - state = undefined; - }; - - /** - * Get the current presence state. - * @returns {matrixService.presence} the presence state - */ - this.getState = function() { - return state; - }; - - /** - * Set the presence state. - * If the state has changed, the Home Server will be notified. - * @param {matrixService.presence} newState the new presence state - */ - this.setState = function(newState) { - if (newState !== state) { - console.log("mPresence - New state: " + newState); - - state = newState; - - // Inform the HS on the new user state - matrixService.setUserPresence(state).then( - function() { - - }, - function(error) { - console.log("mPresence - Failed to send new presence state: " + JSON.stringify(error)); - }); - } - }; - - /** - * Callback called when the user made no action on the page for UNAVAILABLE_TIME ms. - * @private - */ - function onUnvailableTimerFire() { - self.setState(matrixService.presence.unavailable); - } - - /** - * Callback called when the user made an action on the page - * @private - */ - function resetTimer() { - // User is still here - self.setState(matrixService.presence.online); - - // Re-arm the timer - $timeout.cancel(timer); - timer = $timeout(onUnvailableTimerFire, UNAVAILABLE_TIME); - } - -}]); - - diff --git a/syweb/webclient/components/matrix/recents-service.js b/syweb/webclient/components/matrix/recents-service.js deleted file mode 100644 index 3d82b8218b..0000000000 --- a/syweb/webclient/components/matrix/recents-service.js +++ /dev/null @@ -1,99 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -/* -This service manages shared state between *instances* of recent lists. The -recents controller will hook into this central service to get things like: -- which rooms should be highlighted -- which rooms have been binged -- which room is currently selected -- etc. -This is preferable to polluting the $rootScope with recents specific info, and -makes the dependency on this shared state *explicit*. -*/ -angular.module('recentsService', []) -.factory('recentsService', ['$rootScope', 'eventHandlerService', function($rootScope, eventHandlerService) { - // notify listeners when variables in the service are updated. We need to do - // this since we do not tie them to any scope. - var BROADCAST_SELECTED_ROOM_ID = "recentsService:BROADCAST_SELECTED_ROOM_ID(room_id)"; - var selectedRoomId = undefined; - - var BROADCAST_UNREAD_MESSAGES = "recentsService:BROADCAST_UNREAD_MESSAGES(room_id, unreadCount)"; - var unreadMessages = { - // room_id: <number> - }; - - var BROADCAST_UNREAD_BING_MESSAGES = "recentsService:BROADCAST_UNREAD_BING_MESSAGES(room_id, event)"; - var unreadBingMessages = { - // room_id: bingEvent - }; - - // listen for new unread messages - $rootScope.$on(eventHandlerService.MSG_EVENT, function(ngEvent, event, isLive) { - if (isLive && event.room_id !== selectedRoomId) { - if (eventHandlerService.eventContainsBingWord(event)) { - if (!unreadBingMessages[event.room_id]) { - unreadBingMessages[event.room_id] = {}; - } - unreadBingMessages[event.room_id] = event; - $rootScope.$broadcast(BROADCAST_UNREAD_BING_MESSAGES, event.room_id, event); - } - - if (!unreadMessages[event.room_id]) { - unreadMessages[event.room_id] = 0; - } - unreadMessages[event.room_id] += 1; - $rootScope.$broadcast(BROADCAST_UNREAD_MESSAGES, event.room_id, unreadMessages[event.room_id]); - } - }); - - return { - BROADCAST_SELECTED_ROOM_ID: BROADCAST_SELECTED_ROOM_ID, - BROADCAST_UNREAD_MESSAGES: BROADCAST_UNREAD_MESSAGES, - - getSelectedRoomId: function() { - return selectedRoomId; - }, - - setSelectedRoomId: function(room_id) { - selectedRoomId = room_id; - $rootScope.$broadcast(BROADCAST_SELECTED_ROOM_ID, room_id); - }, - - getUnreadMessages: function() { - return unreadMessages; - }, - - getUnreadBingMessages: function() { - return unreadBingMessages; - }, - - markAsRead: function(room_id) { - if (unreadMessages[room_id]) { - unreadMessages[room_id] = 0; - } - if (unreadBingMessages[room_id]) { - unreadBingMessages[room_id] = undefined; - } - $rootScope.$broadcast(BROADCAST_UNREAD_MESSAGES, room_id, 0); - $rootScope.$broadcast(BROADCAST_UNREAD_BING_MESSAGES, room_id, undefined); - } - - }; - -}]); diff --git a/syweb/webclient/components/utilities/utilities-service.js b/syweb/webclient/components/utilities/utilities-service.js deleted file mode 100644 index b417cc5b39..0000000000 --- a/syweb/webclient/components/utilities/utilities-service.js +++ /dev/null @@ -1,151 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -'use strict'; - -/* - * This service contains multipurpose helper functions. - */ -angular.module('mUtilities', []) -.service('mUtilities', ['$q', function ($q) { - /* - * Get the size of an image - * @param {File|Blob} imageFile the file containing the image - * @returns {promise} A promise that will be resolved by an object with 2 members: - * width & height - */ - this.getImageSize = function(imageFile) { - var deferred = $q.defer(); - - // Load the file into an html element - var img = document.createElement("img"); - - var reader = new FileReader(); - reader.onload = function(e) { - img.src = e.target.result; - - // Once ready, returns its size - img.onload = function() { - deferred.resolve({ - width: img.width, - height: img.height - }); - }; - img.onerror = function(e) { - deferred.reject(e); - }; - }; - reader.onerror = function(e) { - deferred.reject(e); - }; - reader.readAsDataURL(imageFile); - - return deferred.promise; - }; - - /* - * Resize the image to fit in a square of the side maxSize. - * The aspect ratio is kept. The returned image data uses JPEG compression. - * Source: http://hacks.mozilla.org/2011/01/how-to-develop-a-html5-image-uploader/ - * @param {File} imageFile the file containing the image - * @param {Integer} maxSize the max side size - * @returns {promise} A promise that will be resolved by a Blob object containing - * the resized image data - */ - this.resizeImage = function(imageFile, maxSize) { - var self = this; - var deferred = $q.defer(); - - var canvas = document.createElement("canvas"); - - var img = document.createElement("img"); - var reader = new FileReader(); - reader.onload = function(e) { - - img.src = e.target.result; - - // Once ready, returns its size - img.onload = function() { - var ctx = canvas.getContext("2d"); - ctx.drawImage(img, 0, 0); - - var MAX_WIDTH = maxSize; - var MAX_HEIGHT = maxSize; - var width = img.width; - var height = img.height; - - if (width > height) { - if (width > MAX_WIDTH) { - height *= MAX_WIDTH / width; - width = MAX_WIDTH; - } - } else { - if (height > MAX_HEIGHT) { - width *= MAX_HEIGHT / height; - height = MAX_HEIGHT; - } - } - canvas.width = width; - canvas.height = height; - var ctx = canvas.getContext("2d"); - ctx.drawImage(img, 0, 0, width, height); - - // Extract image data in the same format as the original one. - // The 0.7 compression value will work with formats that supports it like JPEG. - var dataUrl = canvas.toDataURL(imageFile.type, 0.7); - deferred.resolve(self.dataURItoBlob(dataUrl)); - }; - img.onerror = function(e) { - deferred.reject(e); - }; - }; - reader.onerror = function(e) { - deferred.reject(e); - }; - reader.readAsDataURL(imageFile); - - return deferred.promise; - }; - - /* - * Convert a dataURI string to a blob - * Source: http://stackoverflow.com/a/17682951 - * @param {String} dataURI the dataURI can be a base64 encoded string or an URL encoded string. - * @returns {Blob} the blob - */ - this.dataURItoBlob = function(dataURI) { - // convert base64 to raw binary data held in a string - // doesn't handle URLEncoded DataURIs - var byteString; - if (dataURI.split(',')[0].indexOf('base64') >= 0) - byteString = atob(dataURI.split(',')[1]); - else - byteString = unescape(dataURI.split(',')[1]); - // separate out the mime component - var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; - - // write the bytes of the string to an ArrayBuffer - var ab = new ArrayBuffer(byteString.length); - var ia = new Uint8Array(ab); - for (var i = 0; i < byteString.length; i++) { - ia[i] = byteString.charCodeAt(i); - } - - // write the ArrayBuffer to a blob, and you're done - return new Blob([ab],{type: mimeString}); - }; - -}]); \ No newline at end of file diff --git a/syweb/webclient/favicon.ico b/syweb/webclient/favicon.ico deleted file mode 100644 index ba193fabc8..0000000000 --- a/syweb/webclient/favicon.ico +++ /dev/null Binary files differdiff --git a/syweb/webclient/home/home-controller.js b/syweb/webclient/home/home-controller.js deleted file mode 100644 index a9538a0309..0000000000 --- a/syweb/webclient/home/home-controller.js +++ /dev/null @@ -1,209 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -angular.module('HomeController', ['matrixService', 'eventHandlerService', 'RecentsController']) -.controller('HomeController', ['$scope', '$location', 'matrixService', 'eventHandlerService', 'modelService', 'recentsService', - function($scope, $location, matrixService, eventHandlerService, modelService, recentsService) { - - $scope.config = matrixService.config(); - $scope.public_rooms = []; - $scope.newRoomId = ""; - $scope.feedback = ""; - - $scope.newRoom = { - room_id: "", - private: false - }; - - $scope.goToRoom = { - room_id: "" - }; - - $scope.joinAlias = { - room_alias: "" - }; - - $scope.profile = { - displayName: "", - avatarUrl: "" - }; - - $scope.newChat = { - user: "" - }; - - recentsService.setSelectedRoomId(undefined); - - var refresh = function() { - - matrixService.publicRooms().then( - function(response) { - $scope.public_rooms = response.data.chunk; - for (var i = 0; i < $scope.public_rooms.length; i++) { - var room = $scope.public_rooms[i]; - - if (room.aliases && room.aliases.length > 0) { - room.room_display_name = room.aliases[0]; - room.room_alias = room.aliases[0]; - } - else if (room.name) { - room.room_display_name = room.name; - } - else { - room.room_display_name = room.room_id; - } - } - } - ); - }; - - $scope.createNewRoom = function(room_alias, isPrivate) { - - var visibility = "public"; - if (isPrivate) { - visibility = "private"; - } - - matrixService.create(room_alias, visibility).then( - function(response) { - // This room has been created. Refresh the rooms list - console.log("Created room " + response.data.room_alias + " with id: "+ - response.data.room_id); - modelService.createRoomIdToAliasMapping( - response.data.room_id, response.data.room_alias); - }, - function(error) { - $scope.feedback = "Failure: " + JSON.stringify(error.data); - }); - }; - - // Go to a room - $scope.goToRoom = function(room_id) { - matrixService.join(room_id).then( - function(response) { - var final_room_id = room_id; - if (response.data.hasOwnProperty("room_id")) { - final_room_id = response.data.room_id; - } - - // TODO: factor out the common housekeeping whenever we try to join a room or alias - matrixService.roomState(final_room_id).then( - function(response) { - eventHandlerService.handleEvents(response.data, false, true); - }, - function(error) { - $scope.feedback = "Failed to get room state for: " + final_room_id; - } - ); - - $location.url("room/" + final_room_id); - }, - function(error) { - $scope.feedback = "Can't join room: " + JSON.stringify(error.data); - } - ); - }; - - $scope.joinAlias = function(room_alias) { - matrixService.joinAlias(room_alias).then( - function(response) { - // TODO: factor out the common housekeeping whenever we try to join a room or alias - matrixService.roomState(response.room_id).then( - function(response) { - eventHandlerService.handleEvents(response.data, false, true); - }, - function(error) { - $scope.feedback = "Failed to get room state for: " + response.room_id; - } - ); - // Go to this room - $location.url("room/" + room_alias); - }, - function(error) { - $scope.feedback = "Can't join room: " + JSON.stringify(error.data); - } - ); - }; - - // FIXME: factor this out between user-controller and home-controller etc. - $scope.messageUser = function() { - - // FIXME: create a new room every time, for now - - matrixService.create(null, 'private').then( - function(response) { - // This room has been created. Refresh the rooms list - var room_id = response.data.room_id; - console.log("Created room with id: "+ room_id); - - matrixService.invite(room_id, $scope.newChat.user).then( - function() { - $scope.feedback = "Invite sent successfully"; - $scope.$parent.goToPage("/room/" + room_id); - }, - function(reason) { - $scope.feedback = "Failure: " + JSON.stringify(reason); - }); - }, - function(error) { - $scope.feedback = "Failure: " + JSON.stringify(error.data); - }); - }; - - - $scope.onInit = function() { - // Load profile data - // Display name - matrixService.getDisplayName($scope.config.user_id).then( - function(response) { - $scope.profile.displayName = response.data.displayname; - var config = matrixService.config(); - config.display_name = response.data.displayname; - matrixService.setConfig(config); - matrixService.saveConfig(); - }, - function(error) { - $scope.feedback = "Can't load display name"; - } - ); - // Avatar - matrixService.getProfilePictureUrl($scope.config.user_id).then( - function(response) { - $scope.profile.avatarUrl = response.data.avatar_url; - }, - function(error) { - $scope.feedback = "Can't load avatar URL"; - } - ); - - // Listen to room creation event in order to update the public rooms list - $scope.$on(eventHandlerService.ROOM_CREATE_EVENT, function(ngEvent, event, isLive) { - if (isLive) { - // As we do not know if this room is public, do a full list refresh - refresh(); - } - }); - - refresh(); - }; - - // Clean data when user logs out - $scope.$on(eventHandlerService.RESET_EVENT, function() { - $scope.public_rooms = []; - }); -}]); diff --git a/syweb/webclient/home/home.html b/syweb/webclient/home/home.html deleted file mode 100644 index 0af382916e..0000000000 --- a/syweb/webclient/home/home.html +++ /dev/null @@ -1,78 +0,0 @@ -<div ng-controller="HomeController" data-ng-init="onInit()"> - - <div id="wrapper"> - - <div id="genericHeading"> - <a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a> - </div> - - <h1>Welcome to homeserver {{ config.homeserver }}</h1> - - <div> - <div class="profile-avatar"> - <img ng-src="{{ (null !== profile.avatarUrl) ? profile.avatarUrl : 'img/default-profile.png' }}"/> - </div> - <div id="user-ids"> - <div id="user-displayname">{{ profile.displayName }}</div> - <div>{{ config.user_id }}</div> - </div> - </div> - - <h3>Recent conversations</h3> - <div ng-include="'recents/recents.html'"></div> - <br/> - - <h3>Public rooms</h3> - - <table class="publicTable"> - <tbody ng-repeat="room in public_rooms | orderBy:'room_display_name'" - class="publicRoomEntry" - ng-class="room.room_display_name.toLowerCase().indexOf('#matrix:') === 0 ? 'roomHighlight' : ''"> - <tr> - <td class="publicRoomEntry"> - <a href="#/room/{{ room.room_alias ? room.room_alias : room.room_id }}" > - {{ room.room_display_name }} - </a> - </td> - <td> - <div class="publicRoomJoinedUsers" - ng-show="room.num_joined_members"> - {{ room.num_joined_members }} {{ room.num_joined_members == 1 ? 'user' : 'users' }} - </div> - </td> - </tr> - <tr> - <td colspan="2" class="publicRoomTopic"> - {{ room.topic }} - </td> - </tr> - </tbody> - </table> - <br/> - - <div> - <form> - <input size="40" ng-model="newRoom.room_alias" ng-enter="createNewRoom(newRoom.room_alias, newRoom.private)" placeholder="(e.g. foo)"/> - <input type="checkbox" ng-model="newRoom.private">private - <button ng-disabled="!newRoom.room_alias" ng-click="createNewRoom(newRoom.room_alias, newRoom.private)">Create room</button> - </form> - </div> - <div> - <form> - <input size="40" ng-model="joinAlias.room_alias" ng-enter="joinAlias(joinAlias.room_alias)" placeholder="(e.g. #foo:example.org)"/> - <button ng-disabled="!joinAlias.room_alias" ng-click="joinAlias(joinAlias.room_alias)">Join room</button> - </form> - </div> - <div> - <form> - <input size="40" ng-model="newChat.user" ng-enter="messageUser()" placeholder="e.g. @user:domain.com"/> - <button ng-disabled="!newChat.user" ng-click="messageUser()">Message user</button> - </form> - </div> - - <br/> - - {{ feedback }} - - </div> -</div> diff --git a/syweb/webclient/img/attach.png b/syweb/webclient/img/attach.png deleted file mode 100644 index d95eabaf00..0000000000 --- a/syweb/webclient/img/attach.png +++ /dev/null Binary files differdiff --git a/syweb/webclient/img/close.png b/syweb/webclient/img/close.png deleted file mode 100644 index fbcdb51e6b..0000000000 --- a/syweb/webclient/img/close.png +++ /dev/null Binary files differdiff --git a/syweb/webclient/img/default-profile.png b/syweb/webclient/img/default-profile.png deleted file mode 100644 index 6f81a3c417..0000000000 --- a/syweb/webclient/img/default-profile.png +++ /dev/null Binary files differdiff --git a/syweb/webclient/img/gradient.png b/syweb/webclient/img/gradient.png deleted file mode 100644 index 8ac9e2193f..0000000000 --- a/syweb/webclient/img/gradient.png +++ /dev/null Binary files differdiff --git a/syweb/webclient/img/green_phone.png b/syweb/webclient/img/green_phone.png deleted file mode 100644 index 28807c749b..0000000000 --- a/syweb/webclient/img/green_phone.png +++ /dev/null Binary files differdiff --git a/syweb/webclient/img/logo-small.png b/syweb/webclient/img/logo-small.png deleted file mode 100644 index 411206dcdc..0000000000 --- a/syweb/webclient/img/logo-small.png +++ /dev/null Binary files differdiff --git a/syweb/webclient/img/logo.png b/syweb/webclient/img/logo.png deleted file mode 100644 index c4b53a8487..0000000000 --- a/syweb/webclient/img/logo.png +++ /dev/null Binary files differdiff --git a/syweb/webclient/img/settings.png b/syweb/webclient/img/settings.png deleted file mode 100644 index ac99fe402b..0000000000 --- a/syweb/webclient/img/settings.png +++ /dev/null Binary files differdiff --git a/syweb/webclient/img/video.png b/syweb/webclient/img/video.png deleted file mode 100644 index e90afea0c1..0000000000 --- a/syweb/webclient/img/video.png +++ /dev/null Binary files differdiff --git a/syweb/webclient/img/voice.png b/syweb/webclient/img/voice.png deleted file mode 100644 index fe464999c0..0000000000 --- a/syweb/webclient/img/voice.png +++ /dev/null Binary files differdiff --git a/syweb/webclient/index.html b/syweb/webclient/index.html deleted file mode 100644 index 2dddd14cfc..0000000000 --- a/syweb/webclient/index.html +++ /dev/null @@ -1,139 +0,0 @@ -<!doctype html> -<html xmlns:ng="http://angularjs.org" ng-app="matrixWebClient" ng-controller="MatrixWebClientController"> -<head> - <title>[matrix]</title> - - <link rel="stylesheet" href="app.css"> - <link rel="stylesheet" href="mobile.css"> - <link rel="stylesheet" href="bootstrap.css"> - - <link rel="icon" href="favicon.ico"> - - <meta name="viewport" content="width=device-width"> - - <script type="text/javascript" src="js/jquery-1.8.3.min.js"></script> - <script type="text/javascript" src="js/recaptcha_ajax.js"></script> - <script src="js/angular.js"></script> - <script src="js/angular-route.min.js"></script> - <script src="js/angular-sanitize.min.js"></script> - <script src="js/jquery.peity.min.js"></script> - <script src="js/angular-peity.js"></script> - <script type="text/javascript" src="js/ui-bootstrap-tpls-0.11.2.js"></script> - <script type="text/javascript" src="js/ng-infinite-scroll-matrix.js"></script> - <script type="text/javascript" src="js/autofill-event.js"></script> - <script type="text/javascript" src="js/elastic.js"></script> - <script src="app.js"></script> - <script src="config.js"></script> - <script src="app-controller.js"></script> - <script src="app-directive.js"></script> - <script src="app-filter.js"></script> - <script src="home/home-controller.js"></script> - <script src="login/login-controller.js"></script> - <script src="login/register-controller.js"></script> - <script src="recents/recents-controller.js"></script> - <script src="recents/recents-filter.js"></script> - <script src="room/room-controller.js"></script> - <script src="room/room-directive.js"></script> - <script src="settings/settings-controller.js"></script> - <script src="user/user-controller.js"></script> - <script src="components/matrix/matrix-service.js"></script> - <script src="components/matrix/matrix-filter.js"></script> - <script src="components/matrix/matrix-call.js"></script> - <script src="components/matrix/matrix-phone-service.js"></script> - <script src="components/matrix/event-stream-service.js"></script> - <script src="components/matrix/event-handler-service.js"></script> - <script src="components/matrix/notification-service.js"></script> - <script src="components/matrix/recents-service.js"></script> - <script src="components/matrix/commands-service.js"></script> - <script src="components/matrix/model-service.js"></script> - <script src="components/matrix/presence-service.js"></script> - <script src="components/fileInput/file-input-directive.js"></script> - <script src="components/fileUpload/file-upload-service.js"></script> - <script src="components/utilities/utilities-service.js"></script> -</head> - -<body> - <div id="videoBackground" ng-class="videoMode"> - <div id="videoContainer" ng-class="videoMode"> - <div id="videoContainerPadding"></div> - <div ng-class="[videoMode, currentCall.state]" ng-show="currentCall && currentCall.type == 'video' && (currentCall.state == 'connected' || currentCall.state == 'connecting' || currentCall.state == 'invite_sent' || currentCall.state == 'ended')"><video id="localVideo"></video></div> - <div ng-class="[videoMode, currentCall.state]" ng-show="currentCall && currentCall.type == 'video' && (currentCall.state == 'connected' || (currentCall.state == 'ended' && currentCall.didConnect))"><video id="remoteVideo"></video></div> - </div> - </div> - - <div id="header"> - <!-- Do not show buttons on the login page --> - <div id="headerContent" ng-hide="'/login' == location || '/register' == location"> - <div id="callBar" ng-show="currentCall"> - <img id="callPeerImage" ng-show="currentCall.userProfile.avatar_url" ngSrc="{{ currentCall.userProfile.avatar_url }}" /> - <img class="callIcon" src="img/green_phone.png" ng-show="!!currentCall" ng-class="currentCall.state" /> - <div id="callPeerNameAndState"> - <span id="callPeerName">{{ currentCall.userProfile.displayname }}</span> - <br /> - <span id="callState"> - <span ng-show="currentCall.state == 'invite_sent'">Calling...</span> - <span ng-show="currentCall.state == 'ringing' && currentCall && currentCall.type == 'video'">Incoming Video Call</span> - <span ng-show="currentCall.state == 'ringing' && currentCall && currentCall.type == 'voice'">Incoming Voice Call</span> - <span ng-show="currentCall.state == 'connecting'">Call Connecting...</span> - <span ng-show="currentCall.state == 'connected'">Call Connected</span> - <span ng-show="currentCall.state == 'ended' && currentCall.hangupReason == 'ice_failed'">Media Connection Failed{{ haveTurn ? "" : " (VoIP relaying unsupported by Home Server)" }}</span> - <span ng-show="currentCall.state == 'ended' && !currentCall.hangupReason && !currentCall.didConnect && currentCall.direction == 'outbound' && currentCall.hangupParty == 'remote'">Call Rejected</span> - <span ng-show="currentCall.state == 'ended' && !currentCall.hangupReason && !currentCall.didConnect && currentCall.direction == 'outbound' && currentCall.hangupParty == 'local'">Call Canceled</span> - <span ng-show="currentCall.state == 'ended' && currentCall.hangupReason == 'invite_timeout' && !currentCall.didConnect && currentCall.direction == 'outbound' && currentCall.hangupParty == 'local'">User Not Responding</span> - <span ng-show="currentCall.state == 'ended' && !currentCall.hangupReason && currentCall.didConnect && currentCall.direction == 'outbound'">Call Ended</span> - <span ng-show="currentCall.state == 'ended' && !currentCall.hangupReason && !currentCall.didConnect && currentCall.direction == 'inbound'">Call Canceled</span> - <span ng-show="currentCall.state == 'ended' && !currentCall.hangupReason && currentCall.didConnect && currentCall.direction == 'inbound'">Call Ended</span> - <span ng-show="currentCall.state == 'wait_local_media'">Waiting for media permission...</span> - </span> - </div> - <span ng-show="currentCall.state == 'ringing'"> - <button ng-click="answerCall()" ng-disabled="!isWebRTCSupported()" title="{{isWebRTCSupported() ? '' : 'Your browser does not support VoIP' }}">Answer {{ currentCall.type }} call</button> - <button ng-click="hangupCall()">Reject</button> - </span> - <button ng-click="hangupCall()" ng-show="currentCall && currentCall.state != 'ringing' && currentCall.state != 'ended' && currentCall.state != 'fledgling'">Hang up</button> - <audio id="ringAudio" loop> - <source src="media/ring.ogg" type="audio/ogg" /> - <source src="media/ring.mp3" type="audio/mpeg" /> - </audio> - <audio id="ringbackAudio" loop> - <source src="media/ringback.ogg" type="audio/ogg" /> - <source src="media/ringback.mp3" type="audio/mpeg" /> - </audio> - <audio id="callendAudio"> - <source src="media/callend.ogg" type="audio/ogg" /> - <source src="media/callend.mp3" type="audio/mpeg" /> - </audio> - <audio id="busyAudio"> - <source src="media/busy.ogg" type="audio/ogg" /> - <source src="media/busy.mp3" type="audio/mpeg" /> - </audio> - </div> - - <a href id="headerUserId" ng-click='goToUserPage(user_id)'>{{ user_id }}</a> - - <button ng-click='goToPage("/")'>Home</button> - <button ng-click='goToPage("settings")'>Settings</button> - <button ng-click="logout()">Log out</button> - </div> - </div> - - <div class="page" ng-hide="unsupportedBrowser" ng-view></div> - - <div class="page" ng-show="unsupportedBrowser"> - <div id="unsupportedBrowser" ng-show="unsupportedBrowser"> - Sorry, your browser is not supported. <br/> - Reason: {{ unsupportedBrowser.reason }} - - <br/><br/> - Your browser: <br/> - {{ unsupportedBrowser.browser }} - </div> - </div> - - <div id="footer" ng-hide="location.indexOf('/room') === 0"> - <div id="footerContent"> - © 2014 Matrix.org - </div> - </div> -</body> -</html> diff --git a/syweb/webclient/js/angular-animate.js b/syweb/webclient/js/angular-animate.js deleted file mode 100644 index c15f793c1b..0000000000 --- a/syweb/webclient/js/angular-animate.js +++ /dev/null @@ -1,1880 +0,0 @@ -/** - * @license AngularJS v1.3.0-rc.1 - * (c) 2010-2014 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular, undefined) {'use strict'; - -/* jshint maxlen: false */ - -/** - * @ngdoc module - * @name ngAnimate - * @description - * - * The `ngAnimate` module provides support for JavaScript, CSS3 transition and CSS3 keyframe animation hooks within existing core and custom directives. - * - * <div doc-module-components="ngAnimate"></div> - * - * # Usage - * - * To see animations in action, all that is required is to define the appropriate CSS classes - * or to register a JavaScript animation via the myModule.animation() function. The directives that support animation automatically are: - * `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation - * by using the `$animate` service. - * - * Below is a more detailed breakdown of the supported animation events provided by pre-existing ng directives: - * - * | Directive | Supported Animations | - * |-----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| - * | {@link ng.directive:ngRepeat#usage_animations ngRepeat} | enter, leave and move | - * | {@link ngRoute.directive:ngView#usage_animations ngView} | enter and leave | - * | {@link ng.directive:ngInclude#usage_animations ngInclude} | enter and leave | - * | {@link ng.directive:ngSwitch#usage_animations ngSwitch} | enter and leave | - * | {@link ng.directive:ngIf#usage_animations ngIf} | enter and leave | - * | {@link ng.directive:ngClass#usage_animations ngClass} | add and remove (the CSS class(es) present) | - * | {@link ng.directive:ngShow#usage_animations ngShow} & {@link ng.directive:ngHide#usage_animations ngHide} | add and remove (the ng-hide class value) | - * | {@link ng.directive:form#usage_animations form} & {@link ng.directive:ngModel#usage_animations ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) | - * | {@link ngMessages.directive:ngMessage#usage_animations ngMessages} | add and remove (ng-active & ng-inactive) | - * | {@link ngMessages.directive:ngMessage#usage_animations ngMessage} | enter and leave | - * - * You can find out more information about animations upon visiting each directive page. - * - * Below is an example of how to apply animations to a directive that supports animation hooks: - * - * ```html - * <style type="text/css"> - * .slide.ng-enter, .slide.ng-leave { - * -webkit-transition:0.5s linear all; - * transition:0.5s linear all; - * } - * - * .slide.ng-enter { } /* starting animations for enter */ - * .slide.ng-enter.ng-enter-active { } /* terminal animations for enter */ - * .slide.ng-leave { } /* starting animations for leave */ - * .slide.ng-leave.ng-leave-active { } /* terminal animations for leave */ - * </style> - * - * <!-- - * the animate service will automatically add .ng-enter and .ng-leave to the element - * to trigger the CSS transition/animations - * --> - * <ANY class="slide" ng-include="..."></ANY> - * ``` - * - * Keep in mind that, by default, if an animation is running, any child elements cannot be animated - * until the parent element's animation has completed. This blocking feature can be overridden by - * placing the `ng-animate-children` attribute on a parent container tag. - * - * ```html - * <div class="slide-animation" ng-if="on" ng-animate-children> - * <div class="fade-animation" ng-if="on"> - * <div class="explode-animation" ng-if="on"> - * ... - * </div> - * </div> - * </div> - * ``` - * - * When the `on` expression value changes and an animation is triggered then each of the elements within - * will all animate without the block being applied to child elements. - * - * ## Are animations run when the application starts? - * No they are not. When an application is bootstrapped Angular will disable animations from running to avoid - * a frenzy of animations from being triggered as soon as the browser has rendered the screen. For this to work, - * Angular will wait for two digest cycles until enabling animations. From there on, any animation-triggering - * layout changes in the application will trigger animations as normal. - * - * In addition, upon bootstrap, if the routing system or any directives or load remote data (via $http) then Angular - * will automatically extend the wait time to enable animations once **all** of the outbound HTTP requests - * are complete. - * - * <h2>CSS-defined Animations</h2> - * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes - * are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported - * and can be used to play along with this naming structure. - * - * The following code below demonstrates how to perform animations using **CSS transitions** with Angular: - * - * ```html - * <style type="text/css"> - * /* - * The animate class is apart of the element and the ng-enter class - * is attached to the element once the enter animation event is triggered - * */ - * .reveal-animation.ng-enter { - * -webkit-transition: 1s linear all; /* Safari/Chrome */ - * transition: 1s linear all; /* All other modern browsers and IE10+ */ - * - * /* The animation preparation code */ - * opacity: 0; - * } - * - * /* - * Keep in mind that you want to combine both CSS - * classes together to avoid any CSS-specificity - * conflicts - * */ - * .reveal-animation.ng-enter.ng-enter-active { - * /* The animation code itself */ - * opacity: 1; - * } - * </style> - * - * <div class="view-container"> - * <div ng-view class="reveal-animation"></div> - * </div> - * ``` - * - * The following code below demonstrates how to perform animations using **CSS animations** with Angular: - * - * ```html - * <style type="text/css"> - * .reveal-animation.ng-enter { - * -webkit-animation: enter_sequence 1s linear; /* Safari/Chrome */ - * animation: enter_sequence 1s linear; /* IE10+ and Future Browsers */ - * } - * @-webkit-keyframes enter_sequence { - * from { opacity:0; } - * to { opacity:1; } - * } - * @keyframes enter_sequence { - * from { opacity:0; } - * to { opacity:1; } - * } - * </style> - * - * <div class="view-container"> - * <div ng-view class="reveal-animation"></div> - * </div> - * ``` - * - * Both CSS3 animations and transitions can be used together and the animate service will figure out the correct duration and delay timing. - * - * Upon DOM mutation, the event class is added first (something like `ng-enter`), then the browser prepares itself to add - * the active class (in this case `ng-enter-active`) which then triggers the animation. The animation module will automatically - * detect the CSS code to determine when the animation ends. Once the animation is over then both CSS classes will be - * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end - * immediately resulting in a DOM element that is at its final state. This final state is when the DOM element - * has no CSS transition/animation classes applied to it. - * - * ### Structural transition animations - * - * Structural transitions (such as enter, leave and move) will always apply a `0s none` transition - * value to force the browser into rendering the styles defined in the setup (.ng-enter, .ng-leave - * or .ng-move) class. This means that any active transition animations operating on the element - * will be cut off to make way for the enter, leave or move animation. - * - * ### Class-based transition animations - * - * Class-based transitions refer to transition animations that are triggered when a CSS class is - * added to or removed from the element (via `$animate.addClass`, `$animate.removeClass`, - * `$animate.setClass`, or by directives such as `ngClass`, `ngModel` and `form`). - * They are different when compared to structural animations since they **do not cancel existing - * animations** nor do they **block successive transitions** from rendering on the same element. - * This distinction allows for **multiple class-based transitions** to be performed on the same element. - * - * In addition to ngAnimate supporting the default (natural) functionality of class-based transition - * animations, ngAnimate also decorates the element with starting and ending CSS classes to aid the - * developer in further styling the element throughout the transition animation. Earlier versions - * of ngAnimate may have caused natural CSS transitions to break and not render properly due to - * $animate temporarily blocking transitions using `0s none` in order to allow the setup CSS class - * (the `-add` or `-remove` class) to be applied without triggering an animation. However, as of - * **version 1.3**, this workaround has been removed with ngAnimate and all non-ngAnimate CSS - * class transitions are compatible with ngAnimate. - * - * There is, however, one special case when dealing with class-based transitions in ngAnimate. - * When rendering class-based transitions that make use of the setup and active CSS classes - * (e.g. `.fade-add` and `.fade-add-active` for when `.fade` is added) be sure to define - * the transition value **on the active CSS class** and not the setup class. - * - * ```css - * .fade-add { - * /* remember to place a 0s transition here - * to ensure that the styles are applied instantly - * even if the element already has a transition style */ - * transition:0s linear all; - * - * /* starting CSS styles */ - * opacity:1; - * } - * .fade-add.fade-add-active { - * /* this will be the length of the animation */ - * transition:1s linear all; - * opacity:0; - * } - * ``` - * - * The setup CSS class (in this case `.fade-add`) also has a transition style property, however, it - * has a duration of zero. This may not be required, however, incase the browser is unable to render - * the styling present in this CSS class instantly then it could be that the browser is attempting - * to perform an unnecessary transition. - * - * This workaround, however, does not apply to standard class-based transitions that are rendered - * when a CSS class containing a transition is applied to an element: - * - * ```css - * .fade { - * /* this works as expected */ - * transition:1s linear all; - * opacity:0; - * } - * ``` - * - * Please keep this in mind when coding the CSS markup that will be used within class-based transitions. - * Also, try not to mix the two class-based animation flavors together since the CSS code may become - * overly complex. - * - * ### CSS Staggering Animations - * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a - * curtain-like effect. The ngAnimate module, as of 1.2.0, supports staggering animations and the stagger effect can be - * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for - * the animation. The style property expected within the stagger class can either be a **transition-delay** or an - * **animation-delay** property (or both if your animation contains both transitions and keyframe animations). - * - * ```css - * .my-animation.ng-enter { - * /* standard transition code */ - * -webkit-transition: 1s linear all; - * transition: 1s linear all; - * opacity:0; - * } - * .my-animation.ng-enter-stagger { - * /* this will have a 100ms delay between each successive leave animation */ - * -webkit-transition-delay: 0.1s; - * transition-delay: 0.1s; - * - * /* in case the stagger doesn't work then these two values - * must be set to 0 to avoid an accidental CSS inheritance */ - * -webkit-transition-duration: 0s; - * transition-duration: 0s; - * } - * .my-animation.ng-enter.ng-enter-active { - * /* standard transition styles */ - * opacity:1; - * } - * ``` - * - * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations - * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this - * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation - * will also be reset if more than 10ms has passed after the last animation has been fired. - * - * The following code will issue the **ng-leave-stagger** event on the element provided: - * - * ```js - * var kids = parent.children(); - * - * $animate.leave(kids[0]); //stagger index=0 - * $animate.leave(kids[1]); //stagger index=1 - * $animate.leave(kids[2]); //stagger index=2 - * $animate.leave(kids[3]); //stagger index=3 - * $animate.leave(kids[4]); //stagger index=4 - * - * $timeout(function() { - * //stagger has reset itself - * $animate.leave(kids[5]); //stagger index=0 - * $animate.leave(kids[6]); //stagger index=1 - * }, 100, false); - * ``` - * - * Stagger animations are currently only supported within CSS-defined animations. - * - * ## JavaScript-defined Animations - * In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations on browsers that do not - * yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module. - * - * ```js - * //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application. - * var ngModule = angular.module('YourApp', ['ngAnimate']); - * ngModule.animation('.my-crazy-animation', function() { - * return { - * enter: function(element, done) { - * //run the animation here and call done when the animation is complete - * return function(cancelled) { - * //this (optional) function will be called when the animation - * //completes or when the animation is cancelled (the cancelled - * //flag will be set to true if cancelled). - * }; - * }, - * leave: function(element, done) { }, - * move: function(element, done) { }, - * - * //animation that can be triggered before the class is added - * beforeAddClass: function(element, className, done) { }, - * - * //animation that can be triggered after the class is added - * addClass: function(element, className, done) { }, - * - * //animation that can be triggered before the class is removed - * beforeRemoveClass: function(element, className, done) { }, - * - * //animation that can be triggered after the class is removed - * removeClass: function(element, className, done) { } - * }; - * }); - * ``` - * - * JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run - * a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits - * the element's CSS class attribute value and then run the matching animation event function (if found). - * In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function will - * be executed. It should be also noted that only simple, single class selectors are allowed (compound class selectors are not supported). - * - * Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned. - * As explained above, these callbacks are triggered based on the animation event. Therefore if an enter animation is run, - * and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation - * or transition code that is defined via a stylesheet). - * - */ - -angular.module('ngAnimate', ['ng']) - - /** - * @ngdoc provider - * @name $animateProvider - * @description - * - * The `$animateProvider` allows developers to register JavaScript animation event handlers directly inside of a module. - * When an animation is triggered, the $animate service will query the $animate service to find any animations that match - * the provided name value. - * - * Requires the {@link ngAnimate `ngAnimate`} module to be installed. - * - * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application. - * - */ - .directive('ngAnimateChildren', function() { - var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren'; - return function(scope, element, attrs) { - var val = attrs.ngAnimateChildren; - if (angular.isString(val) && val.length === 0) { //empty attribute - element.data(NG_ANIMATE_CHILDREN, true); - } else { - scope.$watch(val, function(value) { - element.data(NG_ANIMATE_CHILDREN, !!value); - }); - } - }; - }) - - //this private service is only used within CSS-enabled animations - //IE8 + IE9 do not support rAF natively, but that is fine since they - //also don't support transitions and keyframes which means that the code - //below will never be used by the two browsers. - .factory('$$animateReflow', ['$$rAF', '$document', function($$rAF, $document) { - var bod = $document[0].body; - return function(fn) { - //the returned function acts as the cancellation function - return $$rAF(function() { - //the line below will force the browser to perform a repaint - //so that all the animated elements within the animation frame - //will be properly updated and drawn on screen. This is - //required to perform multi-class CSS based animations with - //Firefox. DO NOT REMOVE THIS LINE. - var a = bod.offsetWidth + 1; - fn(); - }); - }; - }]) - - .config(['$provide', '$animateProvider', function($provide, $animateProvider) { - var noop = angular.noop; - var forEach = angular.forEach; - var selectors = $animateProvider.$$selectors; - var isArray = angular.isArray; - - var ELEMENT_NODE = 1; - var NG_ANIMATE_STATE = '$$ngAnimateState'; - var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren'; - var NG_ANIMATE_CLASS_NAME = 'ng-animate'; - var rootAnimateState = {running: true}; - - function extractElementNode(element) { - for(var i = 0; i < element.length; i++) { - var elm = element[i]; - if (elm.nodeType == ELEMENT_NODE) { - return elm; - } - } - } - - function prepareElement(element) { - return element && angular.element(element); - } - - function stripCommentsFromElement(element) { - return angular.element(extractElementNode(element)); - } - - function isMatchingElement(elm1, elm2) { - return extractElementNode(elm1) == extractElementNode(elm2); - } - - $provide.decorator('$animate', - ['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest', - function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest) { - - $rootElement.data(NG_ANIMATE_STATE, rootAnimateState); - - // Wait until all directive and route-related templates are downloaded and - // compiled. The $templateRequest.totalPendingRequests variable keeps track of - // all of the remote templates being currently downloaded. If there are no - // templates currently downloading then the watcher will still fire anyway. - var deregisterWatch = $rootScope.$watch( - function() { return $templateRequest.totalPendingRequests; }, - function(val, oldVal) { - if (val !== 0) return; - deregisterWatch(); - - // Now that all templates have been downloaded, $animate will wait until - // the post digest queue is empty before enabling animations. By having two - // calls to $postDigest calls we can ensure that the flag is enabled at the - // very end of the post digest queue. Since all of the animations in $animate - // use $postDigest, it's important that the code below executes at the end. - // This basically means that the page is fully downloaded and compiled before - // any animations are triggered. - $rootScope.$$postDigest(function() { - $rootScope.$$postDigest(function() { - rootAnimateState.running = false; - }); - }); - } - ); - - var globalAnimationCounter = 0; - var classNameFilter = $animateProvider.classNameFilter(); - var isAnimatableClassName = !classNameFilter - ? function() { return true; } - : function(className) { - return classNameFilter.test(className); - }; - - function classBasedAnimationsBlocked(element, setter) { - var data = element.data(NG_ANIMATE_STATE) || {}; - if (setter) { - data.running = true; - data.structural = true; - element.data(NG_ANIMATE_STATE, data); - } - return data.disabled || (data.running && data.structural); - } - - function runAnimationPostDigest(fn) { - var cancelFn, defer = $$q.defer(); - defer.promise.$$cancelFn = function() { - cancelFn && cancelFn(); - }; - $rootScope.$$postDigest(function() { - cancelFn = fn(function() { - defer.resolve(); - }); - }); - return defer.promise; - } - - function resolveElementClasses(element, cache, runningAnimations) { - runningAnimations = runningAnimations || {}; - var map = {}; - - forEach(cache.add, function(className) { - if (className && className.length) { - map[className] = map[className] || 0; - map[className]++; - } - }); - - forEach(cache.remove, function(className) { - if (className && className.length) { - map[className] = map[className] || 0; - map[className]--; - } - }); - - var lookup = []; - forEach(runningAnimations, function(data, selector) { - forEach(selector.split(' '), function(s) { - lookup[s]=data; - }); - }); - - var toAdd = [], toRemove = []; - forEach(map, function(status, className) { - var hasClass = angular.$$hasClass(element[0], className); - var matchingAnimation = lookup[className] || {}; - - // When addClass and removeClass is called then $animate will check to - // see if addClass and removeClass cancel each other out. When there are - // more calls to removeClass than addClass then the count falls below 0 - // and then the removeClass animation will be allowed. Otherwise if the - // count is above 0 then that means an addClass animation will commence. - // Once an animation is allowed then the code will also check to see if - // there exists any on-going animation that is already adding or remvoing - // the matching CSS class. - if (status < 0) { - //does it have the class or will it have the class - if (hasClass || matchingAnimation.event == 'addClass') { - toRemove.push(className); - } - } else if (status > 0) { - //is the class missing or will it be removed? - if (!hasClass || matchingAnimation.event == 'removeClass') { - toAdd.push(className); - } - } - }); - - return (toAdd.length + toRemove.length) > 0 && [toAdd.join(' '), toRemove.join(' ')]; - } - - function lookup(name) { - if (name) { - var matches = [], - flagMap = {}, - classes = name.substr(1).split('.'); - - //the empty string value is the default animation - //operation which performs CSS transition and keyframe - //animations sniffing. This is always included for each - //element animation procedure if the browser supports - //transitions and/or keyframe animations. The default - //animation is added to the top of the list to prevent - //any previous animations from affecting the element styling - //prior to the element being animated. - if ($sniffer.transitions || $sniffer.animations) { - matches.push($injector.get(selectors[''])); - } - - for(var i=0; i < classes.length; i++) { - var klass = classes[i], - selectorFactoryName = selectors[klass]; - if (selectorFactoryName && !flagMap[klass]) { - matches.push($injector.get(selectorFactoryName)); - flagMap[klass] = true; - } - } - return matches; - } - } - - function animationRunner(element, animationEvent, className) { - //transcluded directives may sometimes fire an animation using only comment nodes - //best to catch this early on to prevent any animation operations from occurring - var node = element[0]; - if (!node) { - return; - } - - var classNameAdd; - var classNameRemove; - if (isArray(className)) { - classNameAdd = className[0]; - classNameRemove = className[1]; - if (!classNameAdd) { - className = classNameRemove; - animationEvent = 'removeClass'; - } else if (!classNameRemove) { - className = classNameAdd; - animationEvent = 'addClass'; - } else { - className = classNameAdd + ' ' + classNameRemove; - } - } - - var isSetClassOperation = animationEvent == 'setClass'; - var isClassBased = isSetClassOperation || - animationEvent == 'addClass' || - animationEvent == 'removeClass'; - - var currentClassName = element.attr('class'); - var classes = currentClassName + ' ' + className; - if (!isAnimatableClassName(classes)) { - return; - } - - var beforeComplete = noop, - beforeCancel = [], - before = [], - afterComplete = noop, - afterCancel = [], - after = []; - - var animationLookup = (' ' + classes).replace(/\s+/g,'.'); - forEach(lookup(animationLookup), function(animationFactory) { - var created = registerAnimation(animationFactory, animationEvent); - if (!created && isSetClassOperation) { - registerAnimation(animationFactory, 'addClass'); - registerAnimation(animationFactory, 'removeClass'); - } - }); - - function registerAnimation(animationFactory, event) { - var afterFn = animationFactory[event]; - var beforeFn = animationFactory['before' + event.charAt(0).toUpperCase() + event.substr(1)]; - if (afterFn || beforeFn) { - if (event == 'leave') { - beforeFn = afterFn; - //when set as null then animation knows to skip this phase - afterFn = null; - } - after.push({ - event : event, fn : afterFn - }); - before.push({ - event : event, fn : beforeFn - }); - return true; - } - } - - function run(fns, cancellations, allCompleteFn) { - var animations = []; - forEach(fns, function(animation) { - animation.fn && animations.push(animation); - }); - - var count = 0; - function afterAnimationComplete(index) { - if (cancellations) { - (cancellations[index] || noop)(); - if (++count < animations.length) return; - cancellations = null; - } - allCompleteFn(); - } - - //The code below adds directly to the array in order to work with - //both sync and async animations. Sync animations are when the done() - //operation is called right away. DO NOT REFACTOR! - forEach(animations, function(animation, index) { - var progress = function() { - afterAnimationComplete(index); - }; - switch(animation.event) { - case 'setClass': - cancellations.push(animation.fn(element, classNameAdd, classNameRemove, progress)); - break; - case 'addClass': - cancellations.push(animation.fn(element, classNameAdd || className, progress)); - break; - case 'removeClass': - cancellations.push(animation.fn(element, classNameRemove || className, progress)); - break; - default: - cancellations.push(animation.fn(element, progress)); - break; - } - }); - - if (cancellations && cancellations.length === 0) { - allCompleteFn(); - } - } - - return { - node : node, - event : animationEvent, - className : className, - isClassBased : isClassBased, - isSetClassOperation : isSetClassOperation, - before : function(allCompleteFn) { - beforeComplete = allCompleteFn; - run(before, beforeCancel, function() { - beforeComplete = noop; - allCompleteFn(); - }); - }, - after : function(allCompleteFn) { - afterComplete = allCompleteFn; - run(after, afterCancel, function() { - afterComplete = noop; - allCompleteFn(); - }); - }, - cancel : function() { - if (beforeCancel) { - forEach(beforeCancel, function(cancelFn) { - (cancelFn || noop)(true); - }); - beforeComplete(true); - } - if (afterCancel) { - forEach(afterCancel, function(cancelFn) { - (cancelFn || noop)(true); - }); - afterComplete(true); - } - } - }; - } - - /** - * @ngdoc service - * @name $animate - * @kind object - * - * @description - * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move) as well as during addClass and removeClass operations. - * When any of these operations are run, the $animate service - * will examine any JavaScript-defined animations (which are defined by using the $animateProvider provider object) - * as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run. - * - * The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives - * will work out of the box without any extra configuration. - * - * Requires the {@link ngAnimate `ngAnimate`} module to be installed. - * - * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application. - * ## Callback Promises - * With AngularJS 1.3, each of the animation methods, on the `$animate` service, return a promise when called. The - * promise itself is then resolved once the animation has completed itself, has been cancelled or has been - * skipped due to animations being disabled. (Note that even if the animation is cancelled it will still - * call the resolve function of the animation.) - * - * ```js - * $animate.enter(element, container).then(function() { - * //...this is called once the animation is complete... - * }); - * ``` - * - * Also note that, due to the nature of the callback promise, if any Angular-specific code (like changing the scope, - * location of the page, etc...) is executed within the callback promise then be sure to wrap the code using - * `$scope.$apply(...)`; - * - * ```js - * $animate.leave(element).then(function() { - * $scope.$apply(function() { - * $location.path('/new-page'); - * }); - * }); - * ``` - * - * An animation can also be cancelled by calling the `$animate.cancel(promise)` method with the provided - * promise that was returned when the animation was started. - * - * ```js - * var promise = $animate.addClass(element, 'super-long-animation').then(function() { - * //this will still be called even if cancelled - * }); - * - * element.on('click', function() { - * //tooo lazy to wait for the animation to end - * $animate.cancel(promise); - * }); - * ``` - * - * (Keep in mind that the promise cancellation is unique to `$animate` since promises in - * general cannot be cancelled.) - * - */ - return { - /** - * @ngdoc method - * @name $animate#enter - * @kind function - * - * @description - * Appends the element to the parentElement element that resides in the document and then runs the enter animation. Once - * the animation is started, the following CSS classes will be present on the element for the duration of the animation: - * - * Below is a breakdown of each step that occurs during enter animation: - * - * | Animation Step | What the element class attribute looks like | - * |-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------| - * | 1. $animate.enter(...) is called | class="my-animation" | - * | 2. element is inserted into the parentElement element or beside the afterElement element | class="my-animation" | - * | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" | - * | 4. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" | - * | 5. the .ng-enter class is added to the element | class="my-animation ng-animate ng-enter" | - * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-enter" | - * | 7. $animate blocks all CSS transitions on the element to ensure the .ng-enter class styling is applied right away | class="my-animation ng-animate ng-enter" | - * | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-enter" | - * | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-enter" | - * | 10. the .ng-enter-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-enter ng-enter-active" | - * | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-enter ng-enter-active" | - * | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | - * | 13. The returned promise is resolved. | class="my-animation" | - * - * @param {DOMElement} element the element that will be the focus of the enter animation - * @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation - * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation - * @return {Promise} the animation callback promise - */ - enter : function(element, parentElement, afterElement) { - element = angular.element(element); - parentElement = prepareElement(parentElement); - afterElement = prepareElement(afterElement); - - classBasedAnimationsBlocked(element, true); - $delegate.enter(element, parentElement, afterElement); - return runAnimationPostDigest(function(done) { - return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, done); - }); - }, - - /** - * @ngdoc method - * @name $animate#leave - * @kind function - * - * @description - * Runs the leave animation operation and, upon completion, removes the element from the DOM. Once - * the animation is started, the following CSS classes will be added for the duration of the animation: - * - * Below is a breakdown of each step that occurs during leave animation: - * - * | Animation Step | What the element class attribute looks like | - * |-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------| - * | 1. $animate.leave(...) is called | class="my-animation" | - * | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" | - * | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" | - * | 4. the .ng-leave class is added to the element | class="my-animation ng-animate ng-leave" | - * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-leave" | - * | 6. $animate blocks all CSS transitions on the element to ensure the .ng-leave class styling is applied right away | class="my-animation ng-animate ng-leave” | - * | 7. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-leave" | - * | 8. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-leave” | - * | 9. the .ng-leave-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-leave ng-leave-active" | - * | 10. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-leave ng-leave-active" | - * | 11. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | - * | 12. The element is removed from the DOM | ... | - * | 13. The returned promise is resolved. | ... | - * - * @param {DOMElement} element the element that will be the focus of the leave animation - * @return {Promise} the animation callback promise - */ - leave : function(element) { - element = angular.element(element); - - cancelChildAnimations(element); - classBasedAnimationsBlocked(element, true); - this.enabled(false, element); - return runAnimationPostDigest(function(done) { - return performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() { - $delegate.leave(element); - }, done); - }); - }, - - /** - * @ngdoc method - * @name $animate#move - * @kind function - * - * @description - * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parentElement container or - * add the element directly after the afterElement element if present. Then the move animation will be run. Once - * the animation is started, the following CSS classes will be added for the duration of the animation: - * - * Below is a breakdown of each step that occurs during move animation: - * - * | Animation Step | What the element class attribute looks like | - * |------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------| - * | 1. $animate.move(...) is called | class="my-animation" | - * | 2. element is moved into the parentElement element or beside the afterElement element | class="my-animation" | - * | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" | - * | 4. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" | - * | 5. the .ng-move class is added to the element | class="my-animation ng-animate ng-move" | - * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-move" | - * | 7. $animate blocks all CSS transitions on the element to ensure the .ng-move class styling is applied right away | class="my-animation ng-animate ng-move” | - * | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-move" | - * | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-move” | - * | 10. the .ng-move-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-move ng-move-active" | - * | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-move ng-move-active" | - * | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | - * | 13. The returned promise is resolved. | class="my-animation" | - * - * @param {DOMElement} element the element that will be the focus of the move animation - * @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation - * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation - * @return {Promise} the animation callback promise - */ - move : function(element, parentElement, afterElement) { - element = angular.element(element); - parentElement = prepareElement(parentElement); - afterElement = prepareElement(afterElement); - - cancelChildAnimations(element); - classBasedAnimationsBlocked(element, true); - $delegate.move(element, parentElement, afterElement); - return runAnimationPostDigest(function(done) { - return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, done); - }); - }, - - /** - * @ngdoc method - * @name $animate#addClass - * - * @description - * Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class. - * Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide - * the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if no CSS transitions - * or keyframes are defined on the -add-active or base CSS class). - * - * Below is a breakdown of each step that occurs during addClass animation: - * - * | Animation Step | What the element class attribute looks like | - * |----------------------------------------------------------------------------------------------------|------------------------------------------------------------------| - * | 1. $animate.addClass(element, 'super') is called | class="my-animation" | - * | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" | - * | 3. the .super-add class is added to the element | class="my-animation ng-animate super-add" | - * | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate super-add" | - * | 5. the .super and .super-add-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate super super-add super-add-active" | - * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super-add" | - * | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation super super-add super-add-active" | - * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation super" | - * | 9. The super class is kept on the element | class="my-animation super" | - * | 10. The returned promise is resolved. | class="my-animation super" | - * - * @param {DOMElement} element the element that will be animated - * @param {string} className the CSS class that will be added to the element and then animated - * @return {Promise} the animation callback promise - */ - addClass : function(element, className) { - return this.setClass(element, className, []); - }, - - /** - * @ngdoc method - * @name $animate#removeClass - * - * @description - * Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value - * from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in - * order to provide the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if - * no CSS transitions or keyframes are defined on the -remove or base CSS classes). - * - * Below is a breakdown of each step that occurs during removeClass animation: - * - * | Animation Step | What the element class attribute looks like | - * |------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------| - * | 1. $animate.removeClass(element, 'super') is called | class="my-animation super" | - * | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation super ng-animate" | - * | 3. the .super-remove class is added to the element | class="my-animation super ng-animate super-remove" | - * | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation super ng-animate super-remove" | - * | 5. the .super-remove-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate super-remove super-remove-active" | - * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation super ng-animate super-remove" | - * | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate super-remove super-remove-active" | - * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | - * | 9. The returned promise is resolved. | class="my-animation" | - * - * - * @param {DOMElement} element the element that will be animated - * @param {string} className the CSS class that will be animated and then removed from the element - * @return {Promise} the animation callback promise - */ - removeClass : function(element, className) { - return this.setClass(element, [], className); - }, - - /** - * - * @ngdoc method - * @name $animate#setClass - * - * @description Adds and/or removes the given CSS classes to and from the element. - * Once complete, the done() callback will be fired (if provided). - * - * | Animation Step | What the element class attribute looks like | - * |--------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------| - * | 1. $animate.removeClass(element, ‘on’, ‘off’) is called | class="my-animation super off” | - * | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation super ng-animate off” | - * | 3. the .on-add and .off-remove classes are added to the element | class="my-animation ng-animate on-add off-remove off” | - * | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate on-add off-remove off” | - * | 5. the .on, .on-add-active and .off-remove-active classes are added and .off is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active” | - * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" | - * | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" | - * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation on" | - * | 9. The returned promise is resolved. | class="my-animation on" | - * - * @param {DOMElement} element the element which will have its CSS classes changed - * removed from it - * @param {string} add the CSS classes which will be added to the element - * @param {string} remove the CSS class which will be removed from the element - * CSS classes have been set on the element - * @return {Promise} the animation callback promise - */ - setClass : function(element, add, remove) { - var STORAGE_KEY = '$$animateClasses'; - element = angular.element(element); - element = stripCommentsFromElement(element); - - if (classBasedAnimationsBlocked(element)) { - return $delegate.setClass(element, add, remove); - } - - add = isArray(add) ? add : add.split(' '); - remove = isArray(remove) ? remove : remove.split(' '); - - var cache = element.data(STORAGE_KEY); - if (cache) { - cache.add = cache.add.concat(add); - cache.remove = cache.remove.concat(remove); - - //the digest cycle will combine all the animations into one function - return cache.promise; - } else { - element.data(STORAGE_KEY, cache = { - add : add, - remove : remove - }); - } - - return cache.promise = runAnimationPostDigest(function(done) { - var cache = element.data(STORAGE_KEY); - element.removeData(STORAGE_KEY); - - var state = element.data(NG_ANIMATE_STATE) || {}; - var classes = resolveElementClasses(element, cache, state.active); - return !classes - ? done() - : performAnimation('setClass', classes, element, null, null, function() { - $delegate.setClass(element, classes[0], classes[1]); - }, done); - }); - }, - - /** - * @ngdoc method - * @name $animate#cancel - * @kind function - * - * @param {Promise} animationPromise The animation promise that is returned when an animation is started. - * - * @description - * Cancels the provided animation. - */ - cancel : function(promise) { - promise.$$cancelFn(); - }, - - /** - * @ngdoc method - * @name $animate#enabled - * @kind function - * - * @param {boolean=} value If provided then set the animation on or off. - * @param {DOMElement=} element If provided then the element will be used to represent the enable/disable operation - * @return {boolean} Current animation state. - * - * @description - * Globally enables/disables animations. - * - */ - enabled : function(value, element) { - switch(arguments.length) { - case 2: - if (value) { - cleanup(element); - } else { - var data = element.data(NG_ANIMATE_STATE) || {}; - data.disabled = true; - element.data(NG_ANIMATE_STATE, data); - } - break; - - case 1: - rootAnimateState.disabled = !value; - break; - - default: - value = !rootAnimateState.disabled; - break; - } - return !!value; - } - }; - - /* - all animations call this shared animation triggering function internally. - The animationEvent variable refers to the JavaScript animation event that will be triggered - and the className value is the name of the animation that will be applied within the - CSS code. Element, parentElement and afterElement are provided DOM elements for the animation - and the onComplete callback will be fired once the animation is fully complete. - */ - function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) { - - var noopCancel = noop; - var runner = animationRunner(element, animationEvent, className); - if (!runner) { - fireDOMOperation(); - fireBeforeCallbackAsync(); - fireAfterCallbackAsync(); - closeAnimation(); - return noopCancel; - } - - animationEvent = runner.event; - className = runner.className; - var elementEvents = angular.element._data(runner.node); - elementEvents = elementEvents && elementEvents.events; - - if (!parentElement) { - parentElement = afterElement ? afterElement.parent() : element.parent(); - } - - //skip the animation if animations are disabled, a parent is already being animated, - //the element is not currently attached to the document body or then completely close - //the animation if any matching animations are not found at all. - //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case an animation was found. - if (animationsDisabled(element, parentElement)) { - fireDOMOperation(); - fireBeforeCallbackAsync(); - fireAfterCallbackAsync(); - closeAnimation(); - return noopCancel; - } - - var ngAnimateState = element.data(NG_ANIMATE_STATE) || {}; - var runningAnimations = ngAnimateState.active || {}; - var totalActiveAnimations = ngAnimateState.totalActive || 0; - var lastAnimation = ngAnimateState.last; - var skipAnimation = false; - - if (totalActiveAnimations > 0) { - var animationsToCancel = []; - if (!runner.isClassBased) { - if (animationEvent == 'leave' && runningAnimations['ng-leave']) { - skipAnimation = true; - } else { - //cancel all animations when a structural animation takes place - for(var klass in runningAnimations) { - animationsToCancel.push(runningAnimations[klass]); - } - ngAnimateState = {}; - cleanup(element, true); - } - } else if (lastAnimation.event == 'setClass') { - animationsToCancel.push(lastAnimation); - cleanup(element, className); - } - else if (runningAnimations[className]) { - var current = runningAnimations[className]; - if (current.event == animationEvent) { - skipAnimation = true; - } else { - animationsToCancel.push(current); - cleanup(element, className); - } - } - - if (animationsToCancel.length > 0) { - forEach(animationsToCancel, function(operation) { - operation.cancel(); - }); - } - } - - if (runner.isClassBased && !runner.isSetClassOperation && !skipAnimation) { - skipAnimation = (animationEvent == 'addClass') == element.hasClass(className); //opposite of XOR - } - - if (skipAnimation) { - fireDOMOperation(); - fireBeforeCallbackAsync(); - fireAfterCallbackAsync(); - fireDoneCallbackAsync(); - return noopCancel; - } - - runningAnimations = ngAnimateState.active || {}; - totalActiveAnimations = ngAnimateState.totalActive || 0; - - if (animationEvent == 'leave') { - //there's no need to ever remove the listener since the element - //will be removed (destroyed) after the leave animation ends or - //is cancelled midway - element.one('$destroy', function(e) { - var element = angular.element(this); - var state = element.data(NG_ANIMATE_STATE); - if (state) { - var activeLeaveAnimation = state.active['ng-leave']; - if (activeLeaveAnimation) { - activeLeaveAnimation.cancel(); - cleanup(element, 'ng-leave'); - } - } - }); - } - - //the ng-animate class does nothing, but it's here to allow for - //parent animations to find and cancel child animations when needed - element.addClass(NG_ANIMATE_CLASS_NAME); - - var localAnimationCount = globalAnimationCounter++; - totalActiveAnimations++; - runningAnimations[className] = runner; - - element.data(NG_ANIMATE_STATE, { - last : runner, - active : runningAnimations, - index : localAnimationCount, - totalActive : totalActiveAnimations - }); - - //first we run the before animations and when all of those are complete - //then we perform the DOM operation and run the next set of animations - fireBeforeCallbackAsync(); - runner.before(function(cancelled) { - var data = element.data(NG_ANIMATE_STATE); - cancelled = cancelled || - !data || !data.active[className] || - (runner.isClassBased && data.active[className].event != animationEvent); - - fireDOMOperation(); - if (cancelled === true) { - closeAnimation(); - } else { - fireAfterCallbackAsync(); - runner.after(closeAnimation); - } - }); - - return runner.cancel; - - function fireDOMCallback(animationPhase) { - var eventName = '$animate:' + animationPhase; - if (elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) { - $$asyncCallback(function() { - element.triggerHandler(eventName, { - event : animationEvent, - className : className - }); - }); - } - } - - function fireBeforeCallbackAsync() { - fireDOMCallback('before'); - } - - function fireAfterCallbackAsync() { - fireDOMCallback('after'); - } - - function fireDoneCallbackAsync() { - fireDOMCallback('close'); - doneCallback(); - } - - //it is less complicated to use a flag than managing and canceling - //timeouts containing multiple callbacks. - function fireDOMOperation() { - if (!fireDOMOperation.hasBeenRun) { - fireDOMOperation.hasBeenRun = true; - domOperation(); - } - } - - function closeAnimation() { - if (!closeAnimation.hasBeenRun) { - closeAnimation.hasBeenRun = true; - var data = element.data(NG_ANIMATE_STATE); - if (data) { - /* only structural animations wait for reflow before removing an - animation, but class-based animations don't. An example of this - failing would be when a parent HTML tag has a ng-class attribute - causing ALL directives below to skip animations during the digest */ - if (runner && runner.isClassBased) { - cleanup(element, className); - } else { - $$asyncCallback(function() { - var data = element.data(NG_ANIMATE_STATE) || {}; - if (localAnimationCount == data.index) { - cleanup(element, className, animationEvent); - } - }); - element.data(NG_ANIMATE_STATE, data); - } - } - fireDoneCallbackAsync(); - } - } - } - - function cancelChildAnimations(element) { - var node = extractElementNode(element); - if (node) { - var nodes = angular.isFunction(node.getElementsByClassName) ? - node.getElementsByClassName(NG_ANIMATE_CLASS_NAME) : - node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME); - forEach(nodes, function(element) { - element = angular.element(element); - var data = element.data(NG_ANIMATE_STATE); - if (data && data.active) { - forEach(data.active, function(runner) { - runner.cancel(); - }); - } - }); - } - } - - function cleanup(element, className) { - if (isMatchingElement(element, $rootElement)) { - if (!rootAnimateState.disabled) { - rootAnimateState.running = false; - rootAnimateState.structural = false; - } - } else if (className) { - var data = element.data(NG_ANIMATE_STATE) || {}; - - var removeAnimations = className === true; - if (!removeAnimations && data.active && data.active[className]) { - data.totalActive--; - delete data.active[className]; - } - - if (removeAnimations || !data.totalActive) { - element.removeClass(NG_ANIMATE_CLASS_NAME); - element.removeData(NG_ANIMATE_STATE); - } - } - } - - function animationsDisabled(element, parentElement) { - if (rootAnimateState.disabled) { - return true; - } - - if (isMatchingElement(element, $rootElement)) { - return rootAnimateState.running; - } - - var allowChildAnimations, parentRunningAnimation, hasParent; - do { - //the element did not reach the root element which means that it - //is not apart of the DOM. Therefore there is no reason to do - //any animations on it - if (parentElement.length === 0) break; - - var isRoot = isMatchingElement(parentElement, $rootElement); - var state = isRoot ? rootAnimateState : (parentElement.data(NG_ANIMATE_STATE) || {}); - if (state.disabled) { - return true; - } - - //no matter what, for an animation to work it must reach the root element - //this implies that the element is attached to the DOM when the animation is run - if (isRoot) { - hasParent = true; - } - - //once a flag is found that is strictly false then everything before - //it will be discarded and all child animations will be restricted - if (allowChildAnimations !== false) { - var animateChildrenFlag = parentElement.data(NG_ANIMATE_CHILDREN); - if (angular.isDefined(animateChildrenFlag)) { - allowChildAnimations = animateChildrenFlag; - } - } - - parentRunningAnimation = parentRunningAnimation || - state.running || - (state.last && !state.last.isClassBased); - } - while(parentElement = parentElement.parent()); - - return !hasParent || (!allowChildAnimations && parentRunningAnimation); - } - }]); - - $animateProvider.register('', ['$window', '$sniffer', '$timeout', '$$animateReflow', - function($window, $sniffer, $timeout, $$animateReflow) { - // Detect proper transitionend/animationend event names. - var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT; - - // If unprefixed events are not supported but webkit-prefixed are, use the latter. - // Otherwise, just use W3C names, browsers not supporting them at all will just ignore them. - // Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend` - // but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`. - // Register both events in case `window.onanimationend` is not supported because of that, - // do the same for `transitionend` as Safari is likely to exhibit similar behavior. - // Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit - // therefore there is no reason to test anymore for other vendor prefixes: http://caniuse.com/#search=transition - if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) { - CSS_PREFIX = '-webkit-'; - TRANSITION_PROP = 'WebkitTransition'; - TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend'; - } else { - TRANSITION_PROP = 'transition'; - TRANSITIONEND_EVENT = 'transitionend'; - } - - if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) { - CSS_PREFIX = '-webkit-'; - ANIMATION_PROP = 'WebkitAnimation'; - ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend'; - } else { - ANIMATION_PROP = 'animation'; - ANIMATIONEND_EVENT = 'animationend'; - } - - var DURATION_KEY = 'Duration'; - var PROPERTY_KEY = 'Property'; - var DELAY_KEY = 'Delay'; - var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount'; - var ANIMATION_PLAYSTATE_KEY = 'PlayState'; - var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey'; - var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data'; - var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3; - var CLOSING_TIME_BUFFER = 1.5; - var ONE_SECOND = 1000; - - var lookupCache = {}; - var parentCounter = 0; - var animationReflowQueue = []; - var cancelAnimationReflow; - function afterReflow(element, callback) { - if (cancelAnimationReflow) { - cancelAnimationReflow(); - } - animationReflowQueue.push(callback); - cancelAnimationReflow = $$animateReflow(function() { - forEach(animationReflowQueue, function(fn) { - fn(); - }); - - animationReflowQueue = []; - cancelAnimationReflow = null; - lookupCache = {}; - }); - } - - var closingTimer = null; - var closingTimestamp = 0; - var animationElementQueue = []; - function animationCloseHandler(element, totalTime) { - var node = extractElementNode(element); - element = angular.element(node); - - //this item will be garbage collected by the closing - //animation timeout - animationElementQueue.push(element); - - //but it may not need to cancel out the existing timeout - //if the timestamp is less than the previous one - var futureTimestamp = Date.now() + totalTime; - if (futureTimestamp <= closingTimestamp) { - return; - } - - $timeout.cancel(closingTimer); - - closingTimestamp = futureTimestamp; - closingTimer = $timeout(function() { - closeAllAnimations(animationElementQueue); - animationElementQueue = []; - }, totalTime, false); - } - - function closeAllAnimations(elements) { - forEach(elements, function(element) { - var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY); - if (elementData) { - forEach(elementData.closeAnimationFns, function(fn) { - fn(); - }); - } - }); - } - - function getElementAnimationDetails(element, cacheKey) { - var data = cacheKey ? lookupCache[cacheKey] : null; - if (!data) { - var transitionDuration = 0; - var transitionDelay = 0; - var animationDuration = 0; - var animationDelay = 0; - - //we want all the styles defined before and after - forEach(element, function(element) { - if (element.nodeType == ELEMENT_NODE) { - var elementStyles = $window.getComputedStyle(element) || {}; - - var transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY]; - transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration); - - var transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY]; - transitionDelay = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay); - - var animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY]; - animationDelay = Math.max(parseMaxTime(elementStyles[ANIMATION_PROP + DELAY_KEY]), animationDelay); - - var aDuration = parseMaxTime(elementStyles[ANIMATION_PROP + DURATION_KEY]); - - if (aDuration > 0) { - aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1; - } - animationDuration = Math.max(aDuration, animationDuration); - } - }); - data = { - total : 0, - transitionDelay: transitionDelay, - transitionDuration: transitionDuration, - animationDelay: animationDelay, - animationDuration: animationDuration - }; - if (cacheKey) { - lookupCache[cacheKey] = data; - } - } - return data; - } - - function parseMaxTime(str) { - var maxValue = 0; - var values = angular.isString(str) ? - str.split(/\s*,\s*/) : - []; - forEach(values, function(value) { - maxValue = Math.max(parseFloat(value) || 0, maxValue); - }); - return maxValue; - } - - function getCacheKey(element) { - var parentElement = element.parent(); - var parentID = parentElement.data(NG_ANIMATE_PARENT_KEY); - if (!parentID) { - parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter); - parentID = parentCounter; - } - return parentID + '-' + extractElementNode(element).getAttribute('class'); - } - - function animateSetup(animationEvent, element, className) { - var structural = ['ng-enter','ng-leave','ng-move'].indexOf(className) >= 0; - - var cacheKey = getCacheKey(element); - var eventCacheKey = cacheKey + ' ' + className; - var itemIndex = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0; - - var stagger = {}; - if (itemIndex > 0) { - var staggerClassName = className + '-stagger'; - var staggerCacheKey = cacheKey + ' ' + staggerClassName; - var applyClasses = !lookupCache[staggerCacheKey]; - - applyClasses && element.addClass(staggerClassName); - - stagger = getElementAnimationDetails(element, staggerCacheKey); - - applyClasses && element.removeClass(staggerClassName); - } - - element.addClass(className); - - var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {}; - var timings = getElementAnimationDetails(element, eventCacheKey); - var transitionDuration = timings.transitionDuration; - var animationDuration = timings.animationDuration; - - if (structural && transitionDuration === 0 && animationDuration === 0) { - element.removeClass(className); - return false; - } - - var blockTransition = structural && transitionDuration > 0; - var blockAnimation = animationDuration > 0 && - stagger.animationDelay > 0 && - stagger.animationDuration === 0; - - var closeAnimationFns = formerData.closeAnimationFns || []; - element.data(NG_ANIMATE_CSS_DATA_KEY, { - stagger : stagger, - cacheKey : eventCacheKey, - running : formerData.running || 0, - itemIndex : itemIndex, - blockTransition : blockTransition, - closeAnimationFns : closeAnimationFns - }); - - var node = extractElementNode(element); - - if (blockTransition) { - blockTransitions(node, true); - } - - if (blockAnimation) { - blockAnimations(node, true); - } - - return true; - } - - function animateRun(animationEvent, element, className, activeAnimationComplete) { - var node = extractElementNode(element); - var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY); - if (node.getAttribute('class').indexOf(className) == -1 || !elementData) { - activeAnimationComplete(); - return; - } - - if (elementData.blockTransition) { - blockTransitions(node, false); - } - - var activeClassName = ''; - var pendingClassName = ''; - forEach(className.split(' '), function(klass, i) { - var prefix = (i > 0 ? ' ' : '') + klass; - activeClassName += prefix + '-active'; - pendingClassName += prefix + '-pending'; - }); - - var style = ''; - var appliedStyles = []; - var itemIndex = elementData.itemIndex; - var stagger = elementData.stagger; - var staggerTime = 0; - if (itemIndex > 0) { - var transitionStaggerDelay = 0; - if (stagger.transitionDelay > 0 && stagger.transitionDuration === 0) { - transitionStaggerDelay = stagger.transitionDelay * itemIndex; - } - - var animationStaggerDelay = 0; - if (stagger.animationDelay > 0 && stagger.animationDuration === 0) { - animationStaggerDelay = stagger.animationDelay * itemIndex; - appliedStyles.push(CSS_PREFIX + 'animation-play-state'); - } - - staggerTime = Math.round(Math.max(transitionStaggerDelay, animationStaggerDelay) * 100) / 100; - } - - if (!staggerTime) { - element.addClass(activeClassName); - } - - var eventCacheKey = elementData.cacheKey + ' ' + activeClassName; - var timings = getElementAnimationDetails(element, eventCacheKey); - var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration); - if (maxDuration === 0) { - element.removeClass(activeClassName); - animateClose(element, className); - activeAnimationComplete(); - return; - } - - var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay); - var maxDelayTime = maxDelay * ONE_SECOND; - - if (appliedStyles.length > 0) { - //the element being animated may sometimes contain comment nodes in - //the jqLite object, so we're safe to use a single variable to house - //the styles since there is always only one element being animated - var oldStyle = node.getAttribute('style') || ''; - if (oldStyle.charAt(oldStyle.length-1) !== ';') { - oldStyle += ';'; - } - node.setAttribute('style', oldStyle + ' ' + style); - } - - var startTime = Date.now(); - var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT; - var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER; - var totalTime = (staggerTime + animationTime) * ONE_SECOND; - - var staggerTimeout; - if (staggerTime > 0) { - element.addClass(pendingClassName); - staggerTimeout = $timeout(function() { - staggerTimeout = null; - element.addClass(activeClassName); - element.removeClass(pendingClassName); - if (timings.animationDuration > 0) { - blockAnimations(node, false); - } - }, staggerTime * ONE_SECOND, false); - } - - element.on(css3AnimationEvents, onAnimationProgress); - elementData.closeAnimationFns.push(function() { - onEnd(); - activeAnimationComplete(); - }); - - elementData.running++; - animationCloseHandler(element, totalTime); - return onEnd; - - // This will automatically be called by $animate so - // there is no need to attach this internally to the - // timeout done method. - function onEnd(cancelled) { - element.off(css3AnimationEvents, onAnimationProgress); - element.removeClass(activeClassName); - element.removeClass(pendingClassName); - if (staggerTimeout) { - $timeout.cancel(staggerTimeout); - } - animateClose(element, className); - var node = extractElementNode(element); - for (var i in appliedStyles) { - node.style.removeProperty(appliedStyles[i]); - } - } - - function onAnimationProgress(event) { - event.stopPropagation(); - var ev = event.originalEvent || event; - var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now(); - - /* Firefox (or possibly just Gecko) likes to not round values up - * when a ms measurement is used for the animation */ - var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES)); - - /* $manualTimeStamp is a mocked timeStamp value which is set - * within browserTrigger(). This is only here so that tests can - * mock animations properly. Real events fallback to event.timeStamp, - * or, if they don't, then a timeStamp is automatically created for them. - * We're checking to see if the timeStamp surpasses the expected delay, - * but we're using elapsedTime instead of the timeStamp on the 2nd - * pre-condition since animations sometimes close off early */ - if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) { - activeAnimationComplete(); - } - } - } - - function blockTransitions(node, bool) { - node.style[TRANSITION_PROP + PROPERTY_KEY] = bool ? 'none' : ''; - } - - function blockAnimations(node, bool) { - node.style[ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY] = bool ? 'paused' : ''; - } - - function animateBefore(animationEvent, element, className, calculationDecorator) { - if (animateSetup(animationEvent, element, className, calculationDecorator)) { - return function(cancelled) { - cancelled && animateClose(element, className); - }; - } - } - - function animateAfter(animationEvent, element, className, afterAnimationComplete) { - if (element.data(NG_ANIMATE_CSS_DATA_KEY)) { - return animateRun(animationEvent, element, className, afterAnimationComplete); - } else { - animateClose(element, className); - afterAnimationComplete(); - } - } - - function animate(animationEvent, element, className, animationComplete) { - //If the animateSetup function doesn't bother returning a - //cancellation function then it means that there is no animation - //to perform at all - var preReflowCancellation = animateBefore(animationEvent, element, className); - if (!preReflowCancellation) { - animationComplete(); - return; - } - - //There are two cancellation functions: one is before the first - //reflow animation and the second is during the active state - //animation. The first function will take care of removing the - //data from the element which will not make the 2nd animation - //happen in the first place - var cancel = preReflowCancellation; - afterReflow(element, function() { - //once the reflow is complete then we point cancel to - //the new cancellation function which will remove all of the - //animation properties from the active animation - cancel = animateAfter(animationEvent, element, className, animationComplete); - }); - - return function(cancelled) { - (cancel || noop)(cancelled); - }; - } - - function animateClose(element, className) { - element.removeClass(className); - var data = element.data(NG_ANIMATE_CSS_DATA_KEY); - if (data) { - if (data.running) { - data.running--; - } - if (!data.running || data.running === 0) { - element.removeData(NG_ANIMATE_CSS_DATA_KEY); - } - } - } - - return { - enter : function(element, animationCompleted) { - return animate('enter', element, 'ng-enter', animationCompleted); - }, - - leave : function(element, animationCompleted) { - return animate('leave', element, 'ng-leave', animationCompleted); - }, - - move : function(element, animationCompleted) { - return animate('move', element, 'ng-move', animationCompleted); - }, - - beforeSetClass : function(element, add, remove, animationCompleted) { - var className = suffixClasses(remove, '-remove') + ' ' + - suffixClasses(add, '-add'); - var cancellationMethod = animateBefore('setClass', element, className); - if (cancellationMethod) { - afterReflow(element, animationCompleted); - return cancellationMethod; - } - animationCompleted(); - }, - - beforeAddClass : function(element, className, animationCompleted) { - var cancellationMethod = animateBefore('addClass', element, suffixClasses(className, '-add')); - if (cancellationMethod) { - afterReflow(element, animationCompleted); - return cancellationMethod; - } - animationCompleted(); - }, - - beforeRemoveClass : function(element, className, animationCompleted) { - var cancellationMethod = animateBefore('removeClass', element, suffixClasses(className, '-remove')); - if (cancellationMethod) { - afterReflow(element, animationCompleted); - return cancellationMethod; - } - animationCompleted(); - }, - - setClass : function(element, add, remove, animationCompleted) { - remove = suffixClasses(remove, '-remove'); - add = suffixClasses(add, '-add'); - var className = remove + ' ' + add; - return animateAfter('setClass', element, className, animationCompleted); - }, - - addClass : function(element, className, animationCompleted) { - return animateAfter('addClass', element, suffixClasses(className, '-add'), animationCompleted); - }, - - removeClass : function(element, className, animationCompleted) { - return animateAfter('removeClass', element, suffixClasses(className, '-remove'), animationCompleted); - } - }; - - function suffixClasses(classes, suffix) { - var className = ''; - classes = isArray(classes) ? classes : classes.split(/\s+/); - forEach(classes, function(klass, i) { - if (klass && klass.length > 0) { - className += (i > 0 ? ' ' : '') + klass + suffix; - } - }); - return className; - } - }]); - }]); - - -})(window, window.angular); diff --git a/syweb/webclient/js/angular-animate.min.js b/syweb/webclient/js/angular-animate.min.js deleted file mode 100644 index 1ce2a93ac7..0000000000 --- a/syweb/webclient/js/angular-animate.min.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - AngularJS v1.3.0-rc.1 - (c) 2010-2014 Google, Inc. http://angularjs.org - License: MIT -*/ -(function(D,f,E){'use strict';f.module("ngAnimate",["ng"]).directive("ngAnimateChildren",function(){return function(P,w,g){g=g.ngAnimateChildren;f.isString(g)&&0===g.length?w.data("$$ngAnimateChildren",!0):P.$watch(g,function(f){w.data("$$ngAnimateChildren",!!f)})}}).factory("$$animateReflow",["$$rAF","$document",function(f,w){return function(g){return f(function(){g()})}}]).config(["$provide","$animateProvider",function(P,w){function g(f){for(var g=0;g<f.length;g++){var h=f[g];if(1==h.nodeType)return h}} -function Z(f,h){return g(f)==g(h)}var q=f.noop,h=f.forEach,$=w.$$selectors,W=f.isArray,u={running:!0};P.decorator("$animate",["$delegate","$$q","$injector","$sniffer","$rootElement","$$asyncCallback","$rootScope","$document","$templateRequest",function(Q,D,I,E,v,X,x,Y,J){function K(a,c){var d=a.data("$$ngAnimateState")||{};c&&(d.running=!0,d.structural=!0,a.data("$$ngAnimateState",d));return d.disabled||d.running&&d.structural}function L(a){var c,d=D.defer();d.promise.$$cancelFn=function(){c&&c()}; -x.$$postDigest(function(){c=a(function(){d.resolve()})});return d.promise}function M(a,c,d){d=d||{};var e={};h(c.add,function(b){b&&b.length&&(e[b]=e[b]||0,e[b]++)});h(c.remove,function(b){b&&b.length&&(e[b]=e[b]||0,e[b]--)});var R=[];h(d,function(b,a){h(a.split(" "),function(a){R[a]=b})});var g=[],l=[];h(e,function(b,c){var d=f.$$hasClass(a[0],c),e=R[c]||{};0>b?(d||"addClass"==e.event)&&l.push(c):0<b&&(d&&"removeClass"!=e.event||g.push(c))});return 0<g.length+l.length&&[g.join(" "),l.join(" ")]} -function N(a){if(a){var c=[],d={};a=a.substr(1).split(".");(E.transitions||E.animations)&&c.push(I.get($[""]));for(var e=0;e<a.length;e++){var f=a[e],g=$[f];g&&!d[f]&&(c.push(I.get(g)),d[f]=!0)}return c}}function T(a,c,d){function e(b,a){var c=b[a],d=b["before"+a.charAt(0).toUpperCase()+a.substr(1)];if(c||d)return"leave"==a&&(d=c,c=null),U.push({event:a,fn:c}),m.push({event:a,fn:d}),!0}function f(c,r,e){var A=[];h(c,function(b){b.fn&&A.push(b)});var k=0;h(A,function(c,f){var g=function(){a:{if(r){(r[f]|| -q)();if(++k<A.length)break a;r=null}e()}};switch(c.event){case "setClass":r.push(c.fn(a,l,b,g));break;case "addClass":r.push(c.fn(a,l||d,g));break;case "removeClass":r.push(c.fn(a,b||d,g));break;default:r.push(c.fn(a,g))}});r&&0===r.length&&e()}var g=a[0];if(g){var l,b;W(d)&&(l=d[0],b=d[1],l?b?d=l+" "+b:(d=l,c="addClass"):(d=b,c="removeClass"));var r="setClass"==c,A=r||"addClass"==c||"removeClass"==c,y=a.attr("class")+" "+d;if(B(y)){var C=q,k=[],m=[],n=q,s=[],U=[],y=(" "+y).replace(/\s+/g,".");h(N(y), -function(b){!e(b,c)&&r&&(e(b,"addClass"),e(b,"removeClass"))});return{node:g,event:c,className:d,isClassBased:A,isSetClassOperation:r,before:function(b){C=b;f(m,k,function(){C=q;b()})},after:function(b){n=b;f(U,s,function(){n=q;b()})},cancel:function(){k&&(h(k,function(b){(b||q)(!0)}),C(!0));s&&(h(s,function(b){(b||q)(!0)}),n(!0))}}}}}function F(a,c,d,e,g,S,l){function b(b){var l="$animate:"+b;m&&m[l]&&0<m[l].length&&X(function(){d.triggerHandler(l,{event:a,className:c})})}function r(){b("before")} -function A(){b("after")}function y(){y.hasBeenRun||(y.hasBeenRun=!0,S())}function C(){if(!C.hasBeenRun){C.hasBeenRun=!0;var r=d.data("$$ngAnimateState");r&&(k&&k.isClassBased?z(d,c):(X(function(){var b=d.data("$$ngAnimateState")||{};p==b.index&&z(d,c,a)}),d.data("$$ngAnimateState",r)));b("close");l()}}var k=T(d,a,c);if(!k)return y(),r(),A(),C(),q;a=k.event;c=k.className;var m=f.element._data(k.node),m=m&&m.events;e||(e=g?g.parent():d.parent());if(H(d,e))return y(),r(),A(),C(),q;e=d.data("$$ngAnimateState")|| -{};var n=e.active||{},s=e.totalActive||0,U=e.last;g=!1;if(0<s){s=[];if(k.isClassBased)"setClass"==U.event?(s.push(U),z(d,c)):n[c]&&(t=n[c],t.event==a?g=!0:(s.push(t),z(d,c)));else if("leave"==a&&n["ng-leave"])g=!0;else{for(var t in n)s.push(n[t]);e={};z(d,!0)}0<s.length&&h(s,function(b){b.cancel()})}!k.isClassBased||k.isSetClassOperation||g||(g="addClass"==a==d.hasClass(c));if(g)return y(),r(),A(),b("close"),l(),q;n=e.active||{};s=e.totalActive||0;if("leave"==a)d.one("$destroy",function(b){b=f.element(this); -var a=b.data("$$ngAnimateState");a&&(a=a.active["ng-leave"])&&(a.cancel(),z(b,"ng-leave"))});d.addClass("ng-animate");var p=G++;s++;n[c]=k;d.data("$$ngAnimateState",{last:k,active:n,index:p,totalActive:s});r();k.before(function(b){var l=d.data("$$ngAnimateState");b=b||!l||!l.active[c]||k.isClassBased&&l.active[c].event!=a;y();!0===b?C():(A(),k.after(C))});return k.cancel}function p(a){if(a=g(a))a=f.isFunction(a.getElementsByClassName)?a.getElementsByClassName("ng-animate"):a.querySelectorAll(".ng-animate"), -h(a,function(a){a=f.element(a);(a=a.data("$$ngAnimateState"))&&a.active&&h(a.active,function(a){a.cancel()})})}function z(a,c){if(Z(a,v))u.disabled||(u.running=!1,u.structural=!1);else if(c){var d=a.data("$$ngAnimateState")||{},e=!0===c;!e&&d.active&&d.active[c]&&(d.totalActive--,delete d.active[c]);if(e||!d.totalActive)a.removeClass("ng-animate"),a.removeData("$$ngAnimateState")}}function H(a,c){if(u.disabled)return!0;if(Z(a,v))return u.running;var d,e,g;do{if(0===c.length)break;var h=Z(c,v),l=h? -u:c.data("$$ngAnimateState")||{};if(l.disabled)return!0;h&&(g=!0);!1!==d&&(h=c.data("$$ngAnimateChildren"),f.isDefined(h)&&(d=h));e=e||l.running||l.last&&!l.last.isClassBased}while(c=c.parent());return!g||!d&&e}v.data("$$ngAnimateState",u);var V=x.$watch(function(){return J.totalPendingRequests},function(a,c){0===a&&(V(),x.$$postDigest(function(){x.$$postDigest(function(){u.running=!1})}))}),G=0,O=w.classNameFilter(),B=O?function(a){return O.test(a)}:function(){return!0};return{enter:function(a,c, -d){a=f.element(a);c=c&&f.element(c);d=d&&f.element(d);K(a,!0);Q.enter(a,c,d);return L(function(e){return F("enter","ng-enter",f.element(g(a)),c,d,q,e)})},leave:function(a){a=f.element(a);p(a);K(a,!0);this.enabled(!1,a);return L(function(c){return F("leave","ng-leave",f.element(g(a)),null,null,function(){Q.leave(a)},c)})},move:function(a,c,d){a=f.element(a);c=c&&f.element(c);d=d&&f.element(d);p(a);K(a,!0);Q.move(a,c,d);return L(function(e){return F("move","ng-move",f.element(g(a)),c,d,q,e)})},addClass:function(a, -c){return this.setClass(a,c,[])},removeClass:function(a,c){return this.setClass(a,[],c)},setClass:function(a,c,d){a=f.element(a);a=f.element(g(a));if(K(a))return Q.setClass(a,c,d);c=W(c)?c:c.split(" ");d=W(d)?d:d.split(" ");var e=a.data("$$animateClasses");if(e)return e.add=e.add.concat(c),e.remove=e.remove.concat(d),e.promise;a.data("$$animateClasses",e={add:c,remove:d});return e.promise=L(function(c){var d=a.data("$$animateClasses");a.removeData("$$animateClasses");var l=a.data("$$ngAnimateState")|| -{},b=M(a,d,l.active);return b?F("setClass",b,a,null,null,function(){Q.setClass(a,b[0],b[1])},c):c()})},cancel:function(a){a.$$cancelFn()},enabled:function(a,c){switch(arguments.length){case 2:if(a)z(c);else{var d=c.data("$$ngAnimateState")||{};d.disabled=!0;c.data("$$ngAnimateState",d)}break;case 1:u.disabled=!a;break;default:a=!u.disabled}return!!a}}}]);w.register("",["$window","$sniffer","$timeout","$$animateReflow",function(u,w,I,P){function v(a,b){d&&d();c.push(b);d=P(function(){h(c,function(b){b()}); -c=[];d=null;B={}})}function X(a,b){var c=g(a);a=f.element(c);S.push(a);c=Date.now()+b;c<=R||(I.cancel(e),R=c,e=I(function(){x(S);S=[]},b,!1))}function x(a){h(a,function(b){(b=b.data("$$ngAnimateCSS3Data"))&&h(b.closeAnimationFns,function(b){b()})})}function Y(a,b){var c=b?B[b]:null;if(!c){var d=0,e=0,g=0,f=0;h(a,function(b){if(1==b.nodeType){b=u.getComputedStyle(b)||{};d=Math.max(J(b[H+"Duration"]),d);e=Math.max(J(b[H+"Delay"]),e);f=Math.max(J(b[G+"Delay"]),f);var a=J(b[G+"Duration"]);0<a&&(a*=parseInt(b[G+ -"IterationCount"],10)||1);g=Math.max(a,g)}});c={total:0,transitionDelay:e,transitionDuration:d,animationDelay:f,animationDuration:g};b&&(B[b]=c)}return c}function J(a){var b=0;a=f.isString(a)?a.split(/\s*,\s*/):[];h(a,function(a){b=Math.max(parseFloat(a)||0,b)});return b}function K(c,b,d){c=0<=["ng-enter","ng-leave","ng-move"].indexOf(d);var e,f=b.parent(),h=f.data("$$ngAnimateKey");h||(f.data("$$ngAnimateKey",++a),h=a);e=h+"-"+g(b).getAttribute("class");var f=e+" "+d,h=B[f]?++B[f].total:0,k={};if(0< -h){var m=d+"-stagger",k=e+" "+m;(e=!B[k])&&b.addClass(m);k=Y(b,k);e&&b.removeClass(m)}b.addClass(d);var m=b.data("$$ngAnimateCSS3Data")||{},n=Y(b,f);e=n.transitionDuration;n=n.animationDuration;if(c&&0===e&&0===n)return b.removeClass(d),!1;d=c&&0<e;c=0<n&&0<k.animationDelay&&0===k.animationDuration;b.data("$$ngAnimateCSS3Data",{stagger:k,cacheKey:f,running:m.running||0,itemIndex:h,blockTransition:d,closeAnimationFns:m.closeAnimationFns||[]});b=g(b);d&&(b.style[H+"Property"]="none");c&&(b.style[G+ -"PlayState"]="paused");return!0}function L(a,b,c,d){function e(a){b.off(D,f);b.removeClass(m);b.removeClass(n);x&&I.cancel(x);F(b,c);a=g(b);for(var d in s)a.style.removeProperty(s[d])}function f(b){b.stopPropagation();var a=b.originalEvent||b;b=a.$manualTimeStamp||a.timeStamp||Date.now();a=parseFloat(a.elapsedTime.toFixed(3));Math.max(b-E,0)>=B&&a>=w&&d()}var k=g(b);a=b.data("$$ngAnimateCSS3Data");if(-1!=k.getAttribute("class").indexOf(c)&&a){a.blockTransition&&(k.style[H+"Property"]="");var m="", -n="";h(c.split(" "),function(b,a){var c=(0<a?" ":"")+b;m+=c+"-active";n+=c+"-pending"});var s=[],p=a.itemIndex,t=a.stagger,q=0;if(0<p){q=0;0<t.transitionDelay&&0===t.transitionDuration&&(q=t.transitionDelay*p);var u=0;0<t.animationDelay&&0===t.animationDuration&&(u=t.animationDelay*p,s.push(z+"animation-play-state"));q=Math.round(100*Math.max(q,u))/100}q||b.addClass(m);var v=Y(b,a.cacheKey+" "+m),w=Math.max(v.transitionDuration,v.animationDuration);if(0===w)b.removeClass(m),F(b,c),d();else{var p= -Math.max(v.transitionDelay,v.animationDelay),B=1E3*p;0<s.length&&(t=k.getAttribute("style")||"",";"!==t.charAt(t.length-1)&&(t+=";"),k.setAttribute("style",t+" "));var E=Date.now(),D=O+" "+V,p=1E3*(q+1.5*(p+w)),x;0<q&&(b.addClass(n),x=I(function(){x=null;b.addClass(m);b.removeClass(n);0<v.animationDuration&&(k.style[G+"PlayState"]="")},1E3*q,!1));b.on(D,f);a.closeAnimationFns.push(function(){e();d()});a.running++;X(b,p);return e}}else d()}function M(a,b,c,d){if(K(a,b,c,d))return function(a){a&&F(b, -c)}}function N(a,b,c,d){if(b.data("$$ngAnimateCSS3Data"))return L(a,b,c,d);F(b,c);d()}function T(a,b,c,d){var e=M(a,b,c);if(e){var f=e;v(b,function(){f=N(a,b,c,d)});return function(b){(f||q)(b)}}d()}function F(a,b){a.removeClass(b);var c=a.data("$$ngAnimateCSS3Data");c&&(c.running&&c.running--,c.running&&0!==c.running||a.removeData("$$ngAnimateCSS3Data"))}function p(a,b){var c="";a=W(a)?a:a.split(/\s+/);h(a,function(a,d){a&&0<a.length&&(c+=(0<d?" ":"")+a+b)});return c}var z="",H,V,G,O;D.ontransitionend=== -E&&D.onwebkittransitionend!==E?(z="-webkit-",H="WebkitTransition",V="webkitTransitionEnd transitionend"):(H="transition",V="transitionend");D.onanimationend===E&&D.onwebkitanimationend!==E?(z="-webkit-",G="WebkitAnimation",O="webkitAnimationEnd animationend"):(G="animation",O="animationend");var B={},a=0,c=[],d,e=null,R=0,S=[];return{enter:function(a,b){return T("enter",a,"ng-enter",b)},leave:function(a,b){return T("leave",a,"ng-leave",b)},move:function(a,b){return T("move",a,"ng-move",b)},beforeSetClass:function(a, -b,c,d){b=p(c,"-remove")+" "+p(b,"-add");if(b=M("setClass",a,b))return v(a,d),b;d()},beforeAddClass:function(a,b,c){if(b=M("addClass",a,p(b,"-add")))return v(a,c),b;c()},beforeRemoveClass:function(a,b,c){if(b=M("removeClass",a,p(b,"-remove")))return v(a,c),b;c()},setClass:function(a,b,c,d){c=p(c,"-remove");b=p(b,"-add");return N("setClass",a,c+" "+b,d)},addClass:function(a,b,c){return N("addClass",a,p(b,"-add"),c)},removeClass:function(a,b,c){return N("removeClass",a,p(b,"-remove"),c)}}}])}])})(window, -window.angular); -//# sourceMappingURL=angular-animate.min.js.map diff --git a/syweb/webclient/js/angular-mocks.js b/syweb/webclient/js/angular-mocks.js deleted file mode 100755 index 24bbcd4137..0000000000 --- a/syweb/webclient/js/angular-mocks.js +++ /dev/null @@ -1,2259 +0,0 @@ -'use strict'; - -/** - * @ngdoc object - * @name angular.mock - * @description - * - * Namespace from 'angular-mocks.js' which contains testing related code. - */ -angular.mock = {}; - -/** - * ! This is a private undocumented service ! - * - * @name $browser - * - * @description - * This service is a mock implementation of {@link ng.$browser}. It provides fake - * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, - * cookies, etc... - * - * The api of this service is the same as that of the real {@link ng.$browser $browser}, except - * that there are several helper methods available which can be used in tests. - */ -angular.mock.$BrowserProvider = function() { - this.$get = function() { - return new angular.mock.$Browser(); - }; -}; - -angular.mock.$Browser = function() { - var self = this; - - this.isMock = true; - self.$$url = "http://server/"; - self.$$lastUrl = self.$$url; // used by url polling fn - self.pollFns = []; - - // TODO(vojta): remove this temporary api - self.$$completeOutstandingRequest = angular.noop; - self.$$incOutstandingRequestCount = angular.noop; - - - // register url polling fn - - self.onUrlChange = function(listener) { - self.pollFns.push( - function() { - if (self.$$lastUrl != self.$$url) { - self.$$lastUrl = self.$$url; - listener(self.$$url); - } - } - ); - - return listener; - }; - - self.$$checkUrlChange = angular.noop; - - self.cookieHash = {}; - self.lastCookieHash = {}; - self.deferredFns = []; - self.deferredNextId = 0; - - self.defer = function(fn, delay) { - delay = delay || 0; - self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId}); - self.deferredFns.sort(function(a,b){ return a.time - b.time;}); - return self.deferredNextId++; - }; - - - /** - * @name $browser#defer.now - * - * @description - * Current milliseconds mock time. - */ - self.defer.now = 0; - - - self.defer.cancel = function(deferId) { - var fnIndex; - - angular.forEach(self.deferredFns, function(fn, index) { - if (fn.id === deferId) fnIndex = index; - }); - - if (fnIndex !== undefined) { - self.deferredFns.splice(fnIndex, 1); - return true; - } - - return false; - }; - - - /** - * @name $browser#defer.flush - * - * @description - * Flushes all pending requests and executes the defer callbacks. - * - * @param {number=} number of milliseconds to flush. See {@link #defer.now} - */ - self.defer.flush = function(delay) { - if (angular.isDefined(delay)) { - self.defer.now += delay; - } else { - if (self.deferredFns.length) { - self.defer.now = self.deferredFns[self.deferredFns.length-1].time; - } else { - throw new Error('No deferred tasks to be flushed'); - } - } - - while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { - self.deferredFns.shift().fn(); - } - }; - - self.$$baseHref = '/'; - self.baseHref = function() { - return this.$$baseHref; - }; -}; -angular.mock.$Browser.prototype = { - -/** - * @name $browser#poll - * - * @description - * run all fns in pollFns - */ - poll: function poll() { - angular.forEach(this.pollFns, function(pollFn){ - pollFn(); - }); - }, - - addPollFn: function(pollFn) { - this.pollFns.push(pollFn); - return pollFn; - }, - - url: function(url, replace) { - if (url) { - this.$$url = url; - return this; - } - - return this.$$url; - }, - - cookies: function(name, value) { - if (name) { - if (angular.isUndefined(value)) { - delete this.cookieHash[name]; - } else { - if (angular.isString(value) && //strings only - value.length <= 4096) { //strict cookie storage limits - this.cookieHash[name] = value; - } - } - } else { - if (!angular.equals(this.cookieHash, this.lastCookieHash)) { - this.lastCookieHash = angular.copy(this.cookieHash); - this.cookieHash = angular.copy(this.cookieHash); - } - return this.cookieHash; - } - }, - - notifyWhenNoOutstandingRequests: function(fn) { - fn(); - } -}; - - -/** - * @ngdoc provider - * @name $exceptionHandlerProvider - * - * @description - * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors - * passed into the `$exceptionHandler`. - */ - -/** - * @ngdoc service - * @name $exceptionHandler - * - * @description - * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed - * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration - * information. - * - * - * ```js - * describe('$exceptionHandlerProvider', function() { - * - * it('should capture log messages and exceptions', function() { - * - * module(function($exceptionHandlerProvider) { - * $exceptionHandlerProvider.mode('log'); - * }); - * - * inject(function($log, $exceptionHandler, $timeout) { - * $timeout(function() { $log.log(1); }); - * $timeout(function() { $log.log(2); throw 'banana peel'; }); - * $timeout(function() { $log.log(3); }); - * expect($exceptionHandler.errors).toEqual([]); - * expect($log.assertEmpty()); - * $timeout.flush(); - * expect($exceptionHandler.errors).toEqual(['banana peel']); - * expect($log.log.logs).toEqual([[1], [2], [3]]); - * }); - * }); - * }); - * ``` - */ - -angular.mock.$ExceptionHandlerProvider = function() { - var handler; - - /** - * @ngdoc method - * @name $exceptionHandlerProvider#mode - * - * @description - * Sets the logging mode. - * - * @param {string} mode Mode of operation, defaults to `rethrow`. - * - * - `rethrow`: If any errors are passed into the handler in tests, it typically - * means that there is a bug in the application or test, so this mock will - * make these tests fail. - * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` - * mode stores an array of errors in `$exceptionHandler.errors`, to allow later - * assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and - * {@link ngMock.$log#reset reset()} - */ - this.mode = function(mode) { - switch(mode) { - case 'rethrow': - handler = function(e) { - throw e; - }; - break; - case 'log': - var errors = []; - - handler = function(e) { - if (arguments.length == 1) { - errors.push(e); - } else { - errors.push([].slice.call(arguments, 0)); - } - }; - - handler.errors = errors; - break; - default: - throw new Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); - } - }; - - this.$get = function() { - return handler; - }; - - this.mode('rethrow'); -}; - - -/** - * @ngdoc service - * @name $log - * - * @description - * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays - * (one array per logging level). These arrays are exposed as `logs` property of each of the - * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. - * - */ -angular.mock.$LogProvider = function() { - var debug = true; - - function concat(array1, array2, index) { - return array1.concat(Array.prototype.slice.call(array2, index)); - } - - this.debugEnabled = function(flag) { - if (angular.isDefined(flag)) { - debug = flag; - return this; - } else { - return debug; - } - }; - - this.$get = function () { - var $log = { - log: function() { $log.log.logs.push(concat([], arguments, 0)); }, - warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, - info: function() { $log.info.logs.push(concat([], arguments, 0)); }, - error: function() { $log.error.logs.push(concat([], arguments, 0)); }, - debug: function() { - if (debug) { - $log.debug.logs.push(concat([], arguments, 0)); - } - } - }; - - /** - * @ngdoc method - * @name $log#reset - * - * @description - * Reset all of the logging arrays to empty. - */ - $log.reset = function () { - /** - * @ngdoc property - * @name $log#log.logs - * - * @description - * Array of messages logged using {@link ngMock.$log#log}. - * - * @example - * ```js - * $log.log('Some Log'); - * var first = $log.log.logs.unshift(); - * ``` - */ - $log.log.logs = []; - /** - * @ngdoc property - * @name $log#info.logs - * - * @description - * Array of messages logged using {@link ngMock.$log#info}. - * - * @example - * ```js - * $log.info('Some Info'); - * var first = $log.info.logs.unshift(); - * ``` - */ - $log.info.logs = []; - /** - * @ngdoc property - * @name $log#warn.logs - * - * @description - * Array of messages logged using {@link ngMock.$log#warn}. - * - * @example - * ```js - * $log.warn('Some Warning'); - * var first = $log.warn.logs.unshift(); - * ``` - */ - $log.warn.logs = []; - /** - * @ngdoc property - * @name $log#error.logs - * - * @description - * Array of messages logged using {@link ngMock.$log#error}. - * - * @example - * ```js - * $log.error('Some Error'); - * var first = $log.error.logs.unshift(); - * ``` - */ - $log.error.logs = []; - /** - * @ngdoc property - * @name $log#debug.logs - * - * @description - * Array of messages logged using {@link ngMock.$log#debug}. - * - * @example - * ```js - * $log.debug('Some Error'); - * var first = $log.debug.logs.unshift(); - * ``` - */ - $log.debug.logs = []; - }; - - /** - * @ngdoc method - * @name $log#assertEmpty - * - * @description - * Assert that the all of the logging methods have no logged messages. If messages present, an - * exception is thrown. - */ - $log.assertEmpty = function() { - var errors = []; - angular.forEach(['error', 'warn', 'info', 'log', 'debug'], function(logLevel) { - angular.forEach($log[logLevel].logs, function(log) { - angular.forEach(log, function (logItem) { - errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + - (logItem.stack || '')); - }); - }); - }); - if (errors.length) { - errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or "+ - "an expected log message was not checked and removed:"); - errors.push(''); - throw new Error(errors.join('\n---------\n')); - } - }; - - $log.reset(); - return $log; - }; -}; - - -/** - * @ngdoc service - * @name $interval - * - * @description - * Mock implementation of the $interval service. - * - * Use {@link ngMock.$interval#flush `$interval.flush(millis)`} to - * move forward by `millis` milliseconds and trigger any functions scheduled to run in that - * time. - * - * @param {function()} fn A function that should be called repeatedly. - * @param {number} delay Number of milliseconds between each function call. - * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat - * indefinitely. - * @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} A promise which will be notified on each iteration. - */ -angular.mock.$IntervalProvider = function() { - this.$get = ['$rootScope', '$q', - function($rootScope, $q) { - var repeatFns = [], - nextRepeatId = 0, - now = 0; - - var $interval = function(fn, delay, count, invokeApply) { - var deferred = $q.defer(), - promise = deferred.promise, - iteration = 0, - skipApply = (angular.isDefined(invokeApply) && !invokeApply); - - count = (angular.isDefined(count)) ? count : 0; - promise.then(null, null, fn); - - promise.$$intervalId = nextRepeatId; - - function tick() { - deferred.notify(iteration++); - - if (count > 0 && iteration >= count) { - var fnIndex; - deferred.resolve(iteration); - - angular.forEach(repeatFns, function(fn, index) { - if (fn.id === promise.$$intervalId) fnIndex = index; - }); - - if (fnIndex !== undefined) { - repeatFns.splice(fnIndex, 1); - } - } - - if (!skipApply) $rootScope.$apply(); - } - - repeatFns.push({ - nextTime:(now + delay), - delay: delay, - fn: tick, - id: nextRepeatId, - deferred: deferred - }); - repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;}); - - nextRepeatId++; - return promise; - }; - /** - * @ngdoc method - * @name $interval#cancel - * - * @description - * Cancels a task associated with the `promise`. - * - * @param {promise} promise A promise from calling the `$interval` function. - * @returns {boolean} Returns `true` if the task was successfully cancelled. - */ - $interval.cancel = function(promise) { - if(!promise) return false; - var fnIndex; - - angular.forEach(repeatFns, function(fn, index) { - if (fn.id === promise.$$intervalId) fnIndex = index; - }); - - if (fnIndex !== undefined) { - repeatFns[fnIndex].deferred.reject('canceled'); - repeatFns.splice(fnIndex, 1); - return true; - } - - return false; - }; - - /** - * @ngdoc method - * @name $interval#flush - * @description - * - * Runs interval tasks scheduled to be run in the next `millis` milliseconds. - * - * @param {number=} millis maximum timeout amount to flush up until. - * - * @return {number} The amount of time moved forward. - */ - $interval.flush = function(millis) { - now += millis; - while (repeatFns.length && repeatFns[0].nextTime <= now) { - var task = repeatFns[0]; - task.fn(); - task.nextTime += task.delay; - repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;}); - } - return millis; - }; - - return $interval; - }]; -}; - - -/* jshint -W101 */ -/* The R_ISO8061_STR regex is never going to fit into the 100 char limit! - * This directive should go inside the anonymous function but a bug in JSHint means that it would - * not be enacted early enough to prevent the warning. - */ -var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; - -function jsonStringToDate(string) { - var match; - if (match = string.match(R_ISO8061_STR)) { - var date = new Date(0), - tzHour = 0, - tzMin = 0; - if (match[9]) { - tzHour = int(match[9] + match[10]); - tzMin = int(match[9] + match[11]); - } - date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); - date.setUTCHours(int(match[4]||0) - tzHour, - int(match[5]||0) - tzMin, - int(match[6]||0), - int(match[7]||0)); - return date; - } - return string; -} - -function int(str) { - return parseInt(str, 10); -} - -function padNumber(num, digits, trim) { - var neg = ''; - if (num < 0) { - neg = '-'; - num = -num; - } - num = '' + num; - while(num.length < digits) num = '0' + num; - if (trim) - num = num.substr(num.length - digits); - return neg + num; -} - - -/** - * @ngdoc type - * @name angular.mock.TzDate - * @description - * - * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. - * - * Mock of the Date type which has its timezone specified via constructor arg. - * - * The main purpose is to create Date-like instances with timezone fixed to the specified timezone - * offset, so that we can test code that depends on local timezone settings without dependency on - * the time zone settings of the machine where the code is running. - * - * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) - * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* - * - * @example - * !!!! WARNING !!!!! - * This is not a complete Date object so only methods that were implemented can be called safely. - * To make matters worse, TzDate instances inherit stuff from Date via a prototype. - * - * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is - * incomplete we might be missing some non-standard methods. This can result in errors like: - * "Date.prototype.foo called on incompatible Object". - * - * ```js - * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z'); - * newYearInBratislava.getTimezoneOffset() => -60; - * newYearInBratislava.getFullYear() => 2010; - * newYearInBratislava.getMonth() => 0; - * newYearInBratislava.getDate() => 1; - * newYearInBratislava.getHours() => 0; - * newYearInBratislava.getMinutes() => 0; - * newYearInBratislava.getSeconds() => 0; - * ``` - * - */ -angular.mock.TzDate = function (offset, timestamp) { - var self = new Date(0); - if (angular.isString(timestamp)) { - var tsStr = timestamp; - - self.origDate = jsonStringToDate(timestamp); - - timestamp = self.origDate.getTime(); - if (isNaN(timestamp)) - throw { - name: "Illegal Argument", - message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" - }; - } else { - self.origDate = new Date(timestamp); - } - - var localOffset = new Date(timestamp).getTimezoneOffset(); - self.offsetDiff = localOffset*60*1000 - offset*1000*60*60; - self.date = new Date(timestamp + self.offsetDiff); - - self.getTime = function() { - return self.date.getTime() - self.offsetDiff; - }; - - self.toLocaleDateString = function() { - return self.date.toLocaleDateString(); - }; - - self.getFullYear = function() { - return self.date.getFullYear(); - }; - - self.getMonth = function() { - return self.date.getMonth(); - }; - - self.getDate = function() { - return self.date.getDate(); - }; - - self.getHours = function() { - return self.date.getHours(); - }; - - self.getMinutes = function() { - return self.date.getMinutes(); - }; - - self.getSeconds = function() { - return self.date.getSeconds(); - }; - - self.getMilliseconds = function() { - return self.date.getMilliseconds(); - }; - - self.getTimezoneOffset = function() { - return offset * 60; - }; - - self.getUTCFullYear = function() { - return self.origDate.getUTCFullYear(); - }; - - self.getUTCMonth = function() { - return self.origDate.getUTCMonth(); - }; - - self.getUTCDate = function() { - return self.origDate.getUTCDate(); - }; - - self.getUTCHours = function() { - return self.origDate.getUTCHours(); - }; - - self.getUTCMinutes = function() { - return self.origDate.getUTCMinutes(); - }; - - self.getUTCSeconds = function() { - return self.origDate.getUTCSeconds(); - }; - - self.getUTCMilliseconds = function() { - return self.origDate.getUTCMilliseconds(); - }; - - self.getDay = function() { - return self.date.getDay(); - }; - - // provide this method only on browsers that already have it - if (self.toISOString) { - self.toISOString = function() { - return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + - padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + - padNumber(self.origDate.getUTCDate(), 2) + 'T' + - padNumber(self.origDate.getUTCHours(), 2) + ':' + - padNumber(self.origDate.getUTCMinutes(), 2) + ':' + - padNumber(self.origDate.getUTCSeconds(), 2) + '.' + - padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z'; - }; - } - - //hide all methods not implemented in this mock that the Date prototype exposes - var unimplementedMethods = ['getUTCDay', - 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', - 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', - 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', - 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', - 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; - - angular.forEach(unimplementedMethods, function(methodName) { - self[methodName] = function() { - throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock"); - }; - }); - - return self; -}; - -//make "tzDateInstance instanceof Date" return true -angular.mock.TzDate.prototype = Date.prototype; -/* jshint +W101 */ - -angular.mock.animate = angular.module('ngAnimateMock', ['ng']) - - .config(['$provide', function($provide) { - - var reflowQueue = []; - $provide.value('$$animateReflow', function(fn) { - var index = reflowQueue.length; - reflowQueue.push(fn); - return function cancel() { - reflowQueue.splice(index, 1); - }; - }); - - $provide.decorator('$animate', ['$delegate', '$$asyncCallback', '$timeout', '$browser', - function($delegate, $$asyncCallback, $timeout, $browser) { - var animate = { - queue : [], - cancel : $delegate.cancel, - enabled : $delegate.enabled, - triggerCallbackEvents : function() { - $$asyncCallback.flush(); - }, - triggerCallbackPromise : function() { - $timeout.flush(0); - }, - triggerCallbacks : function() { - this.triggerCallbackEvents(); - this.triggerCallbackPromise(); - }, - triggerReflow : function() { - angular.forEach(reflowQueue, function(fn) { - fn(); - }); - reflowQueue = []; - } - }; - - angular.forEach( - ['enter','leave','move','addClass','removeClass','setClass'], function(method) { - animate[method] = function() { - animate.queue.push({ - event : method, - element : arguments[0], - args : arguments - }); - return $delegate[method].apply($delegate, arguments); - }; - }); - - return animate; - }]); - - }]); - - -/** - * @ngdoc function - * @name angular.mock.dump - * @description - * - * *NOTE*: this is not an injectable instance, just a globally available function. - * - * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for - * debugging. - * - * This method is also available on window, where it can be used to display objects on debug - * console. - * - * @param {*} object - any object to turn into string. - * @return {string} a serialized string of the argument - */ -angular.mock.dump = function(object) { - return serialize(object); - - function serialize(object) { - var out; - - if (angular.isElement(object)) { - object = angular.element(object); - out = angular.element('<div></div>'); - angular.forEach(object, function(element) { - out.append(angular.element(element).clone()); - }); - out = out.html(); - } else if (angular.isArray(object)) { - out = []; - angular.forEach(object, function(o) { - out.push(serialize(o)); - }); - out = '[ ' + out.join(', ') + ' ]'; - } else if (angular.isObject(object)) { - if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { - out = serializeScope(object); - } else if (object instanceof Error) { - out = object.stack || ('' + object.name + ': ' + object.message); - } else { - // TODO(i): this prevents methods being logged, - // we should have a better way to serialize objects - out = angular.toJson(object, true); - } - } else { - out = String(object); - } - - return out; - } - - function serializeScope(scope, offset) { - offset = offset || ' '; - var log = [offset + 'Scope(' + scope.$id + '): {']; - for ( var key in scope ) { - if (Object.prototype.hasOwnProperty.call(scope, key) && !key.match(/^(\$|this)/)) { - log.push(' ' + key + ': ' + angular.toJson(scope[key])); - } - } - var child = scope.$$childHead; - while(child) { - log.push(serializeScope(child, offset + ' ')); - child = child.$$nextSibling; - } - log.push('}'); - return log.join('\n' + offset); - } -}; - -/** - * @ngdoc service - * @name $httpBackend - * @description - * Fake HTTP backend implementation suitable for unit testing applications that use the - * {@link ng.$http $http service}. - * - * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less - * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. - * - * During unit testing, we want our unit tests to run quickly and have no external dependencies so - * we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or - * [JSONP](http://en.wikipedia.org/wiki/JSONP) requests to a real server. All we really need is - * to verify whether a certain request has been sent or not, or alternatively just let the - * application make requests, respond with pre-trained responses and assert that the end result is - * what we expect it to be. - * - * This mock implementation can be used to respond with static or dynamic responses via the - * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). - * - * When an Angular application needs some data from a server, it calls the $http service, which - * sends the request to a real server using $httpBackend service. With dependency injection, it is - * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify - * the requests and respond with some testing data without sending a request to a real server. - * - * There are two ways to specify what test data should be returned as http responses by the mock - * backend when the code under test makes http requests: - * - * - `$httpBackend.expect` - specifies a request expectation - * - `$httpBackend.when` - specifies a backend definition - * - * - * # Request Expectations vs Backend Definitions - * - * Request expectations provide a way to make assertions about requests made by the application and - * to define responses for those requests. The test will fail if the expected requests are not made - * or they are made in the wrong order. - * - * Backend definitions allow you to define a fake backend for your application which doesn't assert - * if a particular request was made or not, it just returns a trained response if a request is made. - * The test will pass whether or not the request gets made during testing. - * - * - * <table class="table"> - * <tr><th width="220px"></th><th>Request expectations</th><th>Backend definitions</th></tr> - * <tr> - * <th>Syntax</th> - * <td>.expect(...).respond(...)</td> - * <td>.when(...).respond(...)</td> - * </tr> - * <tr> - * <th>Typical usage</th> - * <td>strict unit tests</td> - * <td>loose (black-box) unit testing</td> - * </tr> - * <tr> - * <th>Fulfills multiple requests</th> - * <td>NO</td> - * <td>YES</td> - * </tr> - * <tr> - * <th>Order of requests matters</th> - * <td>YES</td> - * <td>NO</td> - * </tr> - * <tr> - * <th>Request required</th> - * <td>YES</td> - * <td>NO</td> - * </tr> - * <tr> - * <th>Response required</th> - * <td>optional (see below)</td> - * <td>YES</td> - * </tr> - * </table> - * - * In cases where both backend definitions and request expectations are specified during unit - * testing, the request expectations are evaluated first. - * - * If a request expectation has no response specified, the algorithm will search your backend - * definitions for an appropriate response. - * - * If a request didn't match any expectation or if the expectation doesn't have the response - * defined, the backend definitions are evaluated in sequential order to see if any of them match - * the request. The response from the first matched definition is returned. - * - * - * # Flushing HTTP requests - * - * The $httpBackend used in production always responds to requests asynchronously. If we preserved - * this behavior in unit testing, we'd have to create async unit tests, which are hard to write, - * to follow and to maintain. But neither can the testing mock respond synchronously; that would - * change the execution of the code under test. For this reason, the mock $httpBackend has a - * `flush()` method, which allows the test to explicitly flush pending requests. This preserves - * the async api of the backend, while allowing the test to execute synchronously. - * - * - * # Unit testing with mock $httpBackend - * The following code shows how to setup and use the mock backend when unit testing a controller. - * First we create the controller under test: - * - ```js - // The controller code - function MyController($scope, $http) { - var authToken; - - $http.get('/auth.py').success(function(data, status, headers) { - authToken = headers('A-Token'); - $scope.user = data; - }); - - $scope.saveMessage = function(message) { - var headers = { 'Authorization': authToken }; - $scope.status = 'Saving...'; - - $http.post('/add-msg.py', message, { headers: headers } ).success(function(response) { - $scope.status = ''; - }).error(function() { - $scope.status = 'ERROR!'; - }); - }; - } - ``` - * - * Now we setup the mock backend and create the test specs: - * - ```js - // testing controller - describe('MyController', function() { - var $httpBackend, $rootScope, createController, authRequestHandler; - - beforeEach(inject(function($injector) { - // Set up the mock http service responses - $httpBackend = $injector.get('$httpBackend'); - // backend definition common for all tests - authRequestHandler = $httpBackend.when('GET', '/auth.py') - .respond({userId: 'userX'}, {'A-Token': 'xxx'}); - - // Get hold of a scope (i.e. the root scope) - $rootScope = $injector.get('$rootScope'); - // The $controller service is used to create instances of controllers - var $controller = $injector.get('$controller'); - - createController = function() { - return $controller('MyController', {'$scope' : $rootScope }); - }; - })); - - - afterEach(function() { - $httpBackend.verifyNoOutstandingExpectation(); - $httpBackend.verifyNoOutstandingRequest(); - }); - - - it('should fetch authentication token', function() { - $httpBackend.expectGET('/auth.py'); - var controller = createController(); - $httpBackend.flush(); - }); - - - it('should fail authentication', function() { - - // Notice how you can change the response even after it was set - authRequestHandler.respond(401, ''); - - $httpBackend.expectGET('/auth.py'); - var controller = createController(); - $httpBackend.flush(); - expect($rootScope.status).toBe('Failed...'); - }); - - - it('should send msg to server', function() { - var controller = createController(); - $httpBackend.flush(); - - // now you don’t care about the authentication, but - // the controller will still send the request and - // $httpBackend will respond without you having to - // specify the expectation and response for this request - - $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, ''); - $rootScope.saveMessage('message content'); - expect($rootScope.status).toBe('Saving...'); - $httpBackend.flush(); - expect($rootScope.status).toBe(''); - }); - - - it('should send auth header', function() { - var controller = createController(); - $httpBackend.flush(); - - $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) { - // check if the header was send, if it wasn't the expectation won't - // match the request and the test will fail - return headers['Authorization'] == 'xxx'; - }).respond(201, ''); - - $rootScope.saveMessage('whatever'); - $httpBackend.flush(); - }); - }); - ``` - */ -angular.mock.$HttpBackendProvider = function() { - this.$get = ['$rootScope', createHttpBackendMock]; -}; - -/** - * General factory function for $httpBackend mock. - * Returns instance for unit testing (when no arguments specified): - * - passing through is disabled - * - auto flushing is disabled - * - * Returns instance for e2e testing (when `$delegate` and `$browser` specified): - * - passing through (delegating request to real backend) is enabled - * - auto flushing is enabled - * - * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) - * @param {Object=} $browser Auto-flushing enabled if specified - * @return {Object} Instance of $httpBackend mock - */ -function createHttpBackendMock($rootScope, $delegate, $browser) { - var definitions = [], - expectations = [], - responses = [], - responsesPush = angular.bind(responses, responses.push), - copy = angular.copy; - - function createResponse(status, data, headers, statusText) { - if (angular.isFunction(status)) return status; - - return function() { - return angular.isNumber(status) - ? [status, data, headers, statusText] - : [200, status, data]; - }; - } - - // TODO(vojta): change params to: method, url, data, headers, callback - function $httpBackend(method, url, data, callback, headers, timeout, withCredentials) { - var xhr = new MockXhr(), - expectation = expectations[0], - wasExpected = false; - - function prettyPrint(data) { - return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) - ? data - : angular.toJson(data); - } - - function wrapResponse(wrapped) { - if (!$browser && timeout && timeout.then) timeout.then(handleTimeout); - - return handleResponse; - - function handleResponse() { - var response = wrapped.response(method, url, data, headers); - xhr.$$respHeaders = response[2]; - callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(), - copy(response[3] || '')); - } - - function handleTimeout() { - for (var i = 0, ii = responses.length; i < ii; i++) { - if (responses[i] === handleResponse) { - responses.splice(i, 1); - callback(-1, undefined, ''); - break; - } - } - } - } - - if (expectation && expectation.match(method, url)) { - if (!expectation.matchData(data)) - throw new Error('Expected ' + expectation + ' with different data\n' + - 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); - - if (!expectation.matchHeaders(headers)) - throw new Error('Expected ' + expectation + ' with different headers\n' + - 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + - prettyPrint(headers)); - - expectations.shift(); - - if (expectation.response) { - responses.push(wrapResponse(expectation)); - return; - } - wasExpected = true; - } - - var i = -1, definition; - while ((definition = definitions[++i])) { - if (definition.match(method, url, data, headers || {})) { - if (definition.response) { - // if $browser specified, we do auto flush all requests - ($browser ? $browser.defer : responsesPush)(wrapResponse(definition)); - } else if (definition.passThrough) { - $delegate(method, url, data, callback, headers, timeout, withCredentials); - } else throw new Error('No response defined !'); - return; - } - } - throw wasExpected ? - new Error('No response defined !') : - new Error('Unexpected request: ' + method + ' ' + url + '\n' + - (expectation ? 'Expected ' + expectation : 'No more request expected')); - } - - /** - * @ngdoc method - * @name $httpBackend#when - * @description - * Creates a new backend definition. - * - * @param {string} method HTTP method. - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives - * data string and returns true if the data is as expected. - * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header - * object and returns true if the headers match the current definition. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - * - * - respond – - * `{function([status,] data[, headers, statusText]) - * | function(function(method, url, data, headers)}` - * – The respond method takes a set of static data to be returned or a function that can - * return an array containing response status (number), response data (string), response - * headers (Object), and the text for the status (string). The respond method returns the - * `requestHandler` object for possible overrides. - */ - $httpBackend.when = function(method, url, data, headers) { - var definition = new MockHttpExpectation(method, url, data, headers), - chain = { - respond: function(status, data, headers, statusText) { - definition.passThrough = undefined; - definition.response = createResponse(status, data, headers, statusText); - return chain; - } - }; - - if ($browser) { - chain.passThrough = function() { - definition.response = undefined; - definition.passThrough = true; - return chain; - }; - } - - definitions.push(definition); - return chain; - }; - - /** - * @ngdoc method - * @name $httpBackend#whenGET - * @description - * Creates a new backend definition for GET requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#whenHEAD - * @description - * Creates a new backend definition for HEAD requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#whenDELETE - * @description - * Creates a new backend definition for DELETE requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#whenPOST - * @description - * Creates a new backend definition for POST requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives - * data string and returns true if the data is as expected. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#whenPUT - * @description - * Creates a new backend definition for PUT requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives - * data string and returns true if the data is as expected. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#whenJSONP - * @description - * Creates a new backend definition for JSONP requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - createShortMethods('when'); - - - /** - * @ngdoc method - * @name $httpBackend#expect - * @description - * Creates a new request expectation. - * - * @param {string} method HTTP method. - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that - * receives data string and returns true if the data is as expected, or Object if request body - * is in JSON format. - * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header - * object and returns true if the headers match the current expectation. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - * - * - respond – - * `{function([status,] data[, headers, statusText]) - * | function(function(method, url, data, headers)}` - * – The respond method takes a set of static data to be returned or a function that can - * return an array containing response status (number), response data (string), response - * headers (Object), and the text for the status (string). The respond method returns the - * `requestHandler` object for possible overrides. - */ - $httpBackend.expect = function(method, url, data, headers) { - var expectation = new MockHttpExpectation(method, url, data, headers), - chain = { - respond: function (status, data, headers, statusText) { - expectation.response = createResponse(status, data, headers, statusText); - return chain; - } - }; - - expectations.push(expectation); - return chain; - }; - - - /** - * @ngdoc method - * @name $httpBackend#expectGET - * @description - * Creates a new request expectation for GET requests. For more info see `expect()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. See #expect for more info. - */ - - /** - * @ngdoc method - * @name $httpBackend#expectHEAD - * @description - * Creates a new request expectation for HEAD requests. For more info see `expect()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#expectDELETE - * @description - * Creates a new request expectation for DELETE requests. For more info see `expect()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#expectPOST - * @description - * Creates a new request expectation for POST requests. For more info see `expect()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that - * receives data string and returns true if the data is as expected, or Object if request body - * is in JSON format. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#expectPUT - * @description - * Creates a new request expectation for PUT requests. For more info see `expect()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that - * receives data string and returns true if the data is as expected, or Object if request body - * is in JSON format. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#expectPATCH - * @description - * Creates a new request expectation for PATCH requests. For more info see `expect()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that - * receives data string and returns true if the data is as expected, or Object if request body - * is in JSON format. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#expectJSONP - * @description - * Creates a new request expectation for JSONP requests. For more info see `expect()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - createShortMethods('expect'); - - - /** - * @ngdoc method - * @name $httpBackend#flush - * @description - * Flushes all pending requests using the trained responses. - * - * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, - * all pending requests will be flushed. If there are no pending requests when the flush method - * is called an exception is thrown (as this typically a sign of programming error). - */ - $httpBackend.flush = function(count, digest) { - if (digest !== false) $rootScope.$digest(); - if (!responses.length) throw new Error('No pending request to flush !'); - - if (angular.isDefined(count) && count !== null) { - while (count--) { - if (!responses.length) throw new Error('No more pending request to flush !'); - responses.shift()(); - } - } else { - while (responses.length) { - responses.shift()(); - } - } - $httpBackend.verifyNoOutstandingExpectation(digest); - }; - - - /** - * @ngdoc method - * @name $httpBackend#verifyNoOutstandingExpectation - * @description - * Verifies that all of the requests defined via the `expect` api were made. If any of the - * requests were not made, verifyNoOutstandingExpectation throws an exception. - * - * Typically, you would call this method following each test case that asserts requests using an - * "afterEach" clause. - * - * ```js - * afterEach($httpBackend.verifyNoOutstandingExpectation); - * ``` - */ - $httpBackend.verifyNoOutstandingExpectation = function(digest) { - if (digest !== false) $rootScope.$digest(); - if (expectations.length) { - throw new Error('Unsatisfied requests: ' + expectations.join(', ')); - } - }; - - - /** - * @ngdoc method - * @name $httpBackend#verifyNoOutstandingRequest - * @description - * Verifies that there are no outstanding requests that need to be flushed. - * - * Typically, you would call this method following each test case that asserts requests using an - * "afterEach" clause. - * - * ```js - * afterEach($httpBackend.verifyNoOutstandingRequest); - * ``` - */ - $httpBackend.verifyNoOutstandingRequest = function() { - if (responses.length) { - throw new Error('Unflushed requests: ' + responses.length); - } - }; - - - /** - * @ngdoc method - * @name $httpBackend#resetExpectations - * @description - * Resets all request expectations, but preserves all backend definitions. Typically, you would - * call resetExpectations during a multiple-phase test when you want to reuse the same instance of - * $httpBackend mock. - */ - $httpBackend.resetExpectations = function() { - expectations.length = 0; - responses.length = 0; - }; - - return $httpBackend; - - - function createShortMethods(prefix) { - angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) { - $httpBackend[prefix + method] = function(url, headers) { - return $httpBackend[prefix](method, url, undefined, headers); - }; - }); - - angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { - $httpBackend[prefix + method] = function(url, data, headers) { - return $httpBackend[prefix](method, url, data, headers); - }; - }); - } -} - -function MockHttpExpectation(method, url, data, headers) { - - this.data = data; - this.headers = headers; - - this.match = function(m, u, d, h) { - if (method != m) return false; - if (!this.matchUrl(u)) return false; - if (angular.isDefined(d) && !this.matchData(d)) return false; - if (angular.isDefined(h) && !this.matchHeaders(h)) return false; - return true; - }; - - this.matchUrl = function(u) { - if (!url) return true; - if (angular.isFunction(url.test)) return url.test(u); - if (angular.isFunction(url)) return url(u); - return url == u; - }; - - this.matchHeaders = function(h) { - if (angular.isUndefined(headers)) return true; - if (angular.isFunction(headers)) return headers(h); - return angular.equals(headers, h); - }; - - this.matchData = function(d) { - if (angular.isUndefined(data)) return true; - if (data && angular.isFunction(data.test)) return data.test(d); - if (data && angular.isFunction(data)) return data(d); - if (data && !angular.isString(data)) return angular.equals(data, angular.fromJson(d)); - return data == d; - }; - - this.toString = function() { - return method + ' ' + url; - }; -} - -function createMockXhr() { - return new MockXhr(); -} - -function MockXhr() { - - // hack for testing $http, $httpBackend - MockXhr.$$lastInstance = this; - - this.open = function(method, url, async) { - this.$$method = method; - this.$$url = url; - this.$$async = async; - this.$$reqHeaders = {}; - this.$$respHeaders = {}; - }; - - this.send = function(data) { - this.$$data = data; - }; - - this.setRequestHeader = function(key, value) { - this.$$reqHeaders[key] = value; - }; - - this.getResponseHeader = function(name) { - // the lookup must be case insensitive, - // that's why we try two quick lookups first and full scan last - var header = this.$$respHeaders[name]; - if (header) return header; - - name = angular.lowercase(name); - header = this.$$respHeaders[name]; - if (header) return header; - - header = undefined; - angular.forEach(this.$$respHeaders, function(headerVal, headerName) { - if (!header && angular.lowercase(headerName) == name) header = headerVal; - }); - return header; - }; - - this.getAllResponseHeaders = function() { - var lines = []; - - angular.forEach(this.$$respHeaders, function(value, key) { - lines.push(key + ': ' + value); - }); - return lines.join('\n'); - }; - - this.abort = angular.noop; -} - - -/** - * @ngdoc service - * @name $timeout - * @description - * - * This service is just a simple decorator for {@link ng.$timeout $timeout} service - * that adds a "flush" and "verifyNoPendingTasks" methods. - */ - -angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function ($delegate, $browser) { - - /** - * @ngdoc method - * @name $timeout#flush - * @description - * - * Flushes the queue of pending tasks. - * - * @param {number=} delay maximum timeout amount to flush up until - */ - $delegate.flush = function(delay) { - $browser.defer.flush(delay); - }; - - /** - * @ngdoc method - * @name $timeout#verifyNoPendingTasks - * @description - * - * Verifies that there are no pending tasks that need to be flushed. - */ - $delegate.verifyNoPendingTasks = function() { - if ($browser.deferredFns.length) { - throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' + - formatPendingTasksAsString($browser.deferredFns)); - } - }; - - function formatPendingTasksAsString(tasks) { - var result = []; - angular.forEach(tasks, function(task) { - result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}'); - }); - - return result.join(', '); - } - - return $delegate; -}]; - -angular.mock.$RAFDecorator = ['$delegate', function($delegate) { - var queue = []; - var rafFn = function(fn) { - var index = queue.length; - queue.push(fn); - return function() { - queue.splice(index, 1); - }; - }; - - rafFn.supported = $delegate.supported; - - rafFn.flush = function() { - if(queue.length === 0) { - throw new Error('No rAF callbacks present'); - } - - var length = queue.length; - for(var i=0;i<length;i++) { - queue[i](); - } - - queue = []; - }; - - return rafFn; -}]; - -angular.mock.$AsyncCallbackDecorator = ['$delegate', function($delegate) { - var callbacks = []; - var addFn = function(fn) { - callbacks.push(fn); - }; - addFn.flush = function() { - angular.forEach(callbacks, function(fn) { - fn(); - }); - callbacks = []; - }; - return addFn; -}]; - -/** - * - */ -angular.mock.$RootElementProvider = function() { - this.$get = function() { - return angular.element('<div ng-app></div>'); - }; -}; - -/** - * @ngdoc module - * @name ngMock - * @packageName angular-mocks - * @description - * - * # ngMock - * - * The `ngMock` module provides support to inject and mock Angular services into unit tests. - * In addition, ngMock also extends various core ng services such that they can be - * inspected and controlled in a synchronous manner within test code. - * - * - * <div doc-module-components="ngMock"></div> - * - */ -angular.module('ngMock', ['ng']).provider({ - $browser: angular.mock.$BrowserProvider, - $exceptionHandler: angular.mock.$ExceptionHandlerProvider, - $log: angular.mock.$LogProvider, - $interval: angular.mock.$IntervalProvider, - $httpBackend: angular.mock.$HttpBackendProvider, - $rootElement: angular.mock.$RootElementProvider -}).config(['$provide', function($provide) { - $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); - $provide.decorator('$$rAF', angular.mock.$RAFDecorator); - $provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator); -}]); - -/** - * @ngdoc module - * @name ngMockE2E - * @module ngMockE2E - * @packageName angular-mocks - * @description - * - * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. - * Currently there is only one mock present in this module - - * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. - */ -angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { - $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); -}]); - -/** - * @ngdoc service - * @name $httpBackend - * @module ngMockE2E - * @description - * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of - * applications that use the {@link ng.$http $http service}. - * - * *Note*: For fake http backend implementation suitable for unit testing please see - * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. - * - * This implementation can be used to respond with static or dynamic responses via the `when` api - * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the - * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch - * templates from a webserver). - * - * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application - * is being developed with the real backend api replaced with a mock, it is often desirable for - * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch - * templates or static files from the webserver). To configure the backend with this behavior - * use the `passThrough` request handler of `when` instead of `respond`. - * - * Additionally, we don't want to manually have to flush mocked out requests like we do during unit - * testing. For this reason the e2e $httpBackend flushes mocked out requests - * automatically, closely simulating the behavior of the XMLHttpRequest object. - * - * To setup the application to run with this http backend, you have to create a module that depends - * on the `ngMockE2E` and your application modules and defines the fake backend: - * - * ```js - * myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']); - * myAppDev.run(function($httpBackend) { - * phones = [{name: 'phone1'}, {name: 'phone2'}]; - * - * // returns the current list of phones - * $httpBackend.whenGET('/phones').respond(phones); - * - * // adds a new phone to the phones array - * $httpBackend.whenPOST('/phones').respond(function(method, url, data) { - * var phone = angular.fromJson(data); - * phones.push(phone); - * return [200, phone, {}]; - * }); - * $httpBackend.whenGET(/^\/templates\//).passThrough(); - * //... - * }); - * ``` - * - * Afterwards, bootstrap your app with this new module. - */ - -/** - * @ngdoc method - * @name $httpBackend#when - * @module ngMockE2E - * @description - * Creates a new backend definition. - * - * @param {string} method HTTP method. - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header - * object and returns true if the headers match the current definition. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - * - * - respond – - * `{function([status,] data[, headers, statusText]) - * | function(function(method, url, data, headers)}` - * – The respond method takes a set of static data to be returned or a function that can return - * an array containing response status (number), response data (string), response headers - * (Object), and the text for the status (string). - * - passThrough – `{function()}` – Any request matching a backend definition with - * `passThrough` handler will be passed through to the real backend (an XHR request will be made - * to the server.) - * - Both methods return the `requestHandler` object for possible overrides. - */ - -/** - * @ngdoc method - * @name $httpBackend#whenGET - * @module ngMockE2E - * @description - * Creates a new backend definition for GET requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - */ - -/** - * @ngdoc method - * @name $httpBackend#whenHEAD - * @module ngMockE2E - * @description - * Creates a new backend definition for HEAD requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - */ - -/** - * @ngdoc method - * @name $httpBackend#whenDELETE - * @module ngMockE2E - * @description - * Creates a new backend definition for DELETE requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - */ - -/** - * @ngdoc method - * @name $httpBackend#whenPOST - * @module ngMockE2E - * @description - * Creates a new backend definition for POST requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - */ - -/** - * @ngdoc method - * @name $httpBackend#whenPUT - * @module ngMockE2E - * @description - * Creates a new backend definition for PUT requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - */ - -/** - * @ngdoc method - * @name $httpBackend#whenPATCH - * @module ngMockE2E - * @description - * Creates a new backend definition for PATCH requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - */ - -/** - * @ngdoc method - * @name $httpBackend#whenJSONP - * @module ngMockE2E - * @description - * Creates a new backend definition for JSONP requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives the url - * and returns true if the url match the current definition. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - */ -angular.mock.e2e = {}; -angular.mock.e2e.$httpBackendDecorator = - ['$rootScope', '$delegate', '$browser', createHttpBackendMock]; - - -if(window.jasmine || window.mocha) { - - var currentSpec = null, - isSpecRunning = function() { - return !!currentSpec; - }; - - - (window.beforeEach || window.setup)(function() { - currentSpec = this; - }); - - (window.afterEach || window.teardown)(function() { - var injector = currentSpec.$injector; - - angular.forEach(currentSpec.$modules, function(module) { - if (module && module.$$hashKey) { - module.$$hashKey = undefined; - } - }); - - currentSpec.$injector = null; - currentSpec.$modules = null; - currentSpec = null; - - if (injector) { - injector.get('$rootElement').off(); - injector.get('$browser').pollFns.length = 0; - } - - // clean up jquery's fragment cache - angular.forEach(angular.element.fragments, function(val, key) { - delete angular.element.fragments[key]; - }); - - MockXhr.$$lastInstance = null; - - angular.forEach(angular.callbacks, function(val, key) { - delete angular.callbacks[key]; - }); - angular.callbacks.counter = 0; - }); - - /** - * @ngdoc function - * @name angular.mock.module - * @description - * - * *NOTE*: This function is also published on window for easy access.<br> - * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha - * - * This function registers a module configuration code. It collects the configuration information - * which will be used when the injector is created by {@link angular.mock.inject inject}. - * - * See {@link angular.mock.inject inject} for usage example - * - * @param {...(string|Function|Object)} fns any number of modules which are represented as string - * aliases or as anonymous module initialization functions. The modules are used to - * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. If an - * object literal is passed they will be registered as values in the module, the key being - * the module name and the value being what is returned. - */ - window.module = angular.mock.module = function() { - var moduleFns = Array.prototype.slice.call(arguments, 0); - return isSpecRunning() ? workFn() : workFn; - ///////////////////// - function workFn() { - if (currentSpec.$injector) { - throw new Error('Injector already created, can not register a module!'); - } else { - var modules = currentSpec.$modules || (currentSpec.$modules = []); - angular.forEach(moduleFns, function(module) { - if (angular.isObject(module) && !angular.isArray(module)) { - modules.push(function($provide) { - angular.forEach(module, function(value, key) { - $provide.value(key, value); - }); - }); - } else { - modules.push(module); - } - }); - } - } - }; - - /** - * @ngdoc function - * @name angular.mock.inject - * @description - * - * *NOTE*: This function is also published on window for easy access.<br> - * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha - * - * The inject function wraps a function into an injectable function. The inject() creates new - * instance of {@link auto.$injector $injector} per test, which is then used for - * resolving references. - * - * - * ## Resolving References (Underscore Wrapping) - * Often, we would like to inject a reference once, in a `beforeEach()` block and reuse this - * in multiple `it()` clauses. To be able to do this we must assign the reference to a variable - * that is declared in the scope of the `describe()` block. Since we would, most likely, want - * the variable to have the same name of the reference we have a problem, since the parameter - * to the `inject()` function would hide the outer variable. - * - * To help with this, the injected parameters can, optionally, be enclosed with underscores. - * These are ignored by the injector when the reference name is resolved. - * - * For example, the parameter `_myService_` would be resolved as the reference `myService`. - * Since it is available in the function body as _myService_, we can then assign it to a variable - * defined in an outer scope. - * - * ``` - * // Defined out reference variable outside - * var myService; - * - * // Wrap the parameter in underscores - * beforeEach( inject( function(_myService_){ - * myService = _myService_; - * })); - * - * // Use myService in a series of tests. - * it('makes use of myService', function() { - * myService.doStuff(); - * }); - * - * ``` - * - * See also {@link angular.mock.module angular.mock.module} - * - * ## Example - * Example of what a typical jasmine tests looks like with the inject method. - * ```js - * - * angular.module('myApplicationModule', []) - * .value('mode', 'app') - * .value('version', 'v1.0.1'); - * - * - * describe('MyApp', function() { - * - * // You need to load modules that you want to test, - * // it loads only the "ng" module by default. - * beforeEach(module('myApplicationModule')); - * - * - * // inject() is used to inject arguments of all given functions - * it('should provide a version', inject(function(mode, version) { - * expect(version).toEqual('v1.0.1'); - * expect(mode).toEqual('app'); - * })); - * - * - * // The inject and module method can also be used inside of the it or beforeEach - * it('should override a version and test the new version is injected', function() { - * // module() takes functions or strings (module aliases) - * module(function($provide) { - * $provide.value('version', 'overridden'); // override version here - * }); - * - * inject(function(version) { - * expect(version).toEqual('overridden'); - * }); - * }); - * }); - * - * ``` - * - * @param {...Function} fns any number of functions which will be injected using the injector. - */ - - - - var ErrorAddingDeclarationLocationStack = function(e, errorForStack) { - this.message = e.message; - this.name = e.name; - if (e.line) this.line = e.line; - if (e.sourceId) this.sourceId = e.sourceId; - if (e.stack && errorForStack) - this.stack = e.stack + '\n' + errorForStack.stack; - if (e.stackArray) this.stackArray = e.stackArray; - }; - ErrorAddingDeclarationLocationStack.prototype.toString = Error.prototype.toString; - - window.inject = angular.mock.inject = function() { - var blockFns = Array.prototype.slice.call(arguments, 0); - var errorForStack = new Error('Declaration Location'); - return isSpecRunning() ? workFn.call(currentSpec) : workFn; - ///////////////////// - function workFn() { - var modules = currentSpec.$modules || []; - var strictDi = !!currentSpec.$injectorStrict; - modules.unshift('ngMock'); - modules.unshift('ng'); - var injector = currentSpec.$injector; - if (!injector) { - if (strictDi) { - // If strictDi is enabled, annotate the providerInjector blocks - angular.forEach(modules, function(moduleFn) { - if (typeof moduleFn === "function") { - angular.injector.$$annotate(moduleFn); - } - }); - } - injector = currentSpec.$injector = angular.injector(modules, strictDi); - currentSpec.$injectorStrict = strictDi; - } - for(var i = 0, ii = blockFns.length; i < ii; i++) { - if (currentSpec.$injectorStrict) { - // If the injector is strict / strictDi, and the spec wants to inject using automatic - // annotation, then annotate the function here. - injector.annotate(blockFns[i]); - } - try { - /* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */ - injector.invoke(blockFns[i] || angular.noop, this); - /* jshint +W040 */ - } catch (e) { - if (e.stack && errorForStack) { - throw new ErrorAddingDeclarationLocationStack(e, errorForStack); - } - throw e; - } finally { - errorForStack = null; - } - } - } - }; - - - angular.mock.inject.strictDi = function(value) { - value = arguments.length ? !!value : true; - return isSpecRunning() ? workFn() : workFn; - - function workFn() { - if (value !== currentSpec.$injectorStrict) { - if (currentSpec.$injector) { - throw new Error('Injector already created, can not modify strict annotations'); - } else { - currentSpec.$injectorStrict = value; - } - } - } - }; -} diff --git a/syweb/webclient/js/angular-peity.js b/syweb/webclient/js/angular-peity.js deleted file mode 100644 index 2acb647d91..0000000000 --- a/syweb/webclient/js/angular-peity.js +++ /dev/null @@ -1,69 +0,0 @@ -var angularPeity = angular.module( 'angular-peity', [] ); - -$.fn.peity.defaults.pie = { - fill: ["#ff0000", "#aaaaaa"], - radius: 4, -} - -var buildChartDirective = function ( chartType ) { - return { - restrict: 'E', - scope: { - data: "=", - options: "=" - }, - link: function ( scope, element, attrs ) { - - var options = {}; - if ( scope.options ) { - options = scope.options; - } - - // N.B. live-binding to data by Matthew - scope.$watch('data', function () { - var span = document.createElement( 'span' ); - span.textContent = scope.data.join(); - - if ( !attrs.class ) { - span.className = ""; - } else { - span.className = attrs.class; - } - - if (element[0].nodeType === 8) { - element.replaceWith( span ); - } - else if (element[0].firstChild) { - element.empty(); - element[0].appendChild( span ); - } - else { - element[0].appendChild( span ); - } - - jQuery( span ).peity( chartType, options ); - }); - } - }; -}; - - -angularPeity.directive( 'pieChart', function () { - - return buildChartDirective( "pie" ); - -} ); - - -angularPeity.directive( 'barChart', function () { - - return buildChartDirective( "bar" ); - -} ); - - -angularPeity.directive( 'lineChart', function () { - - return buildChartDirective( "line" ); - -} ); diff --git a/syweb/webclient/js/angular-route.js b/syweb/webclient/js/angular-route.js deleted file mode 100644 index 305d92e855..0000000000 --- a/syweb/webclient/js/angular-route.js +++ /dev/null @@ -1,956 +0,0 @@ -/** - * @license AngularJS v1.3.0-rc.1 - * (c) 2010-2014 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular, undefined) {'use strict'; - -/** - * @ngdoc module - * @name ngRoute - * @description - * - * # ngRoute - * - * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. - * - * ## Example - * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. - * - * - * <div doc-module-components="ngRoute"></div> - */ - /* global -ngRouteModule */ -var ngRouteModule = angular.module('ngRoute', ['ng']). - provider('$route', $RouteProvider), - $routeMinErr = angular.$$minErr('ngRoute'); - -/** - * @ngdoc provider - * @name $routeProvider - * - * @description - * - * Used for configuring routes. - * - * ## Example - * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. - * - * ## Dependencies - * Requires the {@link ngRoute `ngRoute`} module to be installed. - */ -function $RouteProvider(){ - function inherit(parent, extra) { - return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); - } - - var routes = {}; - - /** - * @ngdoc method - * @name $routeProvider#when - * - * @param {string} path Route path (matched against `$location.path`). If `$location.path` - * contains redundant trailing slash or is missing one, the route will still match and the - * `$location.path` will be updated to add or drop the trailing slash to exactly match the - * route definition. - * - * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up - * to the next slash are matched and stored in `$routeParams` under the given `name` - * when the route matches. - * * `path` can contain named groups starting with a colon and ending with a star: - * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` - * when the route matches. - * * `path` can contain optional named groups with a question mark: e.g.`:name?`. - * - * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match - * `/color/brown/largecode/code/with/slashes/edit` and extract: - * - * * `color: brown` - * * `largecode: code/with/slashes`. - * - * - * @param {Object} route Mapping information to be assigned to `$route.current` on route - * match. - * - * Object properties: - * - * - `controller` – `{(string|function()=}` – Controller fn that should be associated with - * newly created scope or the name of a {@link angular.Module#controller registered - * controller} if passed as a string. - * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be - * published to scope under the `controllerAs` name. - * - `template` – `{string=|function()=}` – html template as a string or a function that - * returns an html template as a string which should be used by {@link - * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. - * This property takes precedence over `templateUrl`. - * - * If `template` is a function, it will be called with the following parameters: - * - * - `{Array.<Object>}` - route parameters extracted from the current - * `$location.path()` by applying the current route - * - * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html - * template that should be used by {@link ngRoute.directive:ngView ngView}. - * - * If `templateUrl` is a function, it will be called with the following parameters: - * - * - `{Array.<Object>}` - route parameters extracted from the current - * `$location.path()` by applying the current route - * - * - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should - * be injected into the controller. If any of these dependencies are promises, the router - * will wait for them all to be resolved or one to be rejected before the controller is - * instantiated. - * If all the promises are resolved successfully, the values of the resolved promises are - * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is - * fired. If any of the promises are rejected the - * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object - * is: - * - * - `key` – `{string}`: a name of a dependency to be injected into the controller. - * - `factory` - `{string|function}`: If `string` then it is an alias for a service. - * Otherwise if function, then it is {@link auto.$injector#invoke injected} - * and the return value is treated as the dependency. If the result is a promise, it is - * resolved before its value is injected into the controller. Be aware that - * `ngRoute.$routeParams` will still refer to the previous route within these resolve - * functions. Use `$route.current.params` to access the new route parameters, instead. - * - * - `redirectTo` – {(string|function())=} – value to update - * {@link ng.$location $location} path with and trigger route redirection. - * - * If `redirectTo` is a function, it will be called with the following parameters: - * - * - `{Object.<string>}` - route parameters extracted from the current - * `$location.path()` by applying the current route templateUrl. - * - `{string}` - current `$location.path()` - * - `{Object}` - current `$location.search()` - * - * The custom `redirectTo` function is expected to return a string which will be used - * to update `$location.path()` and `$location.search()`. - * - * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` - * or `$location.hash()` changes. - * - * If the option is set to `false` and url in the browser changes, then - * `$routeUpdate` event is broadcasted on the root scope. - * - * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive - * - * If the option is set to `true`, then the particular route can be matched without being - * case sensitive - * - * @returns {Object} self - * - * @description - * Adds a new route definition to the `$route` service. - */ - this.when = function(path, route) { - routes[path] = angular.extend( - {reloadOnSearch: true}, - route, - path && pathRegExp(path, route) - ); - - // create redirection for trailing slashes - if (path) { - var redirectPath = (path[path.length-1] == '/') - ? path.substr(0, path.length-1) - : path +'/'; - - routes[redirectPath] = angular.extend( - {redirectTo: path}, - pathRegExp(redirectPath, route) - ); - } - - return this; - }; - - /** - * @param path {string} path - * @param opts {Object} options - * @return {?Object} - * - * @description - * Normalizes the given path, returning a regular expression - * and the original path. - * - * Inspired by pathRexp in visionmedia/express/lib/utils.js. - */ - function pathRegExp(path, opts) { - var insensitive = opts.caseInsensitiveMatch, - ret = { - originalPath: path, - regexp: path - }, - keys = ret.keys = []; - - path = path - .replace(/([().])/g, '\\$1') - .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option){ - var optional = option === '?' ? option : null; - var star = option === '*' ? option : null; - keys.push({ name: key, optional: !!optional }); - slash = slash || ''; - return '' - + (optional ? '' : slash) - + '(?:' - + (optional ? slash : '') - + (star && '(.+?)' || '([^/]+)') - + (optional || '') - + ')' - + (optional || ''); - }) - .replace(/([\/$\*])/g, '\\$1'); - - ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); - return ret; - } - - /** - * @ngdoc method - * @name $routeProvider#otherwise - * - * @description - * Sets route definition that will be used on route change when no other route definition - * is matched. - * - * @param {Object|string} params Mapping information to be assigned to `$route.current`. - * If called with a string, the value maps to `redirectTo`. - * @returns {Object} self - */ - this.otherwise = function(params) { - if (typeof params === 'string') { - params = {redirectTo: params}; - } - this.when(null, params); - return this; - }; - - - this.$get = ['$rootScope', - '$location', - '$routeParams', - '$q', - '$injector', - '$templateRequest', - '$sce', - function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce) { - - /** - * @ngdoc service - * @name $route - * @requires $location - * @requires $routeParams - * - * @property {Object} current Reference to the current route definition. - * The route definition contains: - * - * - `controller`: The controller constructor as define in route definition. - * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for - * controller instantiation. The `locals` contain - * the resolved values of the `resolve` map. Additionally the `locals` also contain: - * - * - `$scope` - The current route scope. - * - `$template` - The current route template HTML. - * - * @property {Object} routes Object with all route configuration Objects as its properties. - * - * @description - * `$route` is used for deep-linking URLs to controllers and views (HTML partials). - * It watches `$location.url()` and tries to map the path to an existing route definition. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. - * - * The `$route` service is typically used in conjunction with the - * {@link ngRoute.directive:ngView `ngView`} directive and the - * {@link ngRoute.$routeParams `$routeParams`} service. - * - * @example - * This example shows how changing the URL hash causes the `$route` to match a route against the - * URL, and the `ngView` pulls in the partial. - * - * Note that this example is using {@link ng.directive:script inlined templates} - * to get it working on jsfiddle as well. - * - * <example name="$route-service" module="ngRouteExample" - * deps="angular-route.js" fixBase="true"> - * <file name="index.html"> - * <div ng-controller="MainController"> - * Choose: - * <a href="Book/Moby">Moby</a> | - * <a href="Book/Moby/ch/1">Moby: Ch1</a> | - * <a href="Book/Gatsby">Gatsby</a> | - * <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | - * <a href="Book/Scarlet">Scarlet Letter</a><br/> - * - * <div ng-view></div> - * - * <hr /> - * - * <pre>$location.path() = {{$location.path()}}</pre> - * <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre> - * <pre>$route.current.params = {{$route.current.params}}</pre> - * <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre> - * <pre>$routeParams = {{$routeParams}}</pre> - * </div> - * </file> - * - * <file name="book.html"> - * controller: {{name}}<br /> - * Book Id: {{params.bookId}}<br /> - * </file> - * - * <file name="chapter.html"> - * controller: {{name}}<br /> - * Book Id: {{params.bookId}}<br /> - * Chapter Id: {{params.chapterId}} - * </file> - * - * <file name="script.js"> - * angular.module('ngRouteExample', ['ngRoute']) - * - * .controller('MainController', function($scope, $route, $routeParams, $location) { - * $scope.$route = $route; - * $scope.$location = $location; - * $scope.$routeParams = $routeParams; - * }) - * - * .controller('BookController', function($scope, $routeParams) { - * $scope.name = "BookController"; - * $scope.params = $routeParams; - * }) - * - * .controller('ChapterController', function($scope, $routeParams) { - * $scope.name = "ChapterController"; - * $scope.params = $routeParams; - * }) - * - * .config(function($routeProvider, $locationProvider) { - * $routeProvider - * .when('/Book/:bookId', { - * templateUrl: 'book.html', - * controller: 'BookController', - * resolve: { - * // I will cause a 1 second delay - * delay: function($q, $timeout) { - * var delay = $q.defer(); - * $timeout(delay.resolve, 1000); - * return delay.promise; - * } - * } - * }) - * .when('/Book/:bookId/ch/:chapterId', { - * templateUrl: 'chapter.html', - * controller: 'ChapterController' - * }); - * - * // configure html5 to get links working on jsfiddle - * $locationProvider.html5Mode(true); - * }); - * - * </file> - * - * <file name="protractor.js" type="protractor"> - * it('should load and compile correct template', function() { - * element(by.linkText('Moby: Ch1')).click(); - * var content = element(by.css('[ng-view]')).getText(); - * expect(content).toMatch(/controller\: ChapterController/); - * expect(content).toMatch(/Book Id\: Moby/); - * expect(content).toMatch(/Chapter Id\: 1/); - * - * element(by.partialLinkText('Scarlet')).click(); - * - * content = element(by.css('[ng-view]')).getText(); - * expect(content).toMatch(/controller\: BookController/); - * expect(content).toMatch(/Book Id\: Scarlet/); - * }); - * </file> - * </example> - */ - - /** - * @ngdoc event - * @name $route#$routeChangeStart - * @eventType broadcast on root scope - * @description - * Broadcasted before a route change. At this point the route services starts - * resolving all of the dependencies needed for the route change to occur. - * Typically this involves fetching the view template as well as any dependencies - * defined in `resolve` route property. Once all of the dependencies are resolved - * `$routeChangeSuccess` is fired. - * - * @param {Object} angularEvent Synthetic event object. - * @param {Route} next Future route information. - * @param {Route} current Current route information. - */ - - /** - * @ngdoc event - * @name $route#$routeChangeSuccess - * @eventType broadcast on root scope - * @description - * Broadcasted after a route dependencies are resolved. - * {@link ngRoute.directive:ngView ngView} listens for the directive - * to instantiate the controller and render the view. - * - * @param {Object} angularEvent Synthetic event object. - * @param {Route} current Current route information. - * @param {Route|Undefined} previous Previous route information, or undefined if current is - * first route entered. - */ - - /** - * @ngdoc event - * @name $route#$routeChangeError - * @eventType broadcast on root scope - * @description - * Broadcasted if any of the resolve promises are rejected. - * - * @param {Object} angularEvent Synthetic event object - * @param {Route} current Current route information. - * @param {Route} previous Previous route information. - * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. - */ - - /** - * @ngdoc event - * @name $route#$routeUpdate - * @eventType broadcast on root scope - * @description - * - * The `reloadOnSearch` property has been set to false, and we are reusing the same - * instance of the Controller. - */ - - var forceReload = false, - $route = { - routes: routes, - - /** - * @ngdoc method - * @name $route#reload - * - * @description - * Causes `$route` service to reload the current route even if - * {@link ng.$location $location} hasn't changed. - * - * As a result of that, {@link ngRoute.directive:ngView ngView} - * creates new scope, reinstantiates the controller. - */ - reload: function() { - forceReload = true; - $rootScope.$evalAsync(updateRoute); - }, - - /** - * @ngdoc method - * @name $route#updateParams - * - * @description - * Causes `$route` service to update the current URL, replacing - * current route parameters with those specified in `newParams`. - * Provided property names that match the route's path segment - * definitions will be interpolated into the location's path, while - * remaining properties will be treated as query params. - * - * @param {Object} newParams mapping of URL parameter names to values - */ - updateParams: function(newParams) { - if (this.current && this.current.$$route) { - var searchParams = {}, self=this; - - angular.forEach(Object.keys(newParams), function(key) { - if (!self.current.pathParams[key]) searchParams[key] = newParams[key]; - }); - - newParams = angular.extend({}, this.current.params, newParams); - $location.path(interpolate(this.current.$$route.originalPath, newParams)); - $location.search(angular.extend({}, $location.search(), searchParams)); - } - else { - throw $routeMinErr('norout', 'Tried updating route when with no current route'); - } - } - }; - - $rootScope.$on('$locationChangeSuccess', updateRoute); - - return $route; - - ///////////////////////////////////////////////////// - - /** - * @param on {string} current url - * @param route {Object} route regexp to match the url against - * @return {?Object} - * - * @description - * Check if the route matches the current url. - * - * Inspired by match in - * visionmedia/express/lib/router/router.js. - */ - function switchRouteMatcher(on, route) { - var keys = route.keys, - params = {}; - - if (!route.regexp) return null; - - var m = route.regexp.exec(on); - if (!m) return null; - - for (var i = 1, len = m.length; i < len; ++i) { - var key = keys[i - 1]; - - var val = m[i]; - - if (key && val) { - params[key.name] = val; - } - } - return params; - } - - function updateRoute() { - var next = parseRoute(), - last = $route.current; - - if (next && last && next.$$route === last.$$route - && angular.equals(next.pathParams, last.pathParams) - && !next.reloadOnSearch && !forceReload) { - last.params = next.params; - angular.copy(last.params, $routeParams); - $rootScope.$broadcast('$routeUpdate', last); - } else if (next || last) { - forceReload = false; - $rootScope.$broadcast('$routeChangeStart', next, last); - $route.current = next; - if (next) { - if (next.redirectTo) { - if (angular.isString(next.redirectTo)) { - $location.path(interpolate(next.redirectTo, next.params)).search(next.params) - .replace(); - } else { - $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) - .replace(); - } - } - } - - $q.when(next). - then(function() { - if (next) { - var locals = angular.extend({}, next.resolve), - template, templateUrl; - - angular.forEach(locals, function(value, key) { - locals[key] = angular.isString(value) ? - $injector.get(value) : $injector.invoke(value, null, null, key); - }); - - if (angular.isDefined(template = next.template)) { - if (angular.isFunction(template)) { - template = template(next.params); - } - } else if (angular.isDefined(templateUrl = next.templateUrl)) { - if (angular.isFunction(templateUrl)) { - templateUrl = templateUrl(next.params); - } - templateUrl = $sce.getTrustedResourceUrl(templateUrl); - if (angular.isDefined(templateUrl)) { - next.loadedTemplateUrl = templateUrl; - template = $templateRequest(templateUrl); - } - } - if (angular.isDefined(template)) { - locals['$template'] = template; - } - return $q.all(locals); - } - }). - // after route change - then(function(locals) { - if (next == $route.current) { - if (next) { - next.locals = locals; - angular.copy(next.params, $routeParams); - } - $rootScope.$broadcast('$routeChangeSuccess', next, last); - } - }, function(error) { - if (next == $route.current) { - $rootScope.$broadcast('$routeChangeError', next, last, error); - } - }); - } - } - - - /** - * @returns {Object} the current active route, by matching it against the URL - */ - function parseRoute() { - // Match a route - var params, match; - angular.forEach(routes, function(route, path) { - if (!match && (params = switchRouteMatcher($location.path(), route))) { - match = inherit(route, { - params: angular.extend({}, $location.search(), params), - pathParams: params}); - match.$$route = route; - } - }); - // No route matched; fallback to "otherwise" route - return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); - } - - /** - * @returns {string} interpolation of the redirect path with the parameters - */ - function interpolate(string, params) { - var result = []; - angular.forEach((string||'').split(':'), function(segment, i) { - if (i === 0) { - result.push(segment); - } else { - var segmentMatch = segment.match(/(\w+)(.*)/); - var key = segmentMatch[1]; - result.push(params[key]); - result.push(segmentMatch[2] || ''); - delete params[key]; - } - }); - return result.join(''); - } - }]; -} - -ngRouteModule.provider('$routeParams', $RouteParamsProvider); - - -/** - * @ngdoc service - * @name $routeParams - * @requires $route - * - * @description - * The `$routeParams` service allows you to retrieve the current set of route parameters. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * The route parameters are a combination of {@link ng.$location `$location`}'s - * {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}. - * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. - * - * In case of parameter name collision, `path` params take precedence over `search` params. - * - * The service guarantees that the identity of the `$routeParams` object will remain unchanged - * (but its properties will likely change) even when a route change occurs. - * - * Note that the `$routeParams` are only updated *after* a route change completes successfully. - * This means that you cannot rely on `$routeParams` being correct in route resolve functions. - * Instead you can use `$route.current.params` to access the new route's parameters. - * - * @example - * ```js - * // Given: - * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby - * // Route: /Chapter/:chapterId/Section/:sectionId - * // - * // Then - * $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'} - * ``` - */ -function $RouteParamsProvider() { - this.$get = function() { return {}; }; -} - -ngRouteModule.directive('ngView', ngViewFactory); -ngRouteModule.directive('ngView', ngViewFillContentFactory); - - -/** - * @ngdoc directive - * @name ngView - * @restrict ECA - * - * @description - * # Overview - * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by - * including the rendered template of the current route into the main layout (`index.html`) file. - * Every time the current route changes, the included view changes with it according to the - * configuration of the `$route` service. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * @animations - * enter - animation is used to bring new content into the browser. - * leave - animation is used to animate existing content away. - * - * The enter and leave animation occur concurrently. - * - * @scope - * @priority 400 - * @param {string=} onload Expression to evaluate whenever the view updates. - * - * @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll - * $anchorScroll} to scroll the viewport after the view is updated. - * - * - If the attribute is not set, disable scrolling. - * - If the attribute is set without value, enable scrolling. - * - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated - * as an expression yields a truthy value. - * @example - <example name="ngView-directive" module="ngViewExample" - deps="angular-route.js;angular-animate.js" - animations="true" fixBase="true"> - <file name="index.html"> - <div ng-controller="MainCtrl as main"> - Choose: - <a href="Book/Moby">Moby</a> | - <a href="Book/Moby/ch/1">Moby: Ch1</a> | - <a href="Book/Gatsby">Gatsby</a> | - <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | - <a href="Book/Scarlet">Scarlet Letter</a><br/> - - <div class="view-animate-container"> - <div ng-view class="view-animate"></div> - </div> - <hr /> - - <pre>$location.path() = {{main.$location.path()}}</pre> - <pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre> - <pre>$route.current.params = {{main.$route.current.params}}</pre> - <pre>$routeParams = {{main.$routeParams}}</pre> - </div> - </file> - - <file name="book.html"> - <div> - controller: {{book.name}}<br /> - Book Id: {{book.params.bookId}}<br /> - </div> - </file> - - <file name="chapter.html"> - <div> - controller: {{chapter.name}}<br /> - Book Id: {{chapter.params.bookId}}<br /> - Chapter Id: {{chapter.params.chapterId}} - </div> - </file> - - <file name="animations.css"> - .view-animate-container { - position:relative; - height:100px!important; - position:relative; - background:white; - border:1px solid black; - height:40px; - overflow:hidden; - } - - .view-animate { - padding:10px; - } - - .view-animate.ng-enter, .view-animate.ng-leave { - -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; - - display:block; - width:100%; - border-left:1px solid black; - - position:absolute; - top:0; - left:0; - right:0; - bottom:0; - padding:10px; - } - - .view-animate.ng-enter { - left:100%; - } - .view-animate.ng-enter.ng-enter-active { - left:0; - } - .view-animate.ng-leave.ng-leave-active { - left:-100%; - } - </file> - - <file name="script.js"> - angular.module('ngViewExample', ['ngRoute', 'ngAnimate']) - .config(['$routeProvider', '$locationProvider', - function($routeProvider, $locationProvider) { - $routeProvider - .when('/Book/:bookId', { - templateUrl: 'book.html', - controller: 'BookCtrl', - controllerAs: 'book' - }) - .when('/Book/:bookId/ch/:chapterId', { - templateUrl: 'chapter.html', - controller: 'ChapterCtrl', - controllerAs: 'chapter' - }); - - // configure html5 to get links working on jsfiddle - $locationProvider.html5Mode(true); - }]) - .controller('MainCtrl', ['$route', '$routeParams', '$location', - function($route, $routeParams, $location) { - this.$route = $route; - this.$location = $location; - this.$routeParams = $routeParams; - }]) - .controller('BookCtrl', ['$routeParams', function($routeParams) { - this.name = "BookCtrl"; - this.params = $routeParams; - }]) - .controller('ChapterCtrl', ['$routeParams', function($routeParams) { - this.name = "ChapterCtrl"; - this.params = $routeParams; - }]); - - </file> - - <file name="protractor.js" type="protractor"> - it('should load and compile correct template', function() { - element(by.linkText('Moby: Ch1')).click(); - var content = element(by.css('[ng-view]')).getText(); - expect(content).toMatch(/controller\: ChapterCtrl/); - expect(content).toMatch(/Book Id\: Moby/); - expect(content).toMatch(/Chapter Id\: 1/); - - element(by.partialLinkText('Scarlet')).click(); - - content = element(by.css('[ng-view]')).getText(); - expect(content).toMatch(/controller\: BookCtrl/); - expect(content).toMatch(/Book Id\: Scarlet/); - }); - </file> - </example> - */ - - -/** - * @ngdoc event - * @name ngView#$viewContentLoaded - * @eventType emit on the current ngView scope - * @description - * Emitted every time the ngView content is reloaded. - */ -ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; -function ngViewFactory( $route, $anchorScroll, $animate) { - return { - restrict: 'ECA', - terminal: true, - priority: 400, - transclude: 'element', - link: function(scope, $element, attr, ctrl, $transclude) { - var currentScope, - currentElement, - previousElement, - autoScrollExp = attr.autoscroll, - onloadExp = attr.onload || ''; - - scope.$on('$routeChangeSuccess', update); - update(); - - function cleanupLastView() { - if(previousElement) { - previousElement.remove(); - previousElement = null; - } - if(currentScope) { - currentScope.$destroy(); - currentScope = null; - } - if(currentElement) { - $animate.leave(currentElement).then(function() { - previousElement = null; - }); - previousElement = currentElement; - currentElement = null; - } - } - - function update() { - var locals = $route.current && $route.current.locals, - template = locals && locals.$template; - - if (angular.isDefined(template)) { - var newScope = scope.$new(); - var current = $route.current; - - // Note: This will also link all children of ng-view that were contained in the original - // html. If that content contains controllers, ... they could pollute/change the scope. - // However, using ng-view on an element with additional content does not make sense... - // Note: We can't remove them in the cloneAttchFn of $transclude as that - // function is called before linking the content, which would apply child - // directives to non existing elements. - var clone = $transclude(newScope, function(clone) { - $animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter () { - if (angular.isDefined(autoScrollExp) - && (!autoScrollExp || scope.$eval(autoScrollExp))) { - $anchorScroll(); - } - }); - cleanupLastView(); - }); - - currentElement = clone; - currentScope = current.scope = newScope; - currentScope.$emit('$viewContentLoaded'); - currentScope.$eval(onloadExp); - } else { - cleanupLastView(); - } - } - } - }; -} - -// This directive is called during the $transclude call of the first `ngView` directive. -// It will replace and compile the content of the element with the loaded template. -// We need this directive so that the element content is already filled when -// the link function of another directive on the same element as ngView -// is called. -ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; -function ngViewFillContentFactory($compile, $controller, $route) { - return { - restrict: 'ECA', - priority: -400, - link: function(scope, $element) { - var current = $route.current, - locals = current.locals; - - $element.html(locals.$template); - - var link = $compile($element.contents()); - - if (current.controller) { - locals.$scope = scope; - var controller = $controller(current.controller, locals); - if (current.controllerAs) { - scope[current.controllerAs] = controller; - } - $element.data('$ngControllerController', controller); - $element.children().data('$ngControllerController', controller); - } - - link(scope); - } - }; -} - - -})(window, window.angular); diff --git a/syweb/webclient/js/angular-route.min.js b/syweb/webclient/js/angular-route.min.js deleted file mode 100644 index 03da279ec3..0000000000 --- a/syweb/webclient/js/angular-route.min.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - AngularJS v1.3.0-rc.1 - (c) 2010-2014 Google, Inc. http://angularjs.org - License: MIT -*/ -(function(r,d,z){'use strict';function v(s,h,f){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,e,b,g,u){function w(){k&&(k.remove(),k=null);l&&(l.$destroy(),l=null);n&&(f.leave(n).then(function(){k=null}),k=n,n=null)}function t(){var c=s.current&&s.current.locals;if(d.isDefined(c&&c.$template)){var c=a.$new(),m=s.current;n=u(c,function(c){f.enter(c,null,n||e).then(function(){!d.isDefined(p)||p&&!a.$eval(p)||h()});w()});l=m.scope=c;l.$emit("$viewContentLoaded"); -l.$eval(q)}else w()}var l,n,k,p=b.autoscroll,q=b.onload||"";a.$on("$routeChangeSuccess",t);t()}}}function x(d,h,f){return{restrict:"ECA",priority:-400,link:function(a,e){var b=f.current,g=b.locals;e.html(g.$template);var u=d(e.contents());b.controller&&(g.$scope=a,g=h(b.controller,g),b.controllerAs&&(a[b.controllerAs]=g),e.data("$ngControllerController",g),e.children().data("$ngControllerController",g));u(a)}}}r=d.module("ngRoute",["ng"]).provider("$route",function(){function s(a,e){return d.extend(new (d.extend(function(){}, -{prototype:a})),e)}function h(a,d){var b=d.caseInsensitiveMatch,g={originalPath:a,regexp:a},f=g.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,d,e,b){a="?"===b?b:null;b="*"===b?b:null;f.push({name:e,optional:!!a});d=d||"";return""+(a?"":d)+"(?:"+(a?d:"")+(b&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");g.regexp=new RegExp("^"+a+"$",b?"i":"");return g}var f={};this.when=function(a,e){f[a]=d.extend({reloadOnSearch:!0},e,a&&h(a,e));if(a){var b= -"/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";f[b]=d.extend({redirectTo:a},h(b,e))}return this};this.otherwise=function(a){"string"===typeof a&&(a={redirectTo:a});this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function(a,e,b,g,h,r,t){function l(){var c=n(),m=q.current;if(c&&m&&c.$$route===m.$$route&&d.equals(c.pathParams,m.pathParams)&&!c.reloadOnSearch&&!p)m.params=c.params,d.copy(m.params,b),a.$broadcast("$routeUpdate", -m);else if(c||m)p=!1,a.$broadcast("$routeChangeStart",c,m),(q.current=c)&&c.redirectTo&&(d.isString(c.redirectTo)?e.path(k(c.redirectTo,c.params)).search(c.params).replace():e.url(c.redirectTo(c.pathParams,e.path(),e.search())).replace()),g.when(c).then(function(){if(c){var a=d.extend({},c.resolve),e,b;d.forEach(a,function(c,b){a[b]=d.isString(c)?h.get(c):h.invoke(c,null,null,b)});d.isDefined(e=c.template)?d.isFunction(e)&&(e=e(c.params)):d.isDefined(b=c.templateUrl)&&(d.isFunction(b)&&(b=b(c.params)), -b=t.getTrustedResourceUrl(b),d.isDefined(b)&&(c.loadedTemplateUrl=b,e=r(b)));d.isDefined(e)&&(a.$template=e);return g.all(a)}}).then(function(e){c==q.current&&(c&&(c.locals=e,d.copy(c.params,b)),a.$broadcast("$routeChangeSuccess",c,m))},function(d){c==q.current&&a.$broadcast("$routeChangeError",c,m,d)})}function n(){var c,a;d.forEach(f,function(b,g){var f;if(f=!a){var h=e.path();f=b.keys;var l={};if(b.regexp)if(h=b.regexp.exec(h)){for(var k=1,n=h.length;k<n;++k){var p=f[k-1],q=h[k];p&&q&&(l[p.name]= -q)}f=l}else f=null;else f=null;f=c=f}f&&(a=s(b,{params:d.extend({},e.search(),c),pathParams:c}),a.$$route=b)});return a||f[null]&&s(f[null],{params:{},pathParams:{}})}function k(a,b){var e=[];d.forEach((a||"").split(":"),function(a,c){if(0===c)e.push(a);else{var d=a.match(/(\w+)(.*)/),f=d[1];e.push(b[f]);e.push(d[2]||"");delete b[f]}});return e.join("")}var p=!1,q={routes:f,reload:function(){p=!0;a.$evalAsync(l)},updateParams:function(a){if(this.current&&this.current.$$route){var b={},f=this;d.forEach(Object.keys(a), -function(d){f.current.pathParams[d]||(b[d]=a[d])});a=d.extend({},this.current.params,a);e.path(k(this.current.$$route.originalPath,a));e.search(d.extend({},e.search(),b))}else throw y("norout");}};a.$on("$locationChangeSuccess",l);return q}]});var y=d.$$minErr("ngRoute");r.provider("$routeParams",function(){this.$get=function(){return{}}});r.directive("ngView",v);r.directive("ngView",x);v.$inject=["$route","$anchorScroll","$animate"];x.$inject=["$compile","$controller","$route"]})(window,window.angular); -//# sourceMappingURL=angular-route.min.js.map diff --git a/syweb/webclient/js/angular-sanitize.js b/syweb/webclient/js/angular-sanitize.js deleted file mode 100644 index ec46895f68..0000000000 --- a/syweb/webclient/js/angular-sanitize.js +++ /dev/null @@ -1,647 +0,0 @@ -/** - * @license AngularJS v1.3.0-rc.1 - * (c) 2010-2014 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular, undefined) {'use strict'; - -var $sanitizeMinErr = angular.$$minErr('$sanitize'); - -/** - * @ngdoc module - * @name ngSanitize - * @description - * - * # ngSanitize - * - * The `ngSanitize` module provides functionality to sanitize HTML. - * - * - * <div doc-module-components="ngSanitize"></div> - * - * See {@link ngSanitize.$sanitize `$sanitize`} for usage. - */ - -/* - * HTML Parser By Misko Hevery (misko@hevery.com) - * based on: HTML Parser By John Resig (ejohn.org) - * Original code by Erik Arvidsson, Mozilla Public License - * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js - * - * // Use like so: - * htmlParser(htmlString, { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * }); - * - */ - - -/** - * @ngdoc service - * @name $sanitize - * @kind function - * - * @description - * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are - * then serialized back to properly escaped html string. This means that no unsafe input can make - * it into the returned string, however, since our parser is more strict than a typical browser - * parser, it's possible that some obscure input, which would be recognized as valid HTML by a - * browser, won't make it through the sanitizer. - * The whitelist is configured using the functions `aHrefSanitizationWhitelist` and - * `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}. - * - * @param {string} html Html input. - * @returns {string} Sanitized html. - * - * @example - <example module="sanitizeExample" deps="angular-sanitize.js"> - <file name="index.html"> - <script> - angular.module('sanitizeExample', ['ngSanitize']) - .controller('ExampleController', ['$scope', '$sce', function($scope, $sce) { - $scope.snippet = - '<p style="color:blue">an html\n' + - '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' + - 'snippet</p>'; - $scope.deliberatelyTrustDangerousSnippet = function() { - return $sce.trustAsHtml($scope.snippet); - }; - }]); - </script> - <div ng-controller="ExampleController"> - Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> - <table> - <tr> - <td>Directive</td> - <td>How</td> - <td>Source</td> - <td>Rendered</td> - </tr> - <tr id="bind-html-with-sanitize"> - <td>ng-bind-html</td> - <td>Automatically uses $sanitize</td> - <td><pre><div ng-bind-html="snippet"><br/></div></pre></td> - <td><div ng-bind-html="snippet"></div></td> - </tr> - <tr id="bind-html-with-trust"> - <td>ng-bind-html</td> - <td>Bypass $sanitize by explicitly trusting the dangerous value</td> - <td> - <pre><div ng-bind-html="deliberatelyTrustDangerousSnippet()"> -</div></pre> - </td> - <td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td> - </tr> - <tr id="bind-default"> - <td>ng-bind</td> - <td>Automatically escapes</td> - <td><pre><div ng-bind="snippet"><br/></div></pre></td> - <td><div ng-bind="snippet"></div></td> - </tr> - </table> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should sanitize the html snippet by default', function() { - expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). - toBe('<p>an html\n<em>click here</em>\nsnippet</p>'); - }); - - it('should inline raw snippet if bound to a trusted value', function() { - expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()). - toBe("<p style=\"color:blue\">an html\n" + - "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + - "snippet</p>"); - }); - - it('should escape snippet without any filter', function() { - expect(element(by.css('#bind-default div')).getInnerHtml()). - toBe("<p style=\"color:blue\">an html\n" + - "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + - "snippet</p>"); - }); - - it('should update', function() { - element(by.model('snippet')).clear(); - element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>'); - expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). - toBe('new <b>text</b>'); - expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( - 'new <b onclick="alert(1)">text</b>'); - expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( - "new <b onclick=\"alert(1)\">text</b>"); - }); - </file> - </example> - */ -function $SanitizeProvider() { - this.$get = ['$$sanitizeUri', function($$sanitizeUri) { - return function(html) { - var buf = []; - htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { - return !/^unsafe/.test($$sanitizeUri(uri, isImage)); - })); - return buf.join(''); - }; - }]; -} - -function sanitizeText(chars) { - var buf = []; - var writer = htmlSanitizeWriter(buf, angular.noop); - writer.chars(chars); - return buf.join(''); -} - - -// Regular Expressions for parsing tags and attributes -var START_TAG_REGEXP = - /^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/, - END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/, - ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, - BEGIN_TAG_REGEXP = /^</, - BEGING_END_TAGE_REGEXP = /^<\//, - COMMENT_REGEXP = /<!--(.*?)-->/g, - DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i, - CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g, - SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, - // Match everything outside of normal chars and " (quote character) - NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; - - -// Good source of info about elements and attributes -// http://dev.w3.org/html5/spec/Overview.html#semantics -// http://simon.html5.org/html-elements - -// Safe Void Elements - HTML5 -// http://dev.w3.org/html5/spec/Overview.html#void-elements -var voidElements = makeMap("area,br,col,hr,img,wbr"); - -// Elements that you can, intentionally, leave open (and which close themselves) -// http://dev.w3.org/html5/spec/Overview.html#optional-tags -var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), - optionalEndTagInlineElements = makeMap("rp,rt"), - optionalEndTagElements = angular.extend({}, - optionalEndTagInlineElements, - optionalEndTagBlockElements); - -// Safe Block Elements - HTML5 -var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + - "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + - "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); - -// Inline Elements - HTML5 -var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + - "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + - "samp,small,span,strike,strong,sub,sup,time,tt,u,var")); - - -// Special Elements (can contain anything) -var specialElements = makeMap("script,style"); - -var validElements = angular.extend({}, - voidElements, - blockElements, - inlineElements, - optionalEndTagElements); - -//Attributes that have href and hence need to be sanitized -var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap"); -var validAttrs = angular.extend({}, uriAttrs, makeMap( - 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+ - 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ - 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ - 'scope,scrolling,shape,size,span,start,summary,target,title,type,'+ - 'valign,value,vspace,width')); - -function makeMap(str) { - var obj = {}, items = str.split(','), i; - for (i = 0; i < items.length; i++) obj[items[i]] = true; - return obj; -} - - -/** - * @example - * htmlParser(htmlString, { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * }); - * - * @param {string} html string - * @param {object} handler - */ -function htmlParser( html, handler ) { - if (typeof html !== 'string') { - if (html === null || typeof html === 'undefined') { - html = ''; - } else { - html = '' + html; - } - } - var index, chars, match, stack = [], last = html, text; - stack.last = function() { return stack[ stack.length - 1 ]; }; - - while ( html ) { - text = ''; - chars = true; - - // Make sure we're not in a script or style element - if ( !stack.last() || !specialElements[ stack.last() ] ) { - - // Comment - if ( html.indexOf("<!--") === 0 ) { - // comments containing -- are not allowed unless they terminate the comment - index = html.indexOf("--", 4); - - if ( index >= 0 && html.lastIndexOf("-->", index) === index) { - if (handler.comment) handler.comment( html.substring( 4, index ) ); - html = html.substring( index + 3 ); - chars = false; - } - // DOCTYPE - } else if ( DOCTYPE_REGEXP.test(html) ) { - match = html.match( DOCTYPE_REGEXP ); - - if ( match ) { - html = html.replace( match[0], ''); - chars = false; - } - // end tag - } else if ( BEGING_END_TAGE_REGEXP.test(html) ) { - match = html.match( END_TAG_REGEXP ); - - if ( match ) { - html = html.substring( match[0].length ); - match[0].replace( END_TAG_REGEXP, parseEndTag ); - chars = false; - } - - // start tag - } else if ( BEGIN_TAG_REGEXP.test(html) ) { - match = html.match( START_TAG_REGEXP ); - - if ( match ) { - // We only have a valid start-tag if there is a '>'. - if ( match[4] ) { - html = html.substring( match[0].length ); - match[0].replace( START_TAG_REGEXP, parseStartTag ); - } - chars = false; - } else { - // no ending tag found --- this piece should be encoded as an entity. - text += '<'; - html = html.substring(1); - } - } - - if ( chars ) { - index = html.indexOf("<"); - - text += index < 0 ? html : html.substring( 0, index ); - html = index < 0 ? "" : html.substring( index ); - - if (handler.chars) handler.chars( decodeEntities(text) ); - } - - } else { - html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), - function(all, text){ - text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); - - if (handler.chars) handler.chars( decodeEntities(text) ); - - return ""; - }); - - parseEndTag( "", stack.last() ); - } - - if ( html == last ) { - throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + - "of html: {0}", html); - } - last = html; - } - - // Clean up any remaining tags - parseEndTag(); - - function parseStartTag( tag, tagName, rest, unary ) { - tagName = angular.lowercase(tagName); - if ( blockElements[ tagName ] ) { - while ( stack.last() && inlineElements[ stack.last() ] ) { - parseEndTag( "", stack.last() ); - } - } - - if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) { - parseEndTag( "", tagName ); - } - - unary = voidElements[ tagName ] || !!unary; - - if ( !unary ) - stack.push( tagName ); - - var attrs = {}; - - rest.replace(ATTR_REGEXP, - function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { - var value = doubleQuotedValue - || singleQuotedValue - || unquotedValue - || ''; - - attrs[name] = decodeEntities(value); - }); - if (handler.start) handler.start( tagName, attrs, unary ); - } - - function parseEndTag( tag, tagName ) { - var pos = 0, i; - tagName = angular.lowercase(tagName); - if ( tagName ) - // Find the closest opened tag of the same type - for ( pos = stack.length - 1; pos >= 0; pos-- ) - if ( stack[ pos ] == tagName ) - break; - - if ( pos >= 0 ) { - // Close all the open elements, up the stack - for ( i = stack.length - 1; i >= pos; i-- ) - if (handler.end) handler.end( stack[ i ] ); - - // Remove the open elements from the stack - stack.length = pos; - } - } -} - -var hiddenPre=document.createElement("pre"); -var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/; -/** - * decodes all entities into regular string - * @param value - * @returns {string} A string with decoded entities. - */ -function decodeEntities(value) { - if (!value) { return ''; } - - // Note: IE8 does not preserve spaces at the start/end of innerHTML - // so we must capture them and reattach them afterward - var parts = spaceRe.exec(value); - var spaceBefore = parts[1]; - var spaceAfter = parts[3]; - var content = parts[2]; - if (content) { - hiddenPre.innerHTML=content.replace(/</g,"<"); - // innerText depends on styling as it doesn't display hidden elements. - // Therefore, it's better to use textContent not to cause unnecessary - // reflows. However, IE<9 don't support textContent so the innerText - // fallback is necessary. - content = 'textContent' in hiddenPre ? - hiddenPre.textContent : hiddenPre.innerText; - } - return spaceBefore + content + spaceAfter; -} - -/** - * Escapes all potentially dangerous characters, so that the - * resulting string can be safely inserted into attribute or - * element text. - * @param value - * @returns {string} escaped text - */ -function encodeEntities(value) { - return value. - replace(/&/g, '&'). - replace(SURROGATE_PAIR_REGEXP, function (value) { - var hi = value.charCodeAt(0); - var low = value.charCodeAt(1); - return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'; - }). - replace(NON_ALPHANUMERIC_REGEXP, function(value){ - return '&#' + value.charCodeAt(0) + ';'; - }). - replace(/</g, '<'). - replace(/>/g, '>'); -} - -/** - * create an HTML/XML writer which writes to buffer - * @param {Array} buf use buf.jain('') to get out sanitized html string - * @returns {object} in the form of { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * } - */ -function htmlSanitizeWriter(buf, uriValidator){ - var ignore = false; - var out = angular.bind(buf, buf.push); - return { - start: function(tag, attrs, unary){ - tag = angular.lowercase(tag); - if (!ignore && specialElements[tag]) { - ignore = tag; - } - if (!ignore && validElements[tag] === true) { - out('<'); - out(tag); - angular.forEach(attrs, function(value, key){ - var lkey=angular.lowercase(key); - var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); - if (validAttrs[lkey] === true && - (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { - out(' '); - out(key); - out('="'); - out(encodeEntities(value)); - out('"'); - } - }); - out(unary ? '/>' : '>'); - } - }, - end: function(tag){ - tag = angular.lowercase(tag); - if (!ignore && validElements[tag] === true) { - out('</'); - out(tag); - out('>'); - } - if (tag == ignore) { - ignore = false; - } - }, - chars: function(chars){ - if (!ignore) { - out(encodeEntities(chars)); - } - } - }; -} - - -// define ngSanitize module and register $sanitize service -angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); - -/* global sanitizeText: false */ - -/** - * @ngdoc filter - * @name linky - * @kind function - * - * @description - * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and - * plain email address links. - * - * Requires the {@link ngSanitize `ngSanitize`} module to be installed. - * - * @param {string} text Input text. - * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. - * @returns {string} Html-linkified text. - * - * @usage - <span ng-bind-html="linky_expression | linky"></span> - * - * @example - <example module="linkyExample" deps="angular-sanitize.js"> - <file name="index.html"> - <script> - angular.module('linkyExample', ['ngSanitize']) - .controller('ExampleController', ['$scope', function($scope) { - $scope.snippet = - 'Pretty text with some links:\n'+ - 'http://angularjs.org/,\n'+ - 'mailto:us@somewhere.org,\n'+ - 'another@somewhere.org,\n'+ - 'and one more: ftp://127.0.0.1/.'; - $scope.snippetWithTarget = 'http://angularjs.org/'; - }]); - </script> - <div ng-controller="ExampleController"> - Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> - <table> - <tr> - <td>Filter</td> - <td>Source</td> - <td>Rendered</td> - </tr> - <tr id="linky-filter"> - <td>linky filter</td> - <td> - <pre><div ng-bind-html="snippet | linky"><br></div></pre> - </td> - <td> - <div ng-bind-html="snippet | linky"></div> - </td> - </tr> - <tr id="linky-target"> - <td>linky target</td> - <td> - <pre><div ng-bind-html="snippetWithTarget | linky:'_blank'"><br></div></pre> - </td> - <td> - <div ng-bind-html="snippetWithTarget | linky:'_blank'"></div> - </td> - </tr> - <tr id="escaped-html"> - <td>no filter</td> - <td><pre><div ng-bind="snippet"><br></div></pre></td> - <td><div ng-bind="snippet"></div></td> - </tr> - </table> - </file> - <file name="protractor.js" type="protractor"> - it('should linkify the snippet with urls', function() { - expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). - toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' + - 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); - expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); - }); - - it('should not linkify snippet without the linky filter', function() { - expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). - toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' + - 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); - expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); - }); - - it('should update', function() { - element(by.model('snippet')).clear(); - element(by.model('snippet')).sendKeys('new http://link.'); - expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). - toBe('new http://link.'); - expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); - expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) - .toBe('new http://link.'); - }); - - it('should work with the target property', function() { - expect(element(by.id('linky-target')). - element(by.binding("snippetWithTarget | linky:'_blank'")).getText()). - toBe('http://angularjs.org/'); - expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); - }); - </file> - </example> - */ -angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { - var LINKY_URL_REGEXP = - /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/, - MAILTO_REGEXP = /^mailto:/; - - return function(text, target) { - if (!text) return text; - var match; - var raw = text; - var html = []; - var url; - var i; - while ((match = raw.match(LINKY_URL_REGEXP))) { - // We can not end in these as they are sometimes found at the end of the sentence - url = match[0]; - // if we did not match ftp/http/mailto then assume mailto - if (match[2] == match[3]) url = 'mailto:' + url; - i = match.index; - addText(raw.substr(0, i)); - addLink(url, match[0].replace(MAILTO_REGEXP, '')); - raw = raw.substring(i + match[0].length); - } - addText(raw); - return $sanitize(html.join('')); - - function addText(text) { - if (!text) { - return; - } - html.push(sanitizeText(text)); - } - - function addLink(url, text) { - html.push('<a '); - if (angular.isDefined(target)) { - html.push('target="'); - html.push(target); - html.push('" '); - } - html.push('href="'); - html.push(url); - html.push('">'); - addText(text); - html.push('</a>'); - } - }; -}]); - - -})(window, window.angular); diff --git a/syweb/webclient/js/angular-sanitize.min.js b/syweb/webclient/js/angular-sanitize.min.js deleted file mode 100644 index ce99bba18e..0000000000 --- a/syweb/webclient/js/angular-sanitize.min.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - AngularJS v1.3.0-rc.1 - (c) 2010-2014 Google, Inc. http://angularjs.org - License: MIT -*/ -(function(q,g,r){'use strict';function F(a){var d=[];t(d,g.noop).chars(a);return d.join("")}function m(a){var d={};a=a.split(",");var c;for(c=0;c<a.length;c++)d[a[c]]=!0;return d}function G(a,d){function c(a,b,c,h){b=g.lowercase(b);if(u[b])for(;f.last()&&v[f.last()];)e("",f.last());w[b]&&f.last()==b&&e("",b);(h=x[b]||!!h)||f.push(b);var n={};c.replace(H,function(a,b,d,c,e){n[b]=s(d||c||e||"")});d.start&&d.start(b,n,h)}function e(a,b){var c=0,e;if(b=g.lowercase(b))for(c=f.length-1;0<=c&&f[c]!=b;c--); -if(0<=c){for(e=f.length-1;e>=c;e--)d.end&&d.end(f[e]);f.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,l,f=[],n=a,h;for(f.last=function(){return f[f.length-1]};a;){h="";l=!0;if(f.last()&&y[f.last()])a=a.replace(new RegExp("(.*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(a,b){b=b.replace(I,"$1").replace(J,"$1");d.chars&&d.chars(s(b));return""}),e("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(d.comment&&d.comment(a.substring(4, -b)),a=a.substring(b+3),l=!1);else if(z.test(a)){if(b=a.match(z))a=a.replace(b[0],""),l=!1}else if(K.test(a)){if(b=a.match(A))a=a.substring(b[0].length),b[0].replace(A,e),l=!1}else L.test(a)&&((b=a.match(B))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(B,c)),l=!1):(h+="<",a=a.substring(1)));l&&(b=a.indexOf("<"),h+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),d.chars&&d.chars(s(h)))}if(a==n)throw M("badparse",a);n=a}e()}function s(a){if(!a)return"";var d=N.exec(a);a=d[1];var c=d[3];if(d=d[2])p.innerHTML= -d.replace(/</g,"<"),d="textContent"in p?p.textContent:p.innerText;return a+d+c}function C(a){return a.replace(/&/g,"&").replace(O,function(a){var c=a.charCodeAt(0);a=a.charCodeAt(1);return"&#"+(1024*(c-55296)+(a-56320)+65536)+";"}).replace(P,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"<").replace(/>/g,">")}function t(a,d){var c=!1,e=g.bind(a,a.push);return{start:function(a,l,f){a=g.lowercase(a);!c&&y[a]&&(c=a);c||!0!==D[a]||(e("<"),e(a),g.forEach(l,function(c,f){var k= -g.lowercase(f),l="img"===a&&"src"===k||"background"===k;!0!==Q[k]||!0===E[k]&&!d(c,l)||(e(" "),e(f),e('="'),e(C(c)),e('"'))}),e(f?"/>":">"))},end:function(a){a=g.lowercase(a);c||!0!==D[a]||(e("</"),e(a),e(">"));a==c&&(c=!1)},chars:function(a){c||e(C(a))}}}var M=g.$$minErr("$sanitize"),B=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,A=/^<\/\s*([\w:-]+)[^>]*>/,H=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,L=/^</, -K=/^<\//,I=/\x3c!--(.*?)--\x3e/g,z=/<!DOCTYPE([^>]*?)>/i,J=/<!\[CDATA\[(.*?)]]\x3e/g,O=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,P=/([^\#-~| |!])/g,x=m("area,br,col,hr,img,wbr");q=m("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr");r=m("rp,rt");var w=g.extend({},r,q),u=g.extend({},q,m("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),v=g.extend({},r,m("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")), -y=m("script,style"),D=g.extend({},x,u,v,w),E=m("background,cite,href,longdesc,src,usemap"),Q=g.extend({},E,m("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,target,title,type,valign,value,vspace,width")),p=document.createElement("pre"),N=/^(\s*)([\s\S]*?)(\s*)$/;g.module("ngSanitize",[]).provider("$sanitize", -function(){this.$get=["$$sanitizeUri",function(a){return function(d){var c=[];G(d,t(c,function(c,b){return!/^unsafe/.test(a(c,b))}));return c.join("")}}]});g.module("ngSanitize").filter("linky",["$sanitize",function(a){var d=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/,c=/^mailto:/;return function(e,b){function l(a){a&&k.push(F(a))}function f(a,c){k.push("<a ");g.isDefined(b)&&(k.push('target="'),k.push(b),k.push('" '));k.push('href="');k.push(a);k.push('">');l(c);k.push("</a>")} -if(!e)return e;for(var n,h=e,k=[],m,p;n=h.match(d);)m=n[0],n[2]==n[3]&&(m="mailto:"+m),p=n.index,l(h.substr(0,p)),f(m,n[0].replace(c,"")),h=h.substring(p+n[0].length);l(h);return a(k.join(""))}}])})(window,window.angular); -//# sourceMappingURL=angular-sanitize.min.js.map diff --git a/syweb/webclient/js/angular.js b/syweb/webclient/js/angular.js deleted file mode 100644 index bdc97abb02..0000000000 --- a/syweb/webclient/js/angular.js +++ /dev/null @@ -1,24417 +0,0 @@ -/** - * @license AngularJS v1.3.0-rc.1 - * (c) 2010-2014 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, document, undefined) {'use strict'; - -/** - * @description - * - * This object provides a utility for producing rich Error messages within - * Angular. It can be called as follows: - * - * var exampleMinErr = minErr('example'); - * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); - * - * The above creates an instance of minErr in the example namespace. The - * resulting error will have a namespaced error code of example.one. The - * resulting error will replace {0} with the value of foo, and {1} with the - * value of bar. The object is not restricted in the number of arguments it can - * take. - * - * If fewer arguments are specified than necessary for interpolation, the extra - * interpolation markers will be preserved in the final string. - * - * Since data will be parsed statically during a build step, some restrictions - * are applied with respect to how minErr instances are created and called. - * Instances should have names of the form namespaceMinErr for a minErr created - * using minErr('namespace') . Error codes, namespaces and template strings - * should all be static strings, not variables or general expressions. - * - * @param {string} module The namespace to use for the new minErr instance. - * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning - * error from returned function, for cases when a particular type of error is useful. - * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance - */ - -function minErr(module, ErrorConstructor) { - ErrorConstructor = ErrorConstructor || Error; - return function () { - var code = arguments[0], - prefix = '[' + (module ? module + ':' : '') + code + '] ', - template = arguments[1], - templateArgs = arguments, - stringify = function (obj) { - if (typeof obj === 'function') { - return obj.toString().replace(/ \{[\s\S]*$/, ''); - } else if (typeof obj === 'undefined') { - return 'undefined'; - } else if (typeof obj !== 'string') { - return JSON.stringify(obj); - } - return obj; - }, - message, i; - - message = prefix + template.replace(/\{\d+\}/g, function (match) { - var index = +match.slice(1, -1), arg; - - if (index + 2 < templateArgs.length) { - arg = templateArgs[index + 2]; - if (typeof arg === 'function') { - return arg.toString().replace(/ ?\{[\s\S]*$/, ''); - } else if (typeof arg === 'undefined') { - return 'undefined'; - } else if (typeof arg !== 'string') { - return toJson(arg); - } - return arg; - } - return match; - }); - - message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.1/' + - (module ? module + '/' : '') + code; - for (i = 2; i < arguments.length; i++) { - message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + - encodeURIComponent(stringify(arguments[i])); - } - return new ErrorConstructor(message); - }; -} - -/* We need to tell jshint what variables are being exported */ -/* global angular: true, - msie: true, - jqLite: true, - jQuery: true, - slice: true, - push: true, - toString: true, - ngMinErr: true, - angularModule: true, - uid: true, - REGEX_STRING_REGEXP: true, - VALIDITY_STATE_PROPERTY: true, - - lowercase: true, - uppercase: true, - manualLowercase: true, - manualUppercase: true, - nodeName_: true, - isArrayLike: true, - forEach: true, - sortedKeys: true, - forEachSorted: true, - reverseParams: true, - nextUid: true, - setHashKey: true, - extend: true, - int: true, - inherit: true, - noop: true, - identity: true, - valueFn: true, - isUndefined: true, - isDefined: true, - isObject: true, - isString: true, - isNumber: true, - isDate: true, - isArray: true, - isFunction: true, - isRegExp: true, - isWindow: true, - isScope: true, - isFile: true, - isBlob: true, - isBoolean: true, - isPromiseLike: true, - trim: true, - isElement: true, - makeMap: true, - map: true, - size: true, - includes: true, - arrayRemove: true, - isLeafNode: true, - copy: true, - shallowCopy: true, - equals: true, - csp: true, - concat: true, - sliceArgs: true, - bind: true, - toJsonReplacer: true, - toJson: true, - fromJson: true, - startingTag: true, - tryDecodeURIComponent: true, - parseKeyValue: true, - toKeyValue: true, - encodeUriSegment: true, - encodeUriQuery: true, - angularInit: true, - bootstrap: true, - getTestability: true, - snake_case: true, - bindJQuery: true, - assertArg: true, - assertArgFn: true, - assertNotHasOwnProperty: true, - getter: true, - getBlockNodes: true, - hasOwnProperty: true, - createMap: true, -*/ - -//////////////////////////////////// - -/** - * @ngdoc module - * @name ng - * @module ng - * @description - * - * # ng (core module) - * The ng module is loaded by default when an AngularJS application is started. The module itself - * contains the essential components for an AngularJS application to function. The table below - * lists a high level breakdown of each of the services/factories, filters, directives and testing - * components available within this core module. - * - * <div doc-module-components="ng"></div> - */ - -var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; - -// The name of a form control's ValidityState property. -// This is used so that it's possible for internal tests to create mock ValidityStates. -var VALIDITY_STATE_PROPERTY = 'validity'; - -/** - * @ngdoc function - * @name angular.lowercase - * @module ng - * @kind function - * - * @description Converts the specified string to lowercase. - * @param {string} string String to be converted to lowercase. - * @returns {string} Lowercased string. - */ -var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; -var hasOwnProperty = Object.prototype.hasOwnProperty; - -/** - * @ngdoc function - * @name angular.uppercase - * @module ng - * @kind function - * - * @description Converts the specified string to uppercase. - * @param {string} string String to be converted to uppercase. - * @returns {string} Uppercased string. - */ -var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; - - -var manualLowercase = function(s) { - /* jshint bitwise: false */ - return isString(s) - ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) - : s; -}; -var manualUppercase = function(s) { - /* jshint bitwise: false */ - return isString(s) - ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) - : s; -}; - - -// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish -// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods -// with correct but slower alternatives. -if ('i' !== 'I'.toLowerCase()) { - lowercase = manualLowercase; - uppercase = manualUppercase; -} - - -var /** holds major version number for IE or NaN for real browsers */ - msie, - jqLite, // delay binding since jQuery could be loaded after us. - jQuery, // delay binding - slice = [].slice, - push = [].push, - toString = Object.prototype.toString, - ngMinErr = minErr('ng'), - - /** @name angular */ - angular = window.angular || (window.angular = {}), - angularModule, - uid = 0; - -/** - * IE 11 changed the format of the UserAgent string. - * See http://msdn.microsoft.com/en-us/library/ms537503.aspx - */ -msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); -if (isNaN(msie)) { - msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); -} - - -/** - * @private - * @param {*} obj - * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, - * String ...) - */ -function isArrayLike(obj) { - if (obj == null || isWindow(obj)) { - return false; - } - - var length = obj.length; - - if (obj.nodeType === 1 && length) { - return true; - } - - return isString(obj) || isArray(obj) || length === 0 || - typeof length === 'number' && length > 0 && (length - 1) in obj; -} - -/** - * @ngdoc function - * @name angular.forEach - * @module ng - * @kind function - * - * @description - * Invokes the `iterator` function once for each item in `obj` collection, which can be either an - * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` - * is the value of an object property or an array element, `key` is the object property key or - * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. - * - * It is worth noting that `.forEach` does not iterate over inherited properties because it filters - * using the `hasOwnProperty` method. - * - ```js - var values = {name: 'misko', gender: 'male'}; - var log = []; - angular.forEach(values, function(value, key) { - this.push(key + ': ' + value); - }, log); - expect(log).toEqual(['name: misko', 'gender: male']); - ``` - * - * @param {Object|Array} obj Object to iterate over. - * @param {Function} iterator Iterator function. - * @param {Object=} context Object to become context (`this`) for the iterator function. - * @returns {Object|Array} Reference to `obj`. - */ - -function forEach(obj, iterator, context) { - var key, length; - if (obj) { - if (isFunction(obj)) { - for (key in obj) { - // Need to check if hasOwnProperty exists, - // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function - if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { - iterator.call(context, obj[key], key, obj); - } - } - } else if (isArray(obj) || isArrayLike(obj)) { - var isPrimitive = typeof obj !== 'object'; - for (key = 0, length = obj.length; key < length; key++) { - if (isPrimitive || key in obj) { - iterator.call(context, obj[key], key, obj); - } - } - } else if (obj.forEach && obj.forEach !== forEach) { - obj.forEach(iterator, context, obj); - } else { - for (key in obj) { - if (obj.hasOwnProperty(key)) { - iterator.call(context, obj[key], key, obj); - } - } - } - } - return obj; -} - -function sortedKeys(obj) { - var keys = []; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - keys.push(key); - } - } - return keys.sort(); -} - -function forEachSorted(obj, iterator, context) { - var keys = sortedKeys(obj); - for ( var i = 0; i < keys.length; i++) { - iterator.call(context, obj[keys[i]], keys[i]); - } - return keys; -} - - -/** - * when using forEach the params are value, key, but it is often useful to have key, value. - * @param {function(string, *)} iteratorFn - * @returns {function(*, string)} - */ -function reverseParams(iteratorFn) { - return function(value, key) { iteratorFn(key, value); }; -} - -/** - * A consistent way of creating unique IDs in angular. - * - * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before - * we hit number precision issues in JavaScript. - * - * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M - * - * @returns {number} an unique alpha-numeric string - */ -function nextUid() { - return ++uid; -} - - -/** - * 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 - * @module ng - * @kind function - * - * @description - * Extends the destination object `dst` by copying all of the properties from the `src` object(s) - * to `dst`. You can specify multiple `src` objects. - * - * @param {Object} dst Destination object. - * @param {...Object} src Source object(s). - * @returns {Object} Reference to `dst`. - */ -function extend(dst) { - var h = dst.$$hashKey; - - for (var i = 1, ii = arguments.length; i < ii; i++) { - var obj = arguments[i]; - if (obj) { - var keys = Object.keys(obj); - for (var j = 0, jj = keys.length; j < jj; j++) { - var key = keys[j]; - dst[key] = obj[key]; - } - } - } - - setHashKey(dst, h); - return dst; -} - -function int(str) { - return parseInt(str, 10); -} - - -function inherit(parent, extra) { - return extend(new (extend(function() {}, {prototype:parent}))(), extra); -} - -/** - * @ngdoc function - * @name angular.noop - * @module ng - * @kind function - * - * @description - * A function that performs no operations. This function can be useful when writing code in the - * functional style. - ```js - function foo(callback) { - var result = calculateResult(); - (callback || angular.noop)(result); - } - ``` - */ -function noop() {} -noop.$inject = []; - - -/** - * @ngdoc function - * @name angular.identity - * @module ng - * @kind function - * - * @description - * A function that returns its first argument. This function is useful when writing code in the - * functional style. - * - ```js - function transformer(transformationFn, value) { - return (transformationFn || angular.identity)(value); - }; - ``` - */ -function identity($) {return $;} -identity.$inject = []; - - -function valueFn(value) {return function() {return value;};} - -/** - * @ngdoc function - * @name angular.isUndefined - * @module ng - * @kind function - * - * @description - * Determines if a reference is undefined. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is undefined. - */ -function isUndefined(value){return typeof value === 'undefined';} - - -/** - * @ngdoc function - * @name angular.isDefined - * @module ng - * @kind function - * - * @description - * Determines if a reference is defined. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is defined. - */ -function isDefined(value){return typeof value !== 'undefined';} - - -/** - * @ngdoc function - * @name angular.isObject - * @module ng - * @kind function - * - * @description - * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not - * considered to be objects. Note that JavaScript arrays are objects. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is an `Object` but not `null`. - */ -function isObject(value){ - // http://jsperf.com/isobject4 - return value !== null && typeof value === 'object'; -} - - -/** - * @ngdoc function - * @name angular.isString - * @module ng - * @kind function - * - * @description - * Determines if a reference is a `String`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `String`. - */ -function isString(value){return typeof value === 'string';} - - -/** - * @ngdoc function - * @name angular.isNumber - * @module ng - * @kind function - * - * @description - * Determines if a reference is a `Number`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Number`. - */ -function isNumber(value){return typeof value === 'number';} - - -/** - * @ngdoc function - * @name angular.isDate - * @module ng - * @kind function - * - * @description - * Determines if a value is a date. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Date`. - */ -function isDate(value) { - return toString.call(value) === '[object Date]'; -} - - -/** - * @ngdoc function - * @name angular.isArray - * @module ng - * @kind function - * - * @description - * Determines if a reference is an `Array`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is an `Array`. - */ -var isArray = Array.isArray; - -/** - * @ngdoc function - * @name angular.isFunction - * @module ng - * @kind function - * - * @description - * Determines if a reference is a `Function`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Function`. - */ -function isFunction(value){return typeof value === 'function';} - - -/** - * Determines if a value is a regular expression object. - * - * @private - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `RegExp`. - */ -function isRegExp(value) { - return toString.call(value) === '[object RegExp]'; -} - - -/** - * Checks if `obj` is a window object. - * - * @private - * @param {*} obj Object to check - * @returns {boolean} True if `obj` is a window obj. - */ -function isWindow(obj) { - return obj && obj.window === obj; -} - - -function isScope(obj) { - return obj && obj.$evalAsync && obj.$watch; -} - - -function isFile(obj) { - return toString.call(obj) === '[object File]'; -} - - -function isBlob(obj) { - return toString.call(obj) === '[object Blob]'; -} - - -function isBoolean(value) { - return typeof value === 'boolean'; -} - - -function isPromiseLike(obj) { - return obj && isFunction(obj.then); -} - - -var trim = function(value) { - return isString(value) ? value.trim() : value; -}; - - -/** - * @ngdoc function - * @name angular.isElement - * @module ng - * @kind function - * - * @description - * Determines if a reference is a DOM element (or wrapped jQuery element). - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). - */ -function isElement(node) { - return !!(node && - (node.nodeName // we are a direct element - || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API -} - -/** - * @param str 'key1,key2,...' - * @returns {object} in the form of {key1:true, key2:true, ...} - */ -function makeMap(str) { - var obj = {}, items = str.split(","), i; - for ( i = 0; i < items.length; i++ ) - obj[ items[i] ] = true; - return obj; -} - - -function nodeName_(element) { - return lowercase(element.nodeName || element[0].nodeName); -} - - -function map(obj, iterator, context) { - var results = []; - forEach(obj, function(value, index, list) { - results.push(iterator.call(context, value, index, list)); - }); - return results; -} - - -/** - * @description - * Determines the number of elements in an array, the number of properties an object has, or - * the length of a string. - * - * Note: This function is used to augment the Object type in Angular expressions. See - * {@link angular.Object} for more information about Angular arrays. - * - * @param {Object|Array|string} obj Object, array, or string to inspect. - * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object - * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. - */ -function size(obj, ownPropsOnly) { - var count = 0, key; - - if (isArray(obj) || isString(obj)) { - return obj.length; - } else if (isObject(obj)) { - for (key in obj) - if (!ownPropsOnly || obj.hasOwnProperty(key)) - count++; - } - - return count; -} - - -function includes(array, obj) { - return Array.prototype.indexOf.call(array, obj) != -1; -} - -function arrayRemove(array, value) { - var index = array.indexOf(value); - if (index >=0) - array.splice(index, 1); - return value; -} - -function isLeafNode (node) { - if (node) { - switch (nodeName_(node)) { - case "option": - case "pre": - case "title": - return true; - } - } - return false; -} - -/** - * @ngdoc function - * @name angular.copy - * @module ng - * @kind function - * - * @description - * Creates a deep copy of `source`, which should be an object or an array. - * - * * If no destination is supplied, a copy of the object or array is created. - * * If a destination is provided, all of its elements (for array) or properties (for objects) - * are deleted and then all elements/properties from the source are copied to it. - * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. - * * If `source` is identical to 'destination' an exception will be thrown. - * - * @param {*} source The source that will be used to make a copy. - * Can be any type, including primitives, `null`, and `undefined`. - * @param {(Object|Array)=} destination Destination into which the source is copied. If - * provided, must be of the same type as `source`. - * @returns {*} The copy or updated `destination`, if `destination` was specified. - * - * @example - <example module="copyExample"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <form novalidate class="simple-form"> - Name: <input type="text" ng-model="user.name" /><br /> - E-mail: <input type="email" ng-model="user.email" /><br /> - Gender: <input type="radio" ng-model="user.gender" value="male" />male - <input type="radio" ng-model="user.gender" value="female" />female<br /> - <button ng-click="reset()">RESET</button> - <button ng-click="update(user)">SAVE</button> - </form> - <pre>form = {{user | json}}</pre> - <pre>master = {{master | json}}</pre> - </div> - - <script> - angular.module('copyExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.master= {}; - - $scope.update = function(user) { - // Example with 1 argument - $scope.master= angular.copy(user); - }; - - $scope.reset = function() { - // Example with 2 arguments - angular.copy($scope.master, $scope.user); - }; - - $scope.reset(); - }]); - </script> - </file> - </example> - */ -function copy(source, destination, stackSource, stackDest) { - if (isWindow(source) || isScope(source)) { - throw ngMinErr('cpws', - "Can't copy! Making copies of Window or Scope instances is not supported."); - } - - if (!destination) { - destination = source; - if (source) { - if (isArray(source)) { - destination = copy(source, [], stackSource, stackDest); - } else if (isDate(source)) { - destination = new Date(source.getTime()); - } else if (isRegExp(source)) { - destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); - destination.lastIndex = source.lastIndex; - } else if (isObject(source)) { - var emptyObject = Object.create(Object.getPrototypeOf(source)); - destination = copy(source, emptyObject, stackSource, stackDest); - } - } - } else { - if (source === destination) throw ngMinErr('cpi', - "Can't copy! Source and destination are identical."); - - stackSource = stackSource || []; - stackDest = stackDest || []; - - if (isObject(source)) { - var index = stackSource.indexOf(source); - if (index !== -1) return stackDest[index]; - - stackSource.push(source); - stackDest.push(destination); - } - - var result; - if (isArray(source)) { - destination.length = 0; - for ( var i = 0; i < source.length; i++) { - result = copy(source[i], null, stackSource, stackDest); - if (isObject(source[i])) { - stackSource.push(source[i]); - stackDest.push(result); - } - destination.push(result); - } - } else { - var h = destination.$$hashKey; - if (isArray(destination)) { - destination.length = 0; - } else { - forEach(destination, function(value, key) { - delete destination[key]; - }); - } - for ( var key in source) { - if(source.hasOwnProperty(key)) { - result = copy(source[key], null, stackSource, stackDest); - if (isObject(source[key])) { - stackSource.push(source[key]); - stackDest.push(result); - } - destination[key] = result; - } - } - setHashKey(destination,h); - } - - } - return destination; -} - -/** - * Creates a shallow copy of an object, an array or a primitive. - * - * Assumes that there are no proto properties for objects. - */ -function shallowCopy(src, dst) { - if (isArray(src)) { - dst = dst || []; - - for (var i = 0, ii = src.length; i < ii; i++) { - dst[i] = src[i]; - } - } else if (isObject(src)) { - dst = dst || {}; - - for (var key in src) { - if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { - dst[key] = src[key]; - } - } - } - - return dst || src; -} - - -/** - * @ngdoc function - * @name angular.equals - * @module ng - * @kind function - * - * @description - * Determines if two objects or two values are equivalent. Supports value types, regular - * expressions, arrays and objects. - * - * Two objects or values are considered equivalent if at least one of the following is true: - * - * * Both objects or values pass `===` comparison. - * * Both objects or values are of the same type and all of their properties are equal by - * comparing them with `angular.equals`. - * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) - * * Both values represent the same regular expression (In JavaScript, - * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual - * representation matches). - * - * During a property comparison, properties of `function` type and properties with names - * that begin with `$` are ignored. - * - * Scope and DOMWindow objects are being compared only by identify (`===`). - * - * @param {*} o1 Object or value to compare. - * @param {*} o2 Object or value to compare. - * @returns {boolean} True if arguments are equal. - */ -function equals(o1, o2) { - if (o1 === o2) return true; - if (o1 === null || o2 === null) return false; - if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN - var t1 = typeof o1, t2 = typeof o2, length, key, keySet; - if (t1 == t2) { - if (t1 == 'object') { - if (isArray(o1)) { - if (!isArray(o2)) return false; - if ((length = o1.length) == o2.length) { - for(key=0; key<length; key++) { - if (!equals(o1[key], o2[key])) return false; - } - return true; - } - } else if (isDate(o1)) { - if (!isDate(o2)) return false; - return equals(o1.getTime(), o2.getTime()); - } else if (isRegExp(o1) && isRegExp(o2)) { - return o1.toString() == o2.toString(); - } else { - if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false; - keySet = {}; - for(key in o1) { - if (key.charAt(0) === '$' || isFunction(o1[key])) continue; - if (!equals(o1[key], o2[key])) return false; - keySet[key] = true; - } - for(key in o2) { - if (!keySet.hasOwnProperty(key) && - key.charAt(0) !== '$' && - o2[key] !== undefined && - !isFunction(o2[key])) return false; - } - return true; - } - } - } - return false; -} - -var csp = function() { - if (isDefined(csp.isActive_)) return csp.isActive_; - - var active = !!(document.querySelector('[ng-csp]') || - document.querySelector('[data-ng-csp]')); - - if (!active) { - try { - /* jshint -W031, -W054 */ - new Function(''); - /* jshint +W031, +W054 */ - } catch (e) { - active = true; - } - } - - return (csp.isActive_ = active); -}; - - - -function concat(array1, array2, index) { - return array1.concat(slice.call(array2, index)); -} - -function sliceArgs(args, startIndex) { - return slice.call(args, startIndex || 0); -} - - -/* jshint -W101 */ -/** - * @ngdoc function - * @name angular.bind - * @module ng - * @kind function - * - * @description - * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for - * `fn`). You can supply optional `args` that are prebound to the function. This feature is also - * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as - * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). - * - * @param {Object} self Context which `fn` should be evaluated in. - * @param {function()} fn Function to be bound. - * @param {...*} args Optional arguments to be prebound to the `fn` function call. - * @returns {function()} Function that wraps the `fn` with all the specified bindings. - */ -/* jshint +W101 */ -function bind(self, fn) { - var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; - if (isFunction(fn) && !(fn instanceof RegExp)) { - return curryArgs.length - ? function() { - return arguments.length - ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) - : fn.apply(self, curryArgs); - } - : function() { - return arguments.length - ? fn.apply(self, arguments) - : fn.call(self); - }; - } else { - // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) - return fn; - } -} - - -function toJsonReplacer(key, value) { - var val = value; - - if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { - val = undefined; - } else if (isWindow(value)) { - val = '$WINDOW'; - } else if (value && document === value) { - val = '$DOCUMENT'; - } else if (isScope(value)) { - val = '$SCOPE'; - } - - return val; -} - - -/** - * @ngdoc function - * @name angular.toJson - * @module ng - * @kind function - * - * @description - * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be - * stripped since angular uses this notation internally. - * - * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. - * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace. - * @returns {string|undefined} JSON-ified string representing `obj`. - */ -function toJson(obj, pretty) { - if (typeof obj === 'undefined') return undefined; - return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); -} - - -/** - * @ngdoc function - * @name angular.fromJson - * @module ng - * @kind function - * - * @description - * Deserializes a JSON string. - * - * @param {string} json JSON string to deserialize. - * @returns {Object|Array|string|number} Deserialized thingy. - */ -function fromJson(json) { - return isString(json) - ? JSON.parse(json) - : json; -} - - -/** - * @returns {string} Returns the string representation of the element. - */ -function startingTag(element) { - element = jqLite(element).clone(); - try { - // turns out IE does not let you set .html() on elements which - // are not allowed to have children. So we just ignore it. - element.empty(); - } catch(e) {} - // As Per DOM Standards - var TEXT_NODE = 3; - var elemHtml = jqLite('<div>').append(element).html(); - try { - return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : - elemHtml. - match(/^(<[^>]+>)/)[1]. - replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); - } catch(e) { - return lowercase(elemHtml); - } - -} - - -///////////////////////////////////////////////// - -/** - * Tries to decode the URI component without throwing an exception. - * - * @private - * @param str value potential URI component to check. - * @returns {boolean} True if `value` can be decoded - * with the decodeURIComponent function. - */ -function tryDecodeURIComponent(value) { - try { - return decodeURIComponent(value); - } catch(e) { - // Ignore any invalid uri component - } -} - - -/** - * Parses an escaped url query string into key-value pairs. - * @returns {Object.<string,boolean|Array>} - */ -function parseKeyValue(/**string*/keyValue) { - var obj = {}, key_value, key; - forEach((keyValue || "").split('&'), function(keyValue) { - if ( keyValue ) { - key_value = keyValue.replace(/\+/g,'%20').split('='); - key = tryDecodeURIComponent(key_value[0]); - if ( isDefined(key) ) { - var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; - if (!hasOwnProperty.call(obj, key)) { - obj[key] = val; - } else if(isArray(obj[key])) { - obj[key].push(val); - } else { - obj[key] = [obj[key],val]; - } - } - } - }); - return obj; -} - -function toKeyValue(obj) { - var parts = []; - forEach(obj, function(value, key) { - if (isArray(value)) { - forEach(value, function(arrayValue) { - parts.push(encodeUriQuery(key, true) + - (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); - }); - } else { - parts.push(encodeUriQuery(key, true) + - (value === true ? '' : '=' + encodeUriQuery(value, true))); - } - }); - return parts.length ? parts.join('&') : ''; -} - - -/** - * We need our custom method because encodeURIComponent is too aggressive and doesn't follow - * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path - * segments: - * segment = *pchar - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * pct-encoded = "%" HEXDIG HEXDIG - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ -function encodeUriSegment(val) { - return encodeUriQuery(val, true). - replace(/%26/gi, '&'). - replace(/%3D/gi, '='). - replace(/%2B/gi, '+'); -} - - -/** - * This method is intended for encoding *key* or *value* parts of query component. We need a custom - * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be - * encoded per http://tools.ietf.org/html/rfc3986: - * query = *( pchar / "/" / "?" ) - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * pct-encoded = "%" HEXDIG HEXDIG - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ -function encodeUriQuery(val, pctEncodeSpaces) { - return encodeURIComponent(val). - replace(/%40/gi, '@'). - replace(/%3A/gi, ':'). - replace(/%24/g, '$'). - replace(/%2C/gi, ','). - replace(/%3B/gi, ';'). - replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); -} - -var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; - -function getNgAttribute(element, ngAttr) { - var attr, i, ii = ngAttrPrefixes.length; - element = jqLite(element); - for (i=0; i<ii; ++i) { - attr = ngAttrPrefixes[i] + ngAttr; - if (isString(attr = element.attr(attr))) { - return attr; - } - } - return null; -} - -/** - * @ngdoc directive - * @name ngApp - * @module ng - * - * @element ANY - * @param {angular.Module} ngApp an optional application - * {@link angular.module module} name to load. - * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be - * created in "strict-di" mode. This means that the application will fail to invoke functions which - * do not use explicit function annotation (and are thus unsuitable for minification), as described - * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in - * tracking down the root of these bugs. - * - * @description - * - * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive - * designates the **root element** of the application and is typically placed near the root element - * of the page - e.g. on the `<body>` or `<html>` tags. - * - * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` - * found in the document will be used to define the root element to auto-bootstrap as an - * application. To run multiple applications in an HTML document you must manually bootstrap them using - * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. - * - * You can specify an **AngularJS module** to be used as the root module for the application. This - * module will be loaded into the {@link auto.$injector} when the application is bootstrapped and - * should contain the application code needed or have dependencies on other modules that will - * contain the code. See {@link angular.module} for more information. - * - * In the example below if the `ngApp` directive were not placed on the `html` element then the - * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` - * would not be resolved to `3`. - * - * `ngApp` is the easiest, and most common, way to bootstrap an application. - * - <example module="ngAppDemo"> - <file name="index.html"> - <div ng-controller="ngAppDemoController"> - I can add: {{a}} + {{b}} = {{ a+b }} - </div> - </file> - <file name="script.js"> - angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { - $scope.a = 1; - $scope.b = 2; - }); - </file> - </example> - * - * Using `ngStrictDi`, you would see something like this: - * - <example ng-app-included="true"> - <file name="index.html"> - <div ng-app="ngAppStrictDemo" ng-strict-di> - <div ng-controller="GoodController1"> - I can add: {{a}} + {{b}} = {{ a+b }} - - <p>This renders because the controller does not fail to - instantiate, by using explicit annotation style (see - script.js for details) - </p> - </div> - - <div ng-controller="GoodController2"> - Name: <input ng-model="name"><br /> - Hello, {{name}}! - - <p>This renders because the controller does not fail to - instantiate, by using explicit annotation style - (see script.js for details) - </p> - </div> - - <div ng-controller="BadController"> - I can add: {{a}} + {{b}} = {{ a+b }} - - <p>The controller could not be instantiated, due to relying - on automatic function annotations (which are disabled in - strict mode). As such, the content of this section is not - interpolated, and there should be an error in your web console. - </p> - </div> - </div> - </file> - <file name="script.js"> - angular.module('ngAppStrictDemo', []) - // BadController will fail to instantiate, due to relying on automatic function annotation, - // rather than an explicit annotation - .controller('BadController', function($scope) { - $scope.a = 1; - $scope.b = 2; - }) - // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, - // due to using explicit annotations using the array style and $inject property, respectively. - .controller('GoodController1', ['$scope', function($scope) { - $scope.a = 1; - $scope.b = 2; - }]) - .controller('GoodController2', GoodController2); - function GoodController2($scope) { - $scope.name = "World"; - } - GoodController2.$inject = ['$scope']; - </file> - <file name="style.css"> - div[ng-controller] { - margin-bottom: 1em; - -webkit-border-radius: 4px; - border-radius: 4px; - border: 1px solid; - padding: .5em; - } - div[ng-controller^=Good] { - border-color: #d6e9c6; - background-color: #dff0d8; - color: #3c763d; - } - div[ng-controller^=Bad] { - border-color: #ebccd1; - background-color: #f2dede; - color: #a94442; - margin-bottom: 0; - } - </file> - </example> - */ -function angularInit(element, bootstrap) { - var appElement, - module, - config = {}; - - // The element `element` has priority over any other element - forEach(ngAttrPrefixes, function(prefix) { - var name = prefix + 'app'; - - if (!appElement && element.hasAttribute && element.hasAttribute(name)) { - appElement = element; - module = element.getAttribute(name); - } - }); - forEach(ngAttrPrefixes, function(prefix) { - var name = prefix + 'app'; - var candidate; - - if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { - appElement = candidate; - module = candidate.getAttribute(name); - } - }); - if (appElement) { - config.strictDi = getNgAttribute(appElement, "strict-di") !== null; - bootstrap(appElement, module ? [module] : [], config); - } -} - -/** - * @ngdoc function - * @name angular.bootstrap - * @module ng - * @description - * Use this function to manually start up angular application. - * - * See: {@link guide/bootstrap Bootstrap} - * - * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually. - * They must use {@link ng.directive:ngApp ngApp}. - * - * Angular will detect if it has been loaded into the browser more than once and only allow the - * first loaded script to be bootstrapped and will report a warning to the browser console for - * each of the subsequent scripts. This prevents strange results in applications, where otherwise - * multiple instances of Angular try to work on the DOM. - * - * ```html - * <!doctype html> - * <html> - * <body> - * <div ng-controller="WelcomeController"> - * {{greeting}} - * </div> - * - * <script src="angular.js"></script> - * <script> - * var app = angular.module('demo', []) - * .controller('WelcomeController', function($scope) { - * $scope.greeting = 'Welcome!'; - * }); - * angular.bootstrap(document, ['demo']); - * </script> - * </body> - * </html> - * ``` - * - * @param {DOMElement} element DOM element which is the root of angular application. - * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. - * Each item in the array should be the name of a predefined module or a (DI annotated) - * function that will be invoked by the injector as a run block. - * See: {@link angular.module modules} - * @param {Object=} config an object for defining configuration options for the application. The - * following keys are supported: - * - * - `strictDi`: disable automatic function annotation for the application. This is meant to - * assist in finding bugs which break minified code. - * - * @returns {auto.$injector} Returns the newly created injector for this app. - */ -function bootstrap(element, modules, config) { - if (!isObject(config)) config = {}; - var defaultConfig = { - strictDi: false - }; - config = extend(defaultConfig, config); - var doBootstrap = function() { - element = jqLite(element); - - if (element.injector()) { - var tag = (element[0] === document) ? 'document' : startingTag(element); - //Encode angle brackets to prevent input from being sanitized to empty string #8683 - throw ngMinErr( - 'btstrpd', - "App Already Bootstrapped with this Element '{0}'", - tag.replace(/</,'<').replace(/>/,'>')); - } - - modules = modules || []; - modules.unshift(['$provide', function($provide) { - $provide.value('$rootElement', element); - }]); - - if (config.debugInfoEnabled) { - // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. - modules.push(['$compileProvider', function($compileProvider) { - $compileProvider.debugInfoEnabled(true); - }]); - } - - modules.unshift('ng'); - var injector = createInjector(modules, config.strictDi); - injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', - function bootstrapApply(scope, element, compile, injector) { - scope.$apply(function() { - element.data('$injector', injector); - compile(element)(scope); - }); - }] - ); - return injector; - }; - - var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; - var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; - - if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { - config.debugInfoEnabled = true; - window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); - } - - if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { - return doBootstrap(); - } - - window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); - angular.resumeBootstrap = function(extraModules) { - forEach(extraModules, function(module) { - modules.push(module); - }); - doBootstrap(); - }; -} - -/** - * @ngdoc function - * @name angular.reloadWithDebugInfo - * @module ng - * @description - * Use this function to reload the current application with debug information turned on. - * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. - * - * See {@link ng.$compileProvider#debugInfoEnabled} for more. - */ -function reloadWithDebugInfo() { - window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; - window.location.reload(); -} - -/* - * @name angular.getTestability - * @module ng - * @description - * Get the testability service for the instance of Angular on the given - * element. - * @param {DOMElement} element DOM element which is the root of angular application. - */ -function getTestability(rootElement) { - return angular.element(rootElement).injector().get('$$testability'); -} - -var SNAKE_CASE_REGEXP = /[A-Z]/g; -function snake_case(name, separator) { - separator = separator || '_'; - return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { - return (pos ? separator : '') + letter.toLowerCase(); - }); -} - -var bindJQueryFired = false; -var skipDestroyOnNextJQueryCleanData; -function bindJQuery() { - var originalCleanData; - - if (bindJQueryFired) { - return; - } - - // bind to jQuery if present; - jQuery = window.jQuery; - // Use jQuery if it exists with proper functionality, otherwise default to us. - // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. - // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older - // versions. It will not work for sure with jQuery <1.7, though. - if (jQuery && jQuery.fn.on) { - jqLite = jQuery; - extend(jQuery.fn, { - scope: JQLitePrototype.scope, - isolateScope: JQLitePrototype.isolateScope, - controller: JQLitePrototype.controller, - injector: JQLitePrototype.injector, - inheritedData: JQLitePrototype.inheritedData - }); - - // All nodes removed from the DOM via various jQuery APIs like .remove() - // are passed through jQuery.cleanData. Monkey-patch this method to fire - // the $destroy event on all removed nodes. - originalCleanData = jQuery.cleanData; - jQuery.cleanData = function(elems) { - var events; - if (!skipDestroyOnNextJQueryCleanData) { - for (var i = 0, elem; (elem = elems[i]) != null; i++) { - events = jQuery._data(elem, "events"); - if (events && events.$destroy) { - jQuery(elem).triggerHandler('$destroy'); - } - } - } else { - skipDestroyOnNextJQueryCleanData = false; - } - originalCleanData(elems); - }; - } else { - jqLite = JQLite; - } - - angular.element = jqLite; - - // Prevent double-proxying. - bindJQueryFired = true; -} - -/** - * throw error if the argument is falsy. - */ -function assertArg(arg, name, reason) { - if (!arg) { - throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); - } - return arg; -} - -function assertArgFn(arg, name, acceptArrayAnnotation) { - if (acceptArrayAnnotation && isArray(arg)) { - arg = arg[arg.length - 1]; - } - - assertArg(isFunction(arg), name, 'not a function, got ' + - (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); - return arg; -} - -/** - * throw error if the name given is hasOwnProperty - * @param {String} name the name to test - * @param {String} context the context in which the name is used, such as module or directive - */ -function assertNotHasOwnProperty(name, context) { - if (name === 'hasOwnProperty') { - throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); - } -} - -/** - * Return the value accessible from the object by path. Any undefined traversals are ignored - * @param {Object} obj starting object - * @param {String} path path to traverse - * @param {boolean} [bindFnToScope=true] - * @returns {Object} value as accessible by path - */ -//TODO(misko): this function needs to be removed -function getter(obj, path, bindFnToScope) { - if (!path) return obj; - var keys = path.split('.'); - var key; - var lastInstance = obj; - var len = keys.length; - - for (var i = 0; i < len; i++) { - key = keys[i]; - if (obj) { - obj = (lastInstance = obj)[key]; - } - } - if (!bindFnToScope && isFunction(obj)) { - return bind(lastInstance, obj); - } - return obj; -} - -/** - * Return the DOM siblings between the first and last node in the given array. - * @param {Array} array like object - * @returns {jqLite} jqLite collection containing the nodes - */ -function getBlockNodes(nodes) { - // TODO(perf): just check if all items in `nodes` are siblings and if they are return the original - // collection, otherwise update the original collection. - var node = nodes[0]; - var endNode = nodes[nodes.length - 1]; - var blockNodes = [node]; - - do { - node = node.nextSibling; - if (!node) break; - blockNodes.push(node); - } while (node !== endNode); - - return jqLite(blockNodes); -} - - -/** - * Creates a new object without a prototype. This object is useful for lookup without having to - * guard against prototypically inherited properties via hasOwnProperty. - * - * Related micro-benchmarks: - * - http://jsperf.com/object-create2 - * - http://jsperf.com/proto-map-lookup/2 - * - http://jsperf.com/for-in-vs-object-keys2 - * - * @returns {Object} - */ -function createMap() { - return Object.create(null); -} - -/** - * @ngdoc type - * @name angular.Module - * @module ng - * @description - * - * Interface for configuring angular {@link angular.module modules}. - */ - -function setupModuleLoader(window) { - - var $injectorMinErr = minErr('$injector'); - var ngMinErr = minErr('ng'); - - function ensure(obj, name, factory) { - return obj[name] || (obj[name] = factory()); - } - - var angular = ensure(window, 'angular', Object); - - // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap - angular.$$minErr = angular.$$minErr || minErr; - - return ensure(angular, 'module', function() { - /** @type {Object.<string, angular.Module>} */ - var modules = {}; - - /** - * @ngdoc function - * @name angular.module - * @module ng - * @description - * - * The `angular.module` is a global place for creating, registering and retrieving Angular - * modules. - * All modules (angular core or 3rd party) that should be available to an application must be - * registered using this mechanism. - * - * When passed two or more arguments, a new module is created. If passed only one argument, an - * existing module (the name passed as the first argument to `module`) is retrieved. - * - * - * # Module - * - * A module is a collection of services, directives, controllers, filters, and configuration information. - * `angular.module` is used to configure the {@link auto.$injector $injector}. - * - * ```js - * // Create a new module - * var myModule = angular.module('myModule', []); - * - * // register a new service - * myModule.value('appName', 'MyCoolApp'); - * - * // configure existing services inside initialization blocks. - * myModule.config(['$locationProvider', function($locationProvider) { - * // Configure existing providers - * $locationProvider.hashPrefix('!'); - * }]); - * ``` - * - * Then you can create an injector and load your modules like this: - * - * ```js - * var injector = angular.injector(['ng', 'myModule']) - * ``` - * - * However it's more likely that you'll just use - * {@link ng.directive:ngApp ngApp} or - * {@link angular.bootstrap} to simplify this process for you. - * - * @param {!string} name The name of the module to create or retrieve. - * @param {!Array.<string>=} requires If specified then new module is being created. If - * unspecified then the module is being retrieved for further configuration. - * @param {Function=} configFn Optional configuration function for the module. Same as - * {@link angular.Module#config Module#config()}. - * @returns {module} new module with the {@link angular.Module} api. - */ - return function module(name, requires, configFn) { - var assertNotHasOwnProperty = function(name, context) { - if (name === 'hasOwnProperty') { - throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); - } - }; - - assertNotHasOwnProperty(name, 'module'); - if (requires && modules.hasOwnProperty(name)) { - modules[name] = null; - } - return ensure(modules, name, function() { - if (!requires) { - throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + - "the module name or forgot to load it. If registering a module ensure that you " + - "specify the dependencies as the second argument.", name); - } - - /** @type {!Array.<Array.<*>>} */ - var invokeQueue = []; - - /** @type {!Array.<Function>} */ - var configBlocks = []; - - /** @type {!Array.<Function>} */ - var runBlocks = []; - - var config = invokeLater('$injector', 'invoke', 'push', configBlocks); - - /** @type {angular.Module} */ - var moduleInstance = { - // Private state - _invokeQueue: invokeQueue, - _configBlocks: configBlocks, - _runBlocks: runBlocks, - - /** - * @ngdoc property - * @name angular.Module#requires - * @module ng - * - * @description - * Holds the list of modules which the injector will load before the current module is - * loaded. - */ - requires: requires, - - /** - * @ngdoc property - * @name angular.Module#name - * @module ng - * - * @description - * Name of the module. - */ - name: name, - - - /** - * @ngdoc method - * @name angular.Module#provider - * @module ng - * @param {string} name service name - * @param {Function} providerType Construction function for creating new instance of the - * service. - * @description - * See {@link auto.$provide#provider $provide.provider()}. - */ - provider: invokeLater('$provide', 'provider'), - - /** - * @ngdoc method - * @name angular.Module#factory - * @module ng - * @param {string} name service name - * @param {Function} providerFunction Function for creating new instance of the service. - * @description - * See {@link auto.$provide#factory $provide.factory()}. - */ - factory: invokeLater('$provide', 'factory'), - - /** - * @ngdoc method - * @name angular.Module#service - * @module ng - * @param {string} name service name - * @param {Function} constructor A constructor function that will be instantiated. - * @description - * See {@link auto.$provide#service $provide.service()}. - */ - service: invokeLater('$provide', 'service'), - - /** - * @ngdoc method - * @name angular.Module#value - * @module ng - * @param {string} name service name - * @param {*} object Service instance object. - * @description - * See {@link auto.$provide#value $provide.value()}. - */ - value: invokeLater('$provide', 'value'), - - /** - * @ngdoc method - * @name angular.Module#constant - * @module ng - * @param {string} name constant name - * @param {*} object Constant value. - * @description - * Because the constant are fixed, they get applied before other provide methods. - * See {@link auto.$provide#constant $provide.constant()}. - */ - constant: invokeLater('$provide', 'constant', 'unshift'), - - /** - * @ngdoc method - * @name angular.Module#animation - * @module ng - * @param {string} name animation name - * @param {Function} animationFactory Factory function for creating new instance of an - * animation. - * @description - * - * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. - * - * - * Defines an animation hook that can be later used with - * {@link ngAnimate.$animate $animate} service and directives that use this service. - * - * ```js - * module.animation('.animation-name', function($inject1, $inject2) { - * return { - * eventName : function(element, done) { - * //code to run the animation - * //once complete, then run done() - * return function cancellationFunction(element) { - * //code to cancel the animation - * } - * } - * } - * }) - * ``` - * - * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and - * {@link ngAnimate ngAnimate module} for more information. - */ - animation: invokeLater('$animateProvider', 'register'), - - /** - * @ngdoc method - * @name angular.Module#filter - * @module ng - * @param {string} name Filter name. - * @param {Function} filterFactory Factory function for creating new instance of filter. - * @description - * See {@link ng.$filterProvider#register $filterProvider.register()}. - */ - filter: invokeLater('$filterProvider', 'register'), - - /** - * @ngdoc method - * @name angular.Module#controller - * @module ng - * @param {string|Object} name Controller name, or an object map of controllers where the - * keys are the names and the values are the constructors. - * @param {Function} constructor Controller constructor function. - * @description - * See {@link ng.$controllerProvider#register $controllerProvider.register()}. - */ - controller: invokeLater('$controllerProvider', 'register'), - - /** - * @ngdoc method - * @name angular.Module#directive - * @module ng - * @param {string|Object} name Directive name, or an object map of directives where the - * keys are the names and the values are the factories. - * @param {Function} directiveFactory Factory function for creating new instance of - * directives. - * @description - * See {@link ng.$compileProvider#directive $compileProvider.directive()}. - */ - directive: invokeLater('$compileProvider', 'directive'), - - /** - * @ngdoc method - * @name angular.Module#config - * @module ng - * @param {Function} configFn Execute this function on module load. Useful for service - * configuration. - * @description - * Use this method to register work which needs to be performed on module loading. - * For more about how to configure services, see - * {@link providers#providers_provider-recipe Provider Recipe}. - */ - config: config, - - /** - * @ngdoc method - * @name angular.Module#run - * @module ng - * @param {Function} initializationFn Execute this function after injector creation. - * Useful for application initialization. - * @description - * Use this method to register work which should be performed when the injector is done - * loading all modules. - */ - run: function(block) { - runBlocks.push(block); - return this; - } - }; - - if (configFn) { - config(configFn); - } - - return moduleInstance; - - /** - * @param {string} provider - * @param {string} method - * @param {String=} insertMethod - * @returns {angular.Module} - */ - function invokeLater(provider, method, insertMethod, queue) { - if (!queue) queue = invokeQueue; - return function() { - queue[insertMethod || 'push']([provider, method, arguments]); - return moduleInstance; - }; - } - }); - }; - }); - -} - -/* global angularModule: true, - version: true, - - $LocaleProvider, - $CompileProvider, - - htmlAnchorDirective, - inputDirective, - inputDirective, - formDirective, - scriptDirective, - selectDirective, - styleDirective, - optionDirective, - ngBindDirective, - ngBindHtmlDirective, - ngBindTemplateDirective, - ngClassDirective, - ngClassEvenDirective, - ngClassOddDirective, - ngCspDirective, - ngCloakDirective, - ngControllerDirective, - ngFormDirective, - ngHideDirective, - ngIfDirective, - ngIncludeDirective, - ngIncludeFillContentDirective, - ngInitDirective, - ngNonBindableDirective, - ngPluralizeDirective, - ngRepeatDirective, - ngShowDirective, - ngStyleDirective, - ngSwitchDirective, - ngSwitchWhenDirective, - ngSwitchDefaultDirective, - ngOptionsDirective, - ngTranscludeDirective, - ngModelDirective, - ngListDirective, - ngChangeDirective, - patternDirective, - patternDirective, - requiredDirective, - requiredDirective, - minlengthDirective, - minlengthDirective, - maxlengthDirective, - maxlengthDirective, - ngValueDirective, - ngModelOptionsDirective, - ngAttributeAliasDirectives, - ngEventDirectives, - - $AnchorScrollProvider, - $AnimateProvider, - $BrowserProvider, - $CacheFactoryProvider, - $ControllerProvider, - $DocumentProvider, - $ExceptionHandlerProvider, - $FilterProvider, - $InterpolateProvider, - $IntervalProvider, - $HttpProvider, - $HttpBackendProvider, - $LocationProvider, - $LogProvider, - $ParseProvider, - $RootScopeProvider, - $QProvider, - $$QProvider, - $$SanitizeUriProvider, - $SceProvider, - $SceDelegateProvider, - $SnifferProvider, - $TemplateCacheProvider, - $TemplateRequestProvider, - $$TestabilityProvider, - $TimeoutProvider, - $$RAFProvider, - $$AsyncCallbackProvider, - $WindowProvider -*/ - - -/** - * @ngdoc object - * @name angular.version - * @module ng - * @description - * An object that contains information about the current AngularJS version. This object has the - * following properties: - * - * - `full` – `{string}` – Full version string, such as "0.9.18". - * - `major` – `{number}` – Major version number, such as "0". - * - `minor` – `{number}` – Minor version number, such as "9". - * - `dot` – `{number}` – Dot version number, such as "18". - * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". - */ -var version = { - full: '1.3.0-rc.1', // all of these placeholder strings will be replaced by grunt's - major: 1, // package task - minor: 3, - dot: 0, - codeName: 'backyard-atomicity' -}; - - -function publishExternalAPI(angular){ - extend(angular, { - 'bootstrap': bootstrap, - 'copy': copy, - 'extend': extend, - 'equals': equals, - 'element': jqLite, - 'forEach': forEach, - 'injector': createInjector, - 'noop': noop, - 'bind': bind, - 'toJson': toJson, - 'fromJson': fromJson, - 'identity': identity, - 'isUndefined': isUndefined, - 'isDefined': isDefined, - 'isString': isString, - 'isFunction': isFunction, - 'isObject': isObject, - 'isNumber': isNumber, - 'isElement': isElement, - 'isArray': isArray, - 'version': version, - 'isDate': isDate, - 'lowercase': lowercase, - 'uppercase': uppercase, - 'callbacks': {counter: 0}, - 'getTestability': getTestability, - '$$minErr': minErr, - '$$csp': csp, - 'reloadWithDebugInfo': reloadWithDebugInfo, - '$$hasClass': jqLiteHasClass - }); - - angularModule = setupModuleLoader(window); - try { - angularModule('ngLocale'); - } catch (e) { - angularModule('ngLocale', []).provider('$locale', $LocaleProvider); - } - - angularModule('ng', ['ngLocale'], ['$provide', - function ngModule($provide) { - // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. - $provide.provider({ - $$sanitizeUri: $$SanitizeUriProvider - }); - $provide.provider('$compile', $CompileProvider). - directive({ - a: htmlAnchorDirective, - input: inputDirective, - textarea: inputDirective, - form: formDirective, - script: scriptDirective, - select: selectDirective, - style: styleDirective, - option: optionDirective, - ngBind: ngBindDirective, - ngBindHtml: ngBindHtmlDirective, - ngBindTemplate: ngBindTemplateDirective, - ngClass: ngClassDirective, - ngClassEven: ngClassEvenDirective, - ngClassOdd: ngClassOddDirective, - ngCloak: ngCloakDirective, - ngController: ngControllerDirective, - ngForm: ngFormDirective, - ngHide: ngHideDirective, - ngIf: ngIfDirective, - ngInclude: ngIncludeDirective, - ngInit: ngInitDirective, - ngNonBindable: ngNonBindableDirective, - ngPluralize: ngPluralizeDirective, - ngRepeat: ngRepeatDirective, - ngShow: ngShowDirective, - ngStyle: ngStyleDirective, - ngSwitch: ngSwitchDirective, - ngSwitchWhen: ngSwitchWhenDirective, - ngSwitchDefault: ngSwitchDefaultDirective, - ngOptions: ngOptionsDirective, - ngTransclude: ngTranscludeDirective, - ngModel: ngModelDirective, - ngList: ngListDirective, - ngChange: ngChangeDirective, - pattern: patternDirective, - ngPattern: patternDirective, - required: requiredDirective, - ngRequired: requiredDirective, - minlength: minlengthDirective, - ngMinlength: minlengthDirective, - maxlength: maxlengthDirective, - ngMaxlength: maxlengthDirective, - ngValue: ngValueDirective, - ngModelOptions: ngModelOptionsDirective - }). - directive({ - ngInclude: ngIncludeFillContentDirective - }). - directive(ngAttributeAliasDirectives). - directive(ngEventDirectives); - $provide.provider({ - $anchorScroll: $AnchorScrollProvider, - $animate: $AnimateProvider, - $browser: $BrowserProvider, - $cacheFactory: $CacheFactoryProvider, - $controller: $ControllerProvider, - $document: $DocumentProvider, - $exceptionHandler: $ExceptionHandlerProvider, - $filter: $FilterProvider, - $interpolate: $InterpolateProvider, - $interval: $IntervalProvider, - $http: $HttpProvider, - $httpBackend: $HttpBackendProvider, - $location: $LocationProvider, - $log: $LogProvider, - $parse: $ParseProvider, - $rootScope: $RootScopeProvider, - $q: $QProvider, - $$q: $$QProvider, - $sce: $SceProvider, - $sceDelegate: $SceDelegateProvider, - $sniffer: $SnifferProvider, - $templateCache: $TemplateCacheProvider, - $templateRequest: $TemplateRequestProvider, - $$testability: $$TestabilityProvider, - $timeout: $TimeoutProvider, - $window: $WindowProvider, - $$rAF: $$RAFProvider, - $$asyncCallback : $$AsyncCallbackProvider - }); - } - ]); -} - -/* global JQLitePrototype: true, - addEventListenerFn: true, - removeEventListenerFn: true, - BOOLEAN_ATTR: true, - ALIASED_ATTR: true, -*/ - -////////////////////////////////// -//JQLite -////////////////////////////////// - -/** - * @ngdoc function - * @name angular.element - * @module ng - * @kind function - * - * @description - * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. - * - * If jQuery is available, `angular.element` is an alias for the - * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` - * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." - * - * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows - * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most - * commonly needed functionality with the goal of having a very small footprint.</div> - * - * To use jQuery, simply load it before `DOMContentLoaded` event fired. - * - * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or - * jqLite; they are never raw DOM references.</div> - * - * ## Angular's jqLite - * jqLite provides only the following jQuery methods: - * - * - [`addClass()`](http://api.jquery.com/addClass/) - * - [`after()`](http://api.jquery.com/after/) - * - [`append()`](http://api.jquery.com/append/) - * - [`attr()`](http://api.jquery.com/attr/) - * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData - * - [`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/) - * - [`detach()`](http://api.jquery.com/detach/) - * - [`empty()`](http://api.jquery.com/empty/) - * - [`eq()`](http://api.jquery.com/eq/) - * - [`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/) - Does not support selectors - * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData - * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors - * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or 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/) - * - [`remove()`](http://api.jquery.com/remove/) - * - [`removeAttr()`](http://api.jquery.com/removeAttr/) - * - [`removeClass()`](http://api.jquery.com/removeClass/) - * - [`removeData()`](http://api.jquery.com/removeData/) - * - [`replaceWith()`](http://api.jquery.com/replaceWith/) - * - [`text()`](http://api.jquery.com/text/) - * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. - * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces - * - [`val()`](http://api.jquery.com/val/) - * - [`wrap()`](http://api.jquery.com/wrap/) - * - * ## jQuery/jqLite Extras - * Angular also provides the following additional methods and events to both jQuery and jqLite: - * - * ### Events - * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event - * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM - * element before it is removed. - * - * ### Methods - * - `controller(name)` - retrieves the controller of the current element or its parent. By default - * retrieves controller associated with the `ngController` directive. If `name` is provided as - * camelCase directive name, then the controller for this directive will be retrieved (e.g. - * `'ngModel'`). - * - `injector()` - retrieves the injector of the current element or its parent. - * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current - * element or its parent. - * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the - * current element. This getter should be used only on elements that contain a directive which starts a new isolate - * scope. Calling `scope()` on this element always returns the original non-isolate scope. - * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top - * parent element is reached. - * - * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. - * @returns {Object} jQuery object. - */ - -JQLite.expando = 'ng339'; - -var jqCache = JQLite.cache = {}, - jqId = 1, - addEventListenerFn = function(element, type, fn) { - element.addEventListener(type, fn, false); - }, - removeEventListenerFn = function(element, type, fn) { - element.removeEventListener(type, fn, false); - }; - -/* - * !!! This is an undocumented "private" function !!! - */ -JQLite._data = function(node) { - //jQuery always returns an object on cache miss - return this.cache[node[this.expando]] || {}; -}; - -function jqNextId() { return ++jqId; } - - -var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; -var MOZ_HACK_REGEXP = /^moz([A-Z])/; -var MOUSE_EVENT_MAP= { mouseleave : "mouseout", mouseenter : "mouseover"}; -var jqLiteMinErr = minErr('jqLite'); - -/** - * Converts snake_case to camelCase. - * Also there is special case for Moz prefix starting with upper case letter. - * @param name Name to normalize - */ -function camelCase(name) { - return name. - replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { - return offset ? letter.toUpperCase() : letter; - }). - replace(MOZ_HACK_REGEXP, 'Moz$1'); -} - -var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; -var HTML_REGEXP = /<|&#?\w+;/; -var TAG_NAME_REGEXP = /<([\w:]+)/; -var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; - -var wrapMap = { - 'option': [1, '<select multiple="multiple">', '</select>'], - - 'thead': [1, '<table>', '</table>'], - 'col': [2, '<table><colgroup>', '</colgroup></table>'], - 'tr': [2, '<table><tbody>', '</tbody></table>'], - 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], - '_default': [0, "", ""] -}; - -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - - -function jqLiteIsTextNode(html) { - return !HTML_REGEXP.test(html); -} - -function jqLiteAcceptsData(node) { - // The window object can accept data but has no nodeType - // Otherwise we are only interested in elements (1) and documents (9) - var nodeType = node.nodeType; - return nodeType === 1 || !nodeType || nodeType === 9; -} - -function jqLiteBuildFragment(html, context) { - var tmp, tag, wrap, - fragment = context.createDocumentFragment(), - nodes = [], i; - - if (jqLiteIsTextNode(html)) { - // Convert non-html into a text node - nodes.push(context.createTextNode(html)); - } else { - // Convert html into DOM nodes - tmp = tmp || fragment.appendChild(context.createElement("div")); - tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); - wrap = wrapMap[tag] || wrapMap._default; - tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; - - // Descend through wrappers to the right content - i = wrap[0]; - while (i--) { - tmp = tmp.lastChild; - } - - nodes = concat(nodes, tmp.childNodes); - - tmp = fragment.firstChild; - tmp.textContent = ""; - } - - // Remove wrapper from fragment - fragment.textContent = ""; - fragment.innerHTML = ""; // Clear inner HTML - forEach(nodes, function(node) { - fragment.appendChild(node); - }); - - return fragment; -} - -function jqLiteParseHTML(html, context) { - context = context || document; - var parsed; - - if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { - return [context.createElement(parsed[1])]; - } - - if ((parsed = jqLiteBuildFragment(html, context))) { - return parsed.childNodes; - } - - return []; -} - -///////////////////////////////////////////// -function JQLite(element) { - if (element instanceof JQLite) { - return element; - } - - var argIsString; - - if (isString(element)) { - element = trim(element); - argIsString = true; - } - if (!(this instanceof JQLite)) { - if (argIsString && element.charAt(0) != '<') { - throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); - } - return new JQLite(element); - } - - if (argIsString) { - jqLiteAddNodes(this, jqLiteParseHTML(element)); - } else { - jqLiteAddNodes(this, element); - } -} - -function jqLiteClone(element) { - return element.cloneNode(true); -} - -function jqLiteDealoc(element, onlyDescendants){ - if (!onlyDescendants) jqLiteRemoveData(element); - - if (element.querySelectorAll) { - var descendants = element.querySelectorAll('*'); - for (var i = 0, l = descendants.length; i < l; i++) { - jqLiteRemoveData(descendants[i]); - } - } -} - -function jqLiteOff(element, type, fn, unsupported) { - if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); - - var expandoStore = jqLiteExpandoStore(element); - var events = expandoStore && expandoStore.events; - var handle = expandoStore && expandoStore.handle; - - if (!handle) return; //no listeners registered - - if (!type) { - for (type in events) { - if (type !== '$destroy') { - removeEventListenerFn(element, type, events[type]); - } - delete events[type]; - } - } else { - forEach(type.split(' '), function(type) { - if (isUndefined(fn)) { - removeEventListenerFn(element, type, events[type]); - delete events[type]; - } else { - arrayRemove(events[type] || [], fn); - } - }); - } -} - -function jqLiteRemoveData(element, name) { - var expandoId = element.ng339; - var expandoStore = expandoId && jqCache[expandoId]; - - if (expandoStore) { - if (name) { - delete expandoStore.data[name]; - return; - } - - if (expandoStore.handle) { - if (expandoStore.events.$destroy) { - expandoStore.handle({}, '$destroy'); - } - jqLiteOff(element); - } - delete jqCache[expandoId]; - element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it - } -} - - -function jqLiteExpandoStore(element, createIfNecessary) { - var expandoId = element.ng339, - expandoStore = expandoId && jqCache[expandoId]; - - if (createIfNecessary && !expandoStore) { - element.ng339 = expandoId = jqNextId(); - expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; - } - - return expandoStore; -} - - -function jqLiteData(element, key, value) { - if (jqLiteAcceptsData(element)) { - - var isSimpleSetter = isDefined(value); - var isSimpleGetter = !isSimpleSetter && key && !isObject(key); - var massGetter = !key; - var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); - var data = expandoStore && expandoStore.data; - - if (isSimpleSetter) { // data('key', value) - data[key] = value; - } else { - if (massGetter) { // data() - return data; - } else { - if (isSimpleGetter) { // data('key') - // don't force creation of expandoStore if it doesn't exist yet - return data && data[key]; - } else { // mass-setter: data({key1: val1, key2: val2}) - extend(data, key); - } - } - } - } -} - -function jqLiteHasClass(element, selector) { - if (!element.getAttribute) return false; - return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). - indexOf( " " + selector + " " ) > -1); -} - -function jqLiteRemoveClass(element, cssClasses) { - if (cssClasses && element.setAttribute) { - forEach(cssClasses.split(' '), function(cssClass) { - element.setAttribute('class', trim( - (" " + (element.getAttribute('class') || '') + " ") - .replace(/[\n\t]/g, " ") - .replace(" " + trim(cssClass) + " ", " ")) - ); - }); - } -} - -function jqLiteAddClass(element, cssClasses) { - if (cssClasses && element.setAttribute) { - var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') - .replace(/[\n\t]/g, " "); - - forEach(cssClasses.split(' '), function(cssClass) { - cssClass = trim(cssClass); - if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { - existingClasses += cssClass + ' '; - } - }); - - element.setAttribute('class', trim(existingClasses)); - } -} - - -function jqLiteAddNodes(root, elements) { - // THIS CODE IS VERY HOT. Don't make changes without benchmarking. - - if (elements) { - - // if a Node (the most common case) - if (elements.nodeType) { - root[root.length++] = elements; - } else { - var length = elements.length; - - // if an Array or NodeList and not a Window - if (typeof length === 'number' && elements.window !== elements) { - if (length) { - for (var i = 0; i < length; i++) { - root[root.length++] = elements[i]; - } - } - } else { - root[root.length++] = elements; - } - } - } -} - - -function jqLiteController(element, name) { - return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); -} - -function jqLiteInheritedData(element, name, value) { - // if element is the document object work with the html element instead - // this makes $(document).scope() possible - if(element.nodeType == 9) { - element = element.documentElement; - } - var names = isArray(name) ? name : [name]; - - while (element) { - for (var i = 0, ii = names.length; i < ii; i++) { - if ((value = jqLite.data(element, names[i])) !== undefined) return value; - } - - // If dealing with a document fragment node with a host element, and no parent, use the host - // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM - // to lookup parent controllers. - element = element.parentNode || (element.nodeType === 11 && element.host); - } -} - -function jqLiteEmpty(element) { - jqLiteDealoc(element, true); - while (element.firstChild) { - element.removeChild(element.firstChild); - } -} - -function jqLiteRemove(element, keepData) { - if (!keepData) jqLiteDealoc(element); - var parent = element.parentNode; - if (parent) parent.removeChild(element); -} - -////////////////////////////////////////// -// Functions which are declared directly. -////////////////////////////////////////// -var JQLitePrototype = JQLite.prototype = { - ready: function(fn) { - var fired = false; - - function trigger() { - if (fired) return; - fired = true; - fn(); - } - - // check if document is already loaded - if (document.readyState === 'complete'){ - setTimeout(trigger); - } else { - this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 - // we can not use jqLite since we are not done loading and jQuery could be loaded later. - // jshint -W064 - JQLite(window).on('load', trigger); // fallback to window.onload for others - // jshint +W064 - this.on('DOMContentLoaded', trigger); - } - }, - toString: function() { - var value = []; - forEach(this, function(e){ value.push('' + e);}); - return '[' + value.join(', ') + ']'; - }, - - eq: function(index) { - return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); - }, - - length: 0, - push: push, - sort: [].sort, - splice: [].splice -}; - -////////////////////////////////////////// -// Functions iterating getter/setters. -// these functions return self on setter and -// value on get. -////////////////////////////////////////// -var BOOLEAN_ATTR = {}; -forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { - BOOLEAN_ATTR[lowercase(value)] = value; -}); -var BOOLEAN_ELEMENTS = {}; -forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { - BOOLEAN_ELEMENTS[value] = true; -}); -var ALIASED_ATTR = { - 'ngMinlength' : 'minlength', - 'ngMaxlength' : 'maxlength', - 'ngMin' : 'min', - 'ngMax' : 'max', - 'ngPattern' : 'pattern' -}; - -function getBooleanAttrName(element, name) { - // check dom last since we will most likely fail on name - var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; - - // booleanAttr is here twice to minimize DOM access - return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; -} - -function getAliasedAttrName(element, name) { - var nodeName = element.nodeName; - return (nodeName === 'INPUT' || nodeName === 'TEXTAREA') && ALIASED_ATTR[name]; -} - -forEach({ - data: jqLiteData, - removeData: jqLiteRemoveData -}, function(fn, name) { - JQLite[name] = fn; -}); - -forEach({ - data: jqLiteData, - inheritedData: jqLiteInheritedData, - - scope: function(element) { - // Can't use jqLiteData here directly so we stay compatible with jQuery! - return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); - }, - - isolateScope: function(element) { - // Can't use jqLiteData here directly so we stay compatible with jQuery! - return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); - }, - - controller: jqLiteController, - - injector: function(element) { - return jqLiteInheritedData(element, '$injector'); - }, - - removeAttr: function(element, name) { - element.removeAttribute(name); - }, - - hasClass: jqLiteHasClass, - - css: function(element, name, value) { - name = camelCase(name); - - if (isDefined(value)) { - element.style[name] = value; - } else { - return element.style[name]; - } - }, - - attr: function(element, name, value){ - var lowercasedName = lowercase(name); - if (BOOLEAN_ATTR[lowercasedName]) { - if (isDefined(value)) { - if (!!value) { - element[name] = true; - element.setAttribute(name, lowercasedName); - } else { - element[name] = false; - element.removeAttribute(lowercasedName); - } - } else { - return (element[name] || - (element.attributes.getNamedItem(name)|| noop).specified) - ? lowercasedName - : undefined; - } - } else if (isDefined(value)) { - element.setAttribute(name, value); - } else if (element.getAttribute) { - // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code - // some elements (e.g. Document) don't have get attribute, so return undefined - var ret = element.getAttribute(name, 2); - // normalize non-existing attributes to undefined (as jQuery) - return ret === null ? undefined : ret; - } - }, - - prop: function(element, name, value) { - if (isDefined(value)) { - element[name] = value; - } else { - return element[name]; - } - }, - - text: (function() { - getText.$dv = ''; - return getText; - - function getText(element, value) { - if (isUndefined(value)) { - var nodeType = element.nodeType; - return (nodeType === 1 || nodeType === 3) ? element.textContent : ''; - } - element.textContent = value; - } - })(), - - val: function(element, value) { - if (isUndefined(value)) { - if (element.multiple && nodeName_(element) === 'select') { - var result = []; - forEach(element.options, function (option) { - if (option.selected) { - result.push(option.value || option.text); - } - }); - return result.length === 0 ? null : result; - } - return element.value; - } - element.value = value; - }, - - html: function(element, value) { - if (isUndefined(value)) { - return element.innerHTML; - } - jqLiteDealoc(element, true); - element.innerHTML = value; - }, - - empty: jqLiteEmpty -}, function(fn, name){ - /** - * Properties: writes return selection, reads return first value - */ - JQLite.prototype[name] = function(arg1, arg2) { - var i, key; - var nodeCount = this.length; - - // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it - // in a way that survives minification. - // jqLiteEmpty takes no arguments but is a setter. - if (fn !== jqLiteEmpty && - (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { - if (isObject(arg1)) { - - // we are a write, but the object properties are the key/values - for (i = 0; i < nodeCount; i++) { - if (fn === jqLiteData) { - // data() takes the whole object in jQuery - fn(this[i], arg1); - } else { - for (key in arg1) { - fn(this[i], key, arg1[key]); - } - } - } - // return self for chaining - return this; - } else { - // we are a read, so read the first child. - // TODO: do we still need this? - var value = fn.$dv; - // Only if we have $dv do we iterate over all, otherwise it is just the first element. - var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount; - for (var j = 0; j < jj; j++) { - var nodeValue = fn(this[j], arg1, arg2); - value = value ? value + nodeValue : nodeValue; - } - return value; - } - } else { - // we are a write, so apply to all children - for (i = 0; i < nodeCount; i++) { - fn(this[i], arg1, arg2); - } - // return self for chaining - return this; - } - }; -}); - -function createEventHandler(element, events) { - var eventHandler = function (event, type) { - - // jQuery specific api - event.isDefaultPrevented = function() { - return event.defaultPrevented; - }; - - var eventFns = events[type || event.type]; - var eventFnsLength = eventFns ? eventFns.length : 0; - - if (!eventFnsLength) return; - - // Copy event handlers in case event handlers array is modified during execution. - if ((eventFnsLength > 1)) { - eventFns = shallowCopy(eventFns); - } - - for (var i = 0; i < eventFnsLength; i++) { - eventFns[i].call(element, event); - } - }; - - // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all - // events on `element` - eventHandler.elem = element; - return eventHandler; -} - -////////////////////////////////////////// -// Functions iterating traversal. -// These functions chain results into a single -// selector. -////////////////////////////////////////// -forEach({ - removeData: jqLiteRemoveData, - - on: function jqLiteOn(element, type, fn, unsupported){ - if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); - - // Do not add event handlers to non-elements because they will not be cleaned up. - if (!jqLiteAcceptsData(element)) { - return; - } - - var expandoStore = jqLiteExpandoStore(element, true); - var events = expandoStore.events; - var handle = expandoStore.handle; - - if (!handle) { - handle = expandoStore.handle = createEventHandler(element, events); - } - - // http://jsperf.com/string-indexof-vs-split - var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; - var i = types.length; - - while (i--) { - type = types[i]; - var eventFns = events[type]; - - if (!eventFns) { - events[type] = []; - - if (type === 'mouseenter' || type === 'mouseleave') { - // Refer to jQuery's implementation of mouseenter & mouseleave - // Read about mouseenter and mouseleave: - // http://www.quirksmode.org/js/events_mouse.html#link8 - - jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) { - var 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 && !target.contains(related)) ){ - handle(event, type); - } - }); - - } else { - if (type !== '$destroy') { - addEventListenerFn(element, type, handle); - } - } - eventFns = events[type]; - } - eventFns.push(fn); - } - }, - - off: jqLiteOff, - - one: function(element, type, fn) { - element = jqLite(element); - - //add the listener twice so that when it is called - //you can remove the original function and still be - //able to call element.off(ev, fn) normally - element.on(type, function onFn() { - element.off(type, fn); - element.off(type, onFn); - }); - element.on(type, fn); - }, - - replaceWith: function(element, replaceNode) { - var index, parent = element.parentNode; - jqLiteDealoc(element); - forEach(new JQLite(replaceNode), function(node){ - if (index) { - parent.insertBefore(node, index.nextSibling); - } else { - parent.replaceChild(node, element); - } - index = node; - }); - }, - - children: function(element) { - var children = []; - forEach(element.childNodes, function(element){ - if (element.nodeType === 1) - children.push(element); - }); - return children; - }, - - contents: function(element) { - return element.contentDocument || element.childNodes || []; - }, - - append: function(element, node) { - var nodeType = element.nodeType; - if (nodeType !== 1 && nodeType !== 11) return; - - node = new JQLite(node); - - for (var i = 0, ii = node.length; i < ii; i++) { - var child = node[i]; - element.appendChild(child); - } - }, - - prepend: function(element, node) { - if (element.nodeType === 1) { - var index = element.firstChild; - forEach(new JQLite(node), function(child){ - element.insertBefore(child, index); - }); - } - }, - - wrap: function(element, wrapNode) { - wrapNode = jqLite(wrapNode).eq(0).clone()[0]; - var parent = element.parentNode; - if (parent) { - parent.replaceChild(wrapNode, element); - } - wrapNode.appendChild(element); - }, - - remove: jqLiteRemove, - - detach: function(element) { - jqLiteRemove(element, true); - }, - - after: function(element, newElement) { - var index = element, parent = element.parentNode; - newElement = new JQLite(newElement); - - for (var i = 0, ii = newElement.length; i < ii; i++) { - var node = newElement[i]; - parent.insertBefore(node, index.nextSibling); - index = node; - } - }, - - addClass: jqLiteAddClass, - removeClass: jqLiteRemoveClass, - - toggleClass: function(element, selector, condition) { - if (selector) { - forEach(selector.split(' '), function(className){ - var classCondition = condition; - if (isUndefined(classCondition)) { - classCondition = !jqLiteHasClass(element, className); - } - (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); - }); - } - }, - - parent: function(element) { - var parent = element.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - - next: function(element) { - return element.nextElementSibling; - }, - - find: function(element, selector) { - if (element.getElementsByTagName) { - return element.getElementsByTagName(selector); - } else { - return []; - } - }, - - clone: jqLiteClone, - - triggerHandler: function(element, event, extraParameters) { - - var dummyEvent, eventFnsCopy, handlerArgs; - var eventName = event.type || event; - var expandoStore = jqLiteExpandoStore(element); - var events = expandoStore && expandoStore.events; - var eventFns = events && events[eventName]; - - if (eventFns) { - - // Create a dummy event to pass to the handlers - dummyEvent = { - preventDefault: function() { this.defaultPrevented = true; }, - isDefaultPrevented: function() { return this.defaultPrevented === true; }, - stopPropagation: noop, - type: eventName, - target: element - }; - - // If a custom event was provided then extend our dummy event with it - if (event.type) { - dummyEvent = extend(dummyEvent, event); - } - - // Copy event handlers in case event handlers array is modified during execution. - eventFnsCopy = shallowCopy(eventFns); - handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; - - forEach(eventFnsCopy, function(fn) { - fn.apply(element, handlerArgs); - }); - - } - } -}, function(fn, name){ - /** - * chaining functions - */ - JQLite.prototype[name] = function(arg1, arg2, arg3) { - var value; - - for(var i = 0, ii = this.length; i < ii; i++) { - if (isUndefined(value)) { - value = fn(this[i], arg1, arg2, arg3); - if (isDefined(value)) { - // any function which returns a value needs to be wrapped - value = jqLite(value); - } - } else { - jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); - } - } - return isDefined(value) ? value : this; - }; - - // bind legacy bind/unbind to on/off - JQLite.prototype.bind = JQLite.prototype.on; - JQLite.prototype.unbind = JQLite.prototype.off; -}); - -/** - * Computes a hash of an 'obj'. - * Hash of a: - * string is string - * number is number as string - * object is either result of calling $$hashKey function on the object or uniquely generated id, - * that is also assigned to the $$hashKey property of the object. - * - * @param obj - * @returns {string} hash string such that the same input will have the same hash string. - * The resulting string key is in 'type:hashKey' format. - */ -function hashKey(obj, nextUidFn) { - var key = obj && obj.$$hashKey; - - if (key) { - if (typeof key === 'function') { - key = obj.$$hashKey(); - } - return key; - } - - var objType = typeof obj; - if (objType == 'function' || (objType == 'object' && obj !== null)) { - key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); - } else { - key = objType + ':' + obj; - } - - return key; -} - -/** - * HashMap which can use objects as keys - */ -function HashMap(array, isolatedUid) { - if (isolatedUid) { - var uid = 0; - this.nextUid = function() { - return ++uid; - }; - } - forEach(array, this.put, this); -} -HashMap.prototype = { - /** - * Store key value pair - * @param key key to store can be any type - * @param value value to store can be any type - */ - put: function(key, value) { - this[hashKey(key, this.nextUid)] = value; - }, - - /** - * @param key - * @returns {Object} the value for the key - */ - get: function(key) { - return this[hashKey(key, this.nextUid)]; - }, - - /** - * Remove the key/value pair - * @param key - */ - remove: function(key) { - var value = this[key = hashKey(key, this.nextUid)]; - delete this[key]; - return value; - } -}; - -/** - * @ngdoc function - * @module ng - * @name angular.injector - * @kind function - * - * @description - * Creates an injector function that can be used for retrieving services as well as for - * dependency injection (see {@link guide/di dependency injection}). - * - - * @param {Array.<string|Function>} modules A list of module functions or their aliases. See - * {@link angular.module}. The `ng` module must be explicitly added. - * @returns {function()} Injector function. See {@link auto.$injector $injector}. - * - * @example - * Typical usage - * ```js - * // create an injector - * var $injector = angular.injector(['ng']); - * - * // use the injector to kick off your application - * // use the type inference to auto inject arguments, or use implicit injection - * $injector.invoke(function($rootScope, $compile, $document) { - * $compile($document)($rootScope); - * $rootScope.$digest(); - * }); - * ``` - * - * Sometimes you want to get access to the injector of a currently running Angular app - * from outside Angular. Perhaps, you want to inject and compile some markup after the - * application has been bootstrapped. You can do this using the extra `injector()` added - * to JQuery/jqLite elements. See {@link angular.element}. - * - * *This is fairly rare but could be the case if a third party library is injecting the - * markup.* - * - * In the following example a new block of HTML containing a `ng-controller` - * directive is added to the end of the document body by JQuery. We then compile and link - * it into the current AngularJS scope. - * - * ```js - * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); - * $(document.body).append($div); - * - * angular.element(document).injector().invoke(function($compile) { - * var scope = angular.element($div).scope(); - * $compile($div)(scope); - * }); - * ``` - */ - - -/** - * @ngdoc module - * @name auto - * @description - * - * Implicit module which gets automatically added to each {@link auto.$injector $injector}. - */ - -var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; -var FN_ARG_SPLIT = /,/; -var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; -var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; -var $injectorMinErr = minErr('$injector'); - -function anonFn(fn) { - // For anonymous functions, showing at the very least the function signature can help in - // debugging. - var fnText = fn.toString().replace(STRIP_COMMENTS, ''), - args = fnText.match(FN_ARGS); - if (args) { - return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; - } - return 'fn'; -} - -function annotate(fn, strictDi, name) { - var $inject, - fnText, - argDecl, - last; - - if (typeof fn === 'function') { - if (!($inject = fn.$inject)) { - $inject = []; - if (fn.length) { - if (strictDi) { - if (!isString(name) || !name) { - name = fn.name || anonFn(fn); - } - throw $injectorMinErr('strictdi', - '{0} is not using explicit annotation and cannot be invoked in strict mode', name); - } - fnText = fn.toString().replace(STRIP_COMMENTS, ''); - argDecl = fnText.match(FN_ARGS); - forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { - arg.replace(FN_ARG, function(all, underscore, name) { - $inject.push(name); - }); - }); - } - fn.$inject = $inject; - } - } else if (isArray(fn)) { - last = fn.length - 1; - assertArgFn(fn[last], 'fn'); - $inject = fn.slice(0, last); - } else { - assertArgFn(fn, 'fn', true); - } - return $inject; -} - -/////////////////////////////////////// - -/** - * @ngdoc service - * @name $injector - * @kind function - * - * @description - * - * `$injector` is used to retrieve object instances as defined by - * {@link auto.$provide provider}, instantiate types, invoke methods, - * and load modules. - * - * The following always holds true: - * - * ```js - * var $injector = angular.injector(); - * expect($injector.get('$injector')).toBe($injector); - * expect($injector.invoke(function($injector) { - * return $injector; - * }).toBe($injector); - * ``` - * - * # Injection Function Annotation - * - * JavaScript does not have annotations, and annotations are needed for dependency injection. The - * following are all valid ways of annotating function with injection arguments and are equivalent. - * - * ```js - * // inferred (only works if code not minified/obfuscated) - * $injector.invoke(function(serviceA){}); - * - * // annotated - * function explicit(serviceA) {}; - * explicit.$inject = ['serviceA']; - * $injector.invoke(explicit); - * - * // inline - * $injector.invoke(['serviceA', function(serviceA){}]); - * ``` - * - * ## Inference - * - * In JavaScript calling `toString()` on a function returns the function definition. The definition - * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with - * minification, and obfuscation tools since these tools change the argument names. - * - * ## `$inject` Annotation - * By adding an `$inject` property onto a function the injection parameters can be specified. - * - * ## Inline - * As an array of injection names, where the last item in the array is the function to call. - */ - -/** - * @ngdoc method - * @name $injector#get - * - * @description - * Return an instance of the service. - * - * @param {string} name The name of the instance to retrieve. - * @return {*} The instance. - */ - -/** - * @ngdoc method - * @name $injector#invoke - * - * @description - * Invoke the method and supply the method arguments from the `$injector`. - * - * @param {!Function} fn The function to invoke. Function parameters are injected according to the - * {@link guide/di $inject Annotation} rules. - * @param {Object=} self The `this` for the invoked method. - * @param {Object=} locals Optional object. If preset then any argument names are read from this - * object first, before the `$injector` is consulted. - * @returns {*} the value returned by the invoked `fn` function. - */ - -/** - * @ngdoc method - * @name $injector#has - * - * @description - * Allows the user to query if the particular service exists. - * - * @param {string} Name of the service to query. - * @returns {boolean} returns true if injector has given service. - */ - -/** - * @ngdoc method - * @name $injector#instantiate - * @description - * Create a new instance of JS type. The method takes a constructor function, invokes the new - * operator, and supplies all of the arguments to the constructor function as specified by the - * constructor annotation. - * - * @param {Function} Type Annotated constructor function. - * @param {Object=} locals Optional object. If preset then any argument names are read from this - * object first, before the `$injector` is consulted. - * @returns {Object} new instance of `Type`. - */ - -/** - * @ngdoc method - * @name $injector#annotate - * - * @description - * Returns an array of service names which the function is requesting for injection. This API is - * used by the injector to determine which services need to be injected into the function when the - * function is invoked. There are three ways in which the function can be annotated with the needed - * dependencies. - * - * # Argument names - * - * The simplest form is to extract the dependencies from the arguments of the function. This is done - * by converting the function into a string using `toString()` method and extracting the argument - * names. - * ```js - * // Given - * function MyController($scope, $route) { - * // ... - * } - * - * // Then - * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); - * ``` - * - * This method does not work with code minification / obfuscation. For this reason the following - * annotation strategies are supported. - * - * # The `$inject` property - * - * If a function has an `$inject` property and its value is an array of strings, then the strings - * represent names of services to be injected into the function. - * ```js - * // Given - * var MyController = function(obfuscatedScope, obfuscatedRoute) { - * // ... - * } - * // Define function dependencies - * MyController['$inject'] = ['$scope', '$route']; - * - * // Then - * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); - * ``` - * - * # The array notation - * - * It is often desirable to inline Injected functions and that's when setting the `$inject` property - * is very inconvenient. In these situations using the array notation to specify the dependencies in - * a way that survives minification is a better choice: - * - * ```js - * // We wish to write this (not minification / obfuscation safe) - * injector.invoke(function($compile, $rootScope) { - * // ... - * }); - * - * // We are forced to write break inlining - * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { - * // ... - * }; - * tmpFn.$inject = ['$compile', '$rootScope']; - * injector.invoke(tmpFn); - * - * // To better support inline function the inline annotation is supported - * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { - * // ... - * }]); - * - * // Therefore - * expect(injector.annotate( - * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) - * ).toEqual(['$compile', '$rootScope']); - * ``` - * - * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to - * be retrieved as described above. - * - * @returns {Array.<string>} The names of the services which the function requires. - */ - - - - -/** - * @ngdoc service - * @name $provide - * - * @description - * - * The {@link auto.$provide $provide} service has a number of methods for registering components - * with the {@link auto.$injector $injector}. Many of these functions are also exposed on - * {@link angular.Module}. - * - * An Angular **service** is a singleton object created by a **service factory**. These **service - * factories** are functions which, in turn, are created by a **service provider**. - * The **service providers** are constructor functions. When instantiated they must contain a - * property called `$get`, which holds the **service factory** function. - * - * When you request a service, the {@link auto.$injector $injector} is responsible for finding the - * correct **service provider**, instantiating it and then calling its `$get` **service factory** - * function to get the instance of the **service**. - * - * Often services have no configuration options and there is no need to add methods to the service - * provider. The provider will be no more than a constructor function with a `$get` property. For - * these cases the {@link auto.$provide $provide} service has additional helper methods to register - * services without specifying a provider. - * - * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the - * {@link auto.$injector $injector} - * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by - * providers and services. - * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by - * services, not providers. - * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, - * that will be wrapped in a **service provider** object, whose `$get` property will contain the - * given factory function. - * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` - * that will be wrapped in a **service provider** object, whose `$get` property will instantiate - * a new object using the given constructor function. - * - * See the individual methods for more information and examples. - */ - -/** - * @ngdoc method - * @name $provide#provider - * @description - * - * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions - * are constructor functions, whose instances are responsible for "providing" a factory for a - * service. - * - * Service provider names start with the name of the service they provide followed by `Provider`. - * For example, the {@link ng.$log $log} service has a provider called - * {@link ng.$logProvider $logProvider}. - * - * Service provider objects can have additional methods which allow configuration of the provider - * and its service. Importantly, you can configure what kind of service is created by the `$get` - * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a - * method {@link ng.$logProvider#debugEnabled debugEnabled} - * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the - * console or not. - * - * @param {string} name The name of the instance. NOTE: the provider will be available under `name + - 'Provider'` key. - * @param {(Object|function())} provider If the provider is: - * - * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using - * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. - * - `Constructor`: a new instance of the provider will be created using - * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. - * - * @returns {Object} registered provider instance - - * @example - * - * The following example shows how to create a simple event tracking service and register it using - * {@link auto.$provide#provider $provide.provider()}. - * - * ```js - * // Define the eventTracker provider - * function EventTrackerProvider() { - * var trackingUrl = '/track'; - * - * // A provider method for configuring where the tracked events should been saved - * this.setTrackingUrl = function(url) { - * trackingUrl = url; - * }; - * - * // The service factory function - * this.$get = ['$http', function($http) { - * var trackedEvents = {}; - * return { - * // Call this to track an event - * event: function(event) { - * var count = trackedEvents[event] || 0; - * count += 1; - * trackedEvents[event] = count; - * return count; - * }, - * // Call this to save the tracked events to the trackingUrl - * save: function() { - * $http.post(trackingUrl, trackedEvents); - * } - * }; - * }]; - * } - * - * describe('eventTracker', function() { - * var postSpy; - * - * beforeEach(module(function($provide) { - * // Register the eventTracker provider - * $provide.provider('eventTracker', EventTrackerProvider); - * })); - * - * beforeEach(module(function(eventTrackerProvider) { - * // Configure eventTracker provider - * eventTrackerProvider.setTrackingUrl('/custom-track'); - * })); - * - * it('tracks events', inject(function(eventTracker) { - * expect(eventTracker.event('login')).toEqual(1); - * expect(eventTracker.event('login')).toEqual(2); - * })); - * - * it('saves to the tracking url', inject(function(eventTracker, $http) { - * postSpy = spyOn($http, 'post'); - * eventTracker.event('login'); - * eventTracker.save(); - * expect(postSpy).toHaveBeenCalled(); - * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); - * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); - * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); - * })); - * }); - * ``` - */ - -/** - * @ngdoc method - * @name $provide#factory - * @description - * - * Register a **service factory**, which will be called to return the service instance. - * This is short for registering a service where its provider consists of only a `$get` property, - * which is the given service factory function. - * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to - * configure your service in a provider. - * - * @param {string} name The name of the instance. - * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand - * for `$provide.provider(name, {$get: $getFn})`. - * @returns {Object} registered provider instance - * - * @example - * Here is an example of registering a service - * ```js - * $provide.factory('ping', ['$http', function($http) { - * return function ping() { - * return $http.send('/ping'); - * }; - * }]); - * ``` - * You would then inject and use this service like this: - * ```js - * someModule.controller('Ctrl', ['ping', function(ping) { - * ping(); - * }]); - * ``` - */ - - -/** - * @ngdoc method - * @name $provide#service - * @description - * - * Register a **service constructor**, which will be invoked with `new` to create the service - * instance. - * This is short for registering a service where its provider's `$get` property is the service - * constructor function that will be used to instantiate the service instance. - * - * You should use {@link auto.$provide#service $provide.service(class)} if you define your service - * as a type/class. - * - * @param {string} name The name of the instance. - * @param {Function} constructor A class (constructor function) that will be instantiated. - * @returns {Object} registered provider instance - * - * @example - * Here is an example of registering a service using - * {@link auto.$provide#service $provide.service(class)}. - * ```js - * var Ping = function($http) { - * this.$http = $http; - * }; - * - * Ping.$inject = ['$http']; - * - * Ping.prototype.send = function() { - * return this.$http.get('/ping'); - * }; - * $provide.service('ping', Ping); - * ``` - * You would then inject and use this service like this: - * ```js - * someModule.controller('Ctrl', ['ping', function(ping) { - * ping.send(); - * }]); - * ``` - */ - - -/** - * @ngdoc method - * @name $provide#value - * @description - * - * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a - * number, an array, an object or a function. This is short for registering a service where its - * provider's `$get` property is a factory function that takes no arguments and returns the **value - * service**. - * - * Value services are similar to constant services, except that they cannot be injected into a - * module configuration function (see {@link angular.Module#config}) but they can be overridden by - * an Angular - * {@link auto.$provide#decorator decorator}. - * - * @param {string} name The name of the instance. - * @param {*} value The value. - * @returns {Object} registered provider instance - * - * @example - * Here are some examples of creating value services. - * ```js - * $provide.value('ADMIN_USER', 'admin'); - * - * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); - * - * $provide.value('halfOf', function(value) { - * return value / 2; - * }); - * ``` - */ - - -/** - * @ngdoc method - * @name $provide#constant - * @description - * - * Register a **constant service**, such as a string, a number, an array, an object or a function, - * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be - * injected into a module configuration function (see {@link angular.Module#config}) and it cannot - * be overridden by an Angular {@link auto.$provide#decorator decorator}. - * - * @param {string} name The name of the constant. - * @param {*} value The constant value. - * @returns {Object} registered instance - * - * @example - * Here a some examples of creating constants: - * ```js - * $provide.constant('SHARD_HEIGHT', 306); - * - * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); - * - * $provide.constant('double', function(value) { - * return value * 2; - * }); - * ``` - */ - - -/** - * @ngdoc method - * @name $provide#decorator - * @description - * - * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator - * intercepts the creation of a service, allowing it to override or modify the behaviour of the - * service. The object returned by the decorator may be the original service, or a new service - * object which replaces or wraps and delegates to the original service. - * - * @param {string} name The name of the service to decorate. - * @param {function()} decorator This function will be invoked when the service needs to be - * instantiated and should return the decorated service instance. 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, - * decorated or delegated to. - * - * @example - * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting - * calls to {@link ng.$log#error $log.warn()}. - * ```js - * $provide.decorator('$log', ['$delegate', function($delegate) { - * $delegate.warn = $delegate.error; - * return $delegate; - * }]); - * ``` - */ - - -function createInjector(modulesToLoad, strictDi) { - strictDi = (strictDi === true); - var INSTANTIATING = {}, - providerSuffix = 'Provider', - path = [], - loadedModules = new HashMap([], true), - providerCache = { - $provide: { - provider: supportObject(provider), - factory: supportObject(factory), - service: supportObject(service), - value: supportObject(value), - constant: supportObject(constant), - decorator: decorator - } - }, - providerInjector = (providerCache.$injector = - createInternalInjector(providerCache, function() { - throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); - })), - instanceCache = {}, - instanceInjector = (instanceCache.$injector = - createInternalInjector(instanceCache, function(servicename) { - var provider = providerInjector.get(servicename + providerSuffix); - return instanceInjector.invoke(provider.$get, provider, undefined, servicename); - })); - - - forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); - - return instanceInjector; - - //////////////////////////////////// - // $provider - //////////////////////////////////// - - function supportObject(delegate) { - return function(key, value) { - if (isObject(key)) { - forEach(key, reverseParams(delegate)); - } else { - return delegate(key, value); - } - }; - } - - function provider(name, provider_) { - assertNotHasOwnProperty(name, 'service'); - if (isFunction(provider_) || isArray(provider_)) { - provider_ = providerInjector.instantiate(provider_); - } - if (!provider_.$get) { - throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); - } - return providerCache[name + providerSuffix] = provider_; - } - - function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } - - function service(name, constructor) { - return factory(name, ['$injector', function($injector) { - return $injector.instantiate(constructor); - }]); - } - - function value(name, val) { return factory(name, valueFn(val)); } - - function constant(name, value) { - assertNotHasOwnProperty(name, 'constant'); - providerCache[name] = value; - instanceCache[name] = value; - } - - function decorator(serviceName, decorFn) { - var origProvider = providerInjector.get(serviceName + providerSuffix), - orig$get = origProvider.$get; - - origProvider.$get = function() { - var origInstance = instanceInjector.invoke(orig$get, origProvider); - return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); - }; - } - - //////////////////////////////////// - // Module Loading - //////////////////////////////////// - function loadModules(modulesToLoad){ - var runBlocks = [], moduleFn; - forEach(modulesToLoad, function(module) { - if (loadedModules.get(module)) return; - loadedModules.put(module, true); - - function runInvokeQueue(queue) { - var i, ii; - for(i = 0, ii = queue.length; i < ii; i++) { - var invokeArgs = queue[i], - provider = providerInjector.get(invokeArgs[0]); - - provider[invokeArgs[1]].apply(provider, invokeArgs[2]); - } - } - - try { - if (isString(module)) { - moduleFn = angularModule(module); - runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); - runInvokeQueue(moduleFn._invokeQueue); - runInvokeQueue(moduleFn._configBlocks); - } else if (isFunction(module)) { - runBlocks.push(providerInjector.invoke(module)); - } else if (isArray(module)) { - runBlocks.push(providerInjector.invoke(module)); - } else { - assertArgFn(module, 'module'); - } - } catch (e) { - if (isArray(module)) { - module = module[module.length - 1]; - } - if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { - // Safari & FF's stack traces don't contain error.message content - // unlike those of Chrome and IE - // So if stack doesn't contain message, we create a new string that contains both. - // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. - /* jshint -W022 */ - e = e.message + '\n' + e.stack; - } - throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", - module, e.stack || e.message || e); - } - }); - return runBlocks; - } - - //////////////////////////////////// - // internal Injector - //////////////////////////////////// - - function createInternalInjector(cache, factory) { - - function getService(serviceName) { - if (cache.hasOwnProperty(serviceName)) { - if (cache[serviceName] === INSTANTIATING) { - throw $injectorMinErr('cdep', 'Circular dependency found: {0}', - serviceName + ' <- ' + path.join(' <- ')); - } - return cache[serviceName]; - } else { - try { - path.unshift(serviceName); - cache[serviceName] = INSTANTIATING; - return cache[serviceName] = factory(serviceName); - } catch (err) { - if (cache[serviceName] === INSTANTIATING) { - delete cache[serviceName]; - } - throw err; - } finally { - path.shift(); - } - } - } - - function invoke(fn, self, locals, serviceName) { - if (typeof locals === 'string') { - serviceName = locals; - locals = null; - } - - var args = [], - $inject = annotate(fn, strictDi, serviceName), - length, i, - key; - - for(i = 0, length = $inject.length; i < length; i++) { - key = $inject[i]; - if (typeof key !== 'string') { - throw $injectorMinErr('itkn', - 'Incorrect injection token! Expected service name as string, got {0}', key); - } - args.push( - locals && locals.hasOwnProperty(key) - ? locals[key] - : getService(key) - ); - } - if (isArray(fn)) { - fn = fn[length]; - } - - // http://jsperf.com/angularjs-invoke-apply-vs-switch - // #5388 - return fn.apply(self, args); - } - - function instantiate(Type, locals, serviceName) { - 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, serviceName); - - return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; - } - - return { - invoke: invoke, - instantiate: instantiate, - get: getService, - annotate: annotate, - has: function(name) { - return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); - } - }; - } -} - -createInjector.$$annotate = annotate; - -/** - * @ngdoc service - * @name $anchorScroll - * @kind function - * @requires $window - * @requires $location - * @requires $rootScope - * - * @description - * When called, it checks current value of `$location.hash()` and scrolls to the related element, - * according to rules specified in - * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). - * - * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. - * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. - * - * @example - <example module="anchorScrollExample"> - <file name="index.html"> - <div id="scrollArea" ng-controller="ScrollController"> - <a ng-click="gotoBottom()">Go to bottom</a> - <a id="bottom"></a> You're at the bottom! - </div> - </file> - <file name="script.js"> - angular.module('anchorScrollExample', []) - .controller('ScrollController', ['$scope', '$location', '$anchorScroll', - function ($scope, $location, $anchorScroll) { - $scope.gotoBottom = function() { - // set the location.hash to the id of - // the element you wish to scroll to. - $location.hash('bottom'); - - // call $anchorScroll() - $anchorScroll(); - }; - }]); - </file> - <file name="style.css"> - #scrollArea { - height: 350px; - overflow: auto; - } - - #bottom { - display: block; - margin-top: 2000px; - } - </file> - </example> - */ -function $AnchorScrollProvider() { - - var autoScrollingEnabled = true; - - this.disableAutoScrolling = function() { - autoScrollingEnabled = false; - }; - - this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { - var document = $window.document; - - // helper function to get first anchor from a NodeList - // can't use filter.filter, as it accepts only instances of Array - // and IE can't convert NodeList to an array using [].slice - // TODO(vojta): use filter if we change it to accept lists as well - function getFirstAnchor(list) { - var result = null; - forEach(list, function(element) { - if (!result && nodeName_(element) === 'a') result = element; - }); - return result; - } - - function scroll() { - var hash = $location.hash(), elm; - - // empty hash, scroll to the top of the page - if (!hash) $window.scrollTo(0, 0); - - // element with given id - else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); - - // first anchor with given name :-D - else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); - - // no element and hash == 'top', scroll to the top of the page - else if (hash === 'top') $window.scrollTo(0, 0); - } - - // does not scroll when user clicks on anchor link that is currently on - // (no url change, no $location.hash() change), browser native does scroll - if (autoScrollingEnabled) { - $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, - function autoScrollWatchAction() { - $rootScope.$evalAsync(scroll); - }); - } - - return scroll; - }]; -} - -var $animateMinErr = minErr('$animate'); - -/** - * @ngdoc provider - * @name $animateProvider - * - * @description - * Default implementation of $animate that doesn't perform any animations, instead just - * synchronously performs DOM - * updates and calls done() callbacks. - * - * In order to enable animations the ngAnimate module has to be loaded. - * - * To see the functional implementation check out src/ngAnimate/animate.js - */ -var $AnimateProvider = ['$provide', function($provide) { - - - this.$$selectors = {}; - - - /** - * @ngdoc method - * @name $animateProvider#register - * - * @description - * Registers a new injectable animation factory function. The factory function produces the - * animation object which contains callback functions for each event that is expected to be - * animated. - * - * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` - * must be called once the element animation is complete. If a function is returned then the - * animation service will use this function to cancel the animation whenever a cancel event is - * triggered. - * - * - * ```js - * return { - * eventFn : function(element, done) { - * //code to run the animation - * //once complete, then run done() - * return function cancellationFunction() { - * //code to cancel the animation - * } - * } - * } - * ``` - * - * @param {string} name The name of the animation. - * @param {Function} factory The factory function that will be executed to return the animation - * object. - */ - this.register = function(name, factory) { - var key = name + '-animation'; - if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', - "Expecting class selector starting with '.' got '{0}'.", name); - this.$$selectors[name.substr(1)] = key; - $provide.factory(key, factory); - }; - - /** - * @ngdoc method - * @name $animateProvider#classNameFilter - * - * @description - * Sets and/or returns the CSS class regular expression that is checked when performing - * an animation. Upon bootstrap the classNameFilter value is not set at all and will - * therefore enable $animate to attempt to perform an animation on any element. - * When setting the classNameFilter value, animations will only be performed on elements - * that successfully match the filter expression. This in turn can boost performance - * for low-powered devices as well as applications containing a lot of structural operations. - * @param {RegExp=} expression The className expression which will be checked against all animations - * @return {RegExp} The current CSS className expression value. If null then there is no expression value - */ - this.classNameFilter = function(expression) { - if(arguments.length === 1) { - this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; - } - return this.$$classNameFilter; - }; - - this.$get = ['$$q', '$$asyncCallback', function($$q, $$asyncCallback) { - - var currentDefer; - function asyncPromise() { - // only serve one instance of a promise in order to save CPU cycles - if (!currentDefer) { - currentDefer = $$q.defer(); - $$asyncCallback(function() { - currentDefer.resolve(); - currentDefer = null; - }); - } - return currentDefer.promise; - } - - /** - * - * @ngdoc service - * @name $animate - * @description The $animate service provides rudimentary DOM manipulation functions to - * insert, remove and move elements within the DOM, as well as adding and removing classes. - * This service is the core service used by the ngAnimate $animator service which provides - * high-level animation hooks for CSS and JavaScript. - * - * $animate is available in the AngularJS core, however, the ngAnimate module must be included - * to enable full out animation support. Otherwise, $animate will only perform simple DOM - * manipulation operations. - * - * To learn more about enabling animation support, click here to visit the {@link ngAnimate - * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service - * page}. - */ - return { - - /** - * - * @ngdoc method - * @name $animate#enter - * @kind function - * @description Inserts the element into the DOM either after the `after` element or - * as the first child within the `parent` element. When the function is called a promise - * is returned that will be resolved at a later time. - * @param {DOMElement} element the element which will be inserted into the DOM - * @param {DOMElement} parent the parent element which will append the element as - * a child (if the after element is not present) - * @param {DOMElement} after the sibling element which will append the element - * after itself - * @return {Promise} the animation callback promise - */ - enter : function(element, parent, after) { - after ? after.after(element) - : parent.prepend(element); - return asyncPromise(); - }, - - /** - * - * @ngdoc method - * @name $animate#leave - * @kind function - * @description Removes the element from the DOM. When the function is called a promise - * is returned that will be resolved at a later time. - * @param {DOMElement} element the element which will be removed from the DOM - * @return {Promise} the animation callback promise - */ - leave : function(element) { - element.remove(); - return asyncPromise(); - }, - - /** - * - * @ngdoc method - * @name $animate#move - * @kind function - * @description Moves the position of the provided element within the DOM to be placed - * either after the `after` element or inside of the `parent` element. When the function - * is called a promise is returned that will be resolved at a later time. - * - * @param {DOMElement} element the element which will be moved around within the - * DOM - * @param {DOMElement} parent the parent element where the element will be - * inserted into (if the after element is not present) - * @param {DOMElement} after the sibling element where the element will be - * positioned next to - * @return {Promise} the animation callback promise - */ - move : function(element, parent, after) { - // Do not remove element before insert. Removing will cause data associated with the - // element to be dropped. Insert will implicitly do the remove. - return this.enter(element, parent, after); - }, - - /** - * - * @ngdoc method - * @name $animate#addClass - * @kind function - * @description Adds the provided className CSS class value to the provided element. - * When the function is called a promise is returned that will be resolved at a later time. - * @param {DOMElement} element the element which will have the className value - * added to it - * @param {string} className the CSS class which will be added to the element - * @return {Promise} the animation callback promise - */ - addClass : function(element, className) { - className = !isString(className) - ? (isArray(className) ? className.join(' ') : '') - : className; - forEach(element, function (element) { - jqLiteAddClass(element, className); - }); - return asyncPromise(); - }, - - /** - * - * @ngdoc method - * @name $animate#removeClass - * @kind function - * @description Removes the provided className CSS class value from the provided element. - * When the function is called a promise is returned that will be resolved at a later time. - * @param {DOMElement} element the element which will have the className value - * removed from it - * @param {string} className the CSS class which will be removed from the element - * @return {Promise} the animation callback promise - */ - removeClass : function(element, className) { - className = !isString(className) - ? (isArray(className) ? className.join(' ') : '') - : className; - forEach(element, function (element) { - jqLiteRemoveClass(element, className); - }); - return asyncPromise(); - }, - - /** - * - * @ngdoc method - * @name $animate#setClass - * @kind function - * @description Adds and/or removes the given CSS classes to and from the element. - * When the function is called a promise is returned that will be resolved at a later time. - * @param {DOMElement} element the element which will have its CSS classes changed - * removed from it - * @param {string} add the CSS classes which will be added to the element - * @param {string} remove the CSS class which will be removed from the element - * @return {Promise} the animation callback promise - */ - setClass : function(element, add, remove) { - this.addClass(element, add); - this.removeClass(element, remove); - return asyncPromise(); - }, - - enabled : noop, - cancel : noop - }; - }]; -}]; - -function $$AsyncCallbackProvider(){ - this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { - return $$rAF.supported - ? function(fn) { return $$rAF(fn); } - : function(fn) { - return $timeout(fn, 0, false); - }; - }]; -} - -/** - * ! This is a private undocumented service ! - * - * @name $browser - * @requires $log - * @description - * This object has two goals: - * - * - hide all the global state in the browser caused by the window object - * - abstract away all the browser specific features and inconsistencies - * - * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` - * service, which can be used for convenient testing of the application without the interaction with - * the real browser apis. - */ -/** - * @param {object} window The global window object. - * @param {object} document jQuery wrapped document. - * @param {function()} XHR XMLHttpRequest constructor. - * @param {object} $log console.log or an object with the same interface. - * @param {object} $sniffer $sniffer service - */ -function Browser(window, document, $log, $sniffer) { - var self = this, - rawDocument = document[0], - location = window.location, - history = window.history, - setTimeout = window.setTimeout, - clearTimeout = window.clearTimeout, - pendingDeferIds = {}; - - self.isMock = false; - - var outstandingRequestCount = 0; - var outstandingRequestCallbacks = []; - - // TODO(vojta): remove this temporary api - self.$$completeOutstandingRequest = completeOutstandingRequest; - self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; - - /** - * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` - * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. - */ - function completeOutstandingRequest(fn) { - try { - fn.apply(null, sliceArgs(arguments, 1)); - } finally { - outstandingRequestCount--; - if (outstandingRequestCount === 0) { - while(outstandingRequestCallbacks.length) { - try { - outstandingRequestCallbacks.pop()(); - } catch (e) { - $log.error(e); - } - } - } - } - } - - /** - * @private - * Note: this method is used only by scenario runner - * TODO(vojta): prefix this method with $$ ? - * @param {function()} callback Function that will be called when no outstanding request - */ - self.notifyWhenNoOutstandingRequests = function(callback) { - // force browser to execute all pollFns - this is needed so that cookies and other pollers fire - // at some deterministic time in respect to the test runner's actions. Leaving things up to the - // regular poller would result in flaky tests. - forEach(pollFns, function(pollFn){ pollFn(); }); - - if (outstandingRequestCount === 0) { - callback(); - } else { - outstandingRequestCallbacks.push(callback); - } - }; - - ////////////////////////////////////////////////////////////// - // Poll Watcher API - ////////////////////////////////////////////////////////////// - var pollFns = [], - pollTimeout; - - /** - * @name $browser#addPollFn - * - * @param {function()} fn Poll function to add - * - * @description - * Adds a function to the list of functions that poller periodically executes, - * and starts polling if not started yet. - * - * @returns {function()} the added function - */ - self.addPollFn = function(fn) { - if (isUndefined(pollTimeout)) startPoller(100, setTimeout); - pollFns.push(fn); - return fn; - }; - - /** - * @param {number} interval How often should browser call poll functions (ms) - * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. - * - * @description - * Configures the poller to run in the specified intervals, using the specified - * setTimeout fn and kicks it off. - */ - function startPoller(interval, setTimeout) { - (function check() { - forEach(pollFns, function(pollFn){ pollFn(); }); - pollTimeout = setTimeout(check, interval); - })(); - } - - ////////////////////////////////////////////////////////////// - // URL API - ////////////////////////////////////////////////////////////// - - var lastBrowserUrl = location.href, - baseElement = document.find('base'), - newLocation = null; - - /** - * @name $browser#url - * - * @description - * GETTER: - * Without any argument, this method just returns current value of location.href. - * - * SETTER: - * With at least one argument, this method sets url to new value. - * If html5 history api supported, pushState/replaceState is used, otherwise - * location.href/location.replace is used. - * Returns its own instance to allow chaining - * - * NOTE: this api is intended for use only by the $location service. Please use the - * {@link ng.$location $location service} to change url. - * - * @param {string} url New url (when used as setter) - * @param {boolean=} replace Should new url replace current history record ? - */ - self.url = function(url, replace) { - // Android Browser BFCache causes location, history reference to become stale. - if (location !== window.location) location = window.location; - if (history !== window.history) history = window.history; - - // setter - if (url) { - if (lastBrowserUrl == url) return; - lastBrowserUrl = url; - if ($sniffer.history) { - if (replace) history.replaceState(null, '', url); - else { - history.pushState(null, '', url); - // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 - baseElement.attr('href', baseElement.attr('href')); - } - } else { - newLocation = url; - if (replace) { - location.replace(url); - } else { - location.href = url; - } - } - return self; - // getter - } else { - // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href - // methods not updating location.href synchronously. - // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 - return newLocation || location.href.replace(/%27/g,"'"); - } - }; - - var urlChangeListeners = [], - urlChangeInit = false; - - function fireUrlChange() { - newLocation = null; - if (lastBrowserUrl == self.url()) return; - - lastBrowserUrl = self.url(); - forEach(urlChangeListeners, function(listener) { - listener(self.url()); - }); - } - - /** - * @name $browser#onUrlChange - * - * @description - * Register callback function that will be called, when url changes. - * - * It's only called when the url is changed from outside of angular: - * - user types different url into address bar - * - user clicks on history (forward/back) button - * - user clicks on a link - * - * It's not called when url is changed by $browser.url() method - * - * The listener gets called with new url as parameter. - * - * NOTE: this api is intended for use only by the $location service. Please use the - * {@link ng.$location $location service} to monitor url changes in angular apps. - * - * @param {function(string)} listener Listener function to be called when url changes. - * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. - */ - self.onUrlChange = function(callback) { - // TODO(vojta): refactor to use node's syntax for events - if (!urlChangeInit) { - // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) - // don't fire popstate when user change the address bar and don't fire hashchange when url - // changed by push/replaceState - - // html5 history api - popstate event - if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); - // hashchange event - if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); - // polling - else self.addPollFn(fireUrlChange); - - urlChangeInit = true; - } - - urlChangeListeners.push(callback); - return callback; - }; - - /** - * Checks whether the url has changed outside of Angular. - * Needs to be exported to be able to check for changes that have been done in sync, - * as hashchange/popstate events fire in async. - */ - self.$$checkUrlChange = fireUrlChange; - - ////////////////////////////////////////////////////////////// - // Misc API - ////////////////////////////////////////////////////////////// - - /** - * @name $browser#baseHref - * - * @description - * Returns current <base href> - * (always relative - without domain) - * - * @returns {string} The current base href - */ - self.baseHref = function() { - var href = baseElement.attr('href'); - return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; - }; - - ////////////////////////////////////////////////////////////// - // Cookies API - ////////////////////////////////////////////////////////////// - var lastCookies = {}; - var lastCookieString = ''; - var cookiePath = self.baseHref(); - - /** - * @name $browser#cookies - * - * @param {string=} name Cookie name - * @param {string=} value Cookie value - * - * @description - * The cookies method provides a 'private' low level access to browser cookies. - * It is not meant to be used directly, use the $cookie service instead. - * - * The return values vary depending on the arguments that the method was called with as follows: - * - * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify - * it - * - cookies(name, value) -> set name to value, if value is undefined delete the cookie - * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that - * way) - * - * @returns {Object} Hash of all cookies (if called without any parameter) - */ - self.cookies = function(name, value) { - var cookieLength, cookieArray, cookie, i, index; - - if (name) { - if (value === undefined) { - rawDocument.cookie = encodeURIComponent(name) + "=;path=" + cookiePath + - ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; - } else { - if (isString(value)) { - cookieLength = (rawDocument.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) + - ';path=' + cookiePath).length + 1; - - // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: - // - 300 cookies - // - 20 cookies per unique domain - // - 4096 bytes per cookie - if (cookieLength > 4096) { - $log.warn("Cookie '"+ name + - "' possibly not set or overflowed because it was too large ("+ - cookieLength + " > 4096 bytes)!"); - } - } - } - } else { - if (rawDocument.cookie !== lastCookieString) { - lastCookieString = rawDocument.cookie; - cookieArray = lastCookieString.split("; "); - lastCookies = {}; - - for (i = 0; i < cookieArray.length; i++) { - cookie = cookieArray[i]; - index = cookie.indexOf('='); - if (index > 0) { //ignore nameless cookies - name = decodeURIComponent(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] = decodeURIComponent(cookie.substring(index + 1)); - } - } - } - } - return lastCookies; - } - }; - - - /** - * @name $browser#defer - * @param {function()} fn A function, who's execution should be deferred. - * @param {number=} [delay=0] of milliseconds to defer the function execution. - * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. - * - * @description - * Executes a fn asynchronously via `setTimeout(fn, delay)`. - * - * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using - * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed - * via `$browser.defer.flush()`. - * - */ - self.defer = function(fn, delay) { - var timeoutId; - outstandingRequestCount++; - timeoutId = setTimeout(function() { - delete pendingDeferIds[timeoutId]; - completeOutstandingRequest(fn); - }, delay || 0); - pendingDeferIds[timeoutId] = true; - return timeoutId; - }; - - - /** - * @name $browser#defer.cancel - * - * @description - * Cancels a deferred task identified with `deferId`. - * - * @param {*} deferId Token returned by the `$browser.defer` function. - * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully - * canceled. - */ - self.defer.cancel = function(deferId) { - if (pendingDeferIds[deferId]) { - delete pendingDeferIds[deferId]; - clearTimeout(deferId); - completeOutstandingRequest(noop); - return true; - } - return false; - }; - -} - -function $BrowserProvider(){ - this.$get = ['$window', '$log', '$sniffer', '$document', - function( $window, $log, $sniffer, $document){ - return new Browser($window, $document, $log, $sniffer); - }]; -} - -/** - * @ngdoc service - * @name $cacheFactory - * - * @description - * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to - * them. - * - * ```js - * - * var cache = $cacheFactory('cacheId'); - * expect($cacheFactory.get('cacheId')).toBe(cache); - * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); - * - * cache.put("key", "value"); - * cache.put("another key", "another value"); - * - * // We've specified no options on creation - * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); - * - * ``` - * - * - * @param {string} cacheId Name or id of the newly created cache. - * @param {object=} options Options object that specifies the cache behavior. Properties: - * - * - `{number=}` `capacity` — turns the cache into LRU cache. - * - * @returns {object} Newly created cache object with the following set of methods: - * - * - `{object}` `info()` — Returns id, size, and options of cache. - * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns - * it. - * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss. - * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache. - * - `{void}` `removeAll()` — Removes all cached values. - * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory. - * - * @example - <example module="cacheExampleApp"> - <file name="index.html"> - <div ng-controller="CacheController"> - <input ng-model="newCacheKey" placeholder="Key"> - <input ng-model="newCacheValue" placeholder="Value"> - <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> - - <p ng-if="keys.length">Cached Values</p> - <div ng-repeat="key in keys"> - <span ng-bind="key"></span> - <span>: </span> - <b ng-bind="cache.get(key)"></b> - </div> - - <p>Cache Info</p> - <div ng-repeat="(key, value) in cache.info()"> - <span ng-bind="key"></span> - <span>: </span> - <b ng-bind="value"></b> - </div> - </div> - </file> - <file name="script.js"> - angular.module('cacheExampleApp', []). - controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { - $scope.keys = []; - $scope.cache = $cacheFactory('cacheId'); - $scope.put = function(key, value) { - if ($scope.cache.get(key) === undefined) { - $scope.keys.push(key); - } - $scope.cache.put(key, value === undefined ? null : value); - }; - }]); - </file> - <file name="style.css"> - p { - margin: 10px 0 3px; - } - </file> - </example> - */ -function $CacheFactoryProvider() { - - this.$get = function() { - var caches = {}; - - function cacheFactory(cacheId, options) { - if (cacheId in caches) { - throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); - } - - var size = 0, - stats = extend({}, options, {id: cacheId}), - data = {}, - capacity = (options && options.capacity) || Number.MAX_VALUE, - lruHash = {}, - freshEnd = null, - staleEnd = null; - - /** - * @ngdoc type - * @name $cacheFactory.Cache - * - * @description - * A cache object used to store and retrieve data, primarily used by - * {@link $http $http} and the {@link ng.directive:script script} directive to cache - * templates and other data. - * - * ```js - * angular.module('superCache') - * .factory('superCache', ['$cacheFactory', function($cacheFactory) { - * return $cacheFactory('super-cache'); - * }]); - * ``` - * - * Example test: - * - * ```js - * it('should behave like a cache', inject(function(superCache) { - * superCache.put('key', 'value'); - * superCache.put('another key', 'another value'); - * - * expect(superCache.info()).toEqual({ - * id: 'super-cache', - * size: 2 - * }); - * - * superCache.remove('another key'); - * expect(superCache.get('another key')).toBeUndefined(); - * - * superCache.removeAll(); - * expect(superCache.info()).toEqual({ - * id: 'super-cache', - * size: 0 - * }); - * })); - * ``` - */ - return caches[cacheId] = { - - /** - * @ngdoc method - * @name $cacheFactory.Cache#put - * @kind function - * - * @description - * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be - * retrieved later, and incrementing the size of the cache if the key was not already - * present in the cache. If behaving like an LRU cache, it will also remove stale - * entries from the set. - * - * It will not insert undefined values into the cache. - * - * @param {string} key the key under which the cached data is stored. - * @param {*} value the value to store alongside the key. If it is undefined, the key - * will not be stored. - * @returns {*} the value stored. - */ - put: function(key, value) { - if (capacity < Number.MAX_VALUE) { - var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); - - refresh(lruEntry); - } - - if (isUndefined(value)) return; - if (!(key in data)) size++; - data[key] = value; - - if (size > capacity) { - this.remove(staleEnd.key); - } - - return value; - }, - - /** - * @ngdoc method - * @name $cacheFactory.Cache#get - * @kind function - * - * @description - * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. - * - * @param {string} key the key of the data to be retrieved - * @returns {*} the value stored. - */ - get: function(key) { - if (capacity < Number.MAX_VALUE) { - var lruEntry = lruHash[key]; - - if (!lruEntry) return; - - refresh(lruEntry); - } - - return data[key]; - }, - - - /** - * @ngdoc method - * @name $cacheFactory.Cache#remove - * @kind function - * - * @description - * Removes an entry from the {@link $cacheFactory.Cache Cache} object. - * - * @param {string} key the key of the entry to be removed - */ - remove: function(key) { - if (capacity < Number.MAX_VALUE) { - var lruEntry = lruHash[key]; - - if (!lruEntry) return; - - if (lruEntry == freshEnd) freshEnd = lruEntry.p; - if (lruEntry == staleEnd) staleEnd = lruEntry.n; - link(lruEntry.n,lruEntry.p); - - delete lruHash[key]; - } - - delete data[key]; - size--; - }, - - - /** - * @ngdoc method - * @name $cacheFactory.Cache#removeAll - * @kind function - * - * @description - * Clears the cache object of any entries. - */ - removeAll: function() { - data = {}; - size = 0; - lruHash = {}; - freshEnd = staleEnd = null; - }, - - - /** - * @ngdoc method - * @name $cacheFactory.Cache#destroy - * @kind function - * - * @description - * Destroys the {@link $cacheFactory.Cache Cache} object entirely, - * removing it from the {@link $cacheFactory $cacheFactory} set. - */ - destroy: function() { - data = null; - stats = null; - lruHash = null; - delete caches[cacheId]; - }, - - - /** - * @ngdoc method - * @name $cacheFactory.Cache#info - * @kind function - * - * @description - * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. - * - * @returns {object} an object with the following properties: - * <ul> - * <li>**id**: the id of the cache instance</li> - * <li>**size**: the number of entries kept in the cache instance</li> - * <li>**...**: any additional properties from the options object when creating the - * cache.</li> - * </ul> - */ - info: function() { - return extend({}, stats, {size: size}); - } - }; - - - /** - * makes the `entry` the freshEnd of the LRU linked list - */ - function refresh(entry) { - if (entry != freshEnd) { - if (!staleEnd) { - staleEnd = entry; - } else if (staleEnd == entry) { - staleEnd = entry.n; - } - - link(entry.n, entry.p); - link(entry, freshEnd); - freshEnd = entry; - freshEnd.n = null; - } - } - - - /** - * bidirectionally links two entries of the LRU linked list - */ - function link(nextEntry, prevEntry) { - if (nextEntry != prevEntry) { - if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify - if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify - } - } - } - - - /** - * @ngdoc method - * @name $cacheFactory#info - * - * @description - * Get information about all the caches that have been created - * - * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` - */ - cacheFactory.info = function() { - var info = {}; - forEach(caches, function(cache, cacheId) { - info[cacheId] = cache.info(); - }); - return info; - }; - - - /** - * @ngdoc method - * @name $cacheFactory#get - * - * @description - * Get access to a cache object by the `cacheId` used when it was created. - * - * @param {string} cacheId Name or id of a cache to access. - * @returns {object} Cache object identified by the cacheId or undefined if no such cache. - */ - cacheFactory.get = function(cacheId) { - return caches[cacheId]; - }; - - - return cacheFactory; - }; -} - -/** - * @ngdoc service - * @name $templateCache - * - * @description - * The first time a template is used, it is loaded in the template cache for quick retrieval. You - * can load templates directly into the cache in a `script` tag, or by consuming the - * `$templateCache` service directly. - * - * Adding via the `script` tag: - * - * ```html - * <script type="text/ng-template" id="templateId.html"> - * <p>This is the content of the template</p> - * </script> - * ``` - * - * **Note:** the `script` tag containing the template does not need to be included in the `head` of - * the document, but it must be below the `ng-app` definition. - * - * Adding via the $templateCache service: - * - * ```js - * var myApp = angular.module('myApp', []); - * myApp.run(function($templateCache) { - * $templateCache.put('templateId.html', 'This is the content of the template'); - * }); - * ``` - * - * To retrieve the template later, simply use it in your HTML: - * ```html - * <div ng-include=" 'templateId.html' "></div> - * ``` - * - * or get it via Javascript: - * ```js - * $templateCache.get('templateId.html') - * ``` - * - * See {@link ng.$cacheFactory $cacheFactory}. - * - */ -function $TemplateCacheProvider() { - this.$get = ['$cacheFactory', function($cacheFactory) { - return $cacheFactory('templates'); - }]; -} - -/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! - * - * DOM-related variables: - * - * - "node" - DOM Node - * - "element" - DOM Element or Node - * - "$node" or "$element" - jqLite-wrapped node or element - * - * - * Compiler related stuff: - * - * - "linkFn" - linking fn of a single directive - * - "nodeLinkFn" - function that aggregates all linking fns for a particular node - * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node - * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) - */ - - -/** - * @ngdoc service - * @name $compile - * @kind function - * - * @description - * Compiles an HTML string or DOM into a template and produces a template function, which - * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. - * - * The compilation is a process of walking the DOM tree and matching DOM elements to - * {@link ng.$compileProvider#directive directives}. - * - * <div class="alert alert-warning"> - * **Note:** This document is an in-depth reference of all directive options. - * For a gentle introduction to directives with examples of common use cases, - * see the {@link guide/directive directive guide}. - * </div> - * - * ## Comprehensive Directive API - * - * There are many different options for a directive. - * - * The difference resides in the return value of the factory function. - * You can either return a "Directive Definition Object" (see below) that defines the directive properties, - * or just the `postLink` function (all other properties will have the default values). - * - * <div class="alert alert-success"> - * **Best Practice:** It's recommended to use the "directive definition object" form. - * </div> - * - * Here's an example directive declared with a Directive Definition Object: - * - * ```js - * var myModule = angular.module(...); - * - * myModule.directive('directiveName', function factory(injectables) { - * var directiveDefinitionObject = { - * priority: 0, - * template: '<div></div>', // or // function(tElement, tAttrs) { ... }, - * // or - * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, - * transclude: false, - * restrict: 'A', - * scope: false, - * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, - * controllerAs: 'stringAlias', - * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], - * compile: function compile(tElement, tAttrs, transclude) { - * return { - * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, - * post: function postLink(scope, iElement, iAttrs, controller) { ... } - * } - * // or - * // return function postLink( ... ) { ... } - * }, - * // or - * // link: { - * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, - * // post: function postLink(scope, iElement, iAttrs, controller) { ... } - * // } - * // or - * // link: function postLink( ... ) { ... } - * }; - * return directiveDefinitionObject; - * }); - * ``` - * - * <div class="alert alert-warning"> - * **Note:** Any unspecified options will use the default value. You can see the default values below. - * </div> - * - * Therefore the above can be simplified as: - * - * ```js - * var myModule = angular.module(...); - * - * myModule.directive('directiveName', function factory(injectables) { - * var directiveDefinitionObject = { - * link: function postLink(scope, iElement, iAttrs) { ... } - * }; - * return directiveDefinitionObject; - * // or - * // return function postLink(scope, iElement, iAttrs) { ... } - * }); - * ``` - * - * - * - * ### Directive Definition Object - * - * The directive definition object provides instructions to the {@link ng.$compile - * compiler}. The attributes are: - * - * #### `multiElement` - * When this property is set to true, the HTML compiler will collect DOM nodes between - * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them - * together as the directive elements. It is recomended that this feature be used on directives - * which are not strictly behavioural (such as {@link api/ng.directive:ngClick ngClick}), and which - * do not manipulate or replace child nodes (such as {@link api/ng.directive:ngInclude ngInclude}). - * - * #### `priority` - * When there are multiple directives defined on a single DOM element, sometimes it - * is necessary to specify the order in which the directives are applied. The `priority` is used - * to sort the directives before their `compile` functions get called. Priority is defined as a - * number. Directives with greater numerical `priority` are compiled first. Pre-link functions - * are also run in priority order, but post-link functions are run in reverse order. The order - * of directives with the same priority is undefined. The default priority is `0`. - * - * #### `terminal` - * If set to true then the current `priority` will be the last set of directives - * which will execute (any directives at the current priority will still execute - * as the order of execution on same `priority` is undefined). - * - * #### `scope` - * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the - * same element request a new scope, only one new scope is created. The new scope rule does not - * apply for the root of the template since the root of the template always gets a new scope. - * - * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from - * normal scope in that it does not prototypically inherit from the parent scope. This is useful - * when creating reusable components, which should not accidentally read or modify data in the - * parent scope. - * - * The 'isolate' scope takes an object hash which defines a set of local scope properties - * derived from the parent scope. These local properties are useful for aliasing values for - * templates. Locals definition is a hash of local scope property to its source: - * - * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is - * always a string since DOM attributes are strings. If no `attr` name is specified then the - * attribute name is assumed to be the same as the local name. - * Given `<widget my-attr="hello {{name}}">` and widget definition - * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect - * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the - * `localName` property on the widget scope. The `name` is read from the parent scope (not - * component scope). - * - * * `=` or `=attr` - set up bi-directional binding between a local scope property and the - * parent scope property of name defined via the value of the `attr` attribute. If no `attr` - * name is specified then the attribute name is assumed to be the same as the local name. - * Given `<widget my-attr="parentModel">` and widget definition of - * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the - * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected - * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent - * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You - * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. - * - * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. - * If no `attr` name is specified then the attribute name is assumed to be the same as the - * local name. Given `<widget my-attr="count = count + value">` and widget definition of - * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to - * a function wrapper for the `count = count + value` expression. Often it's desirable to - * pass data from the isolated scope via an expression to the parent scope, this can be - * done by passing a map of local variable names and values into the expression wrapper fn. - * For example, if the expression is `increment(amount)` then we can specify the amount value - * by calling the `localFn` as `localFn({amount: 22})`. - * - * - * #### `bindToController` - * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController` will - * allow a component to have its properties bound to the controller, rather than to scope. When the controller - * is instantiated, the initial values of the isolate scope bindings are already available. - * - * #### `controller` - * Controller constructor function. The controller is instantiated before the - * pre-linking phase and it is shared with other directives (see - * `require` attribute). This allows the directives to communicate with each other and augment - * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: - * - * * `$scope` - Current scope associated with the element - * * `$element` - Current element - * * `$attrs` - Current attributes object for the element - * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: - * `function([scope], cloneLinkingFn, futureParentElement)`. - * * `scope`: optional argument to override the scope. - * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content. - * * `futureParentElement`: - * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. - * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. - * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) - * and when the `cloneLinkinFn` is passed, - * as those elements need to created and cloned in a special way when they are defined outside their - * usual containers (e.g. like `<svg>`). - * * See also the `directive.templateNamespace` property. - * - * - * #### `require` - * Require another directive and inject its controller as the fourth argument to the linking function. The - * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the - * injected argument will be an array in corresponding order. If no such directive can be - * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: - * - * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. - * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. - * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. - * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass - * `null` to the `link` fn if not found. - * - * - * #### `controllerAs` - * Controller alias at the directive scope. An alias for the controller so it - * can be referenced at the directive template. The directive needs to define a scope for this - * configuration to be used. Useful in the case when directive is used as component. - * - * - * #### `restrict` - * String of subset of `EACM` which restricts the directive to a specific directive - * declaration style. If omitted, the defaults (elements and attributes) are used. - * - * * `E` - Element name (default): `<my-directive></my-directive>` - * * `A` - Attribute (default): `<div my-directive="exp"></div>` - * * `C` - Class: `<div class="my-directive: exp;"></div>` - * * `M` - Comment: `<!-- directive: my-directive exp -->` - * - * - * #### `templateNamespace` - * String representing the document type used by the markup in the template. - * AngularJS needs this information as those elements need to be created and cloned - * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. - * - * * `html` - All root nodes in the template are HTML. Root nodes may also be - * top-level elements such as `<svg>` or `<math>`. - * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). - * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). - * - * If no `templateNamespace` is specified, then the namespace is considered to be `html`. - * - * #### `template` - * HTML markup that may: - * * Replace the contents of the directive's element (default). - * * Replace the directive's element itself (if `replace` is true - DEPRECATED). - * * Wrap the contents of the directive's element (if `transclude` is true). - * - * Value may be: - * - * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. - * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile` - * function api below) and returns a string value. - * - * - * #### `templateUrl` - * This is similar to `template` but the template is loaded from the specified URL, asynchronously. - * - * Because template loading is asynchronous the compiler will suspend compilation of directives on that element - * for later when the template has been resolved. In the meantime it will continue to compile and link - * sibling and parent elements as though this element had not contained any directives. - * - * The compiler does not suspend the entire compilation to wait for templates to be loaded because this - * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the - * case when only one deeply nested directive has `templateUrl`. - * - * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} - * - * You can specify `templateUrl` as a string representing the URL or as a function which takes two - * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns - * a string value representing the url. In either case, the template URL is passed through {@link - * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. - * - * - * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) - * specify what the template should replace. Defaults to `false`. - * - * * `true` - the template will replace the directive's element. - * * `false` - the template will replace the contents of the directive's element. - * - * The replacement process migrates all of the attributes / classes from the old element to the new - * one. See the {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive - * Directives Guide} for an example. - * - * There are very few scenarios where element replacement is required for the application function, - * the main one being reusable custom components that are used within SVG contexts - * (because SVG doesn't work with custom elements in the DOM tree). - * - * #### `transclude` - * compile the content of the element and make it available to the directive. - * Typically used with {@link ng.directive:ngTransclude - * ngTransclude}. The advantage of transclusion is that the linking function receives a - * transclusion function which is pre-bound to the correct scope. In a typical setup the widget - * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` - * scope. This makes it possible for the widget to have private state, and the transclusion to - * be bound to the parent (pre-`isolate`) scope. - * - * * `true` - transclude the content of the directive. - * * `'element'` - transclude the whole element including any directives defined at lower priority. - * - * <div class="alert alert-warning"> - * **Note:** When testing an element transclude directive you must not place the directive at the root of the - * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives - * Testing Transclusion Directives}. - * </div> - * - * #### `compile` - * - * ```js - * function compile(tElement, tAttrs, transclude) { ... } - * ``` - * - * The compile function deals with transforming the template DOM. Since most directives do not do - * template transformation, it is not used often. The compile function takes the following arguments: - * - * * `tElement` - template element - The element where the directive has been declared. It is - * safe to do template transformation on the element and child elements only. - * - * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared - * between all directive compile functions. - * - * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` - * - * <div class="alert alert-warning"> - * **Note:** The template instance and the link instance may be different objects if the template has - * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that - * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration - * should be done in a linking function rather than in a compile function. - * </div> - - * <div class="alert alert-warning"> - * **Note:** The compile function cannot handle directives that recursively use themselves in their - * own templates or compile functions. Compiling these directives results in an infinite loop and a - * stack overflow errors. - * - * This can be avoided by manually using $compile in the postLink function to imperatively compile - * a directive's template instead of relying on automatic template compilation via `template` or - * `templateUrl` declaration or manual compilation inside the compile function. - * </div> - * - * <div class="alert alert-error"> - * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it - * e.g. does not know about the right outer scope. Please use the transclude function that is passed - * to the link function instead. - * </div> - - * A compile function can have a return value which can be either a function or an object. - * - * * returning a (post-link) function - is equivalent to registering the linking function via the - * `link` property of the config object when the compile function is empty. - * - * * returning an object with function(s) registered via `pre` and `post` properties - allows you to - * control when a linking function should be called during the linking phase. See info about - * pre-linking and post-linking functions below. - * - * - * #### `link` - * This property is used only if the `compile` property is not defined. - * - * ```js - * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } - * ``` - * - * The link function is responsible for registering DOM listeners as well as updating the DOM. It is - * executed after the template has been cloned. This is where most of the directive logic will be - * put. - * - * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the - * directive for registering {@link ng.$rootScope.Scope#$watch watches}. - * - * * `iElement` - instance element - The element where the directive is to be used. It is safe to - * manipulate the children of the element only in `postLink` function since the children have - * already been linked. - * - * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared - * between all directive linking functions. - * - * * `controller` - a controller instance - A controller instance if at least one directive on the - * element defines a controller. The controller is shared among all the directives, which allows - * the directives to use the controllers as a communication channel. - * - * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. - * This is the same as the `$transclude` - * parameter of directive controllers, see there for details. - * `function([scope], cloneLinkingFn, futureParentElement)`. - * - * #### Pre-linking function - * - * Executed before the child elements are linked. Not safe to do DOM transformation since the - * compiler linking function will fail to locate the correct elements for linking. - * - * #### Post-linking function - * - * Executed after the child elements are linked. - * - * Note that child elements that contain `templateUrl` directives will not have been compiled - * and linked since they are waiting for their template to load asynchronously and their own - * compilation and linking has been suspended until that occurs. - * - * It is safe to do DOM transformation in the post-linking function on elements that are not waiting - * for their async templates to be resolved. - * - * <a name="Attributes"></a> - * ### Attributes - * - * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the - * `link()` or `compile()` functions. It has a variety of uses. - * - * accessing *Normalized attribute names:* - * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. - * the attributes object allows for normalized access to - * the attributes. - * - * * *Directive inter-communication:* All directives share the same instance of the attributes - * object which allows the directives to use the attributes object as inter directive - * communication. - * - * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object - * allowing other directives to read the interpolated value. - * - * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes - * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also - * the only way to easily get the actual value because during the linking phase the interpolation - * hasn't been evaluated yet and so the value is at this time set to `undefined`. - * - * ```js - * function linkingFn(scope, elm, attrs, ctrl) { - * // get the attribute value - * console.log(attrs.ngModel); - * - * // change the attribute - * attrs.$set('ngModel', 'new value'); - * - * // observe changes to interpolated attribute - * attrs.$observe('ngModel', function(value) { - * console.log('ngModel has changed value to ' + value); - * }); - * } - * ``` - * - * Below is an example using `$compileProvider`. - * - * <div class="alert alert-warning"> - * **Note**: Typically directives are registered with `module.directive`. The example below is - * to illustrate how `$compile` works. - * </div> - * - <example module="compileExample"> - <file name="index.html"> - <script> - angular.module('compileExample', [], function($compileProvider) { - // configure new 'compile' directive by passing a directive - // factory function. The factory function injects the '$compile' - $compileProvider.directive('compile', function($compile) { - // directive factory creates a link function - return function(scope, element, attrs) { - scope.$watch( - function(scope) { - // watch the 'compile' expression for changes - return scope.$eval(attrs.compile); - }, - function(value) { - // when the 'compile' expression changes - // assign it into the current DOM - element.html(value); - - // compile the new DOM and link it to the current - // scope. - // NOTE: we only compile .childNodes so that - // we don't get into infinite loop compiling ourselves - $compile(element.contents())(scope); - } - ); - }; - }); - }) - .controller('GreeterController', ['$scope', function($scope) { - $scope.name = 'Angular'; - $scope.html = 'Hello {{name}}'; - }]); - </script> - <div ng-controller="GreeterController"> - <input ng-model="name"> <br> - <textarea ng-model="html"></textarea> <br> - <div compile="html"></div> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should auto compile', function() { - var textarea = $('textarea'); - var output = $('div[compile]'); - // The initial state reads 'Hello Angular'. - expect(output.getText()).toBe('Hello Angular'); - textarea.clear(); - textarea.sendKeys('{{name}}!'); - expect(output.getText()).toBe('Angular!'); - }); - </file> - </example> - - * - * - * @param {string|DOMElement} element Element or HTML string to compile into a template function. - * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives. - * @param {number} maxPriority only apply directives lower than given priority (Only effects the - * root element(s), not their children) - * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template - * (a DOM element/tree) to a scope. Where: - * - * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. - * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the - * `template` and call the `cloneAttachFn` function allowing the caller to attach the - * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is - * called as: <br> `cloneAttachFn(clonedElement, scope)` where: - * - * * `clonedElement` - is a clone of the original `element` passed into the compiler. - * * `scope` - is the current scope with which the linking function is working with. - * - * Calling the linking function returns the element of the template. It is either the original - * element passed in, or the clone of the element if the `cloneAttachFn` is provided. - * - * After linking the view is not updated until after a call to $digest which typically is done by - * Angular automatically. - * - * If you need access to the bound view, there are two ways to do it: - * - * - If you are not asking the linking function to clone the template, create the DOM element(s) - * before you send them to the compiler and keep this reference around. - * ```js - * var element = $compile('<p>{{total}}</p>')(scope); - * ``` - * - * - if on the other hand, you need the element to be cloned, the view reference from the original - * example would not point to the clone, but rather to the original template that was cloned. In - * this case, you can access the clone via the cloneAttachFn: - * ```js - * var templateElement = angular.element('<p>{{total}}</p>'), - * scope = ....; - * - * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { - * //attach the clone to DOM document at the right place - * }); - * - * //now we have reference to the cloned DOM via `clonedElement` - * ``` - * - * - * For information on how the compiler works, see the - * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. - */ - -var $compileMinErr = minErr('$compile'); - -/** - * @ngdoc provider - * @name $compileProvider - * - * @description - */ -$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; -function $CompileProvider($provide, $$sanitizeUriProvider) { - var hasDirectives = {}, - Suffix = 'Directive', - COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/, - CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/, - ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'); - - // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes - // The assumption is that future DOM event attribute names will begin with - // 'on' and be composed of only English letters. - var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; - - /** - * @ngdoc method - * @name $compileProvider#directive - * @kind function - * - * @description - * Register a new directive with the compiler. - * - * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which - * will match as <code>ng-bind</code>), or an object map of directives where the keys are the - * names and the values are the factories. - * @param {Function|Array} directiveFactory An injectable directive factory function. See - * {@link guide/directive} for more info. - * @returns {ng.$compileProvider} Self for chaining. - */ - this.directive = function registerDirective(name, directiveFactory) { - assertNotHasOwnProperty(name, 'directive'); - if (isString(name)) { - assertArg(directiveFactory, 'directiveFactory'); - if (!hasDirectives.hasOwnProperty(name)) { - hasDirectives[name] = []; - $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', - function($injector, $exceptionHandler) { - var directives = []; - forEach(hasDirectives[name], function(directiveFactory, index) { - try { - var directive = $injector.invoke(directiveFactory); - if (isFunction(directive)) { - directive = { compile: valueFn(directive) }; - } else if (!directive.compile && directive.link) { - directive.compile = valueFn(directive.link); - } - directive.priority = directive.priority || 0; - directive.index = index; - directive.name = directive.name || name; - directive.require = directive.require || (directive.controller && directive.name); - directive.restrict = directive.restrict || 'EA'; - directives.push(directive); - } catch (e) { - $exceptionHandler(e); - } - }); - return directives; - }]); - } - hasDirectives[name].push(directiveFactory); - } else { - forEach(name, reverseParams(registerDirective)); - } - return this; - }; - - - /** - * @ngdoc method - * @name $compileProvider#aHrefSanitizationWhitelist - * @kind function - * - * @description - * Retrieves or overrides the default regular expression that is used for whitelisting of safe - * urls during a[href] sanitization. - * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. - * - * Any url about to be assigned to a[href] via data-binding is first normalized and turned into - * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` - * regular expression. If a match is found, the original url is written into the dom. Otherwise, - * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. - * - * @param {RegExp=} regexp New regexp to whitelist urls with. - * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for - * chaining otherwise. - */ - this.aHrefSanitizationWhitelist = function(regexp) { - if (isDefined(regexp)) { - $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); - return this; - } else { - return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); - } - }; - - - /** - * @ngdoc method - * @name $compileProvider#imgSrcSanitizationWhitelist - * @kind function - * - * @description - * Retrieves or overrides the default regular expression that is used for whitelisting of safe - * urls during img[src] sanitization. - * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. - * - * Any url about to be assigned to img[src] via data-binding is first normalized and turned into - * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` - * regular expression. If a match is found, the original url is written into the dom. Otherwise, - * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. - * - * @param {RegExp=} regexp New regexp to whitelist urls with. - * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for - * chaining otherwise. - */ - this.imgSrcSanitizationWhitelist = function(regexp) { - if (isDefined(regexp)) { - $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); - return this; - } else { - return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); - } - }; - - /** - * @ngdoc method - * @name $compileProvider#debugInfoEnabled - * - * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the - * current debugInfoEnabled state - * @returns {*} current value if used as getter or itself (chaining) if used as setter - * - * @kind function - * - * @description - * Call this method to enable/disable various debug runtime information in the compiler such as adding - * binding information and a reference to the current scope on to DOM elements. - * If enabled, the compiler will add the following to DOM elements that have been bound to the scope - * * `ng-binding` CSS class - * * `$binding` data property containing an array of the binding expressions - * - * You may want to use this in production for a significant performance boost. See - * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. - * - * The default value is true. - */ - var debugInfoEnabled = true; - this.debugInfoEnabled = function(enabled) { - if(isDefined(enabled)) { - debugInfoEnabled = enabled; - return this; - } - return debugInfoEnabled; - }; - - this.$get = [ - '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', - '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', - function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, - $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { - - var Attributes = function(element, attributesToCopy) { - if (attributesToCopy) { - var keys = Object.keys(attributesToCopy); - var i, l, key; - - for (i = 0, l = keys.length; i < l; i++) { - key = keys[i]; - this[key] = attributesToCopy[key]; - } - } else { - this.$attr = {}; - } - - this.$$element = element; - }; - - Attributes.prototype = { - $normalize: directiveNormalize, - - - /** - * @ngdoc method - * @name $compile.directive.Attributes#$addClass - * @kind function - * - * @description - * Adds the CSS class value specified by the classVal parameter to the element. If animations - * are enabled then an animation will be triggered for the class addition. - * - * @param {string} classVal The className value that will be added to the element - */ - $addClass : function(classVal) { - if(classVal && classVal.length > 0) { - $animate.addClass(this.$$element, classVal); - } - }, - - /** - * @ngdoc method - * @name $compile.directive.Attributes#$removeClass - * @kind function - * - * @description - * Removes the CSS class value specified by the classVal parameter from the element. If - * animations are enabled then an animation will be triggered for the class removal. - * - * @param {string} classVal The className value that will be removed from the element - */ - $removeClass : function(classVal) { - if(classVal && classVal.length > 0) { - $animate.removeClass(this.$$element, classVal); - } - }, - - /** - * @ngdoc method - * @name $compile.directive.Attributes#$updateClass - * @kind function - * - * @description - * Adds and removes the appropriate CSS class values to the element based on the difference - * between the new and old CSS class values (specified as newClasses and oldClasses). - * - * @param {string} newClasses The current CSS className value - * @param {string} oldClasses The former CSS className value - */ - $updateClass : function(newClasses, oldClasses) { - var toAdd = tokenDifference(newClasses, oldClasses); - if (toAdd && toAdd.length) { - $animate.addClass(this.$$element, toAdd); - } - - var toRemove = tokenDifference(oldClasses, newClasses); - if (toRemove && toRemove.length) { - $animate.removeClass(this.$$element, toRemove); - } - }, - - /** - * Set a normalized attribute on the element in a way such that all directives - * can share the attribute. This function properly handles boolean attributes. - * @param {string} key Normalized key. (ie ngAttribute) - * @param {string|boolean} value The value to set. If `null` attribute will be deleted. - * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. - * Defaults to true. - * @param {string=} attrName Optional none normalized name. Defaults to key. - */ - $set: function(key, value, writeAttr, attrName) { - // TODO: decide whether or not to throw an error if "class" - //is set through this function since it may cause $updateClass to - //become unstable. - - var node = this.$$element[0], - booleanKey = getBooleanAttrName(node, key), - aliasedKey = getAliasedAttrName(node, key), - observer = key, - normalizedVal, - nodeName; - - if (booleanKey) { - this.$$element.prop(key, value); - attrName = booleanKey; - } else if(aliasedKey) { - this[aliasedKey] = value; - observer = aliasedKey; - } - - this[key] = value; - - // translate normalized key to actual key - if (attrName) { - this.$attr[key] = attrName; - } else { - attrName = this.$attr[key]; - if (!attrName) { - this.$attr[key] = attrName = snake_case(key, '-'); - } - } - - nodeName = nodeName_(this.$$element); - - // sanitize a[href] and img[src] values - if ((nodeName === 'a' && key === 'href') || - (nodeName === 'img' && key === 'src')) { - this[key] = value = $$sanitizeUri(value, key === 'src'); - } - - if (writeAttr !== false) { - if (value === null || value === undefined) { - this.$$element.removeAttr(attrName); - } else { - this.$$element.attr(attrName, value); - } - } - - // fire observers - var $$observers = this.$$observers; - $$observers && forEach($$observers[observer], function(fn) { - try { - fn(value); - } catch (e) { - $exceptionHandler(e); - } - }); - }, - - - /** - * @ngdoc method - * @name $compile.directive.Attributes#$observe - * @kind function - * - * @description - * Observes an interpolated attribute. - * - * The observer function will be invoked once during the next `$digest` following - * compilation. The observer is then invoked whenever the interpolated value - * changes. - * - * @param {string} key Normalized key. (ie ngAttribute) . - * @param {function(interpolatedValue)} fn Function that will be called whenever - the interpolated value of the attribute changes. - * See the {@link guide/directive#Attributes Directives} guide for more info. - * @returns {function()} Returns a deregistration function for this observer. - */ - $observe: function(key, fn) { - var attrs = this, - $$observers = (attrs.$$observers || (attrs.$$observers = {})), - listeners = ($$observers[key] || ($$observers[key] = [])); - - listeners.push(fn); - $rootScope.$evalAsync(function() { - if (!listeners.$$inter) { - // no one registered attribute interpolation function, so lets call it manually - fn(attrs[key]); - } - }); - - return function() { - arrayRemove(listeners, fn); - }; - } - }; - - - function safeAddClass($element, className) { - try { - $element.addClass(className); - } catch(e) { - // ignore, since it means that we are trying to set class on - // SVG element, where class name is read-only. - } - } - - - var startSymbol = $interpolate.startSymbol(), - endSymbol = $interpolate.endSymbol(), - denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') - ? identity - : function denormalizeTemplate(template) { - return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); - }, - NG_ATTR_BINDING = /^ngAttr[A-Z]/; - - compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { - var bindings = $element.data('$binding') || []; - - if (isArray(binding)) { - bindings = bindings.concat(binding); - } else { - bindings.push(binding); - } - - $element.data('$binding', bindings); - } : noop; - - compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { - safeAddClass($element, 'ng-binding'); - } : noop; - - compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { - var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; - $element.data(dataName, scope); - } : noop; - - compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { - safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); - } : noop; - - return compile; - - //================================ - - function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, - previousCompileContext) { - if (!($compileNodes instanceof jqLite)) { - // jquery always rewraps, whereas we need to preserve the original selector so that we can - // modify it. - $compileNodes = jqLite($compileNodes); - } - // We can not compile top level text elements since text nodes can be merged and we will - // not be able to attach scope data to them, so we will wrap them in <span> - forEach($compileNodes, function(node, index){ - if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { - $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0]; - } - }); - var compositeLinkFn = - compileNodes($compileNodes, transcludeFn, $compileNodes, - maxPriority, ignoreDirective, previousCompileContext); - compile.$$addScopeClass($compileNodes); - var namespace = null; - var namespaceAdaptedCompileNodes = $compileNodes; - var lastCompileNode; - return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement){ - assertArg(scope, 'scope'); - if (!namespace) { - namespace = detectNamespaceForChildElements(futureParentElement); - } - if (namespace !== 'html' && $compileNodes[0] !== lastCompileNode) { - namespaceAdaptedCompileNodes = jqLite( - wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html()) - ); - } - // When using a directive with replace:true and templateUrl the $compileNodes - // might change, so we need to recreate the namespace adapted compileNodes. - lastCompileNode = $compileNodes[0]; - - // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart - // and sometimes changes the structure of the DOM. - var $linkNode = cloneConnectFn - ? JQLitePrototype.clone.call(namespaceAdaptedCompileNodes) // IMPORTANT!!! - : namespaceAdaptedCompileNodes; - - if (transcludeControllers) { - for (var controllerName in transcludeControllers) { - $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); - } - } - - compile.$$addScopeInfo($linkNode, scope); - - if (cloneConnectFn) cloneConnectFn($linkNode, scope); - if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); - return $linkNode; - }; - } - - function detectNamespaceForChildElements(parentElement) { - // TODO: Make this detect MathML as well... - var node = parentElement && parentElement[0]; - if (!node) { - return 'html'; - } else { - return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg': 'html'; - } - } - - /** - * Compile function matches each node in nodeList against the directives. Once all directives - * for a particular node are collected their compile functions are executed. The compile - * functions return values - the linking functions - are combined into a composite linking - * function, which is the a linking function for the node. - * - * @param {NodeList} nodeList an array of nodes or NodeList to compile - * @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 the nodeList is the root of the compilation tree then - * the rootElement must be set the jqLite collection of the compile root. This is - * needed so that the jqLite collection items can be replaced with widgets. - * @param {number=} maxPriority Max directive priority. - * @returns {Function} A composite linking function of all of the matched directives or null. - */ - function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, - previousCompileContext) { - var linkFns = [], - attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; - - for (var i = 0; i < nodeList.length; i++) { - attrs = new Attributes(); - - // we must always refer to nodeList[i] since the nodes can be replaced underneath us. - directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, - ignoreDirective); - - nodeLinkFn = (directives.length) - ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, - null, [], [], previousCompileContext) - : null; - - if (nodeLinkFn && nodeLinkFn.scope) { - compile.$$addScopeClass(attrs.$$element); - } - - childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || - !(childNodes = nodeList[i].childNodes) || - !childNodes.length) - ? null - : compileNodes(childNodes, - nodeLinkFn ? ( - (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) - && nodeLinkFn.transclude) : transcludeFn); - - if (nodeLinkFn || childLinkFn) { - linkFns.push(i, nodeLinkFn, childLinkFn); - linkFnFound = true; - nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; - } - - //use the previous context only for the first element in the virtual group - previousCompileContext = null; - } - - // return a linking function if we have found anything, null otherwise - return linkFnFound ? compositeLinkFn : null; - - function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { - var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; - var stableNodeList; - - - if (nodeLinkFnFound) { - // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our - // offsets don't get screwed up - var nodeListLength = nodeList.length; - stableNodeList = new Array(nodeListLength); - - // create a sparse array by only copying the elements which have a linkFn - for (i = 0; i < linkFns.length; i+=3) { - idx = linkFns[i]; - stableNodeList[idx] = nodeList[idx]; - } - } else { - stableNodeList = nodeList; - } - - for(i = 0, ii = linkFns.length; i < ii;) { - node = stableNodeList[linkFns[i++]]; - nodeLinkFn = linkFns[i++]; - childLinkFn = linkFns[i++]; - - if (nodeLinkFn) { - if (nodeLinkFn.scope) { - childScope = scope.$new(); - compile.$$addScopeInfo(jqLite(node), childScope); - } else { - childScope = scope; - } - - if ( nodeLinkFn.transcludeOnThisElement ) { - childBoundTranscludeFn = createBoundTranscludeFn( - scope, nodeLinkFn.transclude, parentBoundTranscludeFn, - nodeLinkFn.elementTranscludeOnThisElement); - - } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { - childBoundTranscludeFn = parentBoundTranscludeFn; - - } else if (!parentBoundTranscludeFn && transcludeFn) { - childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); - - } else { - childBoundTranscludeFn = null; - } - - nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); - - } else if (childLinkFn) { - childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); - } - } - } - } - - function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn, elementTransclusion) { - - var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement) { - var scopeCreated = false; - - if (!transcludedScope) { - transcludedScope = scope.$new(); - transcludedScope.$$transcluded = true; - scopeCreated = true; - } - - var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement); - if (scopeCreated && !elementTransclusion) { - clone.on('$destroy', function() { transcludedScope.$destroy(); }); - } - return clone; - }; - - return boundTranscludeFn; - } - - /** - * Looks for directives on the given node and adds them to the directive collection which is - * sorted. - * - * @param node Node to search. - * @param directives An array to which the directives are added to. This array is sorted before - * the function returns. - * @param attrs The shared attrs object which is used to populate the normalized attributes. - * @param {number=} maxPriority Max directive priority. - */ - function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { - var nodeType = node.nodeType, - attrsMap = attrs.$attr, - match, - className; - - switch(nodeType) { - case 1: /* Element */ - // use the node name: <directive> - addDirective(directives, - directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective); - - // iterate over the attributes - for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, - j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { - var attrStartName = false; - var attrEndName = false; - - attr = nAttrs[j]; - if (!msie || msie >= 8 || attr.specified) { - name = attr.name; - value = trim(attr.value); - - // support ngAttr attribute binding - ngAttrName = directiveNormalize(name); - if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { - name = snake_case(ngAttrName.substr(6), '-'); - } - - var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); - if (directiveIsMultiElement(directiveNName)) { - if (ngAttrName === directiveNName + 'Start') { - attrStartName = name; - attrEndName = name.substr(0, name.length - 5) + 'end'; - name = name.substr(0, name.length - 6); - } - } - - nName = directiveNormalize(name.toLowerCase()); - attrsMap[nName] = name; - if (isNgAttr || !attrs.hasOwnProperty(nName)) { - attrs[nName] = value; - if (getBooleanAttrName(node, nName)) { - attrs[nName] = true; // presence means true - } - } - addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); - addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, - attrEndName); - } - } - - // use class as directive - className = node.className; - if (isString(className) && className !== '') { - while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { - nName = directiveNormalize(match[2]); - if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { - attrs[nName] = trim(match[3]); - } - className = className.substr(match.index + match[0].length); - } - } - break; - case 3: /* Text Node */ - addTextInterpolateDirective(directives, node.nodeValue); - break; - case 8: /* Comment */ - try { - match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); - if (match) { - nName = directiveNormalize(match[1]); - if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { - attrs[nName] = trim(match[2]); - } - } - } catch (e) { - // turns out that under some circumstances IE9 throws errors when one attempts to read - // comment's node value. - // Just ignore it and continue. (Can't seem to reproduce in test case.) - } - break; - } - - directives.sort(byPriority); - return directives; - } - - /** - * Given a node with an directive-start it collects all of the siblings until it finds - * directive-end. - * @param node - * @param attrStart - * @param attrEnd - * @returns {*} - */ - function groupScan(node, attrStart, attrEnd) { - var nodes = []; - var depth = 0; - if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { - var startNode = node; - do { - if (!node) { - throw $compileMinErr('uterdir', - "Unterminated attribute, found '{0}' but no matching '{1}' found.", - attrStart, attrEnd); - } - if (node.nodeType == 1 /** Element **/) { - if (node.hasAttribute(attrStart)) depth++; - if (node.hasAttribute(attrEnd)) depth--; - } - nodes.push(node); - node = node.nextSibling; - } while (depth > 0); - } else { - nodes.push(node); - } - - return jqLite(nodes); - } - - /** - * Wrapper for linking function which converts normal linking function into a grouped - * linking function. - * @param linkFn - * @param attrStart - * @param attrEnd - * @returns {Function} - */ - function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { - return function(scope, element, attrs, controllers, transcludeFn) { - element = groupScan(element[0], attrStart, attrEnd); - return linkFn(scope, element, attrs, controllers, transcludeFn); - }; - } - - /** - * 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. - * - * @param {Array} directives Array of collected directives to execute their compile function. - * this needs to be pre-sorted by priority order. - * @param {Node} compileNode The raw DOM node to apply the compile functions to - * @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 {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. - * @param {Object=} originalReplaceDirective An optional directive that will be ignored when - * compiling the transclusion. - * @param {Array.<Function>} preLinkFns - * @param {Array.<Function>} postLinkFns - * @param {Object} previousCompileContext Context used for previous compilation of the current - * node - * @returns {Function} linkFn - */ - function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, - jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, - previousCompileContext) { - previousCompileContext = previousCompileContext || {}; - - var terminalPriority = -Number.MAX_VALUE, - newScopeDirective, - controllerDirectives = previousCompileContext.controllerDirectives, - controllers, - newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, - templateDirective = previousCompileContext.templateDirective, - nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, - hasTranscludeDirective = false, - hasTemplate = false, - hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, - $compileNode = templateAttrs.$$element = jqLite(compileNode), - directive, - directiveName, - $template, - replaceDirective = originalReplaceDirective, - childTranscludeFn = transcludeFn, - linkFn, - directiveValue; - - // executes all directives on the current element - for(var i = 0, ii = directives.length; i < ii; i++) { - directive = directives[i]; - var attrStart = directive.$$start; - var attrEnd = directive.$$end; - - // collect multiblock sections - if (attrStart) { - $compileNode = groupScan(compileNode, attrStart, attrEnd); - } - $template = undefined; - - if (terminalPriority > directive.priority) { - break; // prevent further processing of directives - } - - if (directiveValue = directive.scope) { - - // skip the check for directives with async templates, we'll check the derived sync - // directive when the template arrives - if (!directive.templateUrl) { - if (isObject(directiveValue)) { - // This directive is trying to add an isolated scope. - // Check that there is no scope of any kind already - assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, - directive, $compileNode); - newIsolateScopeDirective = directive; - } else { - // This directive is trying to add a child scope. - // Check that there is no isolated scope already - assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, - $compileNode); - } - } - - newScopeDirective = newScopeDirective || directive; - } - - directiveName = directive.name; - - if (!directive.templateUrl && directive.controller) { - directiveValue = directive.controller; - controllerDirectives = controllerDirectives || {}; - assertNoDuplicate("'" + directiveName + "' controller", - controllerDirectives[directiveName], directive, $compileNode); - controllerDirectives[directiveName] = directive; - } - - if (directiveValue = directive.transclude) { - hasTranscludeDirective = true; - - // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. - // This option should only be used by directives that know how to safely handle element transclusion, - // where the transcluded nodes are added or replaced after linking. - if (!directive.$$tlb) { - assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); - nonTlbTranscludeDirective = directive; - } - - if (directiveValue == 'element') { - hasElementTranscludeDirective = true; - terminalPriority = directive.priority; - $template = $compileNode; - $compileNode = templateAttrs.$$element = - jqLite(document.createComment(' ' + directiveName + ': ' + - templateAttrs[directiveName] + ' ')); - compileNode = $compileNode[0]; - replaceWith(jqCollection, sliceArgs($template), compileNode); - - childTranscludeFn = compile($template, transcludeFn, terminalPriority, - replaceDirective && replaceDirective.name, { - // Don't pass in: - // - controllerDirectives - otherwise we'll create duplicates controllers - // - newIsolateScopeDirective or templateDirective - combining templates with - // element transclusion doesn't make sense. - // - // We need only nonTlbTranscludeDirective so that we prevent putting transclusion - // on the same element more than once. - nonTlbTranscludeDirective: nonTlbTranscludeDirective - }); - } else { - $template = jqLite(jqLiteClone(compileNode)).contents(); - $compileNode.empty(); // clear contents - childTranscludeFn = compile($template, transcludeFn); - } - } - - if (directive.template) { - hasTemplate = true; - assertNoDuplicate('template', templateDirective, directive, $compileNode); - templateDirective = directive; - - directiveValue = (isFunction(directive.template)) - ? directive.template($compileNode, templateAttrs) - : directive.template; - - directiveValue = denormalizeTemplate(directiveValue); - - if (directive.replace) { - replaceDirective = directive; - if (jqLiteIsTextNode(directiveValue)) { - $template = []; - } else { - $template = jqLite(wrapTemplate(directive.templateNamespace, trim(directiveValue))); - } - compileNode = $template[0]; - - if ($template.length != 1 || compileNode.nodeType !== 1) { - throw $compileMinErr('tplrt', - "Template for directive '{0}' must have exactly one root element. {1}", - directiveName, ''); - } - - replaceWith(jqCollection, $compileNode, compileNode); - - var newTemplateAttrs = {$attr: {}}; - - // combine directives from the original node and from the template: - // - take the array of directives for this element - // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) - // - collect directives from the template and sort them by priority - // - combine directives as: processed + template + unprocessed - var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); - var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); - - if (newIsolateScopeDirective) { - markDirectivesAsIsolate(templateDirectives); - } - directives = directives.concat(templateDirectives).concat(unprocessedDirectives); - mergeTemplateAttributes(templateAttrs, newTemplateAttrs); - - ii = directives.length; - } else { - $compileNode.html(directiveValue); - } - } - - if (directive.templateUrl) { - hasTemplate = true; - assertNoDuplicate('template', templateDirective, directive, $compileNode); - templateDirective = directive; - - if (directive.replace) { - replaceDirective = directive; - } - - nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, - templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { - controllerDirectives: controllerDirectives, - newIsolateScopeDirective: newIsolateScopeDirective, - templateDirective: templateDirective, - nonTlbTranscludeDirective: nonTlbTranscludeDirective - }); - ii = directives.length; - } else if (directive.compile) { - try { - linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); - if (isFunction(linkFn)) { - addLinkFns(null, linkFn, attrStart, attrEnd); - } else if (linkFn) { - addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); - } - } catch (e) { - $exceptionHandler(e, startingTag($compileNode)); - } - } - - if (directive.terminal) { - nodeLinkFn.terminal = true; - terminalPriority = Math.max(terminalPriority, directive.priority); - } - - } - - nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; - nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; - nodeLinkFn.elementTranscludeOnThisElement = hasElementTranscludeDirective; - nodeLinkFn.templateOnThisElement = hasTemplate; - nodeLinkFn.transclude = childTranscludeFn; - - previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; - - // might be normal or delayed nodeLinkFn depending on if templateUrl is present - return nodeLinkFn; - - //////////////////// - - function addLinkFns(pre, post, attrStart, attrEnd) { - if (pre) { - if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); - pre.require = directive.require; - pre.directiveName = directiveName; - if (newIsolateScopeDirective === directive || directive.$$isolateScope) { - pre = cloneAndAnnotateFn(pre, {isolateScope: true}); - } - preLinkFns.push(pre); - } - if (post) { - if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); - post.require = directive.require; - post.directiveName = directiveName; - if (newIsolateScopeDirective === directive || directive.$$isolateScope) { - post = cloneAndAnnotateFn(post, {isolateScope: true}); - } - postLinkFns.push(post); - } - } - - - function getControllers(directiveName, require, $element, elementControllers) { - var value, retrievalMethod = 'data', optional = false; - if (isString(require)) { - while((value = require.charAt(0)) == '^' || value == '?') { - require = require.substr(1); - if (value == '^') { - retrievalMethod = 'inheritedData'; - } - optional = optional || value == '?'; - } - value = null; - - if (elementControllers && retrievalMethod === 'data') { - if (value = elementControllers[require]) { - value = value.instance; - } - } - value = value || $element[retrievalMethod]('$' + require + 'Controller'); - - if (!value && !optional) { - throw $compileMinErr('ctreq', - "Controller '{0}', required by directive '{1}', can't be found!", - require, directiveName); - } - return value; - } else if (isArray(require)) { - value = []; - forEach(require, function(require) { - value.push(getControllers(directiveName, require, $element, elementControllers)); - }); - } - return value; - } - - - function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { - var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element, - attrs; - - if (compileNode === linkNode) { - attrs = templateAttrs; - $element = templateAttrs.$$element; - } else { - $element = jqLite(linkNode); - attrs = new Attributes($element, templateAttrs); - } - - if (newIsolateScopeDirective) { - isolateScope = scope.$new(true); - } - - transcludeFn = boundTranscludeFn && controllersBoundTransclude; - if (controllerDirectives) { - // TODO: merge `controllers` and `elementControllers` into single object. - controllers = {}; - elementControllers = {}; - forEach(controllerDirectives, function(directive) { - var locals = { - $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, - $element: $element, - $attrs: attrs, - $transclude: transcludeFn - }, controllerInstance; - - controller = directive.controller; - if (controller == '@') { - controller = attrs[directive.name]; - } - - controllerInstance = $controller(controller, locals, true, directive.controllerAs); - - // For directives with element transclusion the element is a comment, - // but jQuery .data doesn't support attaching data to comment nodes as it's hard to - // clean up (http://bugs.jquery.com/ticket/8335). - // Instead, we save the controllers for the element in a local hash and attach to .data - // later, once we have the actual element. - elementControllers[directive.name] = controllerInstance; - if (!hasElementTranscludeDirective) { - $element.data('$' + directive.name + 'Controller', controllerInstance.instance); - } - - controllers[directive.name] = controllerInstance; - }); - } - - if (newIsolateScopeDirective) { - var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; - - compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || - templateDirective === newIsolateScopeDirective.$$originalDirective))); - compile.$$addScopeClass($element, true); - - var isolateScopeController = controllers && controllers[newIsolateScopeDirective.name]; - var isolateBindingContext = isolateScope; - if (isolateScopeController && isolateScopeController.identifier && - newIsolateScopeDirective.bindToController === true) { - isolateBindingContext = isolateScopeController.instance; - } - forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { - var match = definition.match(LOCAL_REGEXP) || [], - attrName = match[3] || scopeName, - optional = (match[2] == '?'), - mode = match[1], // @, =, or & - lastValue, - parentGet, parentSet, compare; - - isolateScope.$$isolateBindings[scopeName] = mode + attrName; - - switch (mode) { - - case '@': - attrs.$observe(attrName, function(value) { - isolateScope[scopeName] = value; - }); - attrs.$$observers[attrName].$$scope = scope; - if( attrs[attrName] ) { - // If the attribute has been provided then we trigger an interpolation to ensure - // the value is there for use in the link fn - isolateBindingContext[scopeName] = $interpolate(attrs[attrName])(scope); - } - break; - - case '=': - if (optional && !attrs[attrName]) { - return; - } - parentGet = $parse(attrs[attrName]); - if (parentGet.literal) { - compare = equals; - } else { - compare = function(a,b) { return a === b || (a !== a && b !== b); }; - } - parentSet = parentGet.assign || function() { - // reset the change, or we will throw this exception on every $digest - lastValue = isolateBindingContext[scopeName] = parentGet(scope); - throw $compileMinErr('nonassign', - "Expression '{0}' used with directive '{1}' is non-assignable!", - attrs[attrName], newIsolateScopeDirective.name); - }; - lastValue = isolateBindingContext[scopeName] = parentGet(scope); - var unwatch = scope.$watch($parse(attrs[attrName], function parentValueWatch(parentValue) { - if (!compare(parentValue, isolateBindingContext[scopeName])) { - // we are out of sync and need to copy - if (!compare(parentValue, lastValue)) { - // parent changed and it has precedence - isolateBindingContext[scopeName] = parentValue; - } else { - // if the parent can be assigned then do so - parentSet(scope, parentValue = isolateBindingContext[scopeName]); - } - } - return lastValue = parentValue; - }), null, parentGet.literal); - isolateScope.$on('$destroy', unwatch); - break; - - case '&': - parentGet = $parse(attrs[attrName]); - isolateBindingContext[scopeName] = function(locals) { - return parentGet(scope, locals); - }; - break; - - default: - throw $compileMinErr('iscp', - "Invalid isolate scope definition for directive '{0}'." + - " Definition: {... {1}: '{2}' ...}", - newIsolateScopeDirective.name, scopeName, definition); - } - }); - } - if (controllers) { - forEach(controllers, function(controller) { - controller(); - }); - controllers = null; - } - - // PRELINKING - for(i = 0, ii = preLinkFns.length; i < ii; i++) { - linkFn = preLinkFns[i]; - invokeLinkFn(linkFn, - linkFn.isolateScope ? isolateScope : scope, - $element, - attrs, - linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), - transcludeFn - ); - } - - // RECURSION - // We only pass the isolate scope, if the isolate directive has a template, - // otherwise the child elements do not belong to the isolate directive. - var scopeToChild = scope; - if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { - scopeToChild = isolateScope; - } - childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); - - // POSTLINKING - for(i = postLinkFns.length - 1; i >= 0; i--) { - linkFn = postLinkFns[i]; - invokeLinkFn(linkFn, - linkFn.isolateScope ? isolateScope : scope, - $element, - attrs, - linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), - transcludeFn - ); - } - - // This is the function that is injected as `$transclude`. - // Note: all arguments are optional! - function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) { - var transcludeControllers; - - // No scope passed in: - if (!isScope(scope)) { - futureParentElement = cloneAttachFn; - cloneAttachFn = scope; - scope = undefined; - } - - if (hasElementTranscludeDirective) { - transcludeControllers = elementControllers; - } - if (!futureParentElement) { - futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; - } - return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement); - } - } - } - - function markDirectivesAsIsolate(directives) { - // mark all directives as needing isolate scope. - for (var j = 0, jj = directives.length; j < jj; j++) { - directives[j] = inherit(directives[j], {$$isolateScope: true}); - } - } - - /** - * looks up the directive and decorates it with exception handling and proper parameters. We - * call this the boundDirective. - * - * @param {string} name name of the directive to look up. - * @param {string} location The directive must be found in specific format. - * String containing any of theses characters: - * - * * `E`: element name - * * `A': attribute - * * `C`: class - * * `M`: comment - * @returns {boolean} true if directive was added. - */ - function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, - endAttrName) { - if (name === ignoreDirective) return null; - var match = null; - if (hasDirectives.hasOwnProperty(name)) { - for(var directive, directives = $injector.get(name + Suffix), - i = 0, ii = directives.length; i<ii; i++) { - try { - directive = directives[i]; - if ( (maxPriority === undefined || maxPriority > directive.priority) && - directive.restrict.indexOf(location) != -1) { - if (startAttrName) { - directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); - } - tDirectives.push(directive); - match = directive; - } - } catch(e) { $exceptionHandler(e); } - } - } - return match; - } - - - /** - * looks up the directive and returns true if it is a multi-element directive, - * and therefore requires DOM nodes between -start and -end markers to be grouped - * together. - * - * @param {string} name name of the directive to look up. - * @returns true if directive was registered as multi-element. - */ - function directiveIsMultiElement(name) { - if (hasDirectives.hasOwnProperty(name)) { - for(var directive, directives = $injector.get(name + Suffix), - i = 0, ii = directives.length; i<ii; i++) { - directive = directives[i]; - if (directive.multiElement) { - return true; - } - } - } - return false; - } - - /** - * When the element is replaced with HTML template then the new attributes - * on the template need to be merged with the existing attributes in the DOM. - * The desired effect is to have both of the attributes present. - * - * @param {object} dst destination attributes (original DOM) - * @param {object} src source attributes (from the directive template) - */ - function mergeTemplateAttributes(dst, src) { - var srcAttr = src.$attr, - dstAttr = dst.$attr, - $element = dst.$$element; - - // reapply the old attributes to the new element - forEach(dst, function(value, key) { - if (key.charAt(0) != '$') { - if (src[key] && src[key] !== value) { - value += (key === 'style' ? ';' : ' ') + src[key]; - } - dst.$set(key, value, true, srcAttr[key]); - } - }); - - // copy the new attributes on the old attrs object - forEach(src, function(value, key) { - if (key == 'class') { - safeAddClass($element, value); - dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; - } else if (key == 'style') { - $element.attr('style', $element.attr('style') + ';' + value); - dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; - // `dst` will never contain hasOwnProperty as DOM parser won't let it. - // You will get an "InvalidCharacterError: DOM Exception 5" error if you - // have an attribute like "has-own-property" or "data-has-own-property", etc. - } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { - dst[key] = value; - dstAttr[key] = srcAttr[key]; - } - }); - } - - - function compileTemplateUrl(directives, $compileNode, tAttrs, - $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { - var linkQueue = [], - afterTemplateNodeLinkFn, - afterTemplateChildLinkFn, - beforeTemplateCompileNode = $compileNode[0], - origAsyncDirective = directives.shift(), - // The fact that we have to copy and patch the directive seems wrong! - derivedSyncDirective = extend({}, origAsyncDirective, { - templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective - }), - templateUrl = (isFunction(origAsyncDirective.templateUrl)) - ? origAsyncDirective.templateUrl($compileNode, tAttrs) - : origAsyncDirective.templateUrl, - templateNamespace = origAsyncDirective.templateNamespace; - - $compileNode.empty(); - - $templateRequest($sce.getTrustedResourceUrl(templateUrl)) - .then(function(content) { - var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; - - content = denormalizeTemplate(content); - - if (origAsyncDirective.replace) { - if (jqLiteIsTextNode(content)) { - $template = []; - } else { - $template = jqLite(wrapTemplate(templateNamespace, trim(content))); - } - compileNode = $template[0]; - - if ($template.length != 1 || compileNode.nodeType !== 1) { - throw $compileMinErr('tplrt', - "Template for directive '{0}' must have exactly one root element. {1}", - origAsyncDirective.name, templateUrl); - } - - tempTemplateAttrs = {$attr: {}}; - replaceWith($rootElement, $compileNode, compileNode); - var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); - - if (isObject(origAsyncDirective.scope)) { - markDirectivesAsIsolate(templateDirectives); - } - directives = templateDirectives.concat(directives); - mergeTemplateAttributes(tAttrs, tempTemplateAttrs); - } else { - compileNode = beforeTemplateCompileNode; - $compileNode.html(content); - } - - directives.unshift(derivedSyncDirective); - - afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, - childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, - previousCompileContext); - forEach($rootElement, function(node, i) { - if (node == compileNode) { - $rootElement[i] = $compileNode[0]; - } - }); - afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); - - while(linkQueue.length) { - var scope = linkQueue.shift(), - beforeTemplateLinkNode = linkQueue.shift(), - linkRootElement = linkQueue.shift(), - boundTranscludeFn = linkQueue.shift(), - linkNode = $compileNode[0]; - - if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { - var oldClasses = beforeTemplateLinkNode.className; - - if (!(previousCompileContext.hasElementTranscludeDirective && - origAsyncDirective.replace)) { - // it was cloned therefore we have to clone as well. - linkNode = jqLiteClone(compileNode); - } - replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); - - // Copy in CSS classes from original node - safeAddClass(jqLite(linkNode), oldClasses); - } - if (afterTemplateNodeLinkFn.transcludeOnThisElement) { - childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); - } else { - childBoundTranscludeFn = boundTranscludeFn; - } - afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, - childBoundTranscludeFn); - } - linkQueue = null; - }); - - return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { - var childBoundTranscludeFn = boundTranscludeFn; - if (linkQueue) { - linkQueue.push(scope); - linkQueue.push(node); - linkQueue.push(rootElement); - linkQueue.push(childBoundTranscludeFn); - } else { - if (afterTemplateNodeLinkFn.transcludeOnThisElement) { - childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); - } - afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); - } - }; - } - - - /** - * Sorting function for bound directives. - */ - function byPriority(a, b) { - var diff = b.priority - a.priority; - if (diff !== 0) return diff; - if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; - return a.index - b.index; - } - - - function assertNoDuplicate(what, previousDirective, directive, element) { - if (previousDirective) { - throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', - previousDirective.name, directive.name, what, startingTag(element)); - } - } - - - function addTextInterpolateDirective(directives, text) { - var interpolateFn = $interpolate(text, true); - if (interpolateFn) { - directives.push({ - priority: 0, - compile: function textInterpolateCompileFn(templateNode) { - var templateNodeParent = templateNode.parent(), - hasCompileParent = !!templateNodeParent.length; - - // When transcluding a template that has bindings in the root - // we don't have a parent and thus need to add the class during linking fn. - if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); - - return function textInterpolateLinkFn(scope, node) { - var parent = node.parent(); - if (!hasCompileParent) compile.$$addBindingClass(parent); - compile.$$addBindingInfo(parent, interpolateFn.expressions); - scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { - node[0].nodeValue = value; - }); - }; - } - }); - } - } - - - function wrapTemplate(type, template) { - type = lowercase(type || 'html'); - switch(type) { - case 'svg': - case 'math': - var wrapper = document.createElement('div'); - wrapper.innerHTML = '<'+type+'>'+template+'</'+type+'>'; - return wrapper.childNodes[0].childNodes; - default: - return template; - } - } - - - function getTrustedContext(node, attrNormalizedName) { - if (attrNormalizedName == "srcdoc") { - return $sce.HTML; - } - var tag = nodeName_(node); - // maction[xlink:href] can source SVG. It's not limited to <maction>. - if (attrNormalizedName == "xlinkHref" || - (tag == "form" && attrNormalizedName == "action") || - (tag != "img" && (attrNormalizedName == "src" || - attrNormalizedName == "ngSrc"))) { - return $sce.RESOURCE_URL; - } - } - - - function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { - var interpolateFn = $interpolate(value, true); - - // no interpolation found -> ignore - if (!interpolateFn) return; - - - if (name === "multiple" && nodeName_(node) === "select") { - throw $compileMinErr("selmulti", - "Binding to the 'multiple' attribute is not supported. Element: {0}", - startingTag(node)); - } - - directives.push({ - priority: 100, - compile: function() { - return { - pre: function attrInterpolatePreLinkFn(scope, element, attr) { - var $$observers = (attr.$$observers || (attr.$$observers = {})); - - if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { - throw $compileMinErr('nodomevents', - "Interpolations for HTML DOM event attributes are disallowed. Please use the " + - "ng- versions (such as ng-click instead of onclick) instead."); - } - - // we need to interpolate again, in case the attribute value has been updated - // (e.g. by another directive's compile function) - interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name), - ALL_OR_NOTHING_ATTRS[name] || allOrNothing); - - // if attribute was updated so that there is no interpolation going on we don't want to - // register any observers - if (!interpolateFn) return; - - // initialize attr object so that it's ready in case we need the value for isolate - // scope initialization, otherwise the value would not be available from isolate - // directive's linking fn during linking phase - attr[name] = interpolateFn(scope); - - ($$observers[name] || ($$observers[name] = [])).$$inter = true; - (attr.$$observers && attr.$$observers[name].$$scope || scope). - $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { - //special case for class attribute addition + removal - //so that class changes can tap into the animation - //hooks provided by the $animate service. Be sure to - //skip animations when the first digest occurs (when - //both the new and the old values are the same) since - //the CSS classes are the non-interpolated values - if(name === 'class' && newValue != oldValue) { - attr.$updateClass(newValue, oldValue); - } else { - attr.$set(name, newValue); - } - }); - } - }; - } - }); - } - - - /** - * This is a special jqLite.replaceWith, which can replace items which - * have no parents, provided that the containing jqLite collection is provided. - * - * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes - * in the root of the tree. - * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep - * the shell, but replace its DOM node reference. - * @param {Node} newNode The new DOM node. - */ - function replaceWith($rootElement, elementsToRemove, newNode) { - var firstElementToRemove = elementsToRemove[0], - removeCount = elementsToRemove.length, - parent = firstElementToRemove.parentNode, - i, ii; - - if ($rootElement) { - for(i = 0, ii = $rootElement.length; i < ii; i++) { - if ($rootElement[i] == firstElementToRemove) { - $rootElement[i++] = newNode; - for (var j = i, j2 = j + removeCount - 1, - jj = $rootElement.length; - j < jj; j++, j2++) { - if (j2 < jj) { - $rootElement[j] = $rootElement[j2]; - } else { - delete $rootElement[j]; - } - } - $rootElement.length -= removeCount - 1; - - // If the replaced element is also the jQuery .context then replace it - // .context is a deprecated jQuery api, so we should set it only when jQuery set it - // http://api.jquery.com/context/ - if ($rootElement.context === firstElementToRemove) { - $rootElement.context = newNode; - } - break; - } - } - } - - if (parent) { - parent.replaceChild(newNode, firstElementToRemove); - } - - // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it? - var fragment = document.createDocumentFragment(); - fragment.appendChild(firstElementToRemove); - - // Copy over user data (that includes Angular's $scope etc.). Don't copy private - // data here because there's no public interface in jQuery to do that and copying over - // event listeners (which is the main use of private data) wouldn't work anyway. - jqLite(newNode).data(jqLite(firstElementToRemove).data()); - - // Remove data of the replaced element. We cannot just call .remove() - // on the element it since that would deallocate scope that is needed - // for the new node. Instead, remove the data "manually". - if (!jQuery) { - delete jqLite.cache[firstElementToRemove[jqLite.expando]]; - } else { - // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after - // the replaced element. The cleanData version monkey-patched by Angular would cause - // the scope to be trashed and we do need the very same scope to work with the new - // element. However, we cannot just cache the non-patched version and use it here as - // that would break if another library patches the method after Angular does (one - // example is jQuery UI). Instead, set a flag indicating scope destroying should be - // skipped this one time. - skipDestroyOnNextJQueryCleanData = true; - jQuery.cleanData([firstElementToRemove]); - } - - for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { - var element = elementsToRemove[k]; - jqLite(element).remove(); // must do this way to clean up expando - fragment.appendChild(element); - delete elementsToRemove[k]; - } - - elementsToRemove[0] = newNode; - elementsToRemove.length = 1; - } - - - function cloneAndAnnotateFn(fn, annotation) { - return extend(function() { return fn.apply(null, arguments); }, fn, annotation); - } - - - function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { - try { - linkFn(scope, $element, attrs, controllers, transcludeFn); - } catch(e) { - $exceptionHandler(e, startingTag($element)); - } - } - }]; -} - -var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; -/** - * Converts all accepted directives format into proper directive name. - * All of these will become 'myDirective': - * my:Directive - * my-directive - * x-my-directive - * data-my:directive - * - * Also there is special case for Moz prefix starting with upper case letter. - * @param name Name to normalize - */ -function directiveNormalize(name) { - return camelCase(name.replace(PREFIX_REGEXP, '')); -} - -/** - * @ngdoc type - * @name $compile.directive.Attributes - * - * @description - * A shared object between directive compile / linking functions which contains normalized DOM - * element attributes. The values reflect current binding state `{{ }}`. The normalization is - * needed since all of these are treated as equivalent in Angular: - * - * ``` - * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> - * ``` - */ - -/** - * @ngdoc property - * @name $compile.directive.Attributes#$attr - * - * @description - * A map of DOM element attribute names to the normalized name. This is - * needed to do reverse lookup from normalized name back to actual name. - */ - - -/** - * @ngdoc method - * @name $compile.directive.Attributes#$set - * @kind function - * - * @description - * Set DOM element attribute value. - * - * - * @param {string} name Normalized element attribute name of the property to modify. The name is - * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} - * property to the original name. - * @param {string} value Value to set the attribute to. The value can be an interpolated string. - */ - - - -/** - * Closure compiler type information - */ - -function nodesetLinkingFn( - /* angular.Scope */ scope, - /* NodeList */ nodeList, - /* Element */ rootElement, - /* function(Function) */ boundTranscludeFn -){} - -function directiveLinkingFn( - /* nodesetLinkingFn */ nodesetLinkingFn, - /* angular.Scope */ scope, - /* Node */ node, - /* Element */ rootElement, - /* function(Function) */ boundTranscludeFn -){} - -function tokenDifference(str1, str2) { - var values = '', - tokens1 = str1.split(/\s+/), - tokens2 = str2.split(/\s+/); - - outer: - for(var i = 0; i < tokens1.length; i++) { - var token = tokens1[i]; - for(var j = 0; j < tokens2.length; j++) { - if(token == tokens2[j]) continue outer; - } - values += (values.length > 0 ? ' ' : '') + token; - } - return values; -} - -/** - * @ngdoc provider - * @name $controllerProvider - * @description - * The {@link ng.$controller $controller service} is used by Angular to create new - * controllers. - * - * This provider allows controller registration via the - * {@link ng.$controllerProvider#register register} method. - */ -function $ControllerProvider() { - var controllers = {}, - globals = false, - CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; - - - /** - * @ngdoc method - * @name $controllerProvider#register - * @param {string|Object} name Controller name, or an object map of controllers where the keys are - * the names and the values are the constructors. - * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI - * annotations in the array notation). - */ - this.register = function(name, constructor) { - assertNotHasOwnProperty(name, 'controller'); - if (isObject(name)) { - extend(controllers, name); - } else { - controllers[name] = constructor; - } - }; - - /** - * @ngdoc method - * @name $controllerProvider#allowGlobals - * @description If called, allows `$controller` to find controller constructors on `window` - */ - this.allowGlobals = function() { - globals = true; - }; - - - this.$get = ['$injector', '$window', function($injector, $window) { - - /** - * @ngdoc service - * @name $controller - * @requires $injector - * - * @param {Function|string} constructor If called with a function then it's considered to be the - * controller constructor function. Otherwise it's considered to be a string which is used - * to retrieve the controller constructor using the following steps: - * - * * check if a controller with given name is registered via `$controllerProvider` - * * check if evaluating the string on the current scope returns a constructor - * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global - * `window` object (not recommended) - * - * @param {Object} locals Injection locals for Controller. - * @return {Object} Instance of given controller. - * - * @description - * `$controller` service is responsible for instantiating controllers. - * - * It's just a simple call to {@link auto.$injector $injector}, but extracted into - * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). - */ - return function(expression, locals, later, ident) { - // PRIVATE API: - // param `later` --- indicates that the controller's constructor is invoked at a later time. - // If true, $controller will allocate the object with the correct - // prototype chain, but will not invoke the controller until a returned - // callback is invoked. - // param `ident` --- An optional label which overrides the label parsed from the controller - // expression, if any. - var instance, match, constructor, identifier; - later = later === true; - if (ident && isString(ident)) { - identifier = ident; - } - - if(isString(expression)) { - match = expression.match(CNTRL_REG), - constructor = match[1], - identifier = identifier || match[3]; - expression = controllers.hasOwnProperty(constructor) - ? controllers[constructor] - : getter(locals.$scope, constructor, true) || - (globals ? getter($window, constructor, true) : undefined); - - assertArgFn(expression, constructor, true); - } - - if (later) { - // Instantiate controller later: - // This machinery is used to create an instance of the object before calling the - // controller's constructor itself. - // - // This allows properties to be added to the controller before the constructor is - // invoked. Primarily, this is used for isolate scope bindings in $compile. - // - // This feature is not intended for use by applications, and is thus not documented - // publicly. - var Constructor = function() {}; - Constructor.prototype = (isArray(expression) ? - expression[expression.length - 1] : expression).prototype; - instance = new Constructor(); - - if (identifier) { - addIdentifier(locals, identifier, instance, constructor || expression.name); - } - - return extend(function() { - $injector.invoke(expression, instance, locals, constructor); - return instance; - }, { - instance: instance, - identifier: identifier - }); - } - - instance = $injector.instantiate(expression, locals, constructor); - - if (identifier) { - addIdentifier(locals, identifier, instance, constructor || expression.name); - } - - return instance; - }; - - function addIdentifier(locals, identifier, instance, name) { - if (!(locals && isObject(locals.$scope))) { - throw minErr('$controller')('noscp', - "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", - name, identifier); - } - - locals.$scope[identifier] = instance; - } - }]; -} - -/** - * @ngdoc service - * @name $document - * @requires $window - * - * @description - * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. - * - * @example - <example module="documentExample"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <p>$document title: <b ng-bind="title"></b></p> - <p>window.document title: <b ng-bind="windowTitle"></b></p> - </div> - </file> - <file name="script.js"> - angular.module('documentExample', []) - .controller('ExampleController', ['$scope', '$document', function($scope, $document) { - $scope.title = $document[0].title; - $scope.windowTitle = angular.element(window.document)[0].title; - }]); - </file> - </example> - */ -function $DocumentProvider(){ - this.$get = ['$window', function(window){ - return jqLite(window.document); - }]; -} - -/** - * @ngdoc service - * @name $exceptionHandler - * @requires ng.$log - * - * @description - * Any uncaught exception in angular expressions is delegated to this service. - * The default implementation simply delegates to `$log.error` which logs it into - * the browser console. - * - * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by - * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. - * - * ## Example: - * - * ```js - * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { - * return function (exception, cause) { - * exception.message += ' (caused by "' + cause + '")'; - * throw exception; - * }; - * }); - * ``` - * - * This example will override the normal action of `$exceptionHandler`, to make angular - * exceptions fail hard when they happen, instead of just logging to the console. - * - * @param {Error} exception Exception associated with the error. - * @param {string=} cause optional information about the context in which - * the error was thrown. - * - */ -function $ExceptionHandlerProvider() { - this.$get = ['$log', function($log) { - return function(exception, cause) { - $log.error.apply($log, arguments); - }; - }]; -} - -/** - * Parse headers into key value object - * - * @param {string} headers Raw headers as a string - * @returns {Object} Parsed headers as key value object - */ -function parseHeaders(headers) { - var parsed = {}, key, val, i; - - if (!headers) return parsed; - - forEach(headers.split('\n'), function(line) { - i = line.indexOf(':'); - key = lowercase(trim(line.substr(0, i))); - val = trim(line.substr(i + 1)); - - if (key) { - parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; - } - }); - - return parsed; -} - - -/** - * Returns a function that provides access to parsed headers. - * - * Headers are lazy parsed when first requested. - * @see parseHeaders - * - * @param {(string|Object)} headers Headers to provide access to. - * @returns {function(string=)} Returns a getter function which if called with: - * - * - if called with single an argument returns a single header value or null - * - if called with no arguments returns an object containing all headers. - */ -function headersGetter(headers) { - var headersObj = isObject(headers) ? headers : undefined; - - return function(name) { - if (!headersObj) headersObj = parseHeaders(headers); - - if (name) { - return headersObj[lowercase(name)] || null; - } - - return headersObj; - }; -} - - -/** - * Chain all given functions - * - * This function is used for both request and response transforming - * - * @param {*} data Data to transform. - * @param {function(string=)} headers Http headers getter fn. - * @param {(Function|Array.<Function>)} fns Function or an array of functions. - * @returns {*} Transformed data. - */ -function transformData(data, headers, fns) { - if (isFunction(fns)) - return fns(data, headers); - - forEach(fns, function(fn) { - data = fn(data, headers); - }); - - return data; -} - - -function isSuccess(status) { - return 200 <= status && status < 300; -} - - -/** - * @ngdoc provider - * @name $httpProvider - * @description - * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. - * */ -function $HttpProvider() { - var JSON_START = /^\s*(\[|\{[^\{])/, - JSON_END = /[\}\]]\s*$/, - PROTECTION_PREFIX = /^\)\]\}',?\n/, - CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; - - /** - * @ngdoc property - * @name $httpProvider#defaults - * @description - * - * Object containing default values for all {@link ng.$http $http} requests. - * - * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. - * Defaults value is `'XSRF-TOKEN'`. - * - * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the - * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. - * - * - **`defaults.headers`** - {Object} - Default headers for all $http requests. - * Refer to {@link ng.$http#setting-http-headers $http} for documentation on - * setting default headers. - * - **`defaults.headers.common`** - * - **`defaults.headers.post`** - * - **`defaults.headers.put`** - * - **`defaults.headers.patch`** - **/ - var defaults = this.defaults = { - // transform incoming response data - transformResponse: [function(data) { - if (isString(data)) { - // strip json vulnerability protection prefix - data = data.replace(PROTECTION_PREFIX, ''); - if (JSON_START.test(data) && JSON_END.test(data)) - data = fromJson(data); - } - return data; - }], - - // transform outgoing request data - transformRequest: [function(d) { - return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d; - }], - - // default headers - headers: { - common: { - 'Accept': 'application/json, text/plain, */*' - }, - post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), - put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), - patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) - }, - - xsrfCookieName: 'XSRF-TOKEN', - xsrfHeaderName: 'X-XSRF-TOKEN' - }; - - var useApplyAsync = false; - /** - * @ngdoc method - * @name $httpProvider#useApplyAsync - * @description - * - * Configure $http service to combine processing of multiple http responses received at around - * the same time via {@link ng.$rootScope#applyAsync $rootScope.$applyAsync}. This can result in - * significant performance improvement for bigger applications that make many HTTP requests - * concurrently (common during application bootstrap). - * - * Defaults to false. If no value is specifed, returns the current configured value. - * - * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred - * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window - * to load and share the same digest cycle. - * - * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. - * otherwise, returns the current configured value. - **/ - this.useApplyAsync = function(value) { - if (isDefined(value)) { - useApplyAsync = !!value; - return this; - } - return useApplyAsync; - }; - - /** - * Are ordered by request, i.e. they are applied in the same order as the - * array, on request, but reverse order, on response. - */ - var interceptorFactories = this.interceptors = []; - - this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', - function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { - - var defaultCache = $cacheFactory('$http'); - - /** - * Interceptors stored in reverse order. Inner interceptors before outer interceptors. - * The reversal is needed so that we can build up the interception chain around the - * server request. - */ - var reversedInterceptors = []; - - forEach(interceptorFactories, function(interceptorFactory) { - reversedInterceptors.unshift(isString(interceptorFactory) - ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); - }); - - /** - * @ngdoc service - * @kind function - * @name $http - * @requires ng.$httpBackend - * @requires $cacheFactory - * @requires $rootScope - * @requires $q - * @requires $injector - * - * @description - * The `$http` service is a core Angular service that facilitates communication with the remote - * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) - * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). - * - * For unit testing applications that use `$http` service, see - * {@link ngMock.$httpBackend $httpBackend mock}. - * - * For a higher level of abstraction, please check out the {@link ngResource.$resource - * $resource} service. - * - * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by - * 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} - * with two $http specific methods: `success` and `error`. - * - * ```js - * $http({method: 'GET', url: '/someUrl'}). - * success(function(data, status, headers, config) { - * // this callback will be called asynchronously - * // when the response is available - * }). - * error(function(data, status, headers, config) { - * // called asynchronously if an error occurs - * // or server returns response with an error status. - * }); - * ``` - * - * 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 - * details. - * - * 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. - * - * ## Writing Unit Tests that use $http - * When unit testing (using {@link ngMock ngMock}), it is necessary to call - * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending - * request using trained responses. - * - * ``` - * $httpBackend.expectGET(...); - * $http.get(...); - * $httpBackend.flush(); - * ``` - * - * ## Shortcut methods - * - * Shortcut methods are also available. All shortcut methods require passing in the URL, and - * request data must be passed in for POST/PUT requests. - * - * ```js - * $http.get('/someUrl').success(successCallback); - * $http.post('/someUrl', data).success(successCallback); - * ``` - * - * Complete list of shortcut methods: - * - * - {@link ng.$http#get $http.get} - * - {@link ng.$http#head $http.head} - * - {@link ng.$http#post $http.post} - * - {@link ng.$http#put $http.put} - * - {@link ng.$http#delete $http.delete} - * - {@link ng.$http#jsonp $http.jsonp} - * - {@link ng.$http#patch $http.patch} - * - * - * ## Setting HTTP Headers - * - * 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, * / *` - * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) - * - `Content-Type: application/json` - * - `$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 these configuration - * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object - * with the lowercased HTTP method name as the key, e.g. - * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }. - * - * The defaults can also be set at runtime via the `$http.defaults` object in the same - * fashion. For example: - * - * ``` - * module.run(function($http) { - * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' - * }); - * ``` - * - * In addition, you can supply a `headers` property in the config object passed when - * calling `$http(config)`, which overrides the defaults without changing them globally. - * - * - * ## Transforming Requests and Responses - * - * Both requests and responses can be transformed using transformation functions: `transformRequest` - * and `transformResponse`. These properties can be a single function that returns - * the transformed value (`{function(data, headersGetter)`) or an array of such transformation functions, - * which allows you to `push` or `unshift` a new transformation function into the transformation chain. - * - * ### Default Transformations - * - * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and - * `defaults.transformResponse` properties. If a request does not provide its own transformations - * then these will be applied. - * - * You can augment or replace the default transformations by modifying these properties by adding to or - * replacing the array. - * - * Angular provides the following default transformations: - * - * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): - * - * - If the `data` property of the request configuration object contains an object, serialize it - * into JSON format. - * - * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): - * - * - If XSRF prefix is detected, strip it (see Security Considerations section below). - * - If JSON response is detected, deserialize it using a JSON parser. - * - * - * ### Overriding the Default Transformations Per Request - * - * If you wish override the request/response transformations only for a single request then provide - * `transformRequest` and/or `transformResponse` properties on the configuration object passed - * into `$http`. - * - * Note that if you provide these properties on the config object the default transformations will be - * overwritten. If you wish to augment the default transformations then you must include them in your - * local transformation array. - * - * The following code demonstrates adding a new response transformation to be run after the default response - * transformations have been run. - * - * ```js - * function appendTransform(defaults, transform) { - * - * // We can't guarantee that the default transformation is an array - * defaults = angular.isArray(defaults) ? defaults : [defaults]; - * - * // Append the new transformation to the defaults - * return defaults.concat(transform); - * } - * - * $http({ - * url: '...', - * method: 'GET', - * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { - * return doTransform(value); - * }) - * }); - * ``` - * - * - * ## Caching - * - * To enable caching, set the request configuration `cache` property to `true` (to use default - * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). - * When the cache is enabled, `$http` stores the response from the server in the specified - * cache. The next time the same request is made, 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 - * 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 from the first request. - * - * You can change the default cache to a new object (built with - * {@link ng.$cacheFactory `$cacheFactory`}) by updating the - * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set - * their `cache` property to `true` will now use this cache object. - * - * If you set the default cache to `false` then only requests that specify their own custom - * cache object will be cached. - * - * ## Interceptors - * - * Before you start creating interceptors, be sure to understand the - * {@link ng.$q $q and deferred/promise APIs}. - * - * For purposes of global error handling, authentication, or any kind of synchronous or - * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be - * able to intercept requests before they are handed to the server and - * responses before they are handed over to the application code that - * initiated these requests. The interceptors leverage the {@link ng.$q - * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. - * - * The interceptors are service factories that are registered with the `$httpProvider` by - * adding them to the `$httpProvider.interceptors` array. The factory is called and - * injected with dependencies (if specified) and returns the interceptor. - * - * There are two kinds of interceptors (and two kinds of rejection interceptors): - * - * * `request`: interceptors get called with a http `config` object. The function is free to - * modify the `config` object or create a new one. The function needs to return the `config` - * object directly, or a promise containing the `config` or a new `config` object. - * * `requestError`: interceptor gets called when a previous interceptor threw an error or - * resolved with a rejection. - * * `response`: interceptors get called with http `response` object. The function is free to - * modify the `response` object or create a new one. The function needs to return the `response` - * object directly, or as a promise containing the `response` or a new `response` object. - * * `responseError`: interceptor gets called when a previous interceptor threw an error or - * resolved with a rejection. - * - * - * ```js - * // register the interceptor as a service - * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { - * return { - * // optional method - * 'request': function(config) { - * // do something on success - * return config; - * }, - * - * // optional method - * 'requestError': function(rejection) { - * // do something on error - * if (canRecover(rejection)) { - * return responseOrNewPromise - * } - * return $q.reject(rejection); - * }, - * - * - * - * // optional method - * 'response': function(response) { - * // do something on success - * return response; - * }, - * - * // optional method - * 'responseError': function(rejection) { - * // do something on error - * if (canRecover(rejection)) { - * return responseOrNewPromise - * } - * return $q.reject(rejection); - * } - * }; - * }); - * - * $httpProvider.interceptors.push('myHttpInterceptor'); - * - * - * // alternatively, register the interceptor via an anonymous factory - * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { - * return { - * 'request': function(config) { - * // same as above - * }, - * - * 'response': function(response) { - * // same as above - * } - * }; - * }); - * ``` - * - * ## Security Considerations - * - * When designing web applications, consider security threats from: - * - * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) - * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) - * - * Both server and the client must cooperate in order to eliminate these threats. Angular comes - * pre-configured with strategies that address these issues, but for this to work backend server - * cooperation is required. - * - * ### JSON Vulnerability Protection - * - * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) - * allows third party website to turn your JSON resource URL into - * [JSONP](http://en.wikipedia.org/wiki/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. - * - * For example if your server needs to return: - * ```js - * ['one','two'] - * ``` - * - * which is vulnerable to attack, your server can return: - * ```js - * )]}', - * ['one','two'] - * ``` - * - * Angular will strip the prefix, before processing the JSON. - * - * - * ### Cross Site Request Forgery (XSRF) Protection - * - * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which - * 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 - * (by default, `XSRF-TOKEN`) and sets it as an 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. The header will not be set for - * cross-domain requests. - * - * To take advantage of this, your server needs to set a token in a JavaScript readable session - * 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 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 a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) - * for added security. - * - * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName - * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, - * or the per-request config object. - * - * - * @param {object} config Object describing the request to be made and how it should be - * processed. The object has following properties: - * - * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) - * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested. - * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be turned - * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be - * JSONified. - * - **data** – `{string|Object}` – Data to be sent as the request message data. - * - **headers** – `{Object}` – Map of strings or functions which return strings representing - * HTTP headers to send to the server. If the return value of a function is null, the - * header will not be sent. - * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token. - * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token. - * - **transformRequest** – - * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` – - * transform function or an array of such functions. The transform function takes the http - * request body and headers and returns its transformed (typically serialized) version. - * See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations} - * - **transformResponse** – - * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` – - * transform function or an array of such functions. The transform function takes the http - * response body and headers and returns its transformed (typically deserialized) version. - * See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations} - * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the - * GET request, otherwise if a cache instance built with - * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for - * caching. - * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} - * that should abort the request when resolved. - * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the - * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) - * for more information. - * - **responseType** - `{string}` - see - * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). - * - * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the - * standard `then` method and two http specific methods: `success` and `error`. The `then` - * method takes two arguments a success and an error callback which will be called with a - * response object. The `success` and `error` methods take a single argument - a function that - * will be called when the request succeeds or fails respectively. The arguments passed into - * these functions are destructured representation of the response object passed into the - * `then` method. The response object has these properties: - * - * - **data** – `{string|Object}` – The response body transformed with the transform - * functions. - * - **status** – `{number}` – HTTP status code of the response. - * - **headers** – `{function([headerName])}` – Header getter function. - * - **config** – `{Object}` – The configuration object that was used to generate the request. - * - **statusText** – `{string}` – HTTP status text of the response. - * - * @property {Array.<Object>} pendingRequests Array of config objects for currently pending - * requests. This is primarily meant to be used for debugging purposes. - * - * - * @example -<example module="httpExample"> -<file name="index.html"> - <div ng-controller="FetchController"> - <select ng-model="method"> - <option>GET</option> - <option>JSONP</option> - </select> - <input type="text" ng-model="url" size="80"/> - <button id="fetchbtn" ng-click="fetch()">fetch</button><br> - <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> - <button id="samplejsonpbtn" - ng-click="updateModel('JSONP', - 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> - Sample JSONP - </button> - <button id="invalidjsonpbtn" - ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> - Invalid JSONP - </button> - <pre>http status code: {{status}}</pre> - <pre>http response data: {{data}}</pre> - </div> -</file> -<file name="script.js"> - angular.module('httpExample', []) - .controller('FetchController', ['$scope', '$http', '$templateCache', - function($scope, $http, $templateCache) { - $scope.method = 'GET'; - $scope.url = 'http-hello.html'; - - $scope.fetch = function() { - $scope.code = null; - $scope.response = null; - - $http({method: $scope.method, url: $scope.url, cache: $templateCache}). - success(function(data, status) { - $scope.status = status; - $scope.data = data; - }). - error(function(data, status) { - $scope.data = data || "Request failed"; - $scope.status = status; - }); - }; - - $scope.updateModel = function(method, url) { - $scope.method = method; - $scope.url = url; - }; - }]); -</file> -<file name="http-hello.html"> - Hello, $http! -</file> -<file name="protractor.js" type="protractor"> - var status = element(by.binding('status')); - var data = element(by.binding('data')); - var fetchBtn = element(by.id('fetchbtn')); - var sampleGetBtn = element(by.id('samplegetbtn')); - var sampleJsonpBtn = element(by.id('samplejsonpbtn')); - var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); - - it('should make an xhr GET request', function() { - sampleGetBtn.click(); - fetchBtn.click(); - expect(status.getText()).toMatch('200'); - expect(data.getText()).toMatch(/Hello, \$http!/); - }); - - it('should make a JSONP request to angularjs.org', function() { - sampleJsonpBtn.click(); - fetchBtn.click(); - expect(status.getText()).toMatch('200'); - expect(data.getText()).toMatch(/Super Hero!/); - }); - - it('should make JSONP request to invalid URL and invoke the error handler', - function() { - invalidJsonpBtn.click(); - fetchBtn.click(); - expect(status.getText()).toMatch('0'); - expect(data.getText()).toMatch('Request failed'); - }); -</file> -</example> - */ - function $http(requestConfig) { - var config = { - method: 'get', - transformRequest: defaults.transformRequest, - transformResponse: defaults.transformResponse - }; - var headers = mergeHeaders(requestConfig); - - extend(config, requestConfig); - config.headers = headers; - config.method = uppercase(config.method); - - var serverRequest = function(config) { - headers = config.headers; - var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); - - // strip content-type if data is undefined - if (isUndefined(reqData)) { - forEach(headers, function(value, header) { - if (lowercase(header) === 'content-type') { - delete headers[header]; - } - }); - } - - if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { - config.withCredentials = defaults.withCredentials; - } - - // send request - return sendReq(config, reqData, headers).then(transformResponse, transformResponse); - }; - - var chain = [serverRequest, undefined]; - var promise = $q.when(config); - - // apply interceptors - forEach(reversedInterceptors, function(interceptor) { - if (interceptor.request || interceptor.requestError) { - chain.unshift(interceptor.request, interceptor.requestError); - } - if (interceptor.response || interceptor.responseError) { - chain.push(interceptor.response, interceptor.responseError); - } - }); - - while(chain.length) { - var thenFn = chain.shift(); - var rejectFn = chain.shift(); - - promise = promise.then(thenFn, rejectFn); - } - - promise.success = function(fn) { - promise.then(function(response) { - fn(response.data, response.status, response.headers, config); - }); - return promise; - }; - - promise.error = function(fn) { - promise.then(null, function(response) { - fn(response.data, response.status, response.headers, config); - }); - return promise; - }; - - return promise; - - function transformResponse(response) { - // make a copy since the response must be cacheable - var resp = extend({}, response, { - data: transformData(response.data, response.headers, config.transformResponse) - }); - return (isSuccess(response.status)) - ? resp - : $q.reject(resp); - } - - function mergeHeaders(config) { - var defHeaders = defaults.headers, - reqHeaders = extend({}, config.headers), - defHeaderName, lowercaseDefHeaderName, reqHeaderName; - - defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); - - // using for-in instead of forEach to avoid unecessary iteration after header has been found - defaultHeadersIteration: - for (defHeaderName in defHeaders) { - lowercaseDefHeaderName = lowercase(defHeaderName); - - for (reqHeaderName in reqHeaders) { - if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { - continue defaultHeadersIteration; - } - } - - reqHeaders[defHeaderName] = defHeaders[defHeaderName]; - } - - // execute if header value is a function for merged headers - execHeaders(reqHeaders); - return reqHeaders; - - function execHeaders(headers) { - var headerContent; - - forEach(headers, function(headerFn, header) { - if (isFunction(headerFn)) { - headerContent = headerFn(); - if (headerContent != null) { - headers[header] = headerContent; - } else { - delete headers[header]; - } - } - }); - } - } - } - - $http.pendingRequests = []; - - /** - * @ngdoc method - * @name $http#get - * - * @description - * 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 - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name $http#delete - * - * @description - * 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 - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name $http#head - * - * @description - * 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 - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name $http#jsonp - * - * @description - * Shortcut method to perform `JSONP` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request. - * The name of the callback should be the string `JSON_CALLBACK`. - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - createShortMethods('get', 'delete', 'head', 'jsonp'); - - /** - * @ngdoc method - * @name $http#post - * - * @description - * Shortcut method to perform `POST` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {*} data Request content - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name $http#put - * - * @description - * Shortcut method to perform `PUT` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {*} data Request content - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name $http#patch - * - * @description - * Shortcut method to perform `PATCH` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {*} data Request content - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - createShortMethodsWithData('post', 'put', 'patch'); - - /** - * @ngdoc property - * @name $http#defaults - * - * @description - * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of - * default headers, withCredentials as well as request and response transformations. - * - * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. - */ - $http.defaults = defaults; - - - return $http; - - - function createShortMethods(names) { - forEach(arguments, function(name) { - $http[name] = function(url, config) { - return $http(extend(config || {}, { - method: name, - url: url - })); - }; - }); - } - - - function createShortMethodsWithData(name) { - forEach(arguments, function(name) { - $http[name] = function(url, data, config) { - return $http(extend(config || {}, { - method: name, - url: url, - data: data - })); - }; - }); - } - - - /** - * Makes the request. - * - * !!! ACCESSES CLOSURE VARS: - * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests - */ - function sendReq(config, reqData, reqHeaders) { - var deferred = $q.defer(), - promise = deferred.promise, - cache, - cachedResp, - url = buildUrl(config.url, config.params); - - $http.pendingRequests.push(config); - promise.then(removePendingReq, removePendingReq); - - - if ((config.cache || defaults.cache) && config.cache !== false && - (config.method === 'GET' || config.method === 'JSONP')) { - cache = isObject(config.cache) ? config.cache - : isObject(defaults.cache) ? defaults.cache - : defaultCache; - } - - if (cache) { - cachedResp = cache.get(url); - if (isDefined(cachedResp)) { - if (isPromiseLike(cachedResp)) { - // cached request has already been sent, but there is no response yet - cachedResp.then(removePendingReq, removePendingReq); - return cachedResp; - } else { - // serving from cache - if (isArray(cachedResp)) { - resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); - } else { - resolvePromise(cachedResp, 200, {}, 'OK'); - } - } - } else { - // put the promise for the non-transformed response into cache as a placeholder - cache.put(url, promise); - } - } - - - // if we won't have the response in cache, set the xsrf headers and - // send the request to the backend - if (isUndefined(cachedResp)) { - var xsrfValue = urlIsSameOrigin(config.url) - ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] - : undefined; - if (xsrfValue) { - reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; - } - - $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, - config.withCredentials, config.responseType); - } - - return promise; - - - /** - * Callback registered to $httpBackend(): - * - caches the response if desired - * - resolves the raw $http promise - * - calls $apply - */ - function done(status, response, headersString, statusText) { - if (cache) { - if (isSuccess(status)) { - cache.put(url, [status, response, parseHeaders(headersString), statusText]); - } else { - // remove promise from the cache - cache.remove(url); - } - } - - function resolveHttpPromise() { - resolvePromise(response, status, headersString, statusText); - } - - if (useApplyAsync) { - $rootScope.$applyAsync(resolveHttpPromise); - } else { - resolveHttpPromise(); - if (!$rootScope.$$phase) $rootScope.$apply(); - } - } - - - /** - * Resolves the raw $http promise. - */ - function resolvePromise(response, status, headers, statusText) { - // normalize internal statuses to 0 - status = Math.max(status, 0); - - (isSuccess(status) ? deferred.resolve : deferred.reject)({ - data: response, - status: status, - headers: headersGetter(headers), - config: config, - statusText : statusText - }); - } - - - function removePendingReq() { - var idx = $http.pendingRequests.indexOf(config); - if (idx !== -1) $http.pendingRequests.splice(idx, 1); - } - } - - - function buildUrl(url, params) { - if (!params) return url; - var parts = []; - forEachSorted(params, function(value, key) { - if (value === null || isUndefined(value)) return; - if (!isArray(value)) value = [value]; - - forEach(value, function(v) { - if (isObject(v)) { - if (isDate(v)){ - v = v.toISOString(); - } else { - v = toJson(v); - } - } - parts.push(encodeUriQuery(key) + '=' + - encodeUriQuery(v)); - }); - }); - if(parts.length > 0) { - url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); - } - return url; - } - }]; -} - -function createXhr(method) { - //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest - //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest - //if it is available - if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) || - !window.XMLHttpRequest)) { - return new window.ActiveXObject("Microsoft.XMLHTTP"); - } else if (window.XMLHttpRequest) { - return new window.XMLHttpRequest(); - } - - throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest."); -} - -/** - * @ngdoc service - * @name $httpBackend - * @requires $window - * @requires $document - * - * @description - * HTTP backend used by the {@link ng.$http service} that delegates to - * XMLHttpRequest object or JSONP and deals with browser incompatibilities. - * - * You should never need to use this service directly, instead use the higher-level abstractions: - * {@link ng.$http $http} or {@link ngResource.$resource $resource}. - * - * During testing this implementation is swapped with {@link ngMock.$httpBackend mock - * $httpBackend} which can be trained with responses. - */ -function $HttpBackendProvider() { - this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { - return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); - }]; -} - -function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { - var ABORTED = -1; - - // TODO(vojta): fix the signature - return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { - var status; - $browser.$$incOutstandingRequestCount(); - url = url || $browser.url(); - - if (lowercase(method) == 'jsonp') { - var callbackId = '_' + (callbacks.counter++).toString(36); - callbacks[callbackId] = function(data) { - callbacks[callbackId].data = data; - callbacks[callbackId].called = true; - }; - - var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), - callbackId, function(status, text) { - completeRequest(callback, status, callbacks[callbackId].data, "", text); - callbacks[callbackId] = noop; - }); - } else { - - var xhr = createXhr(method); - - xhr.open(method, url, true); - forEach(headers, function(value, key) { - if (isDefined(value)) { - xhr.setRequestHeader(key, value); - } - }); - - // In IE6 and 7, this might be called synchronously when xhr.send below is called and the - // response is in the cache. the promise api will ensure that to the app code the api is - // always async - xhr.onreadystatechange = function() { - // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by - // xhrs that are resolved while the app is in the background (see #5426). - // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before - // continuing - // - // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and - // Safari respectively. - if (xhr && xhr.readyState == 4) { - var responseHeaders = null, - response = null, - statusText = ''; - - if(status !== ABORTED) { - responseHeaders = xhr.getAllResponseHeaders(); - - // responseText is the old-school way of retrieving response (supported by IE8 & 9) - // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) - response = ('response' in xhr) ? xhr.response : xhr.responseText; - } - - // Accessing statusText on an aborted xhr object will - // throw an 'c00c023f error' in IE9 and lower, don't touch it. - if (!(status === ABORTED && msie < 10)) { - statusText = xhr.statusText; - } - - completeRequest(callback, - status || xhr.status, - response, - responseHeaders, - statusText); - } - }; - - if (withCredentials) { - xhr.withCredentials = true; - } - - if (responseType) { - try { - xhr.responseType = responseType; - } catch (e) { - // WebKit added support for the json responseType value on 09/03/2013 - // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are - // known to throw when setting the value "json" as the response type. Other older - // browsers implementing the responseType - // - // The json response type can be ignored if not supported, because JSON payloads are - // parsed on the client-side regardless. - if (responseType !== 'json') { - throw e; - } - } - } - - xhr.send(post || null); - } - - if (timeout > 0) { - var timeoutId = $browserDefer(timeoutRequest, timeout); - } else if (isPromiseLike(timeout)) { - timeout.then(timeoutRequest); - } - - - function timeoutRequest() { - status = ABORTED; - jsonpDone && jsonpDone(); - xhr && xhr.abort(); - } - - function completeRequest(callback, status, response, headersString, statusText) { - // cancel timeout and subsequent timeout promise resolution - timeoutId && $browserDefer.cancel(timeoutId); - jsonpDone = xhr = null; - - // fix status code when it is 0 (0 status is undocumented). - // Occurs when accessing file resources or on Android 4.1 stock browser - // while retrieving files from application cache. - if (status === 0) { - status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; - } - - // normalize IE bug (http://bugs.jquery.com/ticket/1450) - status = status === 1223 ? 204 : status; - statusText = statusText || ''; - - callback(status, response, headersString, statusText); - $browser.$$completeOutstandingRequest(noop); - } - }; - - function jsonpReq(url, callbackId, done) { - // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: - // - fetches local scripts via XHR and evals them - // - adds and immediately removes script elements from the document - var script = rawDocument.createElement('script'), callback = null; - script.type = "text/javascript"; - script.src = url; - script.async = true; - - callback = function(event) { - removeEventListenerFn(script, "load", callback); - removeEventListenerFn(script, "error", callback); - rawDocument.body.removeChild(script); - script = null; - var status = -1; - var text = "unknown"; - - if (event) { - if (event.type === "load" && !callbacks[callbackId].called) { - event = { type: "error" }; - } - text = event.type; - status = event.type === "error" ? 404 : 200; - } - - if (done) { - done(status, text); - } - }; - - addEventListenerFn(script, "load", callback); - addEventListenerFn(script, "error", callback); - rawDocument.body.appendChild(script); - return callback; - } -} - -var $interpolateMinErr = minErr('$interpolate'); - -/** - * @ngdoc provider - * @name $interpolateProvider - * - * @description - * - * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. - * - * @example -<example module="customInterpolationApp"> -<file name="index.html"> -<script> - var customInterpolationApp = angular.module('customInterpolationApp', []); - - customInterpolationApp.config(function($interpolateProvider) { - $interpolateProvider.startSymbol('//'); - $interpolateProvider.endSymbol('//'); - }); - - - customInterpolationApp.controller('DemoController', function() { - this.label = "This binding is brought you by // interpolation symbols."; - }); -</script> -<div ng-app="App" ng-controller="DemoController as demo"> - //demo.label// -</div> -</file> -<file name="protractor.js" type="protractor"> - it('should interpolate binding with custom symbols', function() { - expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); - }); -</file> -</example> - */ -function $InterpolateProvider() { - var startSymbol = '{{'; - var endSymbol = '}}'; - - /** - * @ngdoc method - * @name $interpolateProvider#startSymbol - * @description - * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. - * - * @param {string=} value new value to set the starting symbol to. - * @returns {string|self} Returns the symbol when used as getter and self if used as setter. - */ - this.startSymbol = function(value){ - if (value) { - startSymbol = value; - return this; - } else { - return startSymbol; - } - }; - - /** - * @ngdoc method - * @name $interpolateProvider#endSymbol - * @description - * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. - * - * @param {string=} value new value to set the ending symbol to. - * @returns {string|self} Returns the symbol when used as getter and self if used as setter. - */ - this.endSymbol = function(value){ - if (value) { - endSymbol = value; - return this; - } else { - return endSymbol; - } - }; - - - this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { - var startSymbolLength = startSymbol.length, - endSymbolLength = endSymbol.length, - escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), - escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); - - function escape(ch) { - return '\\\\\\' + ch; - } - - /** - * @ngdoc service - * @name $interpolate - * @kind function - * - * @requires $parse - * @requires $sce - * - * @description - * - * Compiles a string with markup into an interpolation function. This service is used by the - * HTML {@link ng.$compile $compile} service for data binding. See - * {@link ng.$interpolateProvider $interpolateProvider} for configuring the - * interpolation markup. - * - * - * ```js - * var $interpolate = ...; // injected - * var exp = $interpolate('Hello {{name | uppercase}}!'); - * expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); - * ``` - * - * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is - * `true`, the interpolation function will return `undefined` unless all embedded expressions - * evaluate to a value other than `undefined`. - * - * ```js - * var $interpolate = ...; // injected - * var context = {greeting: 'Hello', name: undefined }; - * - * // default "forgiving" mode - * var exp = $interpolate('{{greeting}} {{name}}!'); - * expect(exp(context)).toEqual('Hello !'); - * - * // "allOrNothing" mode - * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); - * expect(exp(context)).toBeUndefined(); - * context.name = 'Angular'; - * expect(exp(context)).toEqual('Hello Angular!'); - * ``` - * - * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. - * - * ####Escaped Interpolation - * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers - * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). - * It will be rendered as a regular start/end marker, and will not be interpreted as an expression - * or binding. - * - * This enables web-servers to prevent script injection attacks and defacing attacks, to some - * degree, while also enabling code examples to work without relying on the - * {@link ng.directive:ngNonBindable ngNonBindable} directive. - * - * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, - * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all - * interpolation start/end markers with their escaped counterparts.** - * - * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered - * output when the $interpolate service processes the text. So, for HTML elements interpolated - * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter - * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, - * this is typically useful only when user-data is used in rendering a template from the server, or - * when otherwise untrusted data is used by a directive. - * - * <example> - * <file name="index.html"> - * <div ng-init="username='A user'"> - * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} - * </p> - * <p><strong>{{username}}</strong> attempts to inject code which will deface the - * application, but fails to accomplish their task, because the server has correctly - * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) - * characters.</p> - * <p>Instead, the result of the attempted script injection is visible, and can be removed - * from the database by an administrator.</p> - * </div> - * </file> - * </example> - * - * @param {string} text The text with markup to interpolate. - * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have - * embedded expression in order to return an interpolation function. Strings with no - * embedded expression will return null for the interpolation function. - * @param {string=} trustedContext when provided, the returned function passes the interpolated - * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, - * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that - * provides Strict Contextual Escaping for details. - * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined - * unless all embedded expressions evaluate to a value other than `undefined`. - * @returns {function(context)} an interpolation function which is used to compute the - * interpolated string. The function has these parameters: - * - * - `context`: evaluation context for all expressions embedded in the interpolated text - */ - function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { - allOrNothing = !!allOrNothing; - var startIndex, - endIndex, - index = 0, - expressions = [], - parseFns = [], - textLength = text.length, - exp, - concat = [], - expressionPositions = []; - - while(index < textLength) { - if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && - ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { - if (index !== startIndex) { - concat.push(unescapeText(text.substring(index, startIndex))); - } - exp = text.substring(startIndex + startSymbolLength, endIndex); - expressions.push(exp); - parseFns.push($parse(exp, parseStringifyInterceptor)); - index = endIndex + endSymbolLength; - expressionPositions.push(concat.length); - concat.push(''); - } else { - // we did not find an interpolation, so we have to add the remainder to the separators array - if (index !== textLength) { - concat.push(unescapeText(text.substring(index))); - } - break; - } - } - - // Concatenating expressions makes it hard to reason about whether some combination of - // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a - // single expression be used for iframe[src], object[src], etc., we ensure that the value - // that's used is assigned or constructed by some JS code somewhere that is more testable or - // make it obvious that you bound the value to some user controlled value. This helps reduce - // the load when auditing for XSS issues. - if (trustedContext && concat.length > 1) { - throw $interpolateMinErr('noconcat', - "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + - "interpolations that concatenate multiple expressions when a trusted value is " + - "required. See http://docs.angularjs.org/api/ng.$sce", text); - } - - if (!mustHaveExpression || expressions.length) { - var compute = function(values) { - for(var i = 0, ii = expressions.length; i < ii; i++) { - if (allOrNothing && isUndefined(values[i])) return; - concat[expressionPositions[i]] = values[i]; - } - return concat.join(''); - }; - - var getValue = function (value) { - return trustedContext ? - $sce.getTrusted(trustedContext, value) : - $sce.valueOf(value); - }; - - var stringify = function (value) { - if (value == null) { // null || undefined - return ''; - } - switch (typeof value) { - case 'string': { - break; - } - case 'number': { - value = '' + value; - break; - } - default: { - value = toJson(value); - } - } - - return value; - }; - - return extend(function interpolationFn(context) { - var i = 0; - var ii = expressions.length; - var values = new Array(ii); - - try { - for (; i < ii; i++) { - values[i] = parseFns[i](context); - } - - return compute(values); - } catch(err) { - var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, - err.toString()); - $exceptionHandler(newErr); - } - - }, { - // all of these properties are undocumented for now - exp: text, //just for compatibility with regular watchers created via $watch - expressions: expressions, - $$watchDelegate: function (scope, listener, objectEquality) { - var lastValue; - return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { - var currValue = compute(values); - if (isFunction(listener)) { - listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); - } - lastValue = currValue; - }, objectEquality); - } - }); - } - - function unescapeText(text) { - return text.replace(escapedStartRegexp, startSymbol). - replace(escapedEndRegexp, endSymbol); - } - - function parseStringifyInterceptor(value) { - try { - return stringify(getValue(value)); - } catch(err) { - var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, - err.toString()); - $exceptionHandler(newErr); - } - } - } - - - /** - * @ngdoc method - * @name $interpolate#startSymbol - * @description - * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. - * - * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change - * the symbol. - * - * @returns {string} start symbol. - */ - $interpolate.startSymbol = function() { - return startSymbol; - }; - - - /** - * @ngdoc method - * @name $interpolate#endSymbol - * @description - * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. - * - * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change - * the symbol. - * - * @returns {string} end symbol. - */ - $interpolate.endSymbol = function() { - return endSymbol; - }; - - return $interpolate; - }]; -} - -function $IntervalProvider() { - this.$get = ['$rootScope', '$window', '$q', '$$q', - function($rootScope, $window, $q, $$q) { - var intervals = {}; - - - /** - * @ngdoc service - * @name $interval - * - * @description - * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` - * milliseconds. - * - * The return value of registering an interval function is a promise. This promise will be - * notified upon each tick of the interval, and will be resolved after `count` iterations, or - * run indefinitely if `count` is not defined. The value of the notification will be the - * number of iterations that have run. - * To cancel an interval, call `$interval.cancel(promise)`. - * - * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to - * move forward by `millis` milliseconds and trigger any functions scheduled to run in that - * time. - * - * <div class="alert alert-warning"> - * **Note**: Intervals created by this service must be explicitly destroyed when you are finished - * with them. In particular they are not automatically destroyed when a controller's scope or a - * directive's element are destroyed. - * You should take this into consideration and make sure to always cancel the interval at the - * appropriate moment. See the example below for more details on how and when to do this. - * </div> - * - * @param {function()} fn A function that should be called repeatedly. - * @param {number} delay Number of milliseconds between each function call. - * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat - * indefinitely. - * @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} A promise which will be notified on each iteration. - * - * @example - * <example module="intervalExample"> - * <file name="index.html"> - * <script> - * angular.module('intervalExample', []) - * .controller('ExampleController', ['$scope', '$interval', - * function($scope, $interval) { - * $scope.format = 'M/d/yy h:mm:ss a'; - * $scope.blood_1 = 100; - * $scope.blood_2 = 120; - * - * var stop; - * $scope.fight = function() { - * // Don't start a new fight if we are already fighting - * if ( angular.isDefined(stop) ) return; - * - * stop = $interval(function() { - * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { - * $scope.blood_1 = $scope.blood_1 - 3; - * $scope.blood_2 = $scope.blood_2 - 4; - * } else { - * $scope.stopFight(); - * } - * }, 100); - * }; - * - * $scope.stopFight = function() { - * if (angular.isDefined(stop)) { - * $interval.cancel(stop); - * stop = undefined; - * } - * }; - * - * $scope.resetFight = function() { - * $scope.blood_1 = 100; - * $scope.blood_2 = 120; - * }; - * - * $scope.$on('$destroy', function() { - * // Make sure that the interval is destroyed too - * $scope.stopFight(); - * }); - * }]) - * // Register the 'myCurrentTime' directive factory method. - * // We inject $interval and dateFilter service since the factory method is DI. - * .directive('myCurrentTime', ['$interval', 'dateFilter', - * function($interval, dateFilter) { - * // return the directive link function. (compile function not needed) - * return function(scope, element, attrs) { - * var format, // date format - * stopTime; // so that we can cancel the time updates - * - * // used to update the UI - * function updateTime() { - * element.text(dateFilter(new Date(), format)); - * } - * - * // watch the expression, and update the UI on change. - * scope.$watch(attrs.myCurrentTime, function(value) { - * format = value; - * updateTime(); - * }); - * - * stopTime = $interval(updateTime, 1000); - * - * // listen on DOM destroy (removal) event, and cancel the next UI update - * // to prevent updating time after the DOM element was removed. - * element.on('$destroy', function() { - * $interval.cancel(stopTime); - * }); - * } - * }]); - * </script> - * - * <div> - * <div ng-controller="ExampleController"> - * Date format: <input ng-model="format"> <hr/> - * Current time is: <span my-current-time="format"></span> - * <hr/> - * Blood 1 : <font color='red'>{{blood_1}}</font> - * Blood 2 : <font color='red'>{{blood_2}}</font> - * <button type="button" data-ng-click="fight()">Fight</button> - * <button type="button" data-ng-click="stopFight()">StopFight</button> - * <button type="button" data-ng-click="resetFight()">resetFight</button> - * </div> - * </div> - * - * </file> - * </example> - */ - function interval(fn, delay, count, invokeApply) { - var setInterval = $window.setInterval, - clearInterval = $window.clearInterval, - iteration = 0, - skipApply = (isDefined(invokeApply) && !invokeApply), - deferred = (skipApply ? $$q : $q).defer(), - promise = deferred.promise; - - count = isDefined(count) ? count : 0; - - promise.then(null, null, fn); - - promise.$$intervalId = setInterval(function tick() { - deferred.notify(iteration++); - - if (count > 0 && iteration >= count) { - deferred.resolve(iteration); - clearInterval(promise.$$intervalId); - delete intervals[promise.$$intervalId]; - } - - if (!skipApply) $rootScope.$apply(); - - }, delay); - - intervals[promise.$$intervalId] = deferred; - - return promise; - } - - - /** - * @ngdoc method - * @name $interval#cancel - * - * @description - * Cancels a task associated with the `promise`. - * - * @param {promise} promise returned by the `$interval` function. - * @returns {boolean} Returns `true` if the task was successfully canceled. - */ - interval.cancel = function(promise) { - if (promise && promise.$$intervalId in intervals) { - intervals[promise.$$intervalId].reject('canceled'); - $window.clearInterval(promise.$$intervalId); - delete intervals[promise.$$intervalId]; - return true; - } - return false; - }; - - return interval; - }]; -} - -/** - * @ngdoc service - * @name $locale - * - * @description - * $locale service provides localization rules for various Angular components. As of right now the - * only public api is: - * - * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) - */ -function $LocaleProvider(){ - this.$get = function() { - return { - id: 'en-us', - - NUMBER_FORMATS: { - DECIMAL_SEP: '.', - GROUP_SEP: ',', - PATTERNS: [ - { // Decimal Pattern - minInt: 1, - minFrac: 0, - maxFrac: 3, - posPre: '', - posSuf: '', - negPre: '-', - negSuf: '', - gSize: 3, - lgSize: 3 - },{ //Currency Pattern - minInt: 1, - minFrac: 2, - maxFrac: 2, - posPre: '\u00A4', - posSuf: '', - negPre: '(\u00A4', - negSuf: ')', - gSize: 3, - lgSize: 3 - } - ], - CURRENCY_SYM: '$' - }, - - DATETIME_FORMATS: { - MONTH: - 'January,February,March,April,May,June,July,August,September,October,November,December' - .split(','), - SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), - DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), - SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), - AMPMS: ['AM','PM'], - medium: 'MMM d, y h:mm:ss a', - short: 'M/d/yy h:mm a', - fullDate: 'EEEE, MMMM d, y', - longDate: 'MMMM d, y', - mediumDate: 'MMM d, y', - shortDate: 'M/d/yy', - mediumTime: 'h:mm:ss a', - shortTime: 'h:mm a' - }, - - pluralCat: function(num) { - if (num === 1) { - return 'one'; - } - return 'other'; - } - }; - }; -} - -var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, - DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; -var $locationMinErr = minErr('$location'); - - -/** - * Encode path using encodeUriSegment, ignoring forward slashes - * - * @param {string} path Path to encode - * @returns {string} - */ -function encodePath(path) { - var segments = path.split('/'), - i = segments.length; - - while (i--) { - segments[i] = encodeUriSegment(segments[i]); - } - - return segments.join('/'); -} - -function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) { - var parsedUrl = urlResolve(absoluteUrl, appBase); - - locationObj.$$protocol = parsedUrl.protocol; - locationObj.$$host = parsedUrl.hostname; - locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; -} - - -function parseAppUrl(relativeUrl, locationObj, appBase) { - var prefixed = (relativeUrl.charAt(0) !== '/'); - if (prefixed) { - relativeUrl = '/' + relativeUrl; - } - var match = urlResolve(relativeUrl, appBase); - locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? - match.pathname.substring(1) : match.pathname); - locationObj.$$search = parseKeyValue(match.search); - locationObj.$$hash = decodeURIComponent(match.hash); - - // make sure path starts with '/'; - if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { - locationObj.$$path = '/' + locationObj.$$path; - } -} - - -/** - * - * @param {string} begin - * @param {string} whole - * @returns {string} returns text from whole after begin or undefined if it does not begin with - * expected string. - */ -function beginsWith(begin, whole) { - if (whole.indexOf(begin) === 0) { - return whole.substr(begin.length); - } -} - - -function stripHash(url) { - var index = url.indexOf('#'); - return index == -1 ? url : url.substr(0, index); -} - - -function stripFile(url) { - return url.substr(0, stripHash(url).lastIndexOf('/') + 1); -} - -/* return the server only (scheme://host:port) */ -function serverBase(url) { - return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); -} - - -/** - * LocationHtml5Url represents an url - * This object is exposed as $location service when HTML5 mode is enabled and supported - * - * @constructor - * @param {string} appBase application base URL - * @param {string} basePrefix url path prefix - */ -function LocationHtml5Url(appBase, basePrefix) { - this.$$html5 = true; - basePrefix = basePrefix || ''; - var appBaseNoFile = stripFile(appBase); - parseAbsoluteUrl(appBase, this, appBase); - - - /** - * Parse given html5 (regular) url string into properties - * @param {string} newAbsoluteUrl HTML5 url - * @private - */ - this.$$parse = function(url) { - var pathUrl = beginsWith(appBaseNoFile, url); - if (!isString(pathUrl)) { - throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, - appBaseNoFile); - } - - parseAppUrl(pathUrl, this, appBase); - - if (!this.$$path) { - this.$$path = '/'; - } - - this.$$compose(); - }; - - /** - * Compose url and update `absUrl` property - * @private - */ - this.$$compose = function() { - var search = toKeyValue(this.$$search), - hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; - - this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; - this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' - }; - - this.$$parseLinkUrl = function(url, relHref) { - if (relHref && relHref[0] === '#') { - // special case for links to hash fragments: - // keep the old url and only replace the hash fragment - this.hash(relHref.slice(1)); - return true; - } - var appUrl, prevAppUrl; - var rewrittenUrl; - - if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { - prevAppUrl = appUrl; - if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { - rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); - } else { - rewrittenUrl = appBase + prevAppUrl; - } - } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { - rewrittenUrl = appBaseNoFile + appUrl; - } else if (appBaseNoFile == url + '/') { - rewrittenUrl = appBaseNoFile; - } - if (rewrittenUrl) { - this.$$parse(rewrittenUrl); - } - return !!rewrittenUrl; - }; -} - - -/** - * LocationHashbangUrl represents url - * This object is exposed as $location service when developer doesn't opt into html5 mode. - * It also serves as the base class for html5 mode fallback on legacy browsers. - * - * @constructor - * @param {string} appBase application base URL - * @param {string} hashPrefix hashbang prefix - */ -function LocationHashbangUrl(appBase, hashPrefix) { - var appBaseNoFile = stripFile(appBase); - - parseAbsoluteUrl(appBase, this, appBase); - - - /** - * Parse given hashbang url into properties - * @param {string} url Hashbang url - * @private - */ - this.$$parse = function(url) { - var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); - var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' - ? beginsWith(hashPrefix, withoutBaseUrl) - : (this.$$html5) - ? withoutBaseUrl - : ''; - - if (!isString(withoutHashUrl)) { - throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, - hashPrefix); - } - parseAppUrl(withoutHashUrl, this, appBase); - - this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); - - this.$$compose(); - - /* - * In Windows, on an anchor node on documents loaded from - * the filesystem, the browser will return a pathname - * prefixed with the drive name ('/C:/path') when a - * pathname without a drive is set: - * * a.setAttribute('href', '/foo') - * * a.pathname === '/C:/foo' //true - * - * Inside of Angular, we're always using pathnames that - * do not include drive names for routing. - */ - function removeWindowsDriveName (path, url, base) { - /* - Matches paths for file protocol on windows, - such as /C:/foo/bar, and captures only /foo/bar. - */ - var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; - - var firstPathSegmentMatch; - - //Get the relative path from the input URL. - if (url.indexOf(base) === 0) { - url = url.replace(base, ''); - } - - // The input URL intentionally contains a first path segment that ends with a colon. - if (windowsFilePathExp.exec(url)) { - return path; - } - - firstPathSegmentMatch = windowsFilePathExp.exec(path); - return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; - } - }; - - /** - * Compose hashbang url and update `absUrl` property - * @private - */ - this.$$compose = function() { - var search = toKeyValue(this.$$search), - hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; - - this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; - this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); - }; - - this.$$parseLinkUrl = function(url, relHref) { - if(stripHash(appBase) == stripHash(url)) { - this.$$parse(url); - return true; - } - return false; - }; -} - - -/** - * LocationHashbangUrl represents url - * This object is exposed as $location service when html5 history api is enabled but the browser - * does not support it. - * - * @constructor - * @param {string} appBase application base URL - * @param {string} hashPrefix hashbang prefix - */ -function LocationHashbangInHtml5Url(appBase, hashPrefix) { - this.$$html5 = true; - LocationHashbangUrl.apply(this, arguments); - - var appBaseNoFile = stripFile(appBase); - - this.$$parseLinkUrl = function(url, relHref) { - if (relHref && relHref[0] === '#') { - // special case for links to hash fragments: - // keep the old url and only replace the hash fragment - this.hash(relHref.slice(1)); - return true; - } - - var rewrittenUrl; - var appUrl; - - if ( appBase == stripHash(url) ) { - rewrittenUrl = url; - } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { - rewrittenUrl = appBase + hashPrefix + appUrl; - } else if ( appBaseNoFile === url + '/') { - rewrittenUrl = appBaseNoFile; - } - if (rewrittenUrl) { - this.$$parse(rewrittenUrl); - } - return !!rewrittenUrl; - }; - - this.$$compose = function() { - var search = toKeyValue(this.$$search), - hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; - - this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; - // include hashPrefix in $$absUrl when $$url is empty so IE8 & 9 do not reload page because of removal of '#' - this.$$absUrl = appBase + hashPrefix + this.$$url; - }; - -} - - -LocationHashbangInHtml5Url.prototype = - LocationHashbangUrl.prototype = - LocationHtml5Url.prototype = { - - /** - * Are we in html5 mode? - * @private - */ - $$html5: false, - - /** - * Has any change been replacing ? - * @private - */ - $$replace: false, - - /** - * @ngdoc method - * @name $location#absUrl - * - * @description - * This method is getter only. - * - * Return full url representation with all segments encoded according to rules specified in - * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). - * - * @return {string} full url - */ - absUrl: locationGetter('$$absUrl'), - - /** - * @ngdoc method - * @name $location#url - * - * @description - * This method is getter / setter. - * - * Return url (e.g. `/path?a=b#hash`) when called without any parameter. - * - * Change path, search and hash, when called with parameter and return `$location`. - * - * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) - * @return {string} url - */ - url: function(url) { - if (isUndefined(url)) - return this.$$url; - - var match = PATH_MATCH.exec(url); - if (match[1]) this.path(decodeURIComponent(match[1])); - if (match[2] || match[1]) this.search(match[3] || ''); - this.hash(match[5] || ''); - - return this; - }, - - /** - * @ngdoc method - * @name $location#protocol - * - * @description - * This method is getter only. - * - * Return protocol of current url. - * - * @return {string} protocol of current url - */ - protocol: locationGetter('$$protocol'), - - /** - * @ngdoc method - * @name $location#host - * - * @description - * This method is getter only. - * - * Return host of current url. - * - * @return {string} host of current url. - */ - host: locationGetter('$$host'), - - /** - * @ngdoc method - * @name $location#port - * - * @description - * This method is getter only. - * - * Return port of current url. - * - * @return {Number} port - */ - port: locationGetter('$$port'), - - /** - * @ngdoc method - * @name $location#path - * - * @description - * This method is getter / setter. - * - * Return path of current url when called without any parameter. - * - * Change path when called with parameter and return `$location`. - * - * Note: Path should always begin with forward slash (/), this method will add the forward slash - * if it is missing. - * - * @param {(string|number)=} path New path - * @return {string} path - */ - path: locationGetterSetter('$$path', function(path) { - path = path ? path.toString() : ''; - return path.charAt(0) == '/' ? path : '/' + path; - }), - - /** - * @ngdoc method - * @name $location#search - * - * @description - * This method is getter / setter. - * - * Return search part (as object) of current url when called without any parameter. - * - * Change search part when called with parameter and return `$location`. - * - * - * ```js - * // given url http://example.com/#/some/path?foo=bar&baz=xoxo - * var searchObject = $location.search(); - * // => {foo: 'bar', baz: 'xoxo'} - * - * - * // set foo to 'yipee' - * $location.search('foo', 'yipee'); - * // => $location - * ``` - * - * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or - * hash object. - * - * When called with a single argument the method acts as a setter, setting the `search` component - * of `$location` to the specified value. - * - * If the argument is a hash object containing an array of values, these values will be encoded - * as duplicate search parameters in the url. - * - * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` - * will override only a single search property. - * - * If `paramValue` is an array, it will override the property of the `search` component of - * `$location` specified via the first argument. - * - * If `paramValue` is `null`, the property specified via the first argument will be deleted. - * - * If `paramValue` is `true`, the property specified via the first argument will be added with no - * value nor trailing equal sign. - * - * @return {Object} If called with no arguments returns the parsed `search` object. If called with - * one or more arguments returns `$location` object itself. - */ - search: function(search, paramValue) { - switch (arguments.length) { - case 0: - return this.$$search; - case 1: - if (isString(search) || isNumber(search)) { - search = search.toString(); - this.$$search = parseKeyValue(search); - } else if (isObject(search)) { - // remove object undefined or null properties - forEach(search, function(value, key) { - if (value == null) delete search[key]; - }); - - this.$$search = search; - } else { - throw $locationMinErr('isrcharg', - 'The first argument of the `$location#search()` call must be a string or an object.'); - } - break; - default: - if (isUndefined(paramValue) || paramValue === null) { - delete this.$$search[search]; - } else { - this.$$search[search] = paramValue; - } - } - - this.$$compose(); - return this; - }, - - /** - * @ngdoc method - * @name $location#hash - * - * @description - * This method is getter / setter. - * - * Return hash fragment when called without any parameter. - * - * Change hash fragment when called with parameter and return `$location`. - * - * @param {(string|number)=} hash New hash fragment - * @return {string} hash - */ - hash: locationGetterSetter('$$hash', function(hash) { - return hash ? hash.toString() : ''; - }), - - /** - * @ngdoc method - * @name $location#replace - * - * @description - * If called, all changes to $location during current `$digest` will be replacing current history - * record, instead of adding new one. - */ - replace: function() { - this.$$replace = true; - return this; - } -}; - -function locationGetter(property) { - return function() { - return this[property]; - }; -} - - -function locationGetterSetter(property, preprocess) { - return function(value) { - if (isUndefined(value)) - return this[property]; - - this[property] = preprocess(value); - this.$$compose(); - - return this; - }; -} - - -/** - * @ngdoc service - * @name $location - * - * @requires $rootElement - * - * @description - * The $location service parses the URL in the browser address bar (based on the - * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL - * available to your application. Changes to the URL in the address bar are reflected into - * $location service and changes to $location are reflected into the browser address bar. - * - * **The $location service:** - * - * - Exposes the current URL in the browser address bar, so you can - * - Watch and observe the URL. - * - Change the URL. - * - Synchronizes the URL with the browser when the user - * - Changes the address bar. - * - Clicks the back or forward button (or clicks a History link). - * - Clicks on a link. - * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). - * - * For more information see {@link guide/$location Developer Guide: Using $location} - */ - -/** - * @ngdoc provider - * @name $locationProvider - * @description - * Use the `$locationProvider` to configure how the application deep linking paths are stored. - */ -function $LocationProvider(){ - var hashPrefix = '', - html5Mode = false; - - /** - * @ngdoc method - * @name $locationProvider#hashPrefix - * @description - * @param {string=} prefix Prefix for hash part (containing path and search) - * @returns {*} current value if used as getter or itself (chaining) if used as setter - */ - this.hashPrefix = function(prefix) { - if (isDefined(prefix)) { - hashPrefix = prefix; - return this; - } else { - return hashPrefix; - } - }; - - /** - * @ngdoc method - * @name $locationProvider#html5Mode - * @description - * @param {boolean=} mode Use HTML5 strategy if available. - * @returns {*} current value if used as getter or itself (chaining) if used as setter - */ - this.html5Mode = function(mode) { - if (isDefined(mode)) { - html5Mode = mode; - return this; - } else { - return html5Mode; - } - }; - - /** - * @ngdoc event - * @name $location#$locationChangeStart - * @eventType broadcast on root scope - * @description - * Broadcasted before a URL will change. This change can be prevented by calling - * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more - * details about event object. Upon successful change - * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. - * - * @param {Object} angularEvent Synthetic event object. - * @param {string} newUrl New URL - * @param {string=} oldUrl URL that was before it was changed. - */ - - /** - * @ngdoc event - * @name $location#$locationChangeSuccess - * @eventType broadcast on root scope - * @description - * Broadcasted after a URL was changed. - * - * @param {Object} angularEvent Synthetic event object. - * @param {string} newUrl New URL - * @param {string=} oldUrl URL that was before it was changed. - */ - - this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', - function( $rootScope, $browser, $sniffer, $rootElement) { - var $location, - LocationMode, - baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' - initialUrl = $browser.url(), - appBase; - - if (html5Mode) { - if (!baseHref) { - throw $locationMinErr('nobase', - "$location in HTML5 mode requires a <base> tag to be present!"); - } - appBase = serverBase(initialUrl) + (baseHref || '/'); - LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; - } else { - appBase = stripHash(initialUrl); - LocationMode = LocationHashbangUrl; - } - $location = new LocationMode(appBase, '#' + hashPrefix); - $location.$$parseLinkUrl(initialUrl, initialUrl); - - var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; - - $rootElement.on('click', function(event) { - // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) - // currently we open nice url link and redirect then - - if (event.ctrlKey || event.metaKey || event.which == 2) return; - - var elm = jqLite(event.target); - - // traverse the DOM up to find first A tag - while (nodeName_(elm[0]) !== 'a') { - // ignore rewriting if no A tag (reached root element, or no parent - removed from document) - if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; - } - - var absHref = elm.prop('href'); - // get the actual href attribute - see - // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx - var relHref = elm.attr('href') || elm.attr('xlink:href'); - - if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { - // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during - // an animation. - absHref = urlResolve(absHref.animVal).href; - } - - // Ignore when url is started with javascript: or mailto: - if (IGNORE_URI_REGEXP.test(absHref)) return; - - if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { - if ($location.$$parseLinkUrl(absHref, relHref)) { - event.preventDefault(); - // update location manually - if ($location.absUrl() != $browser.url()) { - $rootScope.$apply(); - // hack to work around FF6 bug 684208 when scenario runner clicks on links - window.angular['ff-684208-preventDefault'] = true; - } - } - } - }); - - - // rewrite hashbang url <> html5 url - if ($location.absUrl() != initialUrl) { - $browser.url($location.absUrl(), true); - } - - // update $location when $browser url changes - $browser.onUrlChange(function(newUrl) { - if ($location.absUrl() != newUrl) { - $rootScope.$evalAsync(function() { - var oldUrl = $location.absUrl(); - - $location.$$parse(newUrl); - if ($rootScope.$broadcast('$locationChangeStart', newUrl, - oldUrl).defaultPrevented) { - $location.$$parse(oldUrl); - $browser.url(oldUrl); - } else { - afterLocationChange(oldUrl); - } - }); - if (!$rootScope.$$phase) $rootScope.$digest(); - } - }); - - // update browser - var changeCounter = 0; - $rootScope.$watch(function $locationWatch() { - var oldUrl = $browser.url(); - var currentReplace = $location.$$replace; - - if (!changeCounter || oldUrl != $location.absUrl()) { - changeCounter++; - $rootScope.$evalAsync(function() { - if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). - defaultPrevented) { - $location.$$parse(oldUrl); - } else { - $browser.url($location.absUrl(), currentReplace); - afterLocationChange(oldUrl); - } - }); - } - $location.$$replace = false; - - return changeCounter; - }); - - return $location; - - function afterLocationChange(oldUrl) { - $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); - } -}]; -} - -/** - * @ngdoc service - * @name $log - * @requires $window - * - * @description - * Simple service for logging. Default implementation safely writes the message - * into the browser's console (if present). - * - * The main purpose of this service is to simplify debugging and troubleshooting. - * - * The default is to log `debug` messages. You can use - * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. - * - * @example - <example module="logExample"> - <file name="script.js"> - angular.module('logExample', []) - .controller('LogController', ['$scope', '$log', function($scope, $log) { - $scope.$log = $log; - $scope.message = 'Hello World!'; - }]); - </file> - <file name="index.html"> - <div ng-controller="LogController"> - <p>Reload this page with open console, enter text and hit the log button...</p> - Message: - <input type="text" ng-model="message"/> - <button ng-click="$log.log(message)">log</button> - <button ng-click="$log.warn(message)">warn</button> - <button ng-click="$log.info(message)">info</button> - <button ng-click="$log.error(message)">error</button> - </div> - </file> - </example> - */ - -/** - * @ngdoc provider - * @name $logProvider - * @description - * Use the `$logProvider` to configure how the application logs messages - */ -function $LogProvider(){ - var debug = true, - self = this; - - /** - * @ngdoc method - * @name $logProvider#debugEnabled - * @description - * @param {boolean=} flag enable or disable debug level messages - * @returns {*} current value if used as getter or itself (chaining) if used as setter - */ - this.debugEnabled = function(flag) { - if (isDefined(flag)) { - debug = flag; - return this; - } else { - return debug; - } - }; - - this.$get = ['$window', function($window){ - return { - /** - * @ngdoc method - * @name $log#log - * - * @description - * Write a log message - */ - log: consoleLog('log'), - - /** - * @ngdoc method - * @name $log#info - * - * @description - * Write an information message - */ - info: consoleLog('info'), - - /** - * @ngdoc method - * @name $log#warn - * - * @description - * Write a warning message - */ - warn: consoleLog('warn'), - - /** - * @ngdoc method - * @name $log#error - * - * @description - * Write an error message - */ - error: consoleLog('error'), - - /** - * @ngdoc method - * @name $log#debug - * - * @description - * Write a debug message - */ - debug: (function () { - var fn = consoleLog('debug'); - - return function() { - if (debug) { - fn.apply(self, arguments); - } - }; - }()) - }; - - function formatError(arg) { - if (arg instanceof Error) { - if (arg.stack) { - arg = (arg.message && arg.stack.indexOf(arg.message) === -1) - ? 'Error: ' + arg.message + '\n' + arg.stack - : arg.stack; - } else if (arg.sourceURL) { - arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; - } - } - return arg; - } - - function consoleLog(type) { - var console = $window.console || {}, - logFn = console[type] || console.log || noop, - hasApply = false; - - // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. - // The reason behind this is that console.log has type "object" in IE8... - try { - hasApply = !!logFn.apply; - } catch (e) {} - - if (hasApply) { - return function() { - var args = []; - forEach(arguments, function(arg) { - args.push(formatError(arg)); - }); - return logFn.apply(console, args); - }; - } - - // we are IE which either doesn't have window.console => this is noop and we do nothing, - // or we are IE where console.log doesn't have apply so we log at least first 2 args - return function(arg1, arg2) { - logFn(arg1, arg2 == null ? '' : arg2); - }; - } - }]; -} - -var $parseMinErr = minErr('$parse'); - -// Sandboxing Angular Expressions -// ------------------------------ -// Angular expressions are generally considered safe because these expressions only have direct -// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by -// obtaining a reference to native JS functions such as the Function constructor. -// -// As an example, consider the following Angular expression: -// -// {}.toString.constructor('alert("evil JS code")') -// -// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits -// against the expression language, but not to prevent exploits that were enabled by exposing -// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good -// practice and therefore we are not even trying to protect against interaction with an object -// explicitly exposed in this way. -// -// In general, it is not possible to access a Window object from an angular expression unless a -// window or some DOM object that has a reference to window is published onto a Scope. -// Similarly we prevent invocations of function known to be dangerous, as well as assignments to -// native objects. - - -function ensureSafeMemberName(name, fullExpression) { - if (name === "__defineGetter__" || name === "__defineSetter__" - || name === "__lookupGetter__" || name === "__lookupSetter__" - || name === "__proto__") { - throw $parseMinErr('isecfld', - 'Attempting to access a disallowed field in Angular expressions! ' - +'Expression: {0}', fullExpression); - } - return name; -} - -function ensureSafeObject(obj, fullExpression) { - // nifty check if obj is Function that is fast and works across iframes and other contexts - if (obj) { - if (obj.constructor === obj) { - throw $parseMinErr('isecfn', - 'Referencing Function in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } else if (// isWindow(obj) - obj.window === obj) { - throw $parseMinErr('isecwindow', - 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } else if (// isElement(obj) - obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { - throw $parseMinErr('isecdom', - 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } else if (// block Object so that we can't get hold of dangerous Object.* methods - obj === Object) { - throw $parseMinErr('isecobj', - 'Referencing Object in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } - } - return obj; -} - -var CALL = Function.prototype.call; -var APPLY = Function.prototype.apply; -var BIND = Function.prototype.bind; - -function ensureSafeFunction(obj, fullExpression) { - if (obj) { - if (obj.constructor === obj) { - throw $parseMinErr('isecfn', - 'Referencing Function in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } else if (obj === CALL || obj === APPLY || obj === BIND) { - throw $parseMinErr('isecff', - 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } - } -} - -//Keyword constants -var CONSTANTS = createMap(); -forEach({ - 'null': function() { return null; }, - 'true': function() { return true; }, - 'false': function() { return false; }, - 'undefined': function() {} -}, function(constantGetter, name) { - constantGetter.constant = constantGetter.literal = constantGetter.sharedGetter = true; - CONSTANTS[name] = constantGetter; -}); - -//Operators - will be wrapped by binaryFn/unaryFn/assignment/filter -var OPERATORS = extend(createMap(), { - /* jshint bitwise : false */ - '+':function(self, locals, a,b){ - a=a(self, locals); b=b(self, locals); - if (isDefined(a)) { - if (isDefined(b)) { - return a + b; - } - return a; - } - return isDefined(b)?b:undefined;}, - '-':function(self, locals, a,b){ - a=a(self, locals); b=b(self, locals); - return (isDefined(a)?a:0)-(isDefined(b)?b:0); - }, - '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, - '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, - '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, - '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, - '=':noop, - '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, - '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, - '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, - '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, - '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);}, - '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, - '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, - '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, - '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, - '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, - '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, -// '|':function(self, locals, a,b){return a|b;}, - '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, - '!':function(self, locals, a){return !a(self, locals);} -}); -/* jshint bitwise: true */ -var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; - - -///////////////////////////////////////// - - -/** - * @constructor - */ -var Lexer = function (options) { - this.options = options; -}; - -Lexer.prototype = { - constructor: Lexer, - - lex: function (text) { - this.text = text; - this.index = 0; - this.ch = undefined; - this.tokens = []; - - while (this.index < this.text.length) { - this.ch = this.text.charAt(this.index); - if (this.is('"\'')) { - this.readString(this.ch); - } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { - this.readNumber(); - } else if (this.isIdent(this.ch)) { - this.readIdent(); - } else if (this.is('(){}[].,;:?')) { - this.tokens.push({ - index: this.index, - text: this.ch - }); - this.index++; - } else if (this.isWhitespace(this.ch)) { - this.index++; - } else { - var ch2 = this.ch + this.peek(); - var ch3 = ch2 + this.peek(2); - var fn = OPERATORS[this.ch]; - var fn2 = OPERATORS[ch2]; - var fn3 = OPERATORS[ch3]; - if (fn3) { - this.tokens.push({index: this.index, text: ch3, fn: fn3}); - this.index += 3; - } else if (fn2) { - this.tokens.push({index: this.index, text: ch2, fn: fn2}); - this.index += 2; - } else if (fn) { - this.tokens.push({ - index: this.index, - text: this.ch, - fn: fn - }); - this.index += 1; - } else { - this.throwError('Unexpected next character ', this.index, this.index + 1); - } - } - } - return this.tokens; - }, - - is: function(chars) { - return chars.indexOf(this.ch) !== -1; - }, - - peek: function(i) { - var num = i || 1; - return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; - }, - - isNumber: function(ch) { - return ('0' <= ch && ch <= '9'); - }, - - isWhitespace: function(ch) { - // IE treats non-breaking space as \u00A0 - return (ch === ' ' || ch === '\r' || ch === '\t' || - ch === '\n' || ch === '\v' || ch === '\u00A0'); - }, - - isIdent: function(ch) { - return ('a' <= ch && ch <= 'z' || - 'A' <= ch && ch <= 'Z' || - '_' === ch || ch === '$'); - }, - - isExpOperator: function(ch) { - return (ch === '-' || ch === '+' || this.isNumber(ch)); - }, - - throwError: function(error, start, end) { - end = end || this.index; - var colStr = (isDefined(start) - ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' - : ' ' + end); - throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', - error, colStr, this.text); - }, - - readNumber: function() { - var number = ''; - var start = this.index; - while (this.index < this.text.length) { - var ch = lowercase(this.text.charAt(this.index)); - if (ch == '.' || this.isNumber(ch)) { - number += ch; - } else { - var peekCh = this.peek(); - if (ch == 'e' && this.isExpOperator(peekCh)) { - number += ch; - } else if (this.isExpOperator(ch) && - peekCh && this.isNumber(peekCh) && - number.charAt(number.length - 1) == 'e') { - number += ch; - } else if (this.isExpOperator(ch) && - (!peekCh || !this.isNumber(peekCh)) && - number.charAt(number.length - 1) == 'e') { - this.throwError('Invalid exponent'); - } else { - break; - } - } - this.index++; - } - number = 1 * number; - this.tokens.push({ - index: start, - text: number, - constant: true, - fn: function() { return number; } - }); - }, - - readIdent: function() { - var expression = this.text; - - var ident = ''; - var start = this.index; - - var lastDot, peekIndex, methodName, ch; - - while (this.index < this.text.length) { - ch = this.text.charAt(this.index); - if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { - if (ch === '.') lastDot = this.index; - ident += ch; - } else { - break; - } - this.index++; - } - - //check if the identifier ends with . and if so move back one char - if (lastDot && ident[ident.length - 1] === '.') { - this.index--; - ident = ident.slice(0, -1); - lastDot = ident.lastIndexOf('.'); - if (lastDot === -1) { - lastDot = undefined; - } - } - - //check if this is not a method invocation and if it is back out to last dot - if (lastDot) { - peekIndex = this.index; - while (peekIndex < this.text.length) { - ch = this.text.charAt(peekIndex); - if (ch === '(') { - methodName = ident.substr(lastDot - start + 1); - ident = ident.substr(0, lastDot - start); - this.index = peekIndex; - break; - } - if (this.isWhitespace(ch)) { - peekIndex++; - } else { - break; - } - } - } - - this.tokens.push({ - index: start, - text: ident, - fn: CONSTANTS[ident] || getterFn(ident, this.options, expression) - }); - - if (methodName) { - this.tokens.push({ - index: lastDot, - text: '.' - }); - this.tokens.push({ - index: lastDot + 1, - text: methodName - }); - } - }, - - readString: function(quote) { - var start = this.index; - this.index++; - var string = ''; - var rawString = quote; - var escape = false; - while (this.index < this.text.length) { - var ch = this.text.charAt(this.index); - rawString += ch; - if (escape) { - if (ch === 'u') { - var hex = this.text.substring(this.index + 1, this.index + 5); - if (!hex.match(/[\da-f]{4}/i)) - this.throwError('Invalid unicode escape [\\u' + hex + ']'); - this.index += 4; - string += String.fromCharCode(parseInt(hex, 16)); - } else { - var rep = ESCAPE[ch]; - string = string + (rep || ch); - } - escape = false; - } else if (ch === '\\') { - escape = true; - } else if (ch === quote) { - this.index++; - this.tokens.push({ - index: start, - text: rawString, - string: string, - constant: true, - fn: function() { return string; } - }); - return; - } else { - string += ch; - } - this.index++; - } - this.throwError('Unterminated quote', start); - } -}; - - -/** - * @constructor - */ -var Parser = function (lexer, $filter, options) { - this.lexer = lexer; - this.$filter = $filter; - this.options = options; -}; - -Parser.ZERO = extend(function () { - return 0; -}, { - sharedGetter: true, - constant: true -}); - -Parser.prototype = { - constructor: Parser, - - parse: function (text) { - this.text = text; - this.tokens = this.lexer.lex(text); - - var value = this.statements(); - - if (this.tokens.length !== 0) { - this.throwError('is an unexpected token', this.tokens[0]); - } - - value.literal = !!value.literal; - value.constant = !!value.constant; - - return value; - }, - - primary: function () { - var primary; - if (this.expect('(')) { - primary = this.filterChain(); - this.consume(')'); - } else if (this.expect('[')) { - primary = this.arrayDeclaration(); - } else if (this.expect('{')) { - primary = this.object(); - } else { - var token = this.expect(); - primary = token.fn; - if (!primary) { - this.throwError('not a primary expression', token); - } - if (token.constant) { - primary.constant = true; - primary.literal = true; - } - } - - var next, context; - while ((next = this.expect('(', '[', '.'))) { - if (next.text === '(') { - primary = this.functionCall(primary, context); - context = null; - } else if (next.text === '[') { - context = primary; - primary = this.objectIndex(primary); - } else if (next.text === '.') { - context = primary; - primary = this.fieldAccess(primary); - } else { - this.throwError('IMPOSSIBLE'); - } - } - return primary; - }, - - throwError: function(msg, token) { - throw $parseMinErr('syntax', - 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', - token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); - }, - - peekToken: function() { - if (this.tokens.length === 0) - throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); - return this.tokens[0]; - }, - - peek: function(e1, e2, e3, e4) { - if (this.tokens.length > 0) { - var token = this.tokens[0]; - var t = token.text; - if (t === e1 || t === e2 || t === e3 || t === e4 || - (!e1 && !e2 && !e3 && !e4)) { - return token; - } - } - return false; - }, - - expect: function(e1, e2, e3, e4){ - var token = this.peek(e1, e2, e3, e4); - if (token) { - this.tokens.shift(); - return token; - } - return false; - }, - - consume: function(e1){ - if (!this.expect(e1)) { - this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); - } - }, - - unaryFn: function(fn, right) { - return extend(function(self, locals) { - return fn(self, locals, right); - }, { - constant:right.constant - }); - }, - - ternaryFn: function(left, middle, right){ - return extend(function(self, locals){ - return left(self, locals) ? middle(self, locals) : right(self, locals); - }, { - constant: left.constant && middle.constant && right.constant - }); - }, - - binaryFn: function(left, fn, right) { - return extend(function(self, locals) { - return fn(self, locals, left, right); - }, { - constant:left.constant && right.constant - }); - }, - - statements: function() { - var statements = []; - while (true) { - if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) - statements.push(this.filterChain()); - if (!this.expect(';')) { - // optimize for the common case where there is only one statement. - // TODO(size): maybe we should not support multiple statements? - return (statements.length === 1) - ? statements[0] - : function $parseStatements(self, locals) { - var value; - for (var i = 0, ii = statements.length; i < ii; i++) { - value = statements[i](self, locals); - } - return value; - }; - } - } - }, - - filterChain: function() { - var left = this.expression(); - var token; - while ((token = this.expect('|'))) { - left = this.binaryFn(left, token.fn, this.filter()); - } - return left; - }, - - filter: function() { - var token = this.expect(); - var fn = this.$filter(token.text); - var argsFn; - var args; - - if (this.peek(':')) { - argsFn = []; - args = []; // we can safely reuse the array - while (this.expect(':')) { - argsFn.push(this.expression()); - } - } - - return valueFn(function $parseFilter(self, locals, input) { - if (args) { - args[0] = input; - - var i = argsFn.length; - while (i--) { - args[i + 1] = argsFn[i](self, locals); - } - - return fn.apply(undefined, args); - } - - return fn(input); - }); - }, - - expression: function() { - return this.assignment(); - }, - - assignment: function() { - var left = this.ternary(); - var right; - var token; - if ((token = this.expect('='))) { - if (!left.assign) { - this.throwError('implies assignment but [' + - this.text.substring(0, token.index) + '] can not be assigned to', token); - } - right = this.ternary(); - return function $parseAssignment(scope, locals) { - return left.assign(scope, right(scope, locals), locals); - }; - } - return left; - }, - - ternary: function() { - var left = this.logicalOR(); - var middle; - var token; - if ((token = this.expect('?'))) { - middle = this.assignment(); - if ((token = this.expect(':'))) { - return this.ternaryFn(left, middle, this.assignment()); - } else { - this.throwError('expected :', token); - } - } else { - return left; - } - }, - - logicalOR: function() { - var left = this.logicalAND(); - var token; - while ((token = this.expect('||'))) { - left = this.binaryFn(left, token.fn, this.logicalAND()); - } - return left; - }, - - logicalAND: function() { - var left = this.equality(); - var token; - if ((token = this.expect('&&'))) { - left = this.binaryFn(left, token.fn, this.logicalAND()); - } - return left; - }, - - equality: function() { - var left = this.relational(); - var token; - if ((token = this.expect('==','!=','===','!=='))) { - left = this.binaryFn(left, token.fn, this.equality()); - } - return left; - }, - - relational: function() { - var left = this.additive(); - var token; - if ((token = this.expect('<', '>', '<=', '>='))) { - left = this.binaryFn(left, token.fn, this.relational()); - } - return left; - }, - - additive: function() { - var left = this.multiplicative(); - var token; - while ((token = this.expect('+','-'))) { - left = this.binaryFn(left, token.fn, this.multiplicative()); - } - return left; - }, - - multiplicative: function() { - var left = this.unary(); - var token; - while ((token = this.expect('*','/','%'))) { - left = this.binaryFn(left, token.fn, this.unary()); - } - return left; - }, - - unary: function() { - var token; - if (this.expect('+')) { - return this.primary(); - } else if ((token = this.expect('-'))) { - return this.binaryFn(Parser.ZERO, token.fn, this.unary()); - } else if ((token = this.expect('!'))) { - return this.unaryFn(token.fn, this.unary()); - } else { - return this.primary(); - } - }, - - fieldAccess: function(object) { - var expression = this.text; - var field = this.expect().text; - var getter = getterFn(field, this.options, expression); - - return extend(function $parseFieldAccess(scope, locals, self) { - return getter(self || object(scope, locals)); - }, { - assign: function(scope, value, locals) { - var o = object(scope, locals); - if (!o) object.assign(scope, o = {}); - return setter(o, field, value, expression); - } - }); - }, - - objectIndex: function(obj) { - var expression = this.text; - - var indexFn = this.expression(); - this.consume(']'); - - return extend(function $parseObjectIndex(self, locals) { - var o = obj(self, locals), - i = indexFn(self, locals), - v; - - ensureSafeMemberName(i, expression); - if (!o) return undefined; - v = ensureSafeObject(o[i], expression); - return v; - }, { - assign: function(self, value, locals) { - var key = ensureSafeMemberName(indexFn(self, locals), expression); - // prevent overwriting of Function.constructor which would break ensureSafeObject check - var o = ensureSafeObject(obj(self, locals), expression); - if (!o) obj.assign(self, o = {}); - return o[key] = value; - } - }); - }, - - functionCall: function(fnGetter, contextGetter) { - var argsFn = []; - if (this.peekToken().text !== ')') { - do { - argsFn.push(this.expression()); - } while (this.expect(',')); - } - this.consume(')'); - - var expressionText = this.text; - // we can safely reuse the array across invocations - var args = argsFn.length ? [] : null; - - return function $parseFunctionCall(scope, locals) { - var context = contextGetter ? contextGetter(scope, locals) : scope; - var fn = fnGetter(scope, locals, context) || noop; - - if (args) { - var i = argsFn.length; - while (i--) { - args[i] = ensureSafeObject(argsFn[i](scope, locals), expressionText); - } - } - - ensureSafeObject(context, expressionText); - ensureSafeFunction(fn, expressionText); - - // IE stupidity! (IE doesn't have apply for some native functions) - var v = fn.apply - ? fn.apply(context, args) - : fn(args[0], args[1], args[2], args[3], args[4]); - - return ensureSafeObject(v, expressionText); - }; - }, - - // This is used with json array declaration - arrayDeclaration: function () { - var elementFns = []; - var allConstant = true; - if (this.peekToken().text !== ']') { - do { - if (this.peek(']')) { - // Support trailing commas per ES5.1. - break; - } - var elementFn = this.expression(); - elementFns.push(elementFn); - if (!elementFn.constant) { - allConstant = false; - } - } while (this.expect(',')); - } - this.consume(']'); - - return extend(function $parseArrayLiteral(self, locals) { - var array = []; - for (var i = 0, ii = elementFns.length; i < ii; i++) { - array.push(elementFns[i](self, locals)); - } - return array; - }, { - literal: true, - constant: allConstant - }); - }, - - object: function () { - var keyValues = []; - var allConstant = true; - if (this.peekToken().text !== '}') { - do { - if (this.peek('}')) { - // Support trailing commas per ES5.1. - break; - } - var token = this.expect(), - key = token.string || token.text; - this.consume(':'); - var value = this.expression(); - keyValues.push({key: key, value: value}); - if (!value.constant) { - allConstant = false; - } - } while (this.expect(',')); - } - this.consume('}'); - - return extend(function $parseObjectLiteral(self, locals) { - var object = {}; - for (var i = 0, ii = keyValues.length; i < ii; i++) { - var keyValue = keyValues[i]; - object[keyValue.key] = keyValue.value(self, locals); - } - return object; - }, { - literal: true, - constant: allConstant - }); - } -}; - - -////////////////////////////////////////////////// -// Parser helper functions -////////////////////////////////////////////////// - -function setter(obj, path, setValue, fullExp) { - ensureSafeObject(obj, fullExp); - - var element = path.split('.'), key; - for (var i = 0; element.length > 1; i++) { - key = ensureSafeMemberName(element.shift(), fullExp); - var propertyObj = ensureSafeObject(obj[key], fullExp); - if (!propertyObj) { - propertyObj = {}; - obj[key] = propertyObj; - } - obj = propertyObj; - } - key = ensureSafeMemberName(element.shift(), fullExp); - ensureSafeObject(obj[key], fullExp); - obj[key] = setValue; - return setValue; -} - -var getterFnCache = createMap(); - -/** - * Implementation of the "Black Hole" variant from: - * - http://jsperf.com/angularjs-parse-getter/4 - * - http://jsperf.com/path-evaluation-simplified/7 - */ -function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp) { - ensureSafeMemberName(key0, fullExp); - ensureSafeMemberName(key1, fullExp); - ensureSafeMemberName(key2, fullExp); - ensureSafeMemberName(key3, fullExp); - ensureSafeMemberName(key4, fullExp); - - return function cspSafeGetter(scope, locals) { - var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; - - if (pathVal == null) return pathVal; - pathVal = pathVal[key0]; - - if (!key1) return pathVal; - if (pathVal == null) return undefined; - pathVal = pathVal[key1]; - - if (!key2) return pathVal; - if (pathVal == null) return undefined; - pathVal = pathVal[key2]; - - if (!key3) return pathVal; - if (pathVal == null) return undefined; - pathVal = pathVal[key3]; - - if (!key4) return pathVal; - if (pathVal == null) return undefined; - pathVal = pathVal[key4]; - - return pathVal; - }; -} - -function getterFn(path, options, fullExp) { - var fn = getterFnCache[path]; - - if (fn) return fn; - - var pathKeys = path.split('.'), - pathKeysLength = pathKeys.length; - - // http://jsperf.com/angularjs-parse-getter/6 - if (options.csp) { - if (pathKeysLength < 6) { - fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp); - } else { - fn = function(scope, locals) { - var i = 0, val; - do { - val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], - pathKeys[i++], fullExp)(scope, locals); - - locals = undefined; // clear after first iteration - scope = val; - } while (i < pathKeysLength); - return val; - }; - } - } else { - var code = ''; - forEach(pathKeys, function(key, index) { - ensureSafeMemberName(key, fullExp); - code += 'if(s == null) return undefined;\n' + - 's='+ (index - // we simply dereference 's' on any .dot notation - ? 's' - // but if we are first then we check locals first, and if so read it first - : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key + ';\n'; - }); - code += 'return s;'; - - /* jshint -W054 */ - var evaledFnGetter = new Function('s', 'l', code); // s=scope, l=locals - /* jshint +W054 */ - evaledFnGetter.toString = valueFn(code); - evaledFnGetter.assign = function(self, value) { - return setter(self, path, value, path); - }; - - fn = evaledFnGetter; - } - - fn.sharedGetter = true; - getterFnCache[path] = fn; - return fn; -} - -/////////////////////////////////// - -/** - * @ngdoc service - * @name $parse - * @kind function - * - * @description - * - * Converts Angular {@link guide/expression expression} into a function. - * - * ```js - * var getter = $parse('user.name'); - * var setter = getter.assign; - * var context = {user:{name:'angular'}}; - * var locals = {user:{name:'local'}}; - * - * expect(getter(context)).toEqual('angular'); - * setter(context, 'newValue'); - * expect(context.user.name).toEqual('newValue'); - * expect(getter(context, locals)).toEqual('local'); - * ``` - * - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - * - * The returned function also has the following properties: - * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript - * literal. - * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript - * constant literals. - * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be - * set to a function to change its value on the given context. - * - */ - - -/** - * @ngdoc provider - * @name $parseProvider - * - * @description - * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} - * service. - */ -function $ParseProvider() { - var cache = createMap(); - - var $parseOptions = { - csp: false - }; - - - this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { - $parseOptions.csp = $sniffer.csp; - - function wrapSharedExpression(exp) { - var wrapped = exp; - - if (exp.sharedGetter) { - wrapped = function $parseWrapper(self, locals) { - return exp(self, locals); - }; - wrapped.literal = exp.literal; - wrapped.constant = exp.constant; - wrapped.assign = exp.assign; - } - - return wrapped; - } - - return function $parse(exp, interceptorFn) { - var parsedExpression, oneTime, cacheKey; - - switch (typeof exp) { - case 'string': - cacheKey = exp = exp.trim(); - - parsedExpression = cache[cacheKey]; - - if (!parsedExpression) { - if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { - oneTime = true; - exp = exp.substring(2); - } - - var lexer = new Lexer($parseOptions); - var parser = new Parser(lexer, $filter, $parseOptions); - parsedExpression = parser.parse(exp); - - if (parsedExpression.constant) { - parsedExpression.$$watchDelegate = constantWatchDelegate; - } else if (oneTime) { - //oneTime is not part of the exp passed to the Parser so we may have to - //wrap the parsedExpression before adding a $$watchDelegate - parsedExpression = wrapSharedExpression(parsedExpression); - parsedExpression.$$watchDelegate = parsedExpression.literal ? - oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; - } - - cache[cacheKey] = parsedExpression; - } - return addInterceptor(parsedExpression, interceptorFn); - - case 'function': - return addInterceptor(exp, interceptorFn); - - default: - return addInterceptor(noop, interceptorFn); - } - }; - - function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { - var unwatch, lastValue; - return unwatch = scope.$watch(function oneTimeWatch(scope) { - return parsedExpression(scope); - }, function oneTimeListener(value, old, scope) { - lastValue = value; - if (isFunction(listener)) { - listener.apply(this, arguments); - } - if (isDefined(value)) { - scope.$$postDigest(function () { - if (isDefined(lastValue)) { - unwatch(); - } - }); - } - }, objectEquality); - } - - function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { - var unwatch; - return unwatch = scope.$watch(function oneTimeWatch(scope) { - return parsedExpression(scope); - }, function oneTimeListener(value, old, scope) { - if (isFunction(listener)) { - listener.call(this, value, old, scope); - } - if (isAllDefined(value)) { - scope.$$postDigest(function () { - if(isAllDefined(value)) unwatch(); - }); - } - }, objectEquality); - - function isAllDefined(value) { - var allDefined = true; - forEach(value, function (val) { - if (!isDefined(val)) allDefined = false; - }); - return allDefined; - } - } - - function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { - var unwatch; - return unwatch = scope.$watch(function constantWatch(scope) { - return parsedExpression(scope); - }, function constantListener(value, old, scope) { - if (isFunction(listener)) { - listener.apply(this, arguments); - } - unwatch(); - }, objectEquality); - } - - function addInterceptor(parsedExpression, interceptorFn) { - if (!interceptorFn) return parsedExpression; - - var fn = function interceptedExpression(scope, locals) { - var value = parsedExpression(scope, locals); - var result = interceptorFn(value, scope, locals); - // we only return the interceptor's result if the - // initial value is defined (for bind-once) - return isDefined(value) ? result : value; - }; - fn.$$watchDelegate = parsedExpression.$$watchDelegate; - return fn; - } - }]; -} - -/** - * @ngdoc service - * @name $q - * @requires $rootScope - * - * @description - * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). - * - * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred - * implementations, and the other which resembles ES6 promises to some degree. - * - * # $q constructor - * - * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` - * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony, - * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). - * - * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are - * available yet. - * - * It can be used like so: - * - * ```js - * return $q(function(resolve, reject) { - * // perform some asynchronous operation, resolve or reject the promise when appropriate. - * setInterval(function() { - * if (pollStatus > 0) { - * resolve(polledValue); - * } else if (pollStatus < 0) { - * reject(polledValue); - * } else { - * pollStatus = pollAgain(function(value) { - * polledValue = value; - * }); - * } - * }, 10000); - * }). - * then(function(value) { - * // handle success - * }, function(reason) { - * // handle failure - * }); - * ``` - * - * Note: progress/notify callbacks are not currently supported via the ES6-style interface. - * - * However, the more traditional CommonJS-style usage is still available, and documented below. - * - * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an - * interface for interacting with an object that represents the result of an action that is - * performed asynchronously, and may or may not be finished at any given point in time. - * - * From the perspective of dealing with error handling, deferred and promise APIs are to - * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. - * - * ```js - * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet` - * // are available in the current lexical scope (they could have been injected or passed in). - * - * function asyncGreet(name) { - * var deferred = $q.defer(); - * - * setTimeout(function() { - * deferred.notify('About to greet ' + name + '.'); - * - * if (okToGreet(name)) { - * deferred.resolve('Hello, ' + name + '!'); - * } else { - * deferred.reject('Greeting ' + name + ' is not allowed.'); - * } - * }, 1000); - * - * return deferred.promise; - * } - * - * var promise = asyncGreet('Robin Hood'); - * promise.then(function(greeting) { - * alert('Success: ' + greeting); - * }, function(reason) { - * alert('Failed: ' + reason); - * }, function(update) { - * alert('Got notification: ' + update); - * }); - * ``` - * - * At first it might not be obvious why this extra complexity is worth the trouble. The payoff - * comes in the way of guarantees that promise and deferred APIs make, see - * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. - * - * Additionally the promise api allows for composition that is very hard to do with the - * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. - * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the - * section on serial or parallel joining of promises. - * - * # The Deferred API - * - * A new instance of deferred is constructed by calling `$q.defer()`. - * - * The purpose of the deferred object is to expose the associated Promise instance as well as APIs - * that can be used for signaling the successful or unsuccessful completion, as well as the status - * of the task. - * - * **Methods** - * - * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection - * constructed via `$q.reject`, the promise will be rejected instead. - * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to - * resolving it with a rejection constructed via `$q.reject`. - * - `notify(value)` - provides updates on the status of the promise's execution. This may be called - * multiple times before the promise is either resolved or rejected. - * - * **Properties** - * - * - promise – `{Promise}` – promise object associated with this deferred. - * - * - * # The Promise API - * - * A new promise instance is created when a deferred instance is created and can be retrieved by - * calling `deferred.promise`. - * - * The purpose of the promise object is to allow for interested parties to get access to the result - * of the deferred task when it completes. - * - * **Methods** - * - * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or - * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously - * as soon as the result is available. The callbacks are called with a single argument: the result - * or rejection reason. Additionally, the notify callback may be called zero or more times to - * provide a progress indication, before the promise is resolved or rejected. - * - * This method *returns a new promise* which is resolved or rejected via the return value of the - * `successCallback`, `errorCallback`. It also notifies via the return value of the - * `notifyCallback` method. The promise cannot be resolved or rejected from the notifyCallback - * method. - * - * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` - * - * - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise, - * but to do so without modifying the final value. This is useful to release resources or do some - * clean-up that needs to be done whether the promise was rejected or resolved. See the [full - * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for - * more information. - * - * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as - * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to - * make your code IE8 and Android 2.x compatible. - * - * # Chaining promises - * - * Because calling the `then` method of a promise returns a new derived promise, it is easily - * possible to create a chain of promises: - * - * ```js - * promiseB = promiseA.then(function(result) { - * return result + 1; - * }); - * - * // promiseB will be resolved immediately after promiseA is resolved and its value - * // will be the result of promiseA incremented by 1 - * ``` - * - * It is possible to create chains of any length and since a promise can be resolved with another - * promise (which will defer its resolution further), it is possible to pause/defer resolution of - * the promises at any point in the chain. This makes it possible to implement powerful APIs like - * $http's response interceptors. - * - * - * # Differences between Kris Kowal's Q and $q - * - * There are two main differences: - * - * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation - * mechanism in angular, which means faster propagation of resolution or rejection into your - * models and avoiding unnecessary browser repaints, which would result in flickering UI. - * - 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 - * - * ```js - * it('should simulate promise', inject(function($q, $rootScope) { - * var deferred = $q.defer(); - * var promise = deferred.promise; - * var resolvedValue; - * - * promise.then(function(value) { resolvedValue = value; }); - * expect(resolvedValue).toBeUndefined(); - * - * // Simulate resolving of promise - * deferred.resolve(123); - * // Note that the 'then' function does not get called synchronously. - * // This is because we want the promise API to always be async, whether or not - * // it got called synchronously or asynchronously. - * expect(resolvedValue).toBeUndefined(); - * - * // Propagate promise resolution to 'then' functions using $apply(). - * $rootScope.$apply(); - * expect(resolvedValue).toEqual(123); - * })); - * ``` - * - * @param {function(function, function)} resolver Function which is responsible for resolving or - * rejecting the newly created promise. The first parameter is a function which resolves the - * promise, the second parameter is a function which rejects the promise. - * - * @returns {Promise} The newly created promise. - */ -function $QProvider() { - - this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { - return qFactory(function(callback) { - $rootScope.$evalAsync(callback); - }, $exceptionHandler); - }]; -} - -function $$QProvider() { - this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { - return qFactory(function(callback) { - $browser.defer(callback); - }, $exceptionHandler); - }]; -} - -/** - * Constructs a promise manager. - * - * @param {function(function)} nextTick Function for executing functions in the next turn. - * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for - * debugging purposes. - * @returns {object} Promise manager. - */ -function qFactory(nextTick, exceptionHandler) { - var $qMinErr = minErr('$q', TypeError); - function callOnce(self, resolveFn, rejectFn) { - var called = false; - function wrap(fn) { - return function(value) { - if (called) return; - called = true; - fn.call(self, value); - }; - } - - return [wrap(resolveFn), wrap(rejectFn)]; - } - - /** - * @ngdoc method - * @name ng.$q#defer - * @kind function - * - * @description - * Creates a `Deferred` object which represents a task which will finish in the future. - * - * @returns {Deferred} Returns a new instance of deferred. - */ - var defer = function() { - return new Deferred(); - }; - - function Promise() { - this.$$state = { status: 0 }; - } - - Promise.prototype = { - then: function(onFulfilled, onRejected, progressBack) { - var result = new Deferred(); - - this.$$state.pending = this.$$state.pending || []; - this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); - if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); - - return result.promise; - }, - - "catch": function(callback) { - return this.then(null, callback); - }, - - "finally": function(callback, progressBack) { - return this.then(function(value) { - return handleCallback(value, true, callback); - }, function(error) { - return handleCallback(error, false, callback); - }, progressBack); - } - }; - - //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native - function simpleBind(context, fn) { - return function(value) { - fn.call(context, value); - }; - } - - function processQueue(state) { - var fn, promise, pending; - - pending = state.pending; - state.processScheduled = false; - state.pending = undefined; - for (var i = 0, ii = pending.length; i < ii; ++i) { - promise = pending[i][0]; - fn = pending[i][state.status]; - try { - if (isFunction(fn)) { - promise.resolve(fn(state.value)); - } else if (state.status === 1) { - promise.resolve(state.value); - } else { - promise.reject(state.value); - } - } catch(e) { - promise.reject(e); - exceptionHandler(e); - } - } - } - - function scheduleProcessQueue(state) { - if (state.processScheduled || !state.pending) return; - state.processScheduled = true; - nextTick(function() { processQueue(state); }); - } - - function Deferred() { - this.promise = new Promise(); - //Necessary to support unbound execution :/ - this.resolve = simpleBind(this, this.resolve); - this.reject = simpleBind(this, this.reject); - this.notify = simpleBind(this, this.notify); - } - - Deferred.prototype = { - resolve: function(val) { - if (this.promise.$$state.status) return; - if (val === this.promise) { - this.$$reject($qMinErr( - 'qcycle', - "Expected promise to be resolved with value other than itself '{0}'", - val)); - } - else { - this.$$resolve(val); - } - - }, - - $$resolve: function(val) { - var then, fns; - - fns = callOnce(this, this.$$resolve, this.$$reject); - try { - if ((isObject(val) || isFunction(val))) then = val && val.then; - if (isFunction(then)) { - this.promise.$$state.status = -1; - then.call(val, fns[0], fns[1], this.notify); - } else { - this.promise.$$state.value = val; - this.promise.$$state.status = 1; - scheduleProcessQueue(this.promise.$$state); - } - } catch(e) { - fns[1](e); - exceptionHandler(e); - } - }, - - reject: function(reason) { - if (this.promise.$$state.status) return; - this.$$reject(reason); - }, - - $$reject: function(reason) { - this.promise.$$state.value = reason; - this.promise.$$state.status = 2; - scheduleProcessQueue(this.promise.$$state); - }, - - notify: function(progress) { - var callbacks = this.promise.$$state.pending; - - if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { - nextTick(function() { - var callback, result; - for (var i = 0, ii = callbacks.length; i < ii; i++) { - result = callbacks[i][0]; - callback = callbacks[i][3]; - try { - result.notify(isFunction(callback) ? callback(progress) : progress); - } catch(e) { - exceptionHandler(e); - } - } - }); - } - } - }; - - /** - * @ngdoc method - * @name $q#reject - * @kind function - * - * @description - * Creates a promise that is resolved as rejected with the specified `reason`. This api should be - * used to forward rejection in a chain of promises. If you are dealing with the last promise in - * a promise chain, you don't need to worry about it. - * - * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of - * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via - * a promise error callback and you want to forward the error to the promise derived from the - * current promise, you have to "rethrow" the error by returning a rejection constructed via - * `reject`. - * - * ```js - * promiseB = promiseA.then(function(result) { - * // success: do something and resolve promiseB - * // with the old or a new result - * return result; - * }, function(reason) { - * // error: handle the error if possible and - * // resolve promiseB with newPromiseOrValue, - * // otherwise forward the rejection to promiseB - * if (canHandle(reason)) { - * // handle the error and recover - * return newPromiseOrValue; - * } - * return $q.reject(reason); - * }); - * ``` - * - * @param {*} reason Constant, message, exception or an object representing the rejection reason. - * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. - */ - var reject = function(reason) { - var result = new Deferred(); - result.reject(reason); - return result.promise; - }; - - var makePromise = function makePromise(value, resolved) { - var result = new Deferred(); - if (resolved) { - result.resolve(value); - } else { - result.reject(value); - } - return result.promise; - }; - - var handleCallback = function handleCallback(value, isResolved, callback) { - var callbackOutput = null; - try { - if (isFunction(callback)) callbackOutput = callback(); - } catch(e) { - return makePromise(e, false); - } - if (isPromiseLike(callbackOutput)) { - return callbackOutput.then(function() { - return makePromise(value, isResolved); - }, function(error) { - return makePromise(error, false); - }); - } else { - return makePromise(value, isResolved); - } - }; - - /** - * @ngdoc method - * @name $q#when - * @kind function - * - * @description - * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. - * This is useful when you are dealing with an object that might or might not be a promise, or if - * the promise comes from a source that can't be trusted. - * - * @param {*} value Value or a promise - * @returns {Promise} Returns a promise of the passed value or promise - */ - - - var when = function(value, callback, errback, progressBack) { - var result = new Deferred(); - result.resolve(value); - return result.promise.then(callback, errback, progressBack); - }; - - /** - * @ngdoc method - * @name $q#all - * @kind function - * - * @description - * Combines multiple promises into a single promise that is resolved when all of the input - * promises are resolved. - * - * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. - * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, - * each value corresponding to the promise at the same index/key in the `promises` array/hash. - * If any of the promises is resolved with a rejection, this resulting promise will be rejected - * with the same rejection value. - */ - - function all(promises) { - var deferred = new Deferred(), - counter = 0, - results = isArray(promises) ? [] : {}; - - forEach(promises, function(promise, key) { - counter++; - when(promise).then(function(value) { - if (results.hasOwnProperty(key)) return; - results[key] = value; - if (!(--counter)) deferred.resolve(results); - }, function(reason) { - if (results.hasOwnProperty(key)) return; - deferred.reject(reason); - }); - }); - - if (counter === 0) { - deferred.resolve(results); - } - - return deferred.promise; - } - - var $Q = function Q(resolver) { - if (!isFunction(resolver)) { - throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); - } - - if (!(this instanceof Q)) { - // More useful when $Q is the Promise itself. - return new Q(resolver); - } - - var deferred = new Deferred(); - - function resolveFn(value) { - deferred.resolve(value); - } - - function rejectFn(reason) { - deferred.reject(reason); - } - - resolver(resolveFn, rejectFn); - - return deferred.promise; - }; - - $Q.defer = defer; - $Q.reject = reject; - $Q.when = when; - $Q.all = all; - - return $Q; -} - -function $$RAFProvider(){ //rAF - this.$get = ['$window', '$timeout', function($window, $timeout) { - var requestAnimationFrame = $window.requestAnimationFrame || - $window.webkitRequestAnimationFrame || - $window.mozRequestAnimationFrame; - - var cancelAnimationFrame = $window.cancelAnimationFrame || - $window.webkitCancelAnimationFrame || - $window.mozCancelAnimationFrame || - $window.webkitCancelRequestAnimationFrame; - - var rafSupported = !!requestAnimationFrame; - var raf = rafSupported - ? function(fn) { - var id = requestAnimationFrame(fn); - return function() { - cancelAnimationFrame(id); - }; - } - : function(fn) { - var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 - return function() { - $timeout.cancel(timer); - }; - }; - - raf.supported = rafSupported; - - return raf; - }]; -} - -/** - * DESIGN NOTES - * - * 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 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 beginning (unshift) instead of at the end (push) - * - * Child scopes are created and removed often - * - 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 - * are expensive to construct. - */ - - -/** - * @ngdoc provider - * @name $rootScopeProvider - * @description - * - * Provider for the $rootScope service. - */ - -/** - * @ngdoc method - * @name $rootScopeProvider#digestTtl - * @description - * - * 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. - * - * In complex applications it's possible that the dependencies between `$watch`s will result in - * several digest iterations. However if an application needs more than the default 10 digest - * iterations for its model to stabilize then you should investigate what is causing the model to - * continuously change during the digest. - * - * Increasing the TTL could have performance implications, so you should not change it without - * proper justification. - * - * @param {number} limit The number of digest iterations. - */ - - -/** - * @ngdoc service - * @name $rootScope - * @description - * - * Every application has a single root {@link ng.$rootScope.Scope scope}. - * All other scopes are descendant scopes of the root scope. Scopes provide separation - * between the model and the view, via a mechanism for watching the model for changes. - * They also provide an event emission/broadcast and subscription facility. See the - * {@link guide/scope developer guide on scopes}. - */ -function $RootScopeProvider(){ - var TTL = 10; - var $rootScopeMinErr = minErr('$rootScope'); - var lastDirtyWatch = null; - var applyAsyncId = null; - - this.digestTtl = function(value) { - if (arguments.length) { - TTL = value; - } - return TTL; - }; - - this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', - function( $injector, $exceptionHandler, $parse, $browser) { - - /** - * @ngdoc type - * @name $rootScope.Scope - * - * @description - * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the - * {@link auto.$injector $injector}. Child scopes are created using the - * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when - * compiled HTML template is executed.) - * - * Here is a simple scope snippet to show how you can interact with the scope. - * ```html - * <file src="./test/ng/rootScopeSpec.js" tag="docs1" /> - * ``` - * - * # Inheritance - * A scope can inherit from a parent scope, as in this example: - * ```js - var parent = $rootScope; - var child = parent.$new(); - - parent.salutation = "Hello"; - child.name = "World"; - expect(child.salutation).toEqual('Hello'); - - child.salutation = "Welcome"; - expect(child.salutation).toEqual('Welcome'); - expect(parent.salutation).toEqual('Hello'); - * ``` - * - * - * @param {Object.<string, function()>=} providers Map of service factory which need to be - * provided for the current scope. Defaults to {@link ng}. - * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should - * append/override services provided by `providers`. This is handy - * when unit-testing and having the need to override a default - * service. - * @returns {Object} Newly created scope. - * - */ - function Scope() { - this.$id = nextUid(); - this.$$phase = this.$parent = this.$$watchers = - this.$$nextSibling = this.$$prevSibling = - this.$$childHead = this.$$childTail = null; - this['this'] = this.$root = this; - this.$$destroyed = false; - this.$$asyncQueue = []; - this.$$postDigestQueue = []; - this.$$listeners = {}; - this.$$listenerCount = {}; - this.$$isolateBindings = {}; - this.$$applyAsyncQueue = []; - } - - /** - * @ngdoc property - * @name $rootScope.Scope#$id - * - * @description - * Unique scope ID (monotonically increasing) useful for debugging. - */ - - /** - * @ngdoc property - * @name $rootScope.Scope#$parent - * - * @description - * Reference to the parent scope. - */ - - /** - * @ngdoc property - * @name $rootScope.Scope#$root - * - * @description - * Reference to the root scope. - */ - - Scope.prototype = { - constructor: Scope, - /** - * @ngdoc method - * @name $rootScope.Scope#$new - * @kind function - * - * @description - * Creates a new child {@link ng.$rootScope.Scope scope}. - * - * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. - * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. - * - * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is - * desired for the scope and its child scopes to be permanently detached from the parent and - * thus stop participating in model change detection and listener notification by invoking. - * - * @param {boolean} isolate If true, then the scope does not prototypically inherit from the - * parent scope. The scope is isolated, as it can not see parent scope properties. - * When creating widgets, it is useful for the widget to not accidentally read parent - * state. - * - * @returns {Object} The newly created child scope. - * - */ - $new: function(isolate) { - var child; - - if (isolate) { - child = new Scope(); - child.$root = this.$root; - // ensure that there is just one async queue per $rootScope and its children - child.$$asyncQueue = this.$$asyncQueue; - child.$$postDigestQueue = this.$$postDigestQueue; - } else { - // Only create a child scope class if somebody asks for one, - // but cache it to allow the VM to optimize lookups. - if (!this.$$ChildScope) { - this.$$ChildScope = function ChildScope() { - this.$$watchers = this.$$nextSibling = - this.$$childHead = this.$$childTail = null; - this.$$listeners = {}; - this.$$listenerCount = {}; - this.$id = nextUid(); - this.$$ChildScope = null; - }; - this.$$ChildScope.prototype = this; - } - child = new this.$$ChildScope(); - } - child['this'] = child; - child.$parent = this; - child.$$prevSibling = this.$$childTail; - if (this.$$childHead) { - this.$$childTail.$$nextSibling = child; - this.$$childTail = child; - } else { - this.$$childHead = this.$$childTail = child; - } - return child; - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$watch - * @kind function - * - * @description - * Registers a `listener` callback to be executed whenever the `watchExpression` changes. - * - * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest - * $digest()} and should return the value that will be watched. (Since - * {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the - * `watchExpression` can execute multiple times per - * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) - * - The `listener` is called only when the value from the current `watchExpression` and the - * previous call to `watchExpression` are not equal (with the exception of the initial run, - * see below). Inequality is determined according to reference inequality, - * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) - * via the `!==` Javascript operator, unless `objectEquality == true` - * (see next point) - * - When `objectEquality == true`, inequality of the `watchExpression` is determined - * according to the {@link angular.equals} function. To save the value of the object for - * later comparison, the {@link angular.copy} function is used. This therefore means that - * watching complex objects will have adverse memory and performance implications. - * - The watch `listener` may change the model, which may trigger other `listener`s to fire. - * This is achieved by rerunning the watchers until no changes are detected. The rerun - * iteration limit is 10 to prevent an infinite loop deadlock. - * - * - * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, - * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` - * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a - * change is detected, be prepared for multiple calls to your listener.) - * - * After a watcher is registered with the scope, the `listener` fn is called asynchronously - * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the - * watcher. In rare cases, this is undesirable because the listener is called when the result - * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you - * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the - * listener was called due to initialization. - * - * - * - * # Example - * ```js - // let's assume that scope was dependency injected as the $rootScope - var scope = $rootScope; - scope.name = 'misko'; - scope.counter = 0; - - expect(scope.counter).toEqual(0); - scope.$watch('name', function(newValue, oldValue) { - scope.counter = scope.counter + 1; - }); - expect(scope.counter).toEqual(0); - - scope.$digest(); - // the listener is always called during the first $digest loop after it was registered - expect(scope.counter).toEqual(1); - - scope.$digest(); - // but now it will not be called unless the value changes - expect(scope.counter).toEqual(1); - - scope.name = 'adam'; - scope.$digest(); - expect(scope.counter).toEqual(2); - - - - // Using a function as a watchExpression - var food; - scope.foodCounter = 0; - expect(scope.foodCounter).toEqual(0); - scope.$watch( - // This function returns the value being watched. It is called for each turn of the $digest loop - function() { return food; }, - // This is the change listener, called when the value returned from the above function changes - function(newValue, oldValue) { - if ( newValue !== oldValue ) { - // Only increment the counter if the value changed - scope.foodCounter = scope.foodCounter + 1; - } - } - ); - // No digest has been run so the counter will be zero - expect(scope.foodCounter).toEqual(0); - - // Run the digest but since food has not changed count will still be zero - scope.$digest(); - expect(scope.foodCounter).toEqual(0); - - // Update food and run digest. Now the counter will increment - food = 'cheeseburger'; - scope.$digest(); - expect(scope.foodCounter).toEqual(1); - - * ``` - * - * - * - * @param {(function()|string)} watchExpression Expression that is evaluated on each - * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers - * a call to the `listener`. - * - * - `string`: Evaluated as {@link guide/expression expression} - * - `function(scope)`: called with current `scope` as a parameter. - * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value - * of `watchExpression` changes. - * - * - `newVal` contains the current value of the `watchExpression` - * - `oldVal` contains the previous value of the `watchExpression` - * - `scope` refers to the current scope - * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of - * comparing for reference equality. - * @returns {function()} Returns a deregistration function for this listener. - */ - $watch: function(watchExp, listener, objectEquality) { - var get = $parse(watchExp); - - if (get.$$watchDelegate) { - return get.$$watchDelegate(this, listener, objectEquality, get); - } - var scope = this, - array = scope.$$watchers, - watcher = { - fn: listener, - last: initWatchVal, - get: get, - exp: watchExp, - eq: !!objectEquality - }; - - lastDirtyWatch = null; - - if (!isFunction(listener)) { - watcher.fn = noop; - } - - if (!array) { - array = scope.$$watchers = []; - } - // we use unshift since we use a while loop in $digest for speed. - // the while loop reads in reverse order. - array.unshift(watcher); - - return function deregisterWatch() { - arrayRemove(array, watcher); - lastDirtyWatch = null; - }; - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$watchGroup - * @kind function - * - * @description - * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. - * If any one expression in the collection changes the `listener` is executed. - * - * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every - * call to $digest() to see if any items changes. - * - The `listener` is called whenever any expression in the `watchExpressions` array changes. - * - * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually - * watched using {@link ng.$rootScope.Scope#$watch $watch()} - * - * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any - * expression in `watchExpressions` changes - * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching - * those of `watchExpression` - * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching - * those of `watchExpression` - * The `scope` refers to the current scope. - * @returns {function()} Returns a de-registration function for all listeners. - */ - $watchGroup: function(watchExpressions, listener) { - var oldValues = new Array(watchExpressions.length); - var newValues = new Array(watchExpressions.length); - var deregisterFns = []; - var self = this; - var changeReactionScheduled = false; - var firstRun = true; - - if (!watchExpressions.length) { - // No expressions means we call the listener ASAP - var shouldCall = true; - self.$evalAsync(function () { - if (shouldCall) listener(newValues, newValues, self); - }); - return function deregisterWatchGroup() { - shouldCall = false; - }; - } - - if (watchExpressions.length === 1) { - // Special case size of one - return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { - newValues[0] = value; - oldValues[0] = oldValue; - listener(newValues, (value === oldValue) ? newValues : oldValues, scope); - }); - } - - forEach(watchExpressions, function (expr, i) { - var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { - newValues[i] = value; - oldValues[i] = oldValue; - if (!changeReactionScheduled) { - changeReactionScheduled = true; - self.$evalAsync(watchGroupAction); - } - }); - deregisterFns.push(unwatchFn); - }); - - function watchGroupAction() { - changeReactionScheduled = false; - - if (firstRun) { - firstRun = false; - listener(newValues, newValues, self); - } else { - listener(newValues, oldValues, self); - } - } - - return function deregisterWatchGroup() { - while (deregisterFns.length) { - deregisterFns.shift()(); - } - }; - }, - - - /** - * @ngdoc method - * @name $rootScope.Scope#$watchCollection - * @kind function - * - * @description - * Shallow watches the properties of an object and fires whenever any of the properties change - * (for arrays, this implies watching the array items; for object maps, this implies watching - * the properties). If a change is detected, the `listener` callback is fired. - * - * - The `obj` collection is observed via standard $watch operation and is examined on every - * call to $digest() to see if any items have been added, removed, or moved. - * - The `listener` is called whenever anything within the `obj` has changed. Examples include - * adding, removing, and moving items belonging to an object or array. - * - * - * # Example - * ```js - $scope.names = ['igor', 'matias', 'misko', 'james']; - $scope.dataCount = 4; - - $scope.$watchCollection('names', function(newNames, oldNames) { - $scope.dataCount = newNames.length; - }); - - expect($scope.dataCount).toEqual(4); - $scope.$digest(); - - //still at 4 ... no changes - expect($scope.dataCount).toEqual(4); - - $scope.names.pop(); - $scope.$digest(); - - //now there's been a change - expect($scope.dataCount).toEqual(3); - * ``` - * - * - * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The - * expression value should evaluate to an object or an array which is observed on each - * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the - * collection will trigger a call to the `listener`. - * - * @param {function(newCollection, oldCollection, scope)} listener a callback function called - * when a change is detected. - * - The `newCollection` object is the newly modified data obtained from the `obj` expression - * - The `oldCollection` object is a copy of the former collection data. - * Due to performance considerations, the`oldCollection` value is computed only if the - * `listener` function declares two or more arguments. - * - The `scope` argument refers to the current scope. - * - * @returns {function()} Returns a de-registration function for this listener. When the - * de-registration function is executed, the internal watch operation is terminated. - */ - $watchCollection: function(obj, listener) { - var self = this; - // the current value, updated on each dirty-check run - var newValue; - // a shallow copy of the newValue from the last dirty-check run, - // updated to match newValue during dirty-check run - var oldValue; - // a shallow copy of the newValue from when the last change happened - var veryOldValue; - // only track veryOldValue if the listener is asking for it - var trackVeryOldValue = (listener.length > 1); - var changeDetected = 0; - var changeDetector = $parse(obj, $watchCollectionInterceptor); - var internalArray = []; - var internalObject = {}; - var initRun = true; - var oldLength = 0; - - function $watchCollectionInterceptor(_value) { - newValue = _value; - var newLength, key, bothNaN, newItem, oldItem; - - if (!isObject(newValue)) { // if primitive - if (oldValue !== newValue) { - oldValue = newValue; - changeDetected++; - } - } else if (isArrayLike(newValue)) { - if (oldValue !== internalArray) { - // we are transitioning from something which was not an array into array. - oldValue = internalArray; - oldLength = oldValue.length = 0; - changeDetected++; - } - - newLength = newValue.length; - - if (oldLength !== newLength) { - // if lengths do not match we need to trigger change notification - changeDetected++; - oldValue.length = oldLength = newLength; - } - // copy the items to oldValue and look for changes. - for (var i = 0; i < newLength; i++) { - oldItem = oldValue[i]; - newItem = newValue[i]; - - bothNaN = (oldItem !== oldItem) && (newItem !== newItem); - if (!bothNaN && (oldItem !== newItem)) { - changeDetected++; - oldValue[i] = newItem; - } - } - } else { - if (oldValue !== internalObject) { - // we are transitioning from something which was not an object into object. - oldValue = internalObject = {}; - oldLength = 0; - changeDetected++; - } - // copy the items to oldValue and look for changes. - newLength = 0; - for (key in newValue) { - if (newValue.hasOwnProperty(key)) { - newLength++; - newItem = newValue[key]; - oldItem = oldValue[key]; - - if (key in oldValue) { - bothNaN = (oldItem !== oldItem) && (newItem !== newItem); - if (!bothNaN && (oldItem !== newItem)) { - changeDetected++; - oldValue[key] = newItem; - } - } else { - oldLength++; - oldValue[key] = newItem; - changeDetected++; - } - } - } - if (oldLength > newLength) { - // we used to have more keys, need to find them and destroy them. - changeDetected++; - for(key in oldValue) { - if (!newValue.hasOwnProperty(key)) { - oldLength--; - delete oldValue[key]; - } - } - } - } - return changeDetected; - } - - function $watchCollectionAction() { - if (initRun) { - initRun = false; - listener(newValue, newValue, self); - } else { - listener(newValue, veryOldValue, self); - } - - // make a copy for the next time a collection is changed - if (trackVeryOldValue) { - if (!isObject(newValue)) { - //primitive - veryOldValue = newValue; - } else if (isArrayLike(newValue)) { - veryOldValue = new Array(newValue.length); - for (var i = 0; i < newValue.length; i++) { - veryOldValue[i] = newValue[i]; - } - } else { // if object - veryOldValue = {}; - for (var key in newValue) { - if (hasOwnProperty.call(newValue, key)) { - veryOldValue[key] = newValue[key]; - } - } - } - } - } - - return this.$watch(changeDetector, $watchCollectionAction); - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$digest - * @kind function - * - * @description - * 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 `'Maximum iteration limit exceeded.'` if the number of - * iterations exceeds 10. - * - * Usually, you don't call `$digest()` directly in - * {@link ng.directive:ngController controllers} or in - * {@link ng.$compileProvider#directive directives}. - * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within - * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. - * - * If you want to be notified whenever `$digest()` is called, - * you can register a `watchExpression` function with - * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. - * - * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. - * - * # Example - * ```js - var scope = ...; - scope.name = 'misko'; - scope.counter = 0; - - expect(scope.counter).toEqual(0); - scope.$watch('name', function(newValue, oldValue) { - scope.counter = scope.counter + 1; - }); - expect(scope.counter).toEqual(0); - - scope.$digest(); - // the listener is always called during the first $digest loop after it was registered - expect(scope.counter).toEqual(1); - - scope.$digest(); - // but now it will not be called unless the value changes - expect(scope.counter).toEqual(1); - - scope.name = 'adam'; - scope.$digest(); - expect(scope.counter).toEqual(2); - * ``` - * - */ - $digest: function() { - var watch, value, last, - watchers, - asyncQueue = this.$$asyncQueue, - postDigestQueue = this.$$postDigestQueue, - length, - dirty, ttl = TTL, - next, current, target = this, - watchLog = [], - logIdx, logMsg, asyncTask; - - beginPhase('$digest'); - // Check for changes to browser url that happened in sync before the call to $digest - $browser.$$checkUrlChange(); - - if (this === $rootScope && applyAsyncId !== null) { - // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then - // cancel the scheduled $apply and flush the queue of expressions to be evaluated. - $browser.defer.cancel(applyAsyncId); - flushApplyAsync(); - } - - lastDirtyWatch = null; - - do { // "while dirty" loop - dirty = false; - current = target; - - while(asyncQueue.length) { - try { - asyncTask = asyncQueue.shift(); - asyncTask.scope.$eval(asyncTask.expression); - } catch (e) { - $exceptionHandler(e); - } - lastDirtyWatch = null; - } - - traverseScopesLoop: - do { // "traverse the scopes" loop - if ((watchers = current.$$watchers)) { - // process our watches - length = watchers.length; - while (length--) { - try { - watch = watchers[length]; - // Most common watches are on primitives, in which case we can short - // circuit it with === operator, only when === fails do we use .equals - if (watch) { - if ((value = watch.get(current)) !== (last = watch.last) && - !(watch.eq - ? equals(value, last) - : (typeof value === 'number' && typeof last === 'number' - && isNaN(value) && isNaN(last)))) { - dirty = true; - lastDirtyWatch = watch; - watch.last = watch.eq ? copy(value, null) : value; - watch.fn(value, ((last === initWatchVal) ? value : last), current); - if (ttl < 5) { - logIdx = 4 - ttl; - if (!watchLog[logIdx]) watchLog[logIdx] = []; - logMsg = (isFunction(watch.exp)) - ? 'fn: ' + (watch.exp.name || watch.exp.toString()) - : watch.exp; - logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); - watchLog[logIdx].push(logMsg); - } - } else if (watch === lastDirtyWatch) { - // If the most recently dirty watcher is now clean, short circuit since the remaining watchers - // have already been tested. - dirty = false; - break traverseScopesLoop; - } - } - } catch (e) { - $exceptionHandler(e); - } - } - } - - // Insanity Warning: scope depth-first traversal - // yes, this code is a bit crazy, but it works and we have tests to prove it! - // this piece should be kept in sync with the traversal in $broadcast - if (!(next = (current.$$childHead || - (current !== target && current.$$nextSibling)))) { - while(current !== target && !(next = current.$$nextSibling)) { - current = current.$parent; - } - } - } while ((current = next)); - - // `break traverseScopesLoop;` takes us to here - - if((dirty || asyncQueue.length) && !(ttl--)) { - clearPhase(); - throw $rootScopeMinErr('infdig', - '{0} $digest() iterations reached. Aborting!\n' + - 'Watchers fired in the last 5 iterations: {1}', - TTL, toJson(watchLog)); - } - - } while (dirty || asyncQueue.length); - - clearPhase(); - - while(postDigestQueue.length) { - try { - postDigestQueue.shift()(); - } catch (e) { - $exceptionHandler(e); - } - } - }, - - - /** - * @ngdoc event - * @name $rootScope.Scope#$destroy - * @eventType broadcast on scope being destroyed - * - * @description - * Broadcasted when a scope and its children are being destroyed. - * - * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to - * clean up DOM bindings before an element is removed from the DOM. - */ - - /** - * @ngdoc method - * @name $rootScope.Scope#$destroy - * @kind function - * - * @description - * Removes the current scope (and all of its children) from the parent scope. Removal implies - * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer - * propagate to the current scope and its children. Removal also implies that the current - * scope is eligible for garbage collection. - * - * The `$destroy()` is usually used by directives such as - * {@link ng.directive:ngRepeat ngRepeat} for managing the - * unrolling of the loop. - * - * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. - * Application code can register a `$destroy` event handler that will give it a chance to - * perform any necessary cleanup. - * - * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to - * clean up DOM bindings before an element is removed from the DOM. - */ - $destroy: function() { - // we can't destroy the root scope or a scope that has been already destroyed - if (this.$$destroyed) return; - var parent = this.$parent; - - this.$broadcast('$destroy'); - this.$$destroyed = true; - if (this === $rootScope) return; - - for (var eventName in this.$$listenerCount) { - decrementListenerCount(this, this.$$listenerCount[eventName], eventName); - } - - // sever all the references to parent scopes (after this cleanup, the current scope should - // not be retained by any of our references and should be eligible for garbage collection) - if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; - if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; - if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; - if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; - - - // All of the code below is bogus code that works around V8's memory leak via optimized code - // and inline caches. - // - // see: - // - https://code.google.com/p/v8/issues/detail?id=2073#c26 - // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 - // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 - - this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = - this.$$childTail = this.$root = null; - - // don't reset these to null in case some async task tries to register a listener/watch/task - this.$$listeners = {}; - this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = []; - - // prevent NPEs since these methods have references to properties we nulled out - this.$destroy = this.$digest = this.$apply = noop; - this.$on = this.$watch = this.$watchGroup = function() { return noop; }; - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$eval - * @kind function - * - * @description - * Executes the `expression` on the current scope and returns the result. Any exceptions in - * the expression are propagated (uncaught). This is useful when evaluating Angular - * expressions. - * - * # Example - * ```js - var scope = ng.$rootScope.Scope(); - scope.a = 1; - scope.b = 2; - - expect(scope.$eval('a+b')).toEqual(3); - expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); - * ``` - * - * @param {(string|function())=} expression An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/expression expression}. - * - `function(scope)`: execute the function with the current `scope` parameter. - * - * @param {(object)=} locals Local variables object, useful for overriding values in scope. - * @returns {*} The result of evaluating the expression. - */ - $eval: function(expr, locals) { - return $parse(expr)(this, locals); - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$evalAsync - * @kind function - * - * @description - * Executes the expression on the current scope at a later point in time. - * - * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only - * that: - * - * - it will execute after the function that scheduled the evaluation (preferably before DOM - * rendering). - * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after - * `expression` execution. - * - * Any exceptions from the execution of the expression are forwarded to the - * {@link ng.$exceptionHandler $exceptionHandler} service. - * - * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle - * will be scheduled. However, it is encouraged to always call code that changes the model - * from within an `$apply` call. That includes code evaluated via `$evalAsync`. - * - * @param {(string|function())=} expression An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/expression expression}. - * - `function(scope)`: execute the function with the current `scope` parameter. - * - */ - $evalAsync: function(expr) { - // if we are outside of an $digest loop and this is the first time we are scheduling async - // task also schedule async auto-flush - if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { - $browser.defer(function() { - if ($rootScope.$$asyncQueue.length) { - $rootScope.$digest(); - } - }); - } - - this.$$asyncQueue.push({scope: this, expression: expr}); - }, - - $$postDigest : function(fn) { - this.$$postDigestQueue.push(fn); - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$apply - * @kind function - * - * @description - * `$apply()` is used to execute an expression in angular from outside of the angular - * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). - * Because we are calling into the angular framework we need to perform proper scope life - * cycle of {@link ng.$exceptionHandler exception handling}, - * {@link ng.$rootScope.Scope#$digest executing watches}. - * - * ## Life cycle - * - * # Pseudo-Code of `$apply()` - * ```js - function $apply(expr) { - try { - return $eval(expr); - } catch (e) { - $exceptionHandler(e); - } finally { - $root.$digest(); - } - } - * ``` - * - * - * Scope's `$apply()` method transitions through the following stages: - * - * 1. The {@link guide/expression expression} is executed using the - * {@link ng.$rootScope.Scope#$eval $eval()} method. - * 2. Any exceptions from the execution of the expression are forwarded to the - * {@link ng.$exceptionHandler $exceptionHandler} service. - * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the - * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. - * - * - * @param {(string|function())=} exp An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/expression expression}. - * - `function(scope)`: execute the function with current `scope` parameter. - * - * @returns {*} The result of evaluating the expression. - */ - $apply: function(expr) { - try { - beginPhase('$apply'); - return this.$eval(expr); - } catch (e) { - $exceptionHandler(e); - } finally { - clearPhase(); - try { - $rootScope.$digest(); - } catch (e) { - $exceptionHandler(e); - throw e; - } - } - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$applyAsync - * @kind function - * - * @description - * Schedule the invokation of $apply to occur at a later time. The actual time difference - * varies across browsers, but is typically around ~10 milliseconds. - * - * This can be used to queue up multiple expressions which need to be evaluated in the same - * digest. - * - * @param {(string|function())=} exp An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/expression expression}. - * - `function(scope)`: execute the function with current `scope` parameter. - */ - $applyAsync: function(expr) { - var scope = this; - expr && $rootScope.$$applyAsyncQueue.push($applyAsyncExpression); - scheduleApplyAsync(); - - function $applyAsyncExpression() { - scope.$eval(expr); - } - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$on - * @kind function - * - * @description - * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for - * discussion of event life cycle. - * - * The event listener function format is: `function(event, args...)`. The `event` object - * passed into the listener has the following attributes: - * - * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or - * `$broadcast`-ed. - * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the - * event propagates through the scope hierarchy, this property is set to null. - * - `name` - `{string}`: name of the event. - * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel - * further event propagation (available only for events that were `$emit`-ed). - * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag - * to true. - * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. - * - * @param {string} name Event name to listen on. - * @param {function(event, ...args)} listener Function to call when the event is emitted. - * @returns {function()} Returns a deregistration function for this listener. - */ - $on: function(name, listener) { - var namedListeners = this.$$listeners[name]; - if (!namedListeners) { - this.$$listeners[name] = namedListeners = []; - } - namedListeners.push(listener); - - var current = this; - do { - if (!current.$$listenerCount[name]) { - current.$$listenerCount[name] = 0; - } - current.$$listenerCount[name]++; - } while ((current = current.$parent)); - - var self = this; - return function() { - namedListeners[namedListeners.indexOf(listener)] = null; - decrementListenerCount(self, 1, name); - }; - }, - - - /** - * @ngdoc method - * @name $rootScope.Scope#$emit - * @kind function - * - * @description - * Dispatches an event `name` upwards through the scope hierarchy notifying the - * registered {@link ng.$rootScope.Scope#$on} listeners. - * - * The event life cycle starts at the scope on which `$emit` was called. All - * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get - * notified. 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 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. - * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. - * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). - */ - $emit: function(name, args) { - var empty = [], - namedListeners, - scope = this, - stopPropagation = false, - event = { - name: name, - targetScope: scope, - stopPropagation: function() {stopPropagation = true;}, - preventDefault: function() { - event.defaultPrevented = true; - }, - defaultPrevented: false - }, - listenerArgs = concat([event], arguments, 1), - i, length; - - do { - namedListeners = scope.$$listeners[name] || empty; - event.currentScope = scope; - for (i=0, length=namedListeners.length; i<length; i++) { - - // if listeners were deregistered, defragment the array - if (!namedListeners[i]) { - namedListeners.splice(i, 1); - i--; - length--; - continue; - } - try { - //allow all listeners attached to the current scope to run - namedListeners[i].apply(null, listenerArgs); - } catch (e) { - $exceptionHandler(e); - } - } - //if any listener on the current scope stops propagation, prevent bubbling - if (stopPropagation) { - event.currentScope = null; - return event; - } - //traverse upwards - scope = scope.$parent; - } while (scope); - - event.currentScope = null; - - return event; - }, - - - /** - * @ngdoc method - * @name $rootScope.Scope#$broadcast - * @kind function - * - * @description - * Dispatches an event `name` downwards to all child scopes (and their children) notifying the - * registered {@link ng.$rootScope.Scope#$on} listeners. - * - * The event life cycle starts at the scope on which `$broadcast` was called. All - * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get - * notified. Afterwards, the event propagates to all direct and indirect scopes of the current - * scope and calls all registered listeners along the way. The event cannot be canceled. - * - * 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 broadcast. - * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. - * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} - */ - $broadcast: function(name, args) { - var target = this, - current = target, - next = target, - event = { - name: name, - targetScope: target, - preventDefault: function() { - event.defaultPrevented = true; - }, - defaultPrevented: false - }; - - if (!target.$$listenerCount[name]) return event; - - var listenerArgs = concat([event], arguments, 1), - listeners, i, length; - - //down while you can, then up and next sibling or up and next sibling until back at root - while ((current = next)) { - event.currentScope = current; - listeners = current.$$listeners[name] || []; - for (i=0, length = listeners.length; i<length; i++) { - // if listeners were deregistered, defragment the array - if (!listeners[i]) { - listeners.splice(i, 1); - i--; - length--; - continue; - } - - try { - listeners[i].apply(null, listenerArgs); - } catch(e) { - $exceptionHandler(e); - } - } - - // Insanity Warning: scope depth-first traversal - // yes, this code is a bit crazy, but it works and we have tests to prove it! - // this piece should be kept in sync with the traversal in $digest - // (though it differs due to having the extra check for $$listenerCount) - if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || - (current !== target && current.$$nextSibling)))) { - while(current !== target && !(next = current.$$nextSibling)) { - current = current.$parent; - } - } - } - - event.currentScope = null; - return event; - } - }; - - var $rootScope = new Scope(); - - return $rootScope; - - - function beginPhase(phase) { - if ($rootScope.$$phase) { - throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); - } - - $rootScope.$$phase = phase; - } - - function clearPhase() { - $rootScope.$$phase = null; - } - - - function decrementListenerCount(current, count, name) { - do { - current.$$listenerCount[name] -= count; - - if (current.$$listenerCount[name] === 0) { - delete current.$$listenerCount[name]; - } - } while ((current = current.$parent)); - } - - /** - * function used as an initial value for watchers. - * because it's unique we can easily tell it apart from other values - */ - function initWatchVal() {} - - function flushApplyAsync() { - var queue = $rootScope.$$applyAsyncQueue; - while (queue.length) { - try { - queue.shift()(); - } catch(e) { - $exceptionHandler(e); - } - } - applyAsyncId = null; - } - - function scheduleApplyAsync() { - if (applyAsyncId === null) { - applyAsyncId = $browser.defer(function() { - $rootScope.$apply(flushApplyAsync); - }); - } - } - }]; -} - -/** - * @description - * Private service to sanitize uris for links and images. Used by $compile and $sanitize. - */ -function $$SanitizeUriProvider() { - var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, - imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; - - /** - * @description - * Retrieves or overrides the default regular expression that is used for whitelisting of safe - * urls during a[href] sanitization. - * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. - * - * Any url about to be assigned to a[href] via data-binding is first normalized and turned into - * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` - * regular expression. If a match is found, the original url is written into the dom. Otherwise, - * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. - * - * @param {RegExp=} regexp New regexp to whitelist urls with. - * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for - * chaining otherwise. - */ - this.aHrefSanitizationWhitelist = function(regexp) { - if (isDefined(regexp)) { - aHrefSanitizationWhitelist = regexp; - return this; - } - return aHrefSanitizationWhitelist; - }; - - - /** - * @description - * Retrieves or overrides the default regular expression that is used for whitelisting of safe - * urls during img[src] sanitization. - * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. - * - * Any url about to be assigned to img[src] via data-binding is first normalized and turned into - * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` - * regular expression. If a match is found, the original url is written into the dom. Otherwise, - * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. - * - * @param {RegExp=} regexp New regexp to whitelist urls with. - * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for - * chaining otherwise. - */ - this.imgSrcSanitizationWhitelist = function(regexp) { - if (isDefined(regexp)) { - imgSrcSanitizationWhitelist = regexp; - return this; - } - return imgSrcSanitizationWhitelist; - }; - - this.$get = function() { - return function sanitizeUri(uri, isImage) { - var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; - var normalizedVal; - // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case. - if (!msie || msie >= 8 ) { - normalizedVal = urlResolve(uri).href; - if (normalizedVal !== '' && !normalizedVal.match(regex)) { - return 'unsafe:'+normalizedVal; - } - } - return uri; - }; - }; -} - -var $sceMinErr = minErr('$sce'); - -var SCE_CONTEXTS = { - HTML: 'html', - CSS: 'css', - URL: 'url', - // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a - // url. (e.g. ng-include, script src, templateUrl) - RESOURCE_URL: 'resourceUrl', - JS: 'js' -}; - -// Helper functions follow. - -// Copied from: -// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962 -// Prereq: s is a string. -function escapeForRegexp(s) { - return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). - replace(/\x08/g, '\\x08'); -} - - -function adjustMatcher(matcher) { - if (matcher === 'self') { - return matcher; - } else if (isString(matcher)) { - // Strings match exactly except for 2 wildcards - '*' and '**'. - // '*' matches any character except those from the set ':/.?&'. - // '**' matches any character (like .* in a RegExp). - // More than 2 *'s raises an error as it's ill defined. - if (matcher.indexOf('***') > -1) { - throw $sceMinErr('iwcard', - 'Illegal sequence *** in string matcher. String: {0}', matcher); - } - matcher = escapeForRegexp(matcher). - replace('\\*\\*', '.*'). - replace('\\*', '[^:/.?&;]*'); - return new RegExp('^' + matcher + '$'); - } else if (isRegExp(matcher)) { - // The only other type of matcher allowed is a Regexp. - // Match entire URL / disallow partial matches. - // Flags are reset (i.e. no global, ignoreCase or multiline) - return new RegExp('^' + matcher.source + '$'); - } else { - throw $sceMinErr('imatcher', - 'Matchers may only be "self", string patterns or RegExp objects'); - } -} - - -function adjustMatchers(matchers) { - var adjustedMatchers = []; - if (isDefined(matchers)) { - forEach(matchers, function(matcher) { - adjustedMatchers.push(adjustMatcher(matcher)); - }); - } - return adjustedMatchers; -} - - -/** - * @ngdoc service - * @name $sceDelegate - * @kind function - * - * @description - * - * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict - * Contextual Escaping (SCE)} services to AngularJS. - * - * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of - * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is - * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to - * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things - * work because `$sce` delegates to `$sceDelegate` for these operations. - * - * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. - * - * The default instance of `$sceDelegate` should work out of the box with little pain. While you - * can override it completely to change the behavior of `$sce`, the common case would - * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting - * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as - * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist - * $sceDelegateProvider.resourceUrlWhitelist} and {@link - * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} - */ - -/** - * @ngdoc provider - * @name $sceDelegateProvider - * @description - * - * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate - * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure - * that the URLs used for sourcing Angular templates are safe. Refer {@link - * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and - * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} - * - * For the general details about this service in Angular, read the main page for {@link ng.$sce - * Strict Contextual Escaping (SCE)}. - * - * **Example**: Consider the following case. <a name="example"></a> - * - * - your app is hosted at url `http://myapp.example.com/` - * - but some of your templates are hosted on other domains you control such as - * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. - * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. - * - * Here is what a secure configuration for this scenario might look like: - * - * ``` - * angular.module('myApp', []).config(function($sceDelegateProvider) { - * $sceDelegateProvider.resourceUrlWhitelist([ - * // Allow same origin resource loads. - * 'self', - * // Allow loading from our assets domain. Notice the difference between * and **. - * 'http://srv*.assets.example.com/**' - * ]); - * - * // The blacklist overrides the whitelist so the open redirect here is blocked. - * $sceDelegateProvider.resourceUrlBlacklist([ - * 'http://myapp.example.com/clickThru**' - * ]); - * }); - * ``` - */ - -function $SceDelegateProvider() { - this.SCE_CONTEXTS = SCE_CONTEXTS; - - // Resource URLs can also be trusted by policy. - var resourceUrlWhitelist = ['self'], - resourceUrlBlacklist = []; - - /** - * @ngdoc method - * @name $sceDelegateProvider#resourceUrlWhitelist - * @kind function - * - * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value - * provided. This must be an array or null. A snapshot of this array is used so further - * changes to the array are ignored. - * - * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items - * allowed in this array. - * - * Note: **an empty whitelist array will block all URLs**! - * - * @return {Array} the currently set whitelist array. - * - * The **default value** when no whitelist has been explicitly set is `['self']` allowing only - * same origin resource requests. - * - * @description - * Sets/Gets the whitelist of trusted resource URLs. - */ - this.resourceUrlWhitelist = function (value) { - if (arguments.length) { - resourceUrlWhitelist = adjustMatchers(value); - } - return resourceUrlWhitelist; - }; - - /** - * @ngdoc method - * @name $sceDelegateProvider#resourceUrlBlacklist - * @kind function - * - * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value - * provided. This must be an array or null. A snapshot of this array is used so further - * changes to the array are ignored. - * - * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items - * allowed in this array. - * - * The typical usage for the blacklist is to **block - * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as - * these would otherwise be trusted but actually return content from the redirected domain. - * - * Finally, **the blacklist overrides the whitelist** and has the final say. - * - * @return {Array} the currently set blacklist array. - * - * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there - * is no blacklist.) - * - * @description - * Sets/Gets the blacklist of trusted resource URLs. - */ - - this.resourceUrlBlacklist = function (value) { - if (arguments.length) { - resourceUrlBlacklist = adjustMatchers(value); - } - return resourceUrlBlacklist; - }; - - this.$get = ['$injector', function($injector) { - - var htmlSanitizer = function htmlSanitizer(html) { - throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); - }; - - if ($injector.has('$sanitize')) { - htmlSanitizer = $injector.get('$sanitize'); - } - - - function matchUrl(matcher, parsedUrl) { - if (matcher === 'self') { - return urlIsSameOrigin(parsedUrl); - } else { - // definitely a regex. See adjustMatchers() - return !!matcher.exec(parsedUrl.href); - } - } - - function isResourceUrlAllowedByPolicy(url) { - var parsedUrl = urlResolve(url.toString()); - var i, n, allowed = false; - // Ensure that at least one item from the whitelist allows this url. - for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { - if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { - allowed = true; - break; - } - } - if (allowed) { - // Ensure that no item from the blacklist blocked this url. - for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { - if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { - allowed = false; - break; - } - } - } - return allowed; - } - - function generateHolderType(Base) { - var holderType = function TrustedValueHolderType(trustedValue) { - this.$$unwrapTrustedValue = function() { - return trustedValue; - }; - }; - if (Base) { - holderType.prototype = new Base(); - } - holderType.prototype.valueOf = function sceValueOf() { - return this.$$unwrapTrustedValue(); - }; - holderType.prototype.toString = function sceToString() { - return this.$$unwrapTrustedValue().toString(); - }; - return holderType; - } - - var trustedValueHolderBase = generateHolderType(), - byType = {}; - - byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); - byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); - byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); - byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); - byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); - - /** - * @ngdoc method - * @name $sceDelegate#trustAs - * - * @description - * Returns an object that is trusted by angular for use in specified strict - * contextual escaping contexts (such as ng-bind-html, ng-include, any src - * attribute interpolation, any dom event binding attribute interpolation - * such as for onclick, etc.) that uses the provided value. - * See {@link ng.$sce $sce} for enabling strict contextual escaping. - * - * @param {string} type The kind of context in which this value is safe for use. e.g. url, - * resourceUrl, html, js and css. - * @param {*} value The value that that should be considered trusted/safe. - * @returns {*} A value that can be used to stand in for the provided `value` in places - * where Angular expects a $sce.trustAs() return value. - */ - function trustAs(type, trustedValue) { - var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); - if (!Constructor) { - throw $sceMinErr('icontext', - 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', - type, trustedValue); - } - if (trustedValue === null || trustedValue === undefined || trustedValue === '') { - return trustedValue; - } - // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting - // mutable objects, we ensure here that the value passed in is actually a string. - if (typeof trustedValue !== 'string') { - throw $sceMinErr('itype', - 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', - type); - } - return new Constructor(trustedValue); - } - - /** - * @ngdoc method - * @name $sceDelegate#valueOf - * - * @description - * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link - * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. - * - * If the passed parameter is not a value that had been returned by {@link - * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. - * - * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} - * call or anything else. - * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns - * `value` unchanged. - */ - function valueOf(maybeTrusted) { - if (maybeTrusted instanceof trustedValueHolderBase) { - return maybeTrusted.$$unwrapTrustedValue(); - } else { - return maybeTrusted; - } - } - - /** - * @ngdoc method - * @name $sceDelegate#getTrusted - * - * @description - * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and - * returns the originally supplied value if the queried context type is a supertype of the - * created type. If this condition isn't satisfied, throws an exception. - * - * @param {string} type The kind of context in which this value is to be used. - * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`} call. - * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. - */ - function getTrusted(type, maybeTrusted) { - if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { - return maybeTrusted; - } - var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); - if (constructor && maybeTrusted instanceof constructor) { - return maybeTrusted.$$unwrapTrustedValue(); - } - // If we get here, then we may only take one of two actions. - // 1. sanitize the value for the requested type, or - // 2. throw an exception. - if (type === SCE_CONTEXTS.RESOURCE_URL) { - if (isResourceUrlAllowedByPolicy(maybeTrusted)) { - return maybeTrusted; - } else { - throw $sceMinErr('insecurl', - 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', - maybeTrusted.toString()); - } - } else if (type === SCE_CONTEXTS.HTML) { - return htmlSanitizer(maybeTrusted); - } - throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); - } - - return { trustAs: trustAs, - getTrusted: getTrusted, - valueOf: valueOf }; - }]; -} - - -/** - * @ngdoc provider - * @name $sceProvider - * @description - * - * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. - * - enable/disable Strict Contextual Escaping (SCE) in a module - * - override the default implementation with a custom delegate - * - * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. - */ - -/* jshint maxlen: false*/ - -/** - * @ngdoc service - * @name $sce - * @kind function - * - * @description - * - * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. - * - * # Strict Contextual Escaping - * - * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain - * contexts to result in a value that is marked as safe to use for that context. One example of - * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer - * to these contexts as privileged or SCE contexts. - * - * As of version 1.2, Angular ships with SCE enabled by default. - * - * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows - * one to execute arbitrary javascript by the use of the expression() syntax. Refer - * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them. - * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` - * to the top of your HTML document. - * - * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for - * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. - * - * Here's an example of a binding in a privileged context: - * - * ``` - * <input ng-model="userHtml"> - * <div ng-bind-html="userHtml"></div> - * ``` - * - * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE - * disabled, this application allows the user to render arbitrary HTML into the DIV. - * In a more realistic example, one may be rendering user comments, blog articles, etc. via - * bindings. (HTML is just one example of a context where rendering user controlled input creates - * security vulnerabilities.) - * - * For the case of HTML, you might use a library, either on the client side, or on the server side, - * to sanitize unsafe HTML before binding to the value and rendering it in the document. - * - * How would you ensure that every place that used these types of bindings was bound to a value that - * was sanitized by your library (or returned as safe for rendering by your server?) How can you - * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some - * properties/fields and forgot to update the binding to the sanitized value? - * - * To be secure by default, you want to ensure that any such bindings are disallowed unless you can - * determine that something explicitly says it's safe to use a value for binding in that - * context. You can then audit your code (a simple grep would do) to ensure that this is only done - * for those values that you can easily tell are safe - because they were received from your server, - * sanitized by your library, etc. You can organize your codebase to help with this - perhaps - * allowing only the files in a specific directory to do this. Ensuring that the internal API - * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. - * - * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} - * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to - * obtain values that will be accepted by SCE / privileged contexts. - * - * - * ## How does it work? - * - * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted - * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link - * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the - * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. - * - * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link - * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly - * simplified): - * - * ``` - * var ngBindHtmlDirective = ['$sce', function($sce) { - * return function(scope, element, attr) { - * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { - * element.html(value || ''); - * }); - * }; - * }]; - * ``` - * - * ## Impact on loading templates - * - * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as - * `templateUrl`'s specified by {@link guide/directive directives}. - * - * By default, Angular only loads templates from the same domain and protocol as the application - * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl - * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or - * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist - * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. - * - * *Please note*: - * The browser's - * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) - * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) - * policy apply in addition to this and may further restrict whether the template is successfully - * loaded. This means that without the right CORS policy, loading templates from a different domain - * won't work on all browsers. Also, loading templates from `file://` URL does not work on some - * browsers. - * - * ## This feels like too much overhead - * - * It's important to remember that SCE only applies to interpolation expressions. - * - * If your expressions are constant literals, they're automatically trusted and you don't need to - * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. - * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. - * - * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them - * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. - * - * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load - * templates in `ng-include` from your application's domain without having to even know about SCE. - * It blocks loading templates from other domains or loading templates over http from an https - * served document. You can change these by setting your own custom {@link - * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link - * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. - * - * This significantly reduces the overhead. It is far easier to pay the small overhead and have an - * application that's secure and can be audited to verify that with much more ease than bolting - * security onto an application later. - * - * <a name="contexts"></a> - * ## What trusted context types are supported? - * - * | Context | Notes | - * |---------------------|----------------| - * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. | - * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | - * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=` and `<img src=` sanitize their urls and don't constitute an SCE context. | - * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | - * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | - * - * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> - * - * Each element in these arrays must be one of the following: - * - * - **'self'** - * - The special **string**, `'self'`, can be used to match against all URLs of the **same - * domain** as the application document using the **same protocol**. - * - **String** (except the special value `'self'`) - * - The string is matched against the full *normalized / absolute URL* of the resource - * being tested (substring matches are not good enough.) - * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters - * match themselves. - * - `*`: matches zero or more occurrences of any character other than one of the following 6 - * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use - * in a whitelist. - * - `**`: matches zero or more occurrences of *any* character. As such, it's not - * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. - * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might - * not have been the intention.) Its usage at the very end of the path is ok. (e.g. - * http://foo.example.com/templates/**). - * - **RegExp** (*see caveat below*) - * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax - * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to - * accidentally introduce a bug when one updates a complex expression (imho, all regexes should - * have good test coverage.). For instance, the use of `.` in the regex is correct only in a - * small number of cases. A `.` character in the regex used when matching the scheme or a - * subdomain could be matched against a `:` or literal `.` that was likely not intended. It - * is highly recommended to use the string patterns and only fall back to regular expressions - * if they as a last resort. - * - The regular expression must be an instance of RegExp (i.e. not a string.) It is - * matched against the **entire** *normalized / absolute URL* of the resource being tested - * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags - * present on the RegExp (such as multiline, global, ignoreCase) are ignored. - * - If you are generating your JavaScript from some other templating engine (not - * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), - * remember to escape your regular expression (and be aware that you might need more than - * one level of escaping depending on your templating engine and the way you interpolated - * the value.) Do make use of your platform's escaping mechanism as it might be good - * enough before coding your own. e.g. Ruby has - * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) - * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). - * Javascript lacks a similar built in function for escaping. Take a look at Google - * Closure library's [goog.string.regExpEscape(s)]( - * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). - * - * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. - * - * ## Show me an example using SCE. - * - * <example module="mySceApp" deps="angular-sanitize.js"> - * <file name="index.html"> - * <div ng-controller="AppController as myCtrl"> - * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> - * <b>User comments</b><br> - * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when - * $sanitize is available. If $sanitize isn't available, this results in an error instead of an - * exploit. - * <div class="well"> - * <div ng-repeat="userComment in myCtrl.userComments"> - * <b>{{userComment.name}}</b>: - * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> - * <br> - * </div> - * </div> - * </div> - * </file> - * - * <file name="script.js"> - * angular.module('mySceApp', ['ngSanitize']) - * .controller('AppController', ['$http', '$templateCache', '$sce', - * function($http, $templateCache, $sce) { - * var self = this; - * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { - * self.userComments = userComments; - * }); - * self.explicitlyTrustedHtml = $sce.trustAsHtml( - * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + - * 'sanitization."">Hover over this text.</span>'); - * }]); - * </file> - * - * <file name="test_data.json"> - * [ - * { "name": "Alice", - * "htmlComment": - * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" - * }, - * { "name": "Bob", - * "htmlComment": "<i>Yes!</i> Am I the only other one?" - * } - * ] - * </file> - * - * <file name="protractor.js" type="protractor"> - * describe('SCE doc demo', function() { - * it('should sanitize untrusted values', function() { - * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) - * .toBe('<span>Is <i>anyone</i> reading this?</span>'); - * }); - * - * it('should NOT sanitize explicitly trusted values', function() { - * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( - * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + - * 'sanitization."">Hover over this text.</span>'); - * }); - * }); - * </file> - * </example> - * - * - * - * ## Can I disable SCE completely? - * - * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits - * for little coding overhead. It will be much harder to take an SCE disabled application and - * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE - * for cases where you have a lot of existing code that was written before SCE was introduced and - * you're migrating them a module at a time. - * - * That said, here's how you can completely disable SCE: - * - * ``` - * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { - * // Completely disable SCE. For demonstration purposes only! - * // Do not use in new projects. - * $sceProvider.enabled(false); - * }); - * ``` - * - */ -/* jshint maxlen: 100 */ - -function $SceProvider() { - var enabled = true; - - /** - * @ngdoc method - * @name $sceProvider#enabled - * @kind function - * - * @param {boolean=} value If provided, then enables/disables SCE. - * @return {boolean} true if SCE is enabled, false otherwise. - * - * @description - * Enables/disables SCE and returns the current value. - */ - this.enabled = function (value) { - if (arguments.length) { - enabled = !!value; - } - return enabled; - }; - - - /* Design notes on the default implementation for SCE. - * - * The API contract for the SCE delegate - * ------------------------------------- - * The SCE delegate object must provide the following 3 methods: - * - * - trustAs(contextEnum, value) - * This method is used to tell the SCE service that the provided value is OK to use in the - * contexts specified by contextEnum. It must return an object that will be accepted by - * getTrusted() for a compatible contextEnum and return this value. - * - * - valueOf(value) - * For values that were not produced by trustAs(), return them as is. For values that were - * produced by trustAs(), return the corresponding input value to trustAs. Basically, if - * trustAs is wrapping the given values into some type, this operation unwraps it when given - * such a value. - * - * - getTrusted(contextEnum, value) - * This function should return the a value that is safe to use in the context specified by - * contextEnum or throw and exception otherwise. - * - * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be - * opaque or wrapped in some holder object. That happens to be an implementation detail. For - * instance, an implementation could maintain a registry of all trusted objects by context. In - * such a case, trustAs() would return the same object that was passed in. getTrusted() would - * return the same object passed in if it was found in the registry under a compatible context or - * throw an exception otherwise. An implementation might only wrap values some of the time based - * on some criteria. getTrusted() might return a value and not throw an exception for special - * constants or objects even if not wrapped. All such implementations fulfill this contract. - * - * - * A note on the inheritance model for SCE contexts - * ------------------------------------------------ - * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This - * is purely an implementation details. - * - * The contract is simply this: - * - * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) - * will also succeed. - * - * Inheritance happens to capture this in a natural way. In some future, we - * may not use inheritance anymore. That is OK because no code outside of - * sce.js and sceSpecs.js would need to be aware of this detail. - */ - - this.$get = ['$parse', '$sniffer', '$sceDelegate', function( - $parse, $sniffer, $sceDelegate) { - // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows - // the "expression(javascript expression)" syntax which is insecure. - if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { - throw $sceMinErr('iequirks', - 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + - 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + - 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); - } - - var sce = shallowCopy(SCE_CONTEXTS); - - /** - * @ngdoc method - * @name $sce#isEnabled - * @kind function - * - * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you - * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. - * - * @description - * Returns a boolean indicating if SCE is enabled. - */ - sce.isEnabled = function () { - return enabled; - }; - sce.trustAs = $sceDelegate.trustAs; - sce.getTrusted = $sceDelegate.getTrusted; - sce.valueOf = $sceDelegate.valueOf; - - if (!enabled) { - sce.trustAs = sce.getTrusted = function(type, value) { return value; }; - sce.valueOf = identity; - } - - /** - * @ngdoc method - * @name $sce#parseAs - * - * @description - * Converts Angular {@link guide/expression expression} into a function. This is like {@link - * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it - * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, - * *result*)} - * - * @param {string} type The kind of SCE context in which this result will be used. - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - sce.parseAs = function sceParseAs(type, expr) { - var parsed = $parse(expr); - if (parsed.literal && parsed.constant) { - return parsed; - } else { - return $parse(expr, function (value) { - return sce.getTrusted(type, value); - }); - } - }; - - /** - * @ngdoc method - * @name $sce#trustAs - * - * @description - * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, - * returns an object that is trusted by angular for use in specified strict contextual - * escaping contexts (such as ng-bind-html, ng-include, any src attribute - * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) - * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual - * escaping. - * - * @param {string} type The kind of context in which this value is safe for use. e.g. url, - * resource_url, html, js and css. - * @param {*} value The value that that should be considered trusted/safe. - * @returns {*} A value that can be used to stand in for the provided `value` in places - * where Angular expects a $sce.trustAs() return value. - */ - - /** - * @ngdoc method - * @name $sce#trustAsHtml - * - * @description - * Shorthand method. `$sce.trustAsHtml(value)` → - * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} - * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml - * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) - */ - - /** - * @ngdoc method - * @name $sce#trustAsUrl - * - * @description - * Shorthand method. `$sce.trustAsUrl(value)` → - * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} - * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl - * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) - */ - - /** - * @ngdoc method - * @name $sce#trustAsResourceUrl - * - * @description - * Shorthand method. `$sce.trustAsResourceUrl(value)` → - * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} - * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl - * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the return - * value of {@link ng.$sce#trustAs $sce.trustAs}.) - */ - - /** - * @ngdoc method - * @name $sce#trustAsJs - * - * @description - * Shorthand method. `$sce.trustAsJs(value)` → - * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} - * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs - * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) - */ - - /** - * @ngdoc method - * @name $sce#getTrusted - * - * @description - * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, - * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the - * originally supplied value if the queried context type is a supertype of the created type. - * If this condition isn't satisfied, throws an exception. - * - * @param {string} type The kind of context in which this value is to be used. - * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} - * call. - * @returns {*} The value the was originally provided to - * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. - * Otherwise, throws an exception. - */ - - /** - * @ngdoc method - * @name $sce#getTrustedHtml - * - * @description - * Shorthand method. `$sce.getTrustedHtml(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} - * - * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` - */ - - /** - * @ngdoc method - * @name $sce#getTrustedCss - * - * @description - * Shorthand method. `$sce.getTrustedCss(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} - * - * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` - */ - - /** - * @ngdoc method - * @name $sce#getTrustedUrl - * - * @description - * Shorthand method. `$sce.getTrustedUrl(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} - * - * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` - */ - - /** - * @ngdoc method - * @name $sce#getTrustedResourceUrl - * - * @description - * Shorthand method. `$sce.getTrustedResourceUrl(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} - * - * @param {*} value The value to pass to `$sceDelegate.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` - */ - - /** - * @ngdoc method - * @name $sce#getTrustedJs - * - * @description - * Shorthand method. `$sce.getTrustedJs(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} - * - * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` - */ - - /** - * @ngdoc method - * @name $sce#parseAsHtml - * - * @description - * Shorthand method. `$sce.parseAsHtml(expression string)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - /** - * @ngdoc method - * @name $sce#parseAsCss - * - * @description - * Shorthand method. `$sce.parseAsCss(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - /** - * @ngdoc method - * @name $sce#parseAsUrl - * - * @description - * Shorthand method. `$sce.parseAsUrl(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - /** - * @ngdoc method - * @name $sce#parseAsResourceUrl - * - * @description - * Shorthand method. `$sce.parseAsResourceUrl(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - /** - * @ngdoc method - * @name $sce#parseAsJs - * - * @description - * Shorthand method. `$sce.parseAsJs(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - // Shorthand delegations. - var parse = sce.parseAs, - getTrusted = sce.getTrusted, - trustAs = sce.trustAs; - - forEach(SCE_CONTEXTS, function (enumValue, name) { - var lName = lowercase(name); - sce[camelCase("parse_as_" + lName)] = function (expr) { - return parse(enumValue, expr); - }; - sce[camelCase("get_trusted_" + lName)] = function (value) { - return getTrusted(enumValue, value); - }; - sce[camelCase("trust_as_" + lName)] = function (value) { - return trustAs(enumValue, value); - }; - }); - - return sce; - }]; -} - -/** - * !!! This is an undocumented "private" service !!! - * - * @name $sniffer - * @requires $window - * @requires $document - * - * @property {boolean} history Does the browser support html5 history api ? - * @property {boolean} hashchange Does the browser support hashchange event ? - * @property {boolean} transitions Does the browser support CSS transition events ? - * @property {boolean} animations Does the browser support CSS animation events ? - * - * @description - * This is very simple implementation of testing browser's features. - */ -function $SnifferProvider() { - this.$get = ['$window', '$document', function($window, $document) { - var eventSupport = {}, - android = - int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), - boxee = /Boxee/i.test(($window.navigator || {}).userAgent), - document = $document[0] || {}, - documentMode = document.documentMode, - vendorPrefix, - vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, - bodyStyle = document.body && document.body.style, - transitions = false, - animations = false, - match; - - if (bodyStyle) { - for(var prop in bodyStyle) { - if(match = vendorRegex.exec(prop)) { - vendorPrefix = match[0]; - vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); - break; - } - } - - if(!vendorPrefix) { - vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; - } - - transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); - animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); - - if (android && (!transitions||!animations)) { - transitions = isString(document.body.style.webkitTransition); - animations = isString(document.body.style.webkitAnimation); - } - } - - - return { - // Android has history.pushState, but it does not update location correctly - // so let's not use the history API at all. - // http://code.google.com/p/android/issues/detail?id=17471 - // https://github.com/angular/angular.js/issues/904 - - // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has - // so let's not use the history API also - // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined - // jshint -W018 - history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), - // jshint +W018 - hashchange: 'onhashchange' in $window && - // IE8 compatible mode lies - (!documentMode || documentMode > 7), - hasEvent: function(event) { - // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have - // it. In particular the event is not fired when backspace or delete key are pressed or - // when cut operation is performed. - if (event == 'input' && msie == 9) return false; - - if (isUndefined(eventSupport[event])) { - var divElm = document.createElement('div'); - eventSupport[event] = 'on' + event in divElm; - } - - return eventSupport[event]; - }, - csp: csp(), - vendorPrefix: vendorPrefix, - transitions : transitions, - animations : animations, - android: android, - msie : msie, - msieDocumentMode: documentMode - }; - }]; -} - -var $compileMinErr = minErr('$compile'); - -/** - * @ngdoc service - * @name $templateRequest - * - * @description - * The `$templateRequest` service downloads the provided template using `$http` and, upon success, - * stores the contents inside of `$templateCache`. If the HTTP request fails or the response data - * of the HTTP request is empty then a `$compile` error will be thrown (the exception can be thwarted - * by setting the 2nd parameter of the function to true). - * - * @param {string} tpl The HTTP request template URL - * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty - * - * @return {Promise} the HTTP Promise for the given. - * - * @property {number} totalPendingRequests total amount of pending template requests being downloaded. - */ -function $TemplateRequestProvider() { - this.$get = ['$templateCache', '$http', '$q', function($templateCache, $http, $q) { - function handleRequestFn(tpl, ignoreRequestError) { - var self = handleRequestFn; - self.totalPendingRequests++; - - return $http.get(tpl, { cache : $templateCache }) - .then(function(response) { - var html = response.data; - if(!html || html.length === 0) { - return handleError(); - } - - self.totalPendingRequests--; - $templateCache.put(tpl, html); - return html; - }, handleError); - - function handleError() { - self.totalPendingRequests--; - if (!ignoreRequestError) { - throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl); - } - return $q.reject(); - } - } - - handleRequestFn.totalPendingRequests = 0; - - return handleRequestFn; - }]; -} - -function $$TestabilityProvider() { - this.$get = ['$rootScope', '$browser', '$location', - function($rootScope, $browser, $location) { - - /** - * @name $testability - * - * @description - * The private $$testability service provides a collection of methods for use when debugging - * or by automated test and debugging tools. - */ - var testability = {}; - - /** - * @name $$testability#findBindings - * - * @description - * Returns an array of elements that are bound (via ng-bind or {{}}) - * to expressions matching the input. - * - * @param {Element} element The element root to search from. - * @param {string} expression The binding expression to match. - * @param {boolean} opt_exactMatch If true, only returns exact matches - * for the expression. Filters and whitespace are ignored. - */ - testability.findBindings = function(element, expression, opt_exactMatch) { - var bindings = element.getElementsByClassName('ng-binding'); - var matches = []; - forEach(bindings, function(binding) { - var dataBinding = angular.element(binding).data('$binding'); - if (dataBinding) { - forEach(dataBinding, function(bindingName) { - if (opt_exactMatch) { - var matcher = new RegExp('(^|\\s)' + expression + '(\\s|\\||$)'); - if (matcher.test(bindingName)) { - matches.push(binding); - } - } else { - if (bindingName.indexOf(expression) != -1) { - matches.push(binding); - } - } - }); - } - }); - return matches; - }; - - /** - * @name $$testability#findModels - * - * @description - * Returns an array of elements that are two-way found via ng-model to - * expressions matching the input. - * - * @param {Element} element The element root to search from. - * @param {string} expression The model expression to match. - * @param {boolean} opt_exactMatch If true, only returns exact matches - * for the expression. - */ - testability.findModels = function(element, expression, opt_exactMatch) { - var prefixes = ['ng-', 'data-ng-', 'ng\\:']; - for (var p = 0; p < prefixes.length; ++p) { - var attributeEquals = opt_exactMatch ? '=' : '*='; - var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; - var elements = element.querySelectorAll(selector); - if (elements.length) { - return elements; - } - } - }; - - /** - * @name $$testability#getLocation - * - * @description - * Shortcut for getting the location in a browser agnostic way. Returns - * the path, search, and hash. (e.g. /path?a=b#hash) - */ - testability.getLocation = function() { - return $location.url(); - }; - - /** - * @name $$testability#setLocation - * - * @description - * Shortcut for navigating to a location without doing a full page reload. - * - * @param {string} url The location url (path, search and hash, - * e.g. /path?a=b#hash) to go to. - */ - testability.setLocation = function(url) { - if (url !== $location.url()) { - $location.url(url); - $rootScope.$digest(); - } - }; - - /** - * @name $$testability#whenStable - * - * @description - * Calls the callback when $timeout and $http requests are completed. - * - * @param {function} callback - */ - testability.whenStable = function(callback) { - $browser.notifyWhenNoOutstandingRequests(callback); - }; - - return testability; - }]; -} - -function $TimeoutProvider() { - this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', - function($rootScope, $browser, $q, $$q, $exceptionHandler) { - var deferreds = {}; - - - /** - * @ngdoc service - * @name $timeout - * - * @description - * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch - * 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 timeout is reached and the timeout function is executed. - * - * 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, 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 - * 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. - * - */ - function timeout(fn, delay, invokeApply) { - var skipApply = (isDefined(invokeApply) && !invokeApply), - deferred = (skipApply ? $$q : $q).defer(), - promise = deferred.promise, - timeoutId; - - timeoutId = $browser.defer(function() { - try { - deferred.resolve(fn()); - } catch(e) { - deferred.reject(e); - $exceptionHandler(e); - } - finally { - delete deferreds[promise.$$timeoutId]; - } - - if (!skipApply) $rootScope.$apply(); - }, delay); - - promise.$$timeoutId = timeoutId; - deferreds[timeoutId] = deferred; - - return promise; - } - - - /** - * @ngdoc method - * @name $timeout#cancel - * - * @description - * 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. - * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully - * canceled. - */ - timeout.cancel = function(promise) { - if (promise && promise.$$timeoutId in deferreds) { - deferreds[promise.$$timeoutId].reject('canceled'); - delete deferreds[promise.$$timeoutId]; - return $browser.defer.cancel(promise.$$timeoutId); - } - return false; - }; - - return timeout; - }]; -} - -// NOTE: The usage of window and document instead of $window and $document here is -// deliberate. This service depends on the specific behavior of anchor nodes created by the -// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and -// cause us to break tests. In addition, when the browser resolves a URL for XHR, it -// doesn't know about mocked locations and resolves URLs to the real document - which is -// exactly the behavior needed here. There is little value is mocking these out for this -// service. -var urlParsingNode = document.createElement("a"); -var originUrl = urlResolve(window.location.href, true); - - -/** - * - * Implementation Notes for non-IE browsers - * ---------------------------------------- - * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, - * results both in the normalizing and parsing of the URL. Normalizing means that a relative - * URL will be resolved into an absolute URL in the context of the application document. - * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related - * properties are all populated to reflect the normalized URL. This approach has wide - * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See - * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html - * - * Implementation Notes for IE - * --------------------------- - * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other - * browsers. However, the parsed components will not be set if the URL assigned did not specify - * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We - * work around that by performing the parsing in a 2nd step by taking a previously normalized - * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the - * properties such as protocol, hostname, port, etc. - * - * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one - * uses the inner HTML approach to assign the URL as part of an HTML snippet - - * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. - * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. - * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that - * method and IE < 8 is unsupported. - * - * References: - * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement - * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html - * http://url.spec.whatwg.org/#urlutils - * https://github.com/angular/angular.js/pull/2902 - * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ - * - * @kind function - * @param {string} url The URL to be parsed. - * @description Normalizes and parses a URL. - * @returns {object} Returns the normalized URL as a dictionary. - * - * | member name | Description | - * |---------------|----------------| - * | href | A normalized version of the provided URL if it was not an absolute URL | - * | protocol | The protocol including the trailing colon | - * | host | The host and port (if the port is non-default) of the normalizedUrl | - * | search | The search params, minus the question mark | - * | hash | The hash string, minus the hash symbol - * | hostname | The hostname - * | port | The port, without ":" - * | pathname | The pathname, beginning with "/" - * - */ -function urlResolve(url, base) { - var href = url; - - if (msie) { - // Normalize before parse. Refer Implementation Notes on why this is - // done in two steps on IE. - urlParsingNode.setAttribute("href", href); - href = urlParsingNode.href; - } - - urlParsingNode.setAttribute('href', href); - - // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils - return { - href: urlParsingNode.href, - protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', - host: urlParsingNode.host, - search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', - hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', - hostname: urlParsingNode.hostname, - port: urlParsingNode.port, - pathname: (urlParsingNode.pathname.charAt(0) === '/') - ? urlParsingNode.pathname - : '/' + urlParsingNode.pathname - }; -} - -/** - * Parse a request URL and determine whether this is a same-origin request as the application document. - * - * @param {string|object} requestUrl The url of the request as a string that will be resolved - * or a parsed URL object. - * @returns {boolean} Whether the request is for the same origin as the application document. - */ -function urlIsSameOrigin(requestUrl) { - var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; - return (parsed.protocol === originUrl.protocol && - parsed.host === originUrl.host); -} - -/** - * @ngdoc service - * @name $window - * - * @description - * A reference to the browser's `window` object. While `window` - * is globally available in JavaScript, it causes testability problems, because - * it is a global variable. In angular we always refer to it through the - * `$window` service, so it may be overridden, removed or mocked for testing. - * - * Expressions, like the one defined for the `ngClick` directive in the example - * below, are evaluated with respect to the current scope. Therefore, there is - * no risk of inadvertently coding in a dependency on a global value in such an - * expression. - * - * @example - <example module="windowExample"> - <file name="index.html"> - <script> - angular.module('windowExample', []) - .controller('ExampleController', ['$scope', '$window', function ($scope, $window) { - $scope.greeting = 'Hello, World!'; - $scope.doGreeting = function(greeting) { - $window.alert(greeting); - }; - }]); - </script> - <div ng-controller="ExampleController"> - <input type="text" ng-model="greeting" /> - <button ng-click="doGreeting(greeting)">ALERT</button> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should display the greeting in the input box', function() { - element(by.model('greeting')).sendKeys('Hello, E2E Tests'); - // If we click the button it will block the test runner - // element(':button').click(); - }); - </file> - </example> - */ -function $WindowProvider(){ - this.$get = valueFn(window); -} - -/* global currencyFilter: true, - dateFilter: true, - filterFilter: true, - jsonFilter: true, - limitToFilter: true, - lowercaseFilter: true, - numberFilter: true, - orderByFilter: true, - uppercaseFilter: true, - */ - -/** - * @ngdoc provider - * @name $filterProvider - * @description - * - * Filters are just functions which transform input to an output. However filters need to be - * Dependency Injected. To achieve this a filter definition consists of a factory function which is - * annotated with dependencies and is responsible for creating a filter function. - * - * ```js - * // Filter registration - * function MyModule($provide, $filterProvider) { - * // create a service to demonstrate injection (not always needed) - * $provide.value('greet', function(name){ - * return 'Hello ' + name + '!'; - * }); - * - * // register a filter factory which uses the - * // greet service to demonstrate DI. - * $filterProvider.register('greet', function(greet){ - * // return the filter function which uses the greet service - * // to generate salutation - * return function(text) { - * // filters need to be forgiving so check input validity - * return text && greet(text) || text; - * }; - * }); - * } - * ``` - * - * The filter function is registered with the `$injector` under the filter name suffix with - * `Filter`. - * - * ```js - * it('should be the same instance', inject( - * function($filterProvider) { - * $filterProvider.register('reverse', function(){ - * return ...; - * }); - * }, - * function($filter, reverseFilter) { - * expect($filter('reverse')).toBe(reverseFilter); - * }); - * ``` - * - * - * For more information about how angular filters work, and how to create your own filters, see - * {@link guide/filter Filters} in the Angular Developer Guide. - */ - -/** - * @ngdoc service - * @name $filter - * @kind function - * @description - * Filters are used for formatting data displayed to the user. - * - * The general syntax in templates is as follows: - * - * {{ expression [| filter_name[:parameter_value] ... ] }} - * - * @param {String} name Name of the filter function to retrieve - * @return {Function} the filter function - * @example - <example name="$filter" module="filterExample"> - <file name="index.html"> - <div ng-controller="MainCtrl"> - <h3>{{ originalText }}</h3> - <h3>{{ filteredText }}</h3> - </div> - </file> - - <file name="script.js"> - angular.module('filterExample', []) - .controller('MainCtrl', function($scope, $filter) { - $scope.originalText = 'hello'; - $scope.filteredText = $filter('uppercase')($scope.originalText); - }); - </file> - </example> - */ -$FilterProvider.$inject = ['$provide']; -function $FilterProvider($provide) { - var suffix = 'Filter'; - - /** - * @ngdoc method - * @name $filterProvider#register - * @param {string|Object} name Name of the filter function, or an object map of filters where - * the keys are the filter names and the values are the filter factories. - * @returns {Object} Registered filter instance, or if a map of filters was provided then a map - * of the registered filter instances. - */ - function register(name, factory) { - if(isObject(name)) { - var filters = {}; - forEach(name, function(filter, key) { - filters[key] = register(key, filter); - }); - return filters; - } else { - return $provide.factory(name + suffix, factory); - } - } - this.register = register; - - this.$get = ['$injector', function($injector) { - return function(name) { - return $injector.get(name + suffix); - }; - }]; - - //////////////////////////////////////// - - /* global - currencyFilter: false, - dateFilter: false, - filterFilter: false, - jsonFilter: false, - limitToFilter: false, - lowercaseFilter: false, - numberFilter: false, - orderByFilter: false, - uppercaseFilter: false, - */ - - register('currency', currencyFilter); - register('date', dateFilter); - register('filter', filterFilter); - register('json', jsonFilter); - register('limitTo', limitToFilter); - register('lowercase', lowercaseFilter); - register('number', numberFilter); - register('orderBy', orderByFilter); - register('uppercase', uppercaseFilter); -} - -/** - * @ngdoc filter - * @name filter - * @kind function - * - * @description - * Selects a subset of items from `array` and returns it as a new array. - * - * @param {Array} array The source array. - * @param {string|Object|function()} expression The predicate to be used for selecting items from - * `array`. - * - * Can be one of: - * - * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against - * the contents of the `array`. All strings or objects with string properties in `array` that contain this string - * will be returned. The predicate can be negated by prefixing the string with `!`. - * - * - `Object`: A pattern object can be used to filter specific properties on objects contained - * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items - * which have property `name` containing "M" and property `phone` containing "1". A special - * property name `$` can be used (as in `{$:"text"}`) to accept a match against any - * property of the object. That's equivalent to the simple substring match with a `string` - * as described above. The predicate can be negated by prefixing the string with `!`. - * For Example `{name: "!M"}` predicate will return an array of items which have property `name` - * not containing "M". - * - * - `function(value, index)`: A predicate function can be used to write arbitrary filters. The - * function is called for each element of `array`. The final result is an array of those - * elements that the predicate returned true for. - * - * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in - * determining if the expected value (from the filter expression) and actual value (from - * the object in the array) should be considered a match. - * - * Can be one of: - * - * - `function(actual, expected)`: - * The function will be given the object value and the predicate value to compare and - * should return true if the item should be included in filtered result. - * - * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. - * this is essentially strict comparison of expected and actual. - * - * - `false|undefined`: A short hand for a function which will look for a substring match in case - * insensitive way. - * - * @example - <example> - <file name="index.html"> - <div ng-init="friends = [{name:'John', phone:'555-1276'}, - {name:'Mary', phone:'800-BIG-MARY'}, - {name:'Mike', phone:'555-4321'}, - {name:'Adam', phone:'555-5678'}, - {name:'Julie', phone:'555-8765'}, - {name:'Juliette', phone:'555-5678'}]"></div> - - Search: <input ng-model="searchText"> - <table id="searchTextResults"> - <tr><th>Name</th><th>Phone</th></tr> - <tr ng-repeat="friend in friends | filter:searchText"> - <td>{{friend.name}}</td> - <td>{{friend.phone}}</td> - </tr> - </table> - <hr> - Any: <input ng-model="search.$"> <br> - Name only <input ng-model="search.name"><br> - Phone only <input ng-model="search.phone"><br> - Equality <input type="checkbox" ng-model="strict"><br> - <table id="searchObjResults"> - <tr><th>Name</th><th>Phone</th></tr> - <tr ng-repeat="friendObj in friends | filter:search:strict"> - <td>{{friendObj.name}}</td> - <td>{{friendObj.phone}}</td> - </tr> - </table> - </file> - <file name="protractor.js" type="protractor"> - var expectFriendNames = function(expectedNames, key) { - element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { - arr.forEach(function(wd, i) { - expect(wd.getText()).toMatch(expectedNames[i]); - }); - }); - }; - - it('should search across all fields when filtering with a string', function() { - var searchText = element(by.model('searchText')); - searchText.clear(); - searchText.sendKeys('m'); - expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); - - searchText.clear(); - searchText.sendKeys('76'); - expectFriendNames(['John', 'Julie'], 'friend'); - }); - - it('should search in specific fields when filtering with a predicate object', function() { - var searchAny = element(by.model('search.$')); - searchAny.clear(); - searchAny.sendKeys('i'); - expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); - }); - it('should use a equal comparison when comparator is true', function() { - var searchName = element(by.model('search.name')); - var strict = element(by.model('strict')); - searchName.clear(); - searchName.sendKeys('Julie'); - strict.click(); - expectFriendNames(['Julie'], 'friendObj'); - }); - </file> - </example> - */ -function filterFilter() { - return function(array, expression, comparator) { - if (!isArray(array)) return array; - - var comparatorType = typeof(comparator), - predicates = []; - - predicates.check = function(value, index) { - for (var j = 0; j < predicates.length; j++) { - if(!predicates[j](value, index)) { - return false; - } - } - return true; - }; - - if (comparatorType !== 'function') { - if (comparatorType === 'boolean' && comparator) { - comparator = function(obj, text) { - return angular.equals(obj, text); - }; - } else { - comparator = function(obj, text) { - if (obj && text && typeof obj === 'object' && typeof text === 'object') { - for (var objKey in obj) { - if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) && - comparator(obj[objKey], text[objKey])) { - return true; - } - } - return false; - } - text = (''+text).toLowerCase(); - return (''+obj).toLowerCase().indexOf(text) > -1; - }; - } - } - - var search = function(obj, text){ - if (typeof text == 'string' && text.charAt(0) === '!') { - return !search(obj, text.substr(1)); - } - switch (typeof obj) { - case "boolean": - case "number": - case "string": - return comparator(obj, text); - case "object": - switch (typeof text) { - case "object": - return comparator(obj, text); - default: - for ( var objKey in obj) { - if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { - return true; - } - } - break; - } - return false; - case "array": - for ( var i = 0; i < obj.length; i++) { - if (search(obj[i], text)) { - return true; - } - } - return false; - default: - return false; - } - }; - switch (typeof expression) { - case "boolean": - case "number": - case "string": - // Set up expression object and fall through - expression = {$:expression}; - // jshint -W086 - case "object": - // jshint +W086 - for (var key in expression) { - (function(path) { - if (typeof expression[path] === 'undefined') return; - predicates.push(function(value) { - return search(path == '$' ? value : (value && value[path]), expression[path]); - }); - })(key); - } - break; - case 'function': - predicates.push(expression); - break; - default: - return array; - } - var filtered = []; - for ( var j = 0; j < array.length; j++) { - var value = array[j]; - if (predicates.check(value, j)) { - filtered.push(value); - } - } - return filtered; - }; -} - -/** - * @ngdoc filter - * @name currency - * @kind function - * - * @description - * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default - * symbol for current locale is used. - * - * @param {number} amount Input to filter. - * @param {string=} symbol Currency symbol or identifier to be displayed. - * @returns {string} Formatted number. - * - * - * @example - <example module="currencyExample"> - <file name="index.html"> - <script> - angular.module('currencyExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.amount = 1234.56; - }]); - </script> - <div ng-controller="ExampleController"> - <input type="number" ng-model="amount"> <br> - default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> - custom currency identifier (USD$): <span>{{amount | currency:"USD$"}}</span> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should init with 1234.56', function() { - expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); - expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56'); - }); - it('should update', function() { - if (browser.params.browser == 'safari') { - // Safari does not understand the minus key. See - // https://github.com/angular/protractor/issues/481 - return; - } - element(by.model('amount')).clear(); - element(by.model('amount')).sendKeys('-1234'); - expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)'); - expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)'); - }); - </file> - </example> - */ -currencyFilter.$inject = ['$locale']; -function currencyFilter($locale) { - var formats = $locale.NUMBER_FORMATS; - return function(amount, currencySymbol){ - if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; - - // if null or undefined pass it through - return (amount == null) - ? amount - : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). - replace(/\u00A4/g, currencySymbol); - }; -} - -/** - * @ngdoc filter - * @name number - * @kind function - * - * @description - * Formats a number as text. - * - * If the input is not a number an empty string is returned. - * - * @param {number|string} number Number to format. - * @param {(number|string)=} fractionSize Number of decimal places to round the number to. - * If this is not provided then the fraction size is computed from the current locale's number - * formatting pattern. In the case of the default locale, it will be 3. - * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit. - * - * @example - <example module="numberFilterExample"> - <file name="index.html"> - <script> - angular.module('numberFilterExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.val = 1234.56789; - }]); - </script> - <div ng-controller="ExampleController"> - Enter number: <input ng-model='val'><br> - Default formatting: <span id='number-default'>{{val | number}}</span><br> - No fractions: <span>{{val | number:0}}</span><br> - Negative number: <span>{{-val | number:4}}</span> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should format numbers', function() { - expect(element(by.id('number-default')).getText()).toBe('1,234.568'); - expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); - expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); - }); - - it('should update', function() { - element(by.model('val')).clear(); - element(by.model('val')).sendKeys('3374.333'); - expect(element(by.id('number-default')).getText()).toBe('3,374.333'); - expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); - expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); - }); - </file> - </example> - */ - - -numberFilter.$inject = ['$locale']; -function numberFilter($locale) { - var formats = $locale.NUMBER_FORMATS; - return function(number, fractionSize) { - - // if null or undefined pass it through - return (number == null) - ? number - : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, - fractionSize); - }; -} - -var DECIMAL_SEP = '.'; -function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { - if (!isFinite(number) || isObject(number)) return ''; - - var isNegative = number < 0; - number = Math.abs(number); - var numStr = number + '', - formatedText = '', - parts = []; - - var hasExponent = false; - if (numStr.indexOf('e') !== -1) { - var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); - if (match && match[2] == '-' && match[3] > fractionSize + 1) { - numStr = '0'; - number = 0; - } else { - formatedText = numStr; - hasExponent = true; - } - } - - if (!hasExponent) { - var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; - - // determine fractionSize if it is not specified - if (isUndefined(fractionSize)) { - fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); - } - - // safely round numbers in JS without hitting imprecisions of floating-point arithmetics - // inspired by: - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round - number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize); - - if (number === 0) { - isNegative = false; - } - - var fraction = ('' + number).split(DECIMAL_SEP); - var whole = fraction[0]; - fraction = fraction[1] || ''; - - var i, pos = 0, - lgroup = pattern.lgSize, - group = pattern.gSize; - - if (whole.length >= (lgroup + group)) { - pos = whole.length - lgroup; - for (i = 0; i < pos; i++) { - if ((pos - i)%group === 0 && i !== 0) { - formatedText += groupSep; - } - formatedText += whole.charAt(i); - } - } - - for (i = pos; i < whole.length; i++) { - if ((whole.length - i)%lgroup === 0 && i !== 0) { - formatedText += groupSep; - } - formatedText += whole.charAt(i); - } - - // format fraction part. - while(fraction.length < fractionSize) { - fraction += '0'; - } - - if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); - } else { - - if (fractionSize > 0 && number > -1 && number < 1) { - formatedText = number.toFixed(fractionSize); - } - } - - parts.push(isNegative ? pattern.negPre : pattern.posPre); - parts.push(formatedText); - parts.push(isNegative ? pattern.negSuf : pattern.posSuf); - return parts.join(''); -} - -function padNumber(num, digits, trim) { - var neg = ''; - if (num < 0) { - neg = '-'; - num = -num; - } - num = '' + num; - while(num.length < digits) num = '0' + num; - if (trim) - num = num.substr(num.length - digits); - return neg + num; -} - - -function dateGetter(name, size, offset, trim) { - offset = offset || 0; - return function(date) { - var value = date['get' + name](); - if (offset > 0 || value > -offset) - value += offset; - if (value === 0 && offset == -12 ) value = 12; - return padNumber(value, size, trim); - }; -} - -function dateStrGetter(name, shortForm) { - return function(date, formats) { - var value = date['get' + name](); - var get = uppercase(shortForm ? ('SHORT' + name) : name); - - return formats[get][value]; - }; -} - -function timeZoneGetter(date) { - var zone = -1 * date.getTimezoneOffset(); - var paddedZone = (zone >= 0) ? "+" : ""; - - paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + - padNumber(Math.abs(zone % 60), 2); - - return paddedZone; -} - -function getFirstThursdayOfYear(year) { - // 0 = index of January - var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); - // 4 = index of Thursday (+1 to account for 1st = 5) - // 11 = index of *next* Thursday (+1 account for 1st = 12) - return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); -} - -function getThursdayThisWeek(datetime) { - return new Date(datetime.getFullYear(), datetime.getMonth(), - // 4 = index of Thursday - datetime.getDate() + (4 - datetime.getDay())); -} - -function weekGetter(size) { - return function(date) { - var firstThurs = getFirstThursdayOfYear(date.getFullYear()), - thisThurs = getThursdayThisWeek(date); - - var diff = +thisThurs - +firstThurs, - result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week - - return padNumber(result, size); - }; -} - -function ampmGetter(date, formats) { - return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; -} - -var DATE_FORMATS = { - yyyy: dateGetter('FullYear', 4), - yy: dateGetter('FullYear', 2, 0, true), - y: dateGetter('FullYear', 1), - MMMM: dateStrGetter('Month'), - MMM: dateStrGetter('Month', true), - MM: dateGetter('Month', 2, 1), - M: dateGetter('Month', 1, 1), - dd: dateGetter('Date', 2), - d: dateGetter('Date', 1), - HH: dateGetter('Hours', 2), - H: dateGetter('Hours', 1), - hh: dateGetter('Hours', 2, -12), - h: dateGetter('Hours', 1, -12), - mm: dateGetter('Minutes', 2), - m: dateGetter('Minutes', 1), - ss: dateGetter('Seconds', 2), - s: dateGetter('Seconds', 1), - // while ISO 8601 requires fractions to be prefixed with `.` or `,` - // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions - sss: dateGetter('Milliseconds', 3), - EEEE: dateStrGetter('Day'), - EEE: dateStrGetter('Day', true), - a: ampmGetter, - Z: timeZoneGetter, - ww: weekGetter(2), - w: weekGetter(1) -}; - -var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/, - NUMBER_STRING = /^\-?\d+$/; - -/** - * @ngdoc filter - * @name date - * @kind function - * - * @description - * Formats `date` to a string based on the requested `format`. - * - * `format` string can be composed of the following elements: - * - * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) - * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) - * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) - * * `'MMMM'`: Month in year (January-December) - * * `'MMM'`: Month in year (Jan-Dec) - * * `'MM'`: Month in year, padded (01-12) - * * `'M'`: Month in year (1-12) - * * `'dd'`: Day in month, padded (01-31) - * * `'d'`: Day in month (1-31) - * * `'EEEE'`: Day in Week,(Sunday-Saturday) - * * `'EEE'`: Day in Week, (Sun-Sat) - * * `'HH'`: Hour in day, padded (00-23) - * * `'H'`: Hour in day (0-23) - * * `'hh'`: Hour in AM/PM, padded (01-12) - * * `'h'`: Hour in AM/PM, (1-12) - * * `'mm'`: Minute in hour, padded (00-59) - * * `'m'`: Minute in hour (0-59) - * * `'ss'`: Second in minute, padded (00-59) - * * `'s'`: Second in minute (0-59) - * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) - * * `'a'`: AM/PM marker - * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) - * * `'ww'`: ISO-8601 week of year (00-53) - * * `'w'`: ISO-8601 week of year (0-53) - * - * `format` string can also be one of the following predefined - * {@link guide/i18n localizable formats}: - * - * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale - * (e.g. Sep 3, 2010 12:05:08 PM) - * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) - * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale - * (e.g. Friday, September 3, 2010) - * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) - * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) - * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) - * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) - * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) - * - * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. - * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence - * (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 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, - * `mediumDate` is used. - * @param {string=} timezone Timezone to be used for formatting. Right now, only `'UTC'` is supported. - * If not specified, the timezone of the browser will be used. - * @returns {string} Formatted string or the input if input is not recognized as date/millis. - * - * @example - <example> - <file name="index.html"> - <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: - <span>{{1288323623006 | date:'medium'}}</span><br> - <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: - <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> - <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: - <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> - <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: - <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> - </file> - <file name="protractor.js" type="protractor"> - it('should format date', function() { - expect(element(by.binding("1288323623006 | date:'medium'")).getText()). - toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); - expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). - toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); - expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). - toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); - expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). - toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); - }); - </file> - </example> - */ -dateFilter.$inject = ['$locale']; -function dateFilter($locale) { - - - var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; - // 1 2 3 4 5 6 7 8 9 10 11 - function jsonStringToDate(string) { - var match; - if (match = string.match(R_ISO8601_STR)) { - var date = new Date(0), - tzHour = 0, - tzMin = 0, - dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, - timeSetter = match[8] ? date.setUTCHours : date.setHours; - - if (match[9]) { - tzHour = int(match[9] + match[10]); - tzMin = int(match[9] + match[11]); - } - dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); - var h = int(match[4]||0) - tzHour; - var m = int(match[5]||0) - tzMin; - var s = int(match[6]||0); - var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); - timeSetter.call(date, h, m, s, ms); - return date; - } - return string; - } - - - return function(date, format, timezone) { - var text = '', - parts = [], - fn, match; - - format = format || 'mediumDate'; - format = $locale.DATETIME_FORMATS[format] || format; - if (isString(date)) { - date = NUMBER_STRING.test(date) ? int(date) : jsonStringToDate(date); - } - - if (isNumber(date)) { - date = new Date(date); - } - - if (!isDate(date)) { - return date; - } - - while(format) { - match = DATE_FORMATS_SPLIT.exec(format); - if (match) { - parts = concat(parts, match, 1); - format = parts.pop(); - } else { - parts.push(format); - format = null; - } - } - - if (timezone && timezone === 'UTC') { - date = new Date(date.getTime()); - date.setMinutes(date.getMinutes() + date.getTimezoneOffset()); - } - forEach(parts, function(value){ - fn = DATE_FORMATS[value]; - text += fn ? fn(date, $locale.DATETIME_FORMATS) - : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); - }); - - return text; - }; -} - - -/** - * @ngdoc filter - * @name json - * @kind function - * - * @description - * Allows you to convert a JavaScript object into JSON string. - * - * This filter is mostly useful for debugging. When using the double curly {{value}} notation - * the binding is automatically converted to JSON. - * - * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. - * @returns {string} JSON string. - * - * - * @example - <example> - <file name="index.html"> - <pre>{{ {'name':'value'} | json }}</pre> - </file> - <file name="protractor.js" type="protractor"> - it('should jsonify filtered objects', function() { - expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/); - }); - </file> - </example> - * - */ -function jsonFilter() { - return function(object) { - return toJson(object, true); - }; -} - - -/** - * @ngdoc filter - * @name lowercase - * @kind function - * @description - * Converts string to lowercase. - * @see angular.lowercase - */ -var lowercaseFilter = valueFn(lowercase); - - -/** - * @ngdoc filter - * @name uppercase - * @kind function - * @description - * Converts string to uppercase. - * @see angular.uppercase - */ -var uppercaseFilter = valueFn(uppercase); - -/** - * @ngdoc filter - * @name limitTo - * @kind function - * - * @description - * Creates a new array or string containing only a specified number of elements. The elements - * are taken from either the beginning or the end of the source array or string, as specified by - * the value and sign (positive or negative) of `limit`. - * - * @param {Array|string} input Source array or string to be limited. - * @param {string|number} limit The length of the returned array or string. If the `limit` number - * is positive, `limit` number of items from the beginning of the source array/string are copied. - * If the number is negative, `limit` number of items from the end of the source array/string - * are copied. The `limit` will be trimmed if it exceeds `array.length` - * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array - * had less than `limit` elements. - * - * @example - <example module="limitToExample"> - <file name="index.html"> - <script> - angular.module('limitToExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.numbers = [1,2,3,4,5,6,7,8,9]; - $scope.letters = "abcdefghi"; - $scope.numLimit = 3; - $scope.letterLimit = 3; - }]); - </script> - <div ng-controller="ExampleController"> - Limit {{numbers}} to: <input type="integer" ng-model="numLimit"> - <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> - Limit {{letters}} to: <input type="integer" ng-model="letterLimit"> - <p>Output letters: {{ letters | limitTo:letterLimit }}</p> - </div> - </file> - <file name="protractor.js" type="protractor"> - var numLimitInput = element(by.model('numLimit')); - var letterLimitInput = element(by.model('letterLimit')); - var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); - var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); - - it('should limit the number array to first three items', function() { - expect(numLimitInput.getAttribute('value')).toBe('3'); - expect(letterLimitInput.getAttribute('value')).toBe('3'); - expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); - expect(limitedLetters.getText()).toEqual('Output letters: abc'); - }); - - it('should update the output when -3 is entered', function() { - numLimitInput.clear(); - numLimitInput.sendKeys('-3'); - letterLimitInput.clear(); - letterLimitInput.sendKeys('-3'); - expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); - expect(limitedLetters.getText()).toEqual('Output letters: ghi'); - }); - - it('should not exceed the maximum size of input array', function() { - numLimitInput.clear(); - numLimitInput.sendKeys('100'); - letterLimitInput.clear(); - letterLimitInput.sendKeys('100'); - expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); - expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); - }); - </file> - </example> - */ -function limitToFilter(){ - return function(input, limit) { - if (!isArray(input) && !isString(input)) return input; - - if (Math.abs(Number(limit)) === Infinity) { - limit = Number(limit); - } else { - limit = int(limit); - } - - if (isString(input)) { - //NaN check on limit - if (limit) { - return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length); - } else { - return ""; - } - } - - var out = [], - i, n; - - // if abs(limit) exceeds maximum length, trim it - if (limit > input.length) - limit = input.length; - else if (limit < -input.length) - limit = -input.length; - - if (limit > 0) { - i = 0; - n = limit; - } else { - i = input.length + limit; - n = input.length; - } - - for (; i<n; i++) { - out.push(input[i]); - } - - return out; - }; -} - -/** - * @ngdoc filter - * @name orderBy - * @kind function - * - * @description - * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically - * for strings and numerically for numbers. Note: if you notice numbers are not being sorted - * correctly, make sure they are actually being saved as numbers and not strings. - * - * @param {Array} array The array to sort. - * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be - * used by the comparator to determine the order of elements. - * - * Can be one of: - * - * - `function`: Getter function. The result of this function will be sorted using the - * `<`, `=`, `>` operator. - * - `string`: An Angular expression. The result of this expression is used to compare elements - * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by - * 3 first characters of a property called `name`). The result of a constant expression - * is interpreted as a property name to be used in comparisons (for example `"special name"` - * to sort object by the value of their `special name` property). An expression can be - * optionally prefixed with `+` or `-` to control ascending or descending sort order - * (for example, `+name` or `-name`). - * - `Array`: An array of function or string predicates. The first predicate in the array - * is used for sorting, but when two items are equivalent, the next predicate is used. - * - * @param {boolean=} reverse Reverse the order of the array. - * @returns {Array} Sorted copy of the source array. - * - * @example - <example module="orderByExample"> - <file name="index.html"> - <script> - angular.module('orderByExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.friends = - [{name:'John', phone:'555-1212', age:10}, - {name:'Mary', phone:'555-9876', age:19}, - {name:'Mike', phone:'555-4321', age:21}, - {name:'Adam', phone:'555-5678', age:35}, - {name:'Julie', phone:'555-8765', age:29}]; - $scope.predicate = '-age'; - }]); - </script> - <div ng-controller="ExampleController"> - <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> - <hr/> - [ <a href="" ng-click="predicate=''">unsorted</a> ] - <table class="friend"> - <tr> - <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a> - (<a href="" ng-click="predicate = '-name'; reverse=false">^</a>)</th> - <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th> - <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th> - </tr> - <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> - <td>{{friend.name}}</td> - <td>{{friend.phone}}</td> - <td>{{friend.age}}</td> - </tr> - </table> - </div> - </file> - </example> - * - * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the - * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the - * desired parameters. - * - * Example: - * - * @example - <example module="orderByExample"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <table class="friend"> - <tr> - <th><a href="" ng-click="reverse=false;order('name', false)">Name</a> - (<a href="" ng-click="order('-name',false)">^</a>)</th> - <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th> - <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th> - </tr> - <tr ng-repeat="friend in friends"> - <td>{{friend.name}}</td> - <td>{{friend.phone}}</td> - <td>{{friend.age}}</td> - </tr> - </table> - </div> - </file> - - <file name="script.js"> - angular.module('orderByExample', []) - .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { - var orderBy = $filter('orderBy'); - $scope.friends = [ - { name: 'John', phone: '555-1212', age: 10 }, - { name: 'Mary', phone: '555-9876', age: 19 }, - { name: 'Mike', phone: '555-4321', age: 21 }, - { name: 'Adam', phone: '555-5678', age: 35 }, - { name: 'Julie', phone: '555-8765', age: 29 } - ]; - $scope.order = function(predicate, reverse) { - $scope.friends = orderBy($scope.friends, predicate, reverse); - }; - $scope.order('-age',false); - }]); - </file> -</example> - */ -orderByFilter.$inject = ['$parse']; -function orderByFilter($parse){ - return function(array, sortPredicate, reverseOrder) { - if (!(isArrayLike(array))) return array; - if (!sortPredicate) return array; - sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; - sortPredicate = map(sortPredicate, function(predicate){ - var descending = false, get = predicate || identity; - if (isString(predicate)) { - if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { - descending = predicate.charAt(0) == '-'; - predicate = predicate.substring(1); - } - get = $parse(predicate); - if (get.constant) { - var key = get(); - return reverseComparator(function(a,b) { - return compare(a[key], b[key]); - }, descending); - } - } - return reverseComparator(function(a,b){ - return compare(get(a),get(b)); - }, descending); - }); - var arrayCopy = []; - for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } - return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); - - function comparator(o1, o2){ - for ( var i = 0; i < sortPredicate.length; i++) { - var comp = sortPredicate[i](o1, o2); - if (comp !== 0) return comp; - } - return 0; - } - function reverseComparator(comp, descending) { - return descending - ? function(a,b){return comp(b,a);} - : comp; - } - function compare(v1, v2){ - var t1 = typeof v1; - var t2 = typeof v2; - if (t1 == t2) { - if (isDate(v1) && isDate(v2)) { - v1 = v1.valueOf(); - v2 = v2.valueOf(); - } - if (t1 == "string") { - v1 = v1.toLowerCase(); - v2 = v2.toLowerCase(); - } - if (v1 === v2) return 0; - return v1 < v2 ? -1 : 1; - } else { - return t1 < t2 ? -1 : 1; - } - } - }; -} - -function ngDirective(directive) { - if (isFunction(directive)) { - directive = { - link: directive - }; - } - directive.restrict = directive.restrict || 'AC'; - return valueFn(directive); -} - -/** - * @ngdoc directive - * @name a - * @restrict E - * - * @description - * Modifies the default behavior of the html A tag so that the default action is prevented when - * the href attribute is empty. - * - * This change permits the easy creation of action links with the `ngClick` directive - * without changing the location or causing page reloads, e.g.: - * `<a href="" ng-click="list.addItem()">Add Item</a>` - */ -var htmlAnchorDirective = valueFn({ - restrict: 'E', - compile: function(element, attr) { - - if (msie <= 8) { - - // turn <a href ng-click="..">link</a> into a stylable link in IE - // but only if it doesn't have name attribute, in which case it's an anchor - if (!attr.href && !attr.name) { - attr.$set('href', ''); - } - - // add a comment node to anchors to workaround IE bug that causes element content to be reset - // to new attribute content if attribute is updated with value containing @ and element also - // contains value with @ - // see issue #1949 - element.append(document.createComment('IE fix')); - } - - if (!attr.href && !attr.xlinkHref && !attr.name) { - return function(scope, element) { - // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. - var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? - 'xlink:href' : 'href'; - element.on('click', function(event){ - // if we have no href url, then don't navigate anywhere. - if (!element.attr(href)) { - event.preventDefault(); - } - }); - }; - } - } -}); - -/** - * @ngdoc directive - * @name ngHref - * @restrict A - * @priority 99 - * - * @description - * Using Angular markup like `{{hash}}` in an href attribute will - * make the link go to the wrong URL if the user clicks it before - * Angular has a chance to replace the `{{hash}}` markup with its - * value. Until Angular replaces the markup the link will be broken - * and will most likely return a 404 error. - * - * The `ngHref` directive solves this problem. - * - * The wrong way to write it: - * ```html - * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> - * ``` - * - * The correct way to write it: - * ```html - * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> - * ``` - * - * @element A - * @param {template} ngHref any string which can contain `{{}}` markup. - * - * @example - * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes - * in links and their different behaviors: - <example> - <file name="index.html"> - <input ng-model="value" /><br /> - <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> - <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> - <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> - <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> - <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> - <a id="link-6" ng-href="{{value}}">link</a> (link, change location) - </file> - <file name="protractor.js" type="protractor"> - it('should execute ng-click but not reload when href without value', function() { - element(by.id('link-1')).click(); - expect(element(by.model('value')).getAttribute('value')).toEqual('1'); - expect(element(by.id('link-1')).getAttribute('href')).toBe(''); - }); - - it('should execute ng-click but not reload when href empty string', function() { - element(by.id('link-2')).click(); - expect(element(by.model('value')).getAttribute('value')).toEqual('2'); - expect(element(by.id('link-2')).getAttribute('href')).toBe(''); - }); - - it('should execute ng-click and change url when ng-href specified', function() { - expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); - - element(by.id('link-3')).click(); - - // At this point, we navigate away from an Angular page, so we need - // to use browser.driver to get the base webdriver. - - browser.wait(function() { - return browser.driver.getCurrentUrl().then(function(url) { - return url.match(/\/123$/); - }); - }, 5000, 'page should navigate to /123'); - }); - - xit('should execute ng-click but not reload when href empty string and name specified', function() { - element(by.id('link-4')).click(); - expect(element(by.model('value')).getAttribute('value')).toEqual('4'); - expect(element(by.id('link-4')).getAttribute('href')).toBe(''); - }); - - it('should execute ng-click but not reload when no href but name specified', function() { - element(by.id('link-5')).click(); - expect(element(by.model('value')).getAttribute('value')).toEqual('5'); - expect(element(by.id('link-5')).getAttribute('href')).toBe(null); - }); - - it('should only change url when only ng-href', function() { - element(by.model('value')).clear(); - element(by.model('value')).sendKeys('6'); - expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); - - element(by.id('link-6')).click(); - - // At this point, we navigate away from an Angular page, so we need - // to use browser.driver to get the base webdriver. - browser.wait(function() { - return browser.driver.getCurrentUrl().then(function(url) { - return url.match(/\/6$/); - }); - }, 5000, 'page should navigate to /6'); - }); - </file> - </example> - */ - -/** - * @ngdoc directive - * @name ngSrc - * @restrict A - * @priority 99 - * - * @description - * Using Angular markup like `{{hash}}` in a `src` attribute doesn't - * work right: The browser will fetch from the URL with the literal - * text `{{hash}}` until Angular replaces the expression inside - * `{{hash}}`. The `ngSrc` directive solves this problem. - * - * The buggy way to write it: - * ```html - * <img src="http://www.gravatar.com/avatar/{{hash}}"/> - * ``` - * - * The correct way to write it: - * ```html - * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/> - * ``` - * - * @element IMG - * @param {template} ngSrc any string which can contain `{{}}` markup. - */ - -/** - * @ngdoc directive - * @name ngSrcset - * @restrict A - * @priority 99 - * - * @description - * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't - * work right: The browser will fetch from the URL with the literal - * text `{{hash}}` until Angular replaces the expression inside - * `{{hash}}`. The `ngSrcset` directive solves this problem. - * - * The buggy way to write it: - * ```html - * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> - * ``` - * - * The correct way to write it: - * ```html - * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> - * ``` - * - * @element IMG - * @param {template} ngSrcset any string which can contain `{{}}` markup. - */ - -/** - * @ngdoc directive - * @name ngDisabled - * @restrict A - * @priority 100 - * - * @description - * - * We shouldn't do this, because it will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: - * ```html - * <div ng-init="scope = { isDisabled: false }"> - * <button disabled="{{scope.isDisabled}}">Disabled</button> - * </div> - * ``` - * - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as disabled. (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * The `ngDisabled` directive solves this problem for the `disabled` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. - * - * @example - <example> - <file name="index.html"> - Click me to toggle: <input type="checkbox" ng-model="checked"><br/> - <button ng-model="button" ng-disabled="checked">Button</button> - </file> - <file name="protractor.js" type="protractor"> - it('should toggle button', function() { - expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy(); - element(by.model('checked')).click(); - expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); - }); - </file> - </example> - * - * @element INPUT - * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, - * then special attribute "disabled" will be set on the element - */ - - -/** - * @ngdoc directive - * @name ngChecked - * @restrict A - * @priority 100 - * - * @description - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as checked. (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * The `ngChecked` directive solves this problem for the `checked` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. - * @example - <example> - <file name="index.html"> - Check me to check both: <input type="checkbox" ng-model="master"><br/> - <input id="checkSlave" type="checkbox" ng-checked="master"> - </file> - <file name="protractor.js" type="protractor"> - it('should check both checkBoxes', function() { - expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); - element(by.model('master')).click(); - expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); - }); - </file> - </example> - * - * @element INPUT - * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, - * then special attribute "checked" will be set on the element - */ - - -/** - * @ngdoc directive - * @name ngReadonly - * @restrict A - * @priority 100 - * - * @description - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as readonly. (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * The `ngReadonly` directive solves this problem for the `readonly` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. - * @example - <example> - <file name="index.html"> - Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/> - <input type="text" ng-readonly="checked" value="I'm Angular"/> - </file> - <file name="protractor.js" type="protractor"> - it('should toggle readonly attr', function() { - expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); - element(by.model('checked')).click(); - expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); - }); - </file> - </example> - * - * @element INPUT - * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, - * then special attribute "readonly" will be set on the element - */ - - -/** - * @ngdoc directive - * @name ngSelected - * @restrict A - * @priority 100 - * - * @description - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as selected. (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * The `ngSelected` directive solves this problem for the `selected` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. - * - * @example - <example> - <file name="index.html"> - Check me to select: <input type="checkbox" ng-model="selected"><br/> - <select> - <option>Hello!</option> - <option id="greet" ng-selected="selected">Greetings!</option> - </select> - </file> - <file name="protractor.js" type="protractor"> - it('should select Greetings!', function() { - expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); - element(by.model('selected')).click(); - expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); - }); - </file> - </example> - * - * @element OPTION - * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, - * then special attribute "selected" will be set on the element - */ - -/** - * @ngdoc directive - * @name ngOpen - * @restrict A - * @priority 100 - * - * @description - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as open. (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * The `ngOpen` directive solves this problem for the `open` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. - * @example - <example> - <file name="index.html"> - Check me check multiple: <input type="checkbox" ng-model="open"><br/> - <details id="details" ng-open="open"> - <summary>Show/Hide me</summary> - </details> - </file> - <file name="protractor.js" type="protractor"> - it('should toggle open', function() { - expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); - element(by.model('open')).click(); - expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); - }); - </file> - </example> - * - * @element DETAILS - * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, - * then special attribute "open" will be set on the element - */ - -var ngAttributeAliasDirectives = {}; - - -// boolean attrs are evaluated -forEach(BOOLEAN_ATTR, function(propName, attrName) { - // binding to multiple is not supported - if (propName == "multiple") return; - - var normalized = directiveNormalize('ng-' + attrName); - ngAttributeAliasDirectives[normalized] = function() { - return { - restrict: 'A', - priority: 100, - link: function(scope, element, attr) { - scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { - attr.$set(attrName, !!value); - }); - } - }; - }; -}); - -// aliased input attrs are evaluated -forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { - ngAttributeAliasDirectives[ngAttr] = function() { - return { - priority: 100, - link: function(scope, element, attr) { - //special case ngPattern when a literal regular expression value - //is used as the expression (this way we don't have to watch anything). - if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") { - var match = attr.ngPattern.match(REGEX_STRING_REGEXP); - if (match) { - attr.$set("ngPattern", new RegExp(match[1], match[2])); - return; - } - } - - scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { - attr.$set(ngAttr, value); - }); - } - }; - }; -}); - -// ng-src, ng-srcset, ng-href are interpolated -forEach(['src', 'srcset', 'href'], function(attrName) { - var normalized = directiveNormalize('ng-' + attrName); - ngAttributeAliasDirectives[normalized] = function() { - return { - priority: 99, // it needs to run after the attributes are interpolated - link: function(scope, element, attr) { - var propName = attrName, - name = attrName; - - if (attrName === 'href' && - toString.call(element.prop('href')) === '[object SVGAnimatedString]') { - name = 'xlinkHref'; - attr.$attr[name] = 'xlink:href'; - propName = null; - } - - attr.$observe(normalized, function(value) { - if (!value) { - if (attrName === 'href') { - attr.$set(name, null); - } - return; - } - - attr.$set(name, value); - - // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist - // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need - // to set the property as well to achieve the desired effect. - // we use attr[attrName] value since $set can sanitize the url. - if (msie && propName) element.prop(propName, attr[name]); - }); - } - }; - }; -}); - -/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true - */ -var nullFormCtrl = { - $addControl: noop, - $removeControl: noop, - $setValidity: noop, - $$setPending: noop, - $setDirty: noop, - $setPristine: noop, - $setSubmitted: noop, - $$clearControlValidity: noop -}, -SUBMITTED_CLASS = 'ng-submitted'; - -/** - * @ngdoc type - * @name form.FormController - * - * @property {boolean} $pristine True if user has not interacted with the form yet. - * @property {boolean} $dirty True if user has already interacted with the form. - * @property {boolean} $valid True if all of the containing forms and controls are valid. - * @property {boolean} $invalid True if at least one containing control or form is invalid. - * @property {boolean} $submitted True if user has submitted the form even if its invalid. - * - * @property {Object} $error Is an object hash, containing references to controls or - * forms with failing validators, where: - * - * - keys are validation tokens (error names), - * - values are arrays of controls or forms that have a failing validator for given error name. - * - * Built-in validation tokens: - * - * - `email` - * - `max` - * - `maxlength` - * - `min` - * - `minlength` - * - `number` - * - `pattern` - * - `required` - * - `url` - * - * @description - * `FormController` keeps track of all its controls and nested forms as well as the state of them, - * such as being valid/invalid or dirty/pristine. - * - * Each {@link ng.directive:form form} directive creates an instance - * of `FormController`. - * - */ -//asks for $scope to fool the BC controller module -FormController.$inject = ['$element', '$attrs', '$scope', '$animate']; -function FormController(element, attrs, $scope, $animate) { - var form = this, - parentForm = element.parent().controller('form') || nullFormCtrl, - controls = []; - - // init state - form.$error = {}; - form.$$success = {}; - form.$pending = undefined; - form.$name = attrs.name || attrs.ngForm; - form.$dirty = false; - form.$pristine = true; - form.$valid = true; - form.$invalid = false; - form.$submitted = false; - - parentForm.$addControl(form); - - // Setup initial state of the control - element.addClass(PRISTINE_CLASS); - - /** - * @ngdoc method - * @name form.FormController#$rollbackViewValue - * - * @description - * Rollback all form controls pending updates to the `$modelValue`. - * - * Updates may be pending by a debounced event or because the input is waiting for a some future - * event defined in `ng-model-options`. This method is typically needed by the reset button of - * a form that uses `ng-model-options` to pend updates. - */ - form.$rollbackViewValue = function() { - forEach(controls, function(control) { - control.$rollbackViewValue(); - }); - }; - - /** - * @ngdoc method - * @name form.FormController#$commitViewValue - * - * @description - * Commit all form controls pending updates to the `$modelValue`. - * - * Updates may be pending by a debounced event or because the input is waiting for a some future - * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` - * usually handles calling this in response to input events. - */ - form.$commitViewValue = function() { - forEach(controls, function(control) { - control.$commitViewValue(); - }); - }; - - /** - * @ngdoc method - * @name form.FormController#$addControl - * - * @description - * Register a control with the form. - * - * Input elements using ngModelController do this automatically when they are linked. - */ - form.$addControl = function(control) { - // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored - // and not added to the scope. Now we throw an error. - assertNotHasOwnProperty(control.$name, 'input'); - controls.push(control); - - if (control.$name) { - form[control.$name] = control; - } - }; - - /** - * @ngdoc method - * @name form.FormController#$removeControl - * - * @description - * Deregister a control from the form. - * - * Input elements using ngModelController do this automatically when they are destroyed. - */ - form.$removeControl = function(control) { - if (control.$name && form[control.$name] === control) { - delete form[control.$name]; - } - forEach(form.$pending, function(value, name) { - form.$setValidity(name, null, control); - }); - forEach(form.$error, function(value, name) { - form.$setValidity(name, null, control); - }); - - arrayRemove(controls, control); - }; - - - /** - * @ngdoc method - * @name form.FormController#$setValidity - * - * @description - * Sets the validity of a form control. - * - * This method will also propagate to parent forms. - */ - addSetValidityMethod({ - ctrl: this, - $element: element, - set: function(object, property, control) { - var list = object[property]; - if (!list) { - object[property] = [control]; - } else { - var index = list.indexOf(control); - if (index === -1) { - list.push(control); - } - } - }, - unset: function(object, property, control) { - var list = object[property]; - if (!list) { - return; - } - arrayRemove(list, control); - if (list.length === 0) { - delete object[property]; - } - }, - parentForm: parentForm, - $animate: $animate - }); - - /** - * @ngdoc method - * @name form.FormController#$setDirty - * - * @description - * Sets the form to a dirty state. - * - * This method can be called to add the 'ng-dirty' class and set the form to a dirty - * state (ng-dirty class). This method will also propagate to parent forms. - */ - form.$setDirty = function() { - $animate.removeClass(element, PRISTINE_CLASS); - $animate.addClass(element, DIRTY_CLASS); - form.$dirty = true; - form.$pristine = false; - parentForm.$setDirty(); - }; - - /** - * @ngdoc method - * @name form.FormController#$setPristine - * - * @description - * Sets the form to its pristine state. - * - * This method can be called to remove the 'ng-dirty' class and set the form to its pristine - * state (ng-pristine class). This method will also propagate to all the controls contained - * in this form. - * - * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after - * saving or resetting it. - */ - form.$setPristine = function () { - $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); - form.$dirty = false; - form.$pristine = true; - form.$submitted = false; - forEach(controls, function(control) { - control.$setPristine(); - }); - }; - - /** - * @ngdoc method - * @name form.FormController#$setSubmitted - * - * @description - * Sets the form to its submitted state. - */ - form.$setSubmitted = function () { - $animate.addClass(element, SUBMITTED_CLASS); - form.$submitted = true; - parentForm.$setSubmitted(); - }; -} - -/** - * @ngdoc directive - * @name ngForm - * @restrict EAC - * - * @description - * Nestable alias of {@link ng.directive:form `form`} directive. HTML - * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a - * sub-group of controls needs to be determined. - * - * Note: the purpose of `ngForm` is to group controls, - * but not to be a replacement for the `<form>` tag with all of its capabilities - * (e.g. posting to the server, ...). - * - * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into - * related scope, under this name. - * - */ - - /** - * @ngdoc directive - * @name form - * @restrict E - * - * @description - * Directive that instantiates - * {@link form.FormController FormController}. - * - * If the `name` attribute is specified, the form controller is published onto the current scope under - * this name. - * - * # Alias: {@link ng.directive:ngForm `ngForm`} - * - * In Angular forms can be nested. This means that the outer form is valid when all of the child - * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so - * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to - * `<form>` but can be nested. This allows you to have nested forms, which is very useful when - * using Angular validation directives in forms that are dynamically generated using the - * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` - * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an - * `ngForm` directive and nest these in an outer `form` element. - * - * - * # CSS classes - * - `ng-valid` is set if the form is valid. - * - `ng-invalid` is set if the form is invalid. - * - `ng-pristine` is set if the form is pristine. - * - `ng-dirty` is set if the form is dirty. - * - `ng-submitted` is set if the form was submitted. - * - * Keep in mind that ngAnimate can detect each of these classes when added and removed. - * - * - * # Submitting a form and preventing the default action - * - * Since the role of forms in client-side Angular applications is different than in classical - * roundtrip apps, it is desirable for the browser not to translate the form submission into a full - * page reload that sends the data to the server. Instead some javascript logic should be triggered - * to handle the form submission in an application-specific way. - * - * For this reason, Angular prevents the default action (form submission to the server) unless the - * `<form>` element has an `action` attribute specified. - * - * You can use one of the following two ways to specify what javascript method should be called when - * a form is submitted: - * - * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element - * - {@link ng.directive:ngClick ngClick} directive on the first - * button or input field of type submit (input[type=submit]) - * - * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} - * or {@link ng.directive:ngClick ngClick} directives. - * This is because of the following form submission rules in the HTML specification: - * - * - If a form has only one input field then hitting enter in this field triggers form submit - * (`ngSubmit`) - * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter - * doesn't trigger submit - * - if a form has one or more input fields and one or more buttons or input[type=submit] then - * hitting enter in any of the input fields will trigger the click handler on the *first* button or - * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) - * - * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is - * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` - * to have access to the updated model. - * - * ## Animation Hooks - * - * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. - * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any - * other validations that are performed within the form. Animations in ngForm are similar to how - * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well - * as JS animations. - * - * The following example shows a simple way to utilize CSS transitions to style a form element - * that has been rendered as invalid after it has been validated: - * - * <pre> - * //be sure to include ngAnimate as a module to hook into more - * //advanced animations - * .my-form { - * transition:0.5s linear all; - * background: white; - * } - * .my-form.ng-invalid { - * background: red; - * color:white; - * } - * </pre> - * - * @example - <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> - <file name="index.html"> - <script> - angular.module('formExample', []) - .controller('FormController', ['$scope', function($scope) { - $scope.userType = 'guest'; - }]); - </script> - <style> - .my-form { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; - background: transparent; - } - .my-form.ng-invalid { - background: red; - } - </style> - <form name="myForm" ng-controller="FormController" class="my-form"> - userType: <input name="input" ng-model="userType" required> - <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> - <tt>userType = {{userType}}</tt><br> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> - </form> - </file> - <file name="protractor.js" type="protractor"> - it('should initialize to model', function() { - var userType = element(by.binding('userType')); - var valid = element(by.binding('myForm.input.$valid')); - - expect(userType.getText()).toContain('guest'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - var userType = element(by.binding('userType')); - var valid = element(by.binding('myForm.input.$valid')); - var userInput = element(by.model('userType')); - - userInput.clear(); - userInput.sendKeys(''); - - expect(userType.getText()).toEqual('userType ='); - expect(valid.getText()).toContain('false'); - }); - </file> - </example> - * - * @param {string=} name Name of the form. If specified, the form controller will be published into - * related scope, under this name. - */ -var formDirectiveFactory = function(isNgForm) { - return ['$timeout', function($timeout) { - var formDirective = { - name: 'form', - restrict: isNgForm ? 'EAC' : 'E', - controller: FormController, - compile: function() { - return { - pre: function(scope, formElement, attr, controller) { - if (!attr.action) { - // we can't use jq events because if a form is destroyed during submission the default - // action is not prevented. see #1238 - // - // IE 9 is not affected because it doesn't fire a submit event and try to do a full - // page reload if the form was destroyed by submission of the form via a click handler - // on a button in the form. Looks like an IE9 specific bug. - var handleFormSubmission = function(event) { - scope.$apply(function() { - controller.$commitViewValue(); - controller.$setSubmitted(); - }); - - event.preventDefault - ? event.preventDefault() - : event.returnValue = false; // IE - }; - - addEventListenerFn(formElement[0], 'submit', handleFormSubmission); - - // unregister the preventDefault listener so that we don't not leak memory but in a - // way that will achieve the prevention of the default action. - formElement.on('$destroy', function() { - $timeout(function() { - removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); - }, 0, false); - }); - } - - var parentFormCtrl = formElement.parent().controller('form'), - alias = attr.name || attr.ngForm; - - if (alias) { - setter(scope, alias, controller, alias); - } - if (parentFormCtrl) { - formElement.on('$destroy', function() { - parentFormCtrl.$removeControl(controller); - if (alias) { - setter(scope, alias, undefined, alias); - } - extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards - }); - } - } - }; - } - }; - - return formDirective; - }]; -}; - -var formDirective = formDirectiveFactory(); -var ngFormDirective = formDirectiveFactory(true); - -/* global VALID_CLASS: true, - INVALID_CLASS: true, - PRISTINE_CLASS: true, - DIRTY_CLASS: true, - UNTOUCHED_CLASS: true, - TOUCHED_CLASS: true, -*/ - -// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 -var ISO_DATE_REGEXP = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/; -var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; -var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; -var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; -var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; -var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d))?$/; -var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; -var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; -var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d))?$/; -var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; - -var $ngModelMinErr = new minErr('ngModel'); - -var inputType = { - - /** - * @ngdoc input - * @name input[text] - * - * @description - * Standard HTML text input with angular data binding, inherited by most of the `input` elements. - * - * *NOTE* Not every feature offered is available for all input types. - * - * @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 Adds `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 - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. - * This parameter is ignored for input[type=password] controls, which will never trim the - * input. - * - * @example - <example name="text-input-directive" module="textInputExample"> - <file name="index.html"> - <script> - angular.module('textInputExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.text = 'guest'; - $scope.word = /^\s*\w*\s*$/; - }]); - </script> - <form name="myForm" ng-controller="ExampleController"> - Single word: <input type="text" name="input" ng-model="text" - ng-pattern="word" required ng-trim="false"> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.pattern"> - Single word only!</span> - - <tt>text = {{text}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var text = element(by.binding('text')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('text')); - - it('should initialize to model', function() { - expect(text.getText()).toContain('guest'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - input.clear(); - input.sendKeys(''); - - expect(text.getText()).toEqual('text ='); - expect(valid.getText()).toContain('false'); - }); - - it('should be invalid if multi word', function() { - input.clear(); - input.sendKeys('hello world'); - - expect(valid.getText()).toContain('false'); - }); - </file> - </example> - */ - 'text': textInputType, - - /** - * @ngdoc input - * @name input[date] - * - * @description - * Input with date validation and transformation. In browsers that do not yet support - * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 - * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many - * modern browsers do not yet support this input type, it is important to provide cues to users on the - * expected input format via a placeholder or label. The model must always be a Date object. - * - * The timezone to be used to read/write the `Date` instance in the model can be defined using - * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. - * - * @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 than `min`. This must be a - * valid ISO date string (yyyy-MM-dd). - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be - * a valid ISO date string (yyyy-MM-dd). - * @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 - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="date-input-directive" module="dateInputExample"> - <file name="index.html"> - <script> - angular.module('dateInputExample', []) - .controller('DateController', ['$scope', function($scope) { - $scope.value = new Date(2013, 9, 22); - }]); - </script> - <form name="myForm" ng-controller="DateController as dateCtrl"> - Pick a date in 2013: - <input type="date" id="exampleInput" name="input" ng-model="value" - placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.date"> - Not a valid date!</span> - <tt>value = {{value | date: "yyyy-MM-dd"}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var value = element(by.binding('value | date: "yyyy-MM-dd"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (see https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('2013-10-22'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('2015-01-01'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - </file> - </example> - */ - 'date': createDateInputType('date', DATE_REGEXP, - createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), - 'yyyy-MM-dd'), - - /** - * @ngdoc input - * @name input[dateTimeLocal] - * - * @description - * Input with datetime validation and transformation. In browsers that do not yet support - * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. The model must be a Date object. - * - * The timezone to be used to read/write the `Date` instance in the model can be defined using - * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. - * - * @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 than `min`. This must be a - * valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be - * a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). - * @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 - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="datetimelocal-input-directive" module="dateExample"> - <file name="index.html"> - <script> - angular.module('dateExample', []) - .controller('DateController', ['$scope', function($scope) { - $scope.value = new Date(2010, 11, 28, 14, 57); - }]); - </script> - <form name="myForm" ng-controller="DateController as dateCtrl"> - Pick a date between in 2013: - <input type="datetime-local" id="exampleInput" name="input" ng-model="value" - placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required /> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.datetimelocal"> - Not a valid date!</span> - <tt>value = {{value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var value = element(by.binding('value | date: "yyyy-MM-ddTHH:mm:ss"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('2010-12-28T14:57:00'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('2015-01-01T23:59:00'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - </file> - </example> - */ - 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, - createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss']), - 'yyyy-MM-ddTHH:mm:ss'), - - /** - * @ngdoc input - * @name input[time] - * - * @description - * Input with time validation and transformation. In browsers that do not yet support - * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a - * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. - * - * The timezone to be used to read/write the `Date` instance in the model can be defined using - * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. - * - * @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 than `min`. This must be a - * valid ISO time format (HH:mm:ss). - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be a - * valid ISO time format (HH:mm:ss). - * @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 - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="time-input-directive" module="timeExample"> - <file name="index.html"> - <script> - angular.module('timeExample', []) - .controller('DateController', ['$scope', function($scope) { - $scope.value = new Date(1970, 0, 1, 14, 57, 0); - }]); - </script> - <form name="myForm" ng-controller="DateController as dateCtrl"> - Pick a between 8am and 5pm: - <input type="time" id="exampleInput" name="input" ng-model="value" - placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required /> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.time"> - Not a valid date!</span> - <tt>value = {{value | date: "HH:mm:ss"}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var value = element(by.binding('value | date: "HH:mm:ss"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('14:57:00'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('23:59:00'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - </file> - </example> - */ - 'time': createDateInputType('time', TIME_REGEXP, - createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss']), - 'HH:mm:ss'), - - /** - * @ngdoc input - * @name input[week] - * - * @description - * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support - * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * week format (yyyy-W##), for example: `2013-W02`. The model must always be a Date object. - * - * The timezone to be used to read/write the `Date` instance in the model can be defined using - * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. - * - * @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 than `min`. This must be a - * valid ISO week format (yyyy-W##). - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be - * a valid ISO week format (yyyy-W##). - * @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 - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="week-input-directive" module="weekExample"> - <file name="index.html"> - <script> - angular.module('weekExample', []) - .controller('DateController', ['$scope', function($scope) { - $scope.value = new Date(2013, 0, 3); - }]); - </script> - <form name="myForm" ng-controller="DateController as dateCtrl"> - Pick a date between in 2013: - <input id="exampleInput" type="week" name="input" ng-model="value" - placeholder="YYYY-W##" min="2012-W32" max="2013-W52" required /> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.week"> - Not a valid date!</span> - <tt>value = {{value | date: "yyyy-Www"}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var value = element(by.binding('value | date: "yyyy-Www"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('2013-W01'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('2015-W01'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - </file> - </example> - */ - 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), - - /** - * @ngdoc input - * @name input[month] - * - * @description - * Input with month validation and transformation. In browsers that do not yet support - * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * month format (yyyy-MM), for example: `2009-01`. The model must always be a Date object. In the event the model is - * not set to the first of the month, the first of that model's month is assumed. - * - * The timezone to be used to read/write the `Date` instance in the model can be defined using - * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. - * - * @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 than `min`. This must be - * a valid ISO month format (yyyy-MM). - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must - * be a valid ISO month format (yyyy-MM). - * @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 - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="month-input-directive" module="monthExample"> - <file name="index.html"> - <script> - angular.module('monthExample', []) - .controller('DateController', ['$scope', function($scope) { - $scope.value = new Date(2013, 9, 1); - }]); - </script> - <form name="myForm" ng-controller="DateController as dateCtrl"> - Pick a month int 2013: - <input id="exampleInput" type="month" name="input" ng-model="value" - placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.month"> - Not a valid month!</span> - <tt>value = {{value | date: "yyyy-MM"}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var value = element(by.binding('value | date: "yyyy-MM"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('2013-10'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('2015-01'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - </file> - </example> - */ - 'month': createDateInputType('month', MONTH_REGEXP, - createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), - 'yyyy-MM'), - - /** - * @ngdoc input - * @name input[number] - * - * @description - * Text input with number validation and transformation. Sets the `number` validation - * error if not a valid number. - * - * @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 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 - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="number-input-directive" module="numberExample"> - <file name="index.html"> - <script> - angular.module('numberExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.value = 12; - }]); - </script> - <form name="myForm" ng-controller="ExampleController"> - Number: <input type="number" name="input" ng-model="value" - min="0" max="99" required> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.number"> - Not valid number!</span> - <tt>value = {{value}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var value = element(by.binding('value')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); - - it('should initialize to model', function() { - expect(value.getText()).toContain('12'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - input.clear(); - input.sendKeys(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('false'); - }); - - it('should be invalid if over max', function() { - input.clear(); - input.sendKeys('123'); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('false'); - }); - </file> - </example> - */ - 'number': numberInputType, - - - /** - * @ngdoc input - * @name input[url] - * - * @description - * Text input with URL validation. Sets the `url` validation error key if the content is not a - * valid URL. - * - * @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 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 - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="url-input-directive" module="urlExample"> - <file name="index.html"> - <script> - angular.module('urlExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.text = 'http://google.com'; - }]); - </script> - <form name="myForm" ng-controller="ExampleController"> - URL: <input type="url" name="input" ng-model="text" required> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.url"> - Not valid url!</span> - <tt>text = {{text}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var text = element(by.binding('text')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('text')); - - it('should initialize to model', function() { - expect(text.getText()).toContain('http://google.com'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - input.clear(); - input.sendKeys(''); - - expect(text.getText()).toEqual('text ='); - expect(valid.getText()).toContain('false'); - }); - - it('should be invalid if not url', function() { - input.clear(); - input.sendKeys('box'); - - expect(valid.getText()).toContain('false'); - }); - </file> - </example> - */ - 'url': urlInputType, - - - /** - * @ngdoc input - * @name input[email] - * - * @description - * Text input with email validation. Sets the `email` validation error key if not a valid email - * address. - * - * @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 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 - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="email-input-directive" module="emailExample"> - <file name="index.html"> - <script> - angular.module('emailExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.text = 'me@example.com'; - }]); - </script> - <form name="myForm" ng-controller="ExampleController"> - Email: <input type="email" name="input" ng-model="text" required> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.email"> - Not valid email!</span> - <tt>text = {{text}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var text = element(by.binding('text')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('text')); - - it('should initialize to model', function() { - expect(text.getText()).toContain('me@example.com'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - input.clear(); - input.sendKeys(''); - expect(text.getText()).toEqual('text ='); - expect(valid.getText()).toContain('false'); - }); - - it('should be invalid if not email', function() { - input.clear(); - input.sendKeys('xxx'); - - expect(valid.getText()).toContain('false'); - }); - </file> - </example> - */ - 'email': emailInputType, - - - /** - * @ngdoc input - * @name input[radio] - * - * @description - * HTML radio button. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string} value The value to which the expression should be set when selected. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * @param {string} ngValue Angular expression which sets the value to which the expression should - * be set when selected. - * - * @example - <example name="radio-input-directive" module="radioExample"> - <file name="index.html"> - <script> - angular.module('radioExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.color = 'blue'; - $scope.specialValue = { - "id": "12345", - "value": "green" - }; - }]); - </script> - <form name="myForm" ng-controller="ExampleController"> - <input type="radio" ng-model="color" value="red"> Red <br/> - <input type="radio" ng-model="color" ng-value="specialValue"> Green <br/> - <input type="radio" ng-model="color" value="blue"> Blue <br/> - <tt>color = {{color | json}}</tt><br/> - </form> - Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. - </file> - <file name="protractor.js" type="protractor"> - it('should change state', function() { - var color = element(by.binding('color')); - - expect(color.getText()).toContain('blue'); - - element.all(by.model('color')).get(0).click(); - - expect(color.getText()).toContain('red'); - }); - </file> - </example> - */ - 'radio': radioInputType, - - - /** - * @ngdoc input - * @name input[checkbox] - * - * @description - * HTML checkbox. - * - * @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 {expression=} ngTrueValue The value to which the expression should be set when selected. - * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="checkbox-input-directive" module="checkboxExample"> - <file name="index.html"> - <script> - angular.module('checkboxExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.value1 = true; - $scope.value2 = 'YES' - }]); - </script> - <form name="myForm" ng-controller="ExampleController"> - Value1: <input type="checkbox" ng-model="value1"> <br/> - Value2: <input type="checkbox" ng-model="value2" - ng-true-value="'YES'" ng-false-value="'NO'"> <br/> - <tt>value1 = {{value1}}</tt><br/> - <tt>value2 = {{value2}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - it('should change state', function() { - var value1 = element(by.binding('value1')); - var value2 = element(by.binding('value2')); - - expect(value1.getText()).toContain('true'); - expect(value2.getText()).toContain('YES'); - - element(by.model('value1')).click(); - element(by.model('value2')).click(); - - expect(value1.getText()).toContain('false'); - expect(value2.getText()).toContain('NO'); - }); - </file> - </example> - */ - 'checkbox': checkboxInputType, - - 'hidden': noop, - 'button': noop, - 'submit': noop, - 'reset': noop, - 'file': noop -}; - -function testFlags(validity, flags) { - var i, flag; - if (flags) { - for (i=0; i<flags.length; ++i) { - flag = flags[i]; - if (validity[flag]) { - return true; - } - } - } - return false; -} - -function stringBasedInputType(ctrl) { - ctrl.$formatters.push(function(value) { - return ctrl.$isEmpty(value) ? value : value.toString(); - }); -} - -function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { - baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - stringBasedInputType(ctrl); -} - -function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { - var validity = element.prop(VALIDITY_STATE_PROPERTY); - var placeholder = element[0].placeholder, noevent = {}; - var type = lowercase(element[0].type); - - // In composition mode, users are still inputing intermediate text buffer, - // hold the listener until composition is done. - // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent - if (!$sniffer.android) { - var composing = false; - - element.on('compositionstart', function(data) { - composing = true; - }); - - element.on('compositionend', function() { - composing = false; - listener(); - }); - } - - var listener = function(ev) { - if (composing) return; - var value = element.val(), - event = ev && ev.type; - - // IE (11 and under) seem to emit an 'input' event if the placeholder value changes. - // We don't want to dirty the value when this happens, so we abort here. Unfortunately, - // IE also sends input events for other non-input-related things, (such as focusing on a - // form control), so this change is not entirely enough to solve this. - if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) { - placeholder = element[0].placeholder; - return; - } - - // By default we will trim the value - // If the attribute ng-trim exists we will avoid trimming - // If input type is 'password', the value is never trimmed - if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { - value = trim(value); - } - - // If a control is suffering from bad input (due to native validators), browsers discard its - // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the - // control's value is the same empty value twice in a row. - if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { - ctrl.$setViewValue(value, event); - } - }; - - // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the - // input event on backspace, delete or cut - if ($sniffer.hasEvent('input')) { - element.on('input', listener); - } else { - var timeout; - - var deferListener = function(ev) { - if (!timeout) { - timeout = $browser.defer(function() { - listener(ev); - timeout = null; - }); - } - }; - - element.on('keydown', function(event) { - var key = event.keyCode; - - // ignore - // command modifiers arrows - if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; - - deferListener(event); - }); - - // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it - if ($sniffer.hasEvent('paste')) { - element.on('paste cut', deferListener); - } - } - - // if user paste into input using mouse on older browser - // or form autocomplete on newer browser, we need "change" event to catch it - element.on('change', listener); - - ctrl.$render = function() { - element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); - }; -} - -function weekParser(isoWeek) { - if (isDate(isoWeek)) { - return isoWeek; - } - - if (isString(isoWeek)) { - WEEK_REGEXP.lastIndex = 0; - var parts = WEEK_REGEXP.exec(isoWeek); - if (parts) { - var year = +parts[1], - week = +parts[2], - firstThurs = getFirstThursdayOfYear(year), - addDays = (week - 1) * 7; - return new Date(year, 0, firstThurs.getDate() + addDays); - } - } - - return NaN; -} - -function createDateParser(regexp, mapping) { - return function(iso) { - var parts, map; - - if (isDate(iso)) { - return iso; - } - - if (isString(iso)) { - // When a date is JSON'ified to wraps itself inside of an extra - // set of double quotes. This makes the date parsing code unable - // to match the date string and parse it as a date. - if (iso.charAt(0) == '"' && iso.charAt(iso.length-1) == '"') { - iso = iso.substring(1, iso.length-1); - } - if (ISO_DATE_REGEXP.test(iso)) { - return new Date(iso); - } - regexp.lastIndex = 0; - parts = regexp.exec(iso); - - if (parts) { - parts.shift(); - map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0 }; - - forEach(parts, function(part, index) { - if (index < mapping.length) { - map[mapping[index]] = +part; - } - }); - return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0); - } - } - - return NaN; - }; -} - -function createDateInputType(type, regexp, parseDate, format) { - return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { - badInputChecker(scope, element, attr, ctrl); - baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; - - ctrl.$$parserName = type; - ctrl.$parsers.push(function(value) { - if (ctrl.$isEmpty(value)) return null; - if (regexp.test(value)) { - var parsedDate = parseDate(value); - if (timezone === 'UTC') { - parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset()); - } - return parsedDate; - } - return undefined; - }); - - ctrl.$formatters.push(function(value) { - if (isDate(value)) { - return $filter('date')(value, format, timezone); - } - return ''; - }); - - if (isDefined(attr.min) || attr.ngMin) { - var minVal; - ctrl.$validators.min = function(value) { - return ctrl.$isEmpty(value) || isUndefined(minVal) || parseDate(value) >= minVal; - }; - attr.$observe('min', function(val) { - minVal = parseObservedDateValue(val); - ctrl.$validate(); - }); - } - - if (isDefined(attr.max) || attr.ngMax) { - var maxVal; - ctrl.$validators.max = function(value) { - return ctrl.$isEmpty(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; - }; - attr.$observe('max', function(val) { - maxVal = parseObservedDateValue(val); - ctrl.$validate(); - }); - } - - function parseObservedDateValue(val) { - return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined; - } - }; -} - -function badInputChecker(scope, element, attr, ctrl) { - var node = element[0]; - var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); - if (nativeValidation) { - ctrl.$parsers.push(function(value) { - var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; - // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430): - // - also sets validity.badInput (should only be validity.typeMismatch). - // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email) - // - can ignore this case as we can still read out the erroneous email... - return validity.badInput && !validity.typeMismatch ? undefined : value; - }); - } -} - -function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { - badInputChecker(scope, element, attr, ctrl); - baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - - ctrl.$$parserName = 'number'; - ctrl.$parsers.push(function(value) { - if (ctrl.$isEmpty(value)) return null; - if (NUMBER_REGEXP.test(value)) return parseFloat(value); - return undefined; - }); - - ctrl.$formatters.push(function(value) { - if (!ctrl.$isEmpty(value)) { - if (!isNumber(value)) { - throw $ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); - } - value = value.toString(); - } - return value; - }); - - if (attr.min || attr.ngMin) { - var minVal; - ctrl.$validators.min = function(value) { - return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; - }; - - attr.$observe('min', function(val) { - if (isDefined(val) && !isNumber(val)) { - val = parseFloat(val, 10); - } - minVal = isNumber(val) && !isNaN(val) ? val : undefined; - // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); - }); - } - - if (attr.max || attr.ngMax) { - var maxVal; - ctrl.$validators.max = function(value) { - return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; - }; - - attr.$observe('max', function(val) { - if (isDefined(val) && !isNumber(val)) { - val = parseFloat(val, 10); - } - maxVal = isNumber(val) && !isNaN(val) ? val : undefined; - // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); - }); - } -} - -function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { - // Note: no badInputChecker here by purpose as `url` is only a validation - // in browsers, i.e. we can always read out input.value even if it is not valid! - baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - stringBasedInputType(ctrl); - - ctrl.$$parserName = 'url'; - ctrl.$validators.url = function(modelValue, viewValue) { - var value = modelValue || viewValue; - return ctrl.$isEmpty(value) || URL_REGEXP.test(value); - }; -} - -function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { - // Note: no badInputChecker here by purpose as `url` is only a validation - // in browsers, i.e. we can always read out input.value even if it is not valid! - baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - stringBasedInputType(ctrl); - - ctrl.$$parserName = 'email'; - ctrl.$validators.email = function(modelValue, viewValue) { - var value = modelValue || viewValue; - return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); - }; -} - -function radioInputType(scope, element, attr, ctrl) { - // make the name unique, if not defined - if (isUndefined(attr.name)) { - element.attr('name', nextUid()); - } - - var listener = function(ev) { - if (element[0].checked) { - ctrl.$setViewValue(attr.value, ev && ev.type); - } - }; - - element.on('click', listener); - - ctrl.$render = function() { - var value = attr.value; - element[0].checked = (value == ctrl.$viewValue); - }; - - attr.$observe('value', ctrl.$render); -} - -function parseConstantExpr($parse, context, name, expression, fallback) { - var parseFn; - if (isDefined(expression)) { - parseFn = $parse(expression); - if (!parseFn.constant) { - throw minErr('ngModel')('constexpr', 'Expected constant expression for `{0}`, but saw ' + - '`{1}`.', name, expression); - } - return parseFn(context); - } - return fallback; -} - -function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { - var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); - var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); - - var listener = function(ev) { - ctrl.$setViewValue(element[0].checked, ev && ev.type); - }; - - element.on('click', listener); - - ctrl.$render = function() { - element[0].checked = ctrl.$viewValue; - }; - - // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. - ctrl.$isEmpty = function(value) { - return value !== trueValue; - }; - - ctrl.$formatters.push(function(value) { - return equals(value, trueValue); - }); - - ctrl.$parsers.push(function(value) { - return value ? trueValue : falseValue; - }); -} - - -/** - * @ngdoc directive - * @name textarea - * @restrict E - * - * @description - * HTML textarea element control with angular data-binding. The data-binding and validation - * properties of this element are exactly the same as those of the - * {@link ng.directive:input input element}. - * - * @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 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 - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. - */ - - -/** - * @ngdoc directive - * @name input - * @restrict E - * - * @description - * HTML input element control with angular data-binding. Input control follows HTML5 input types - * and polyfills the HTML5 validation behavior for older browsers. - * - * *NOTE* Not every feature offered is available for all input types. - * - * @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 Sets `required` validation error key if the value is not entered. - * @param {boolean=} ngRequired Sets `required` attribute if set to true - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. - * This parameter is ignored for input[type=password] controls, which will never trim the - * input. - * - * @example - <example name="input-directive" module="inputExample"> - <file name="index.html"> - <script> - angular.module('inputExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.user = {name: 'guest', last: 'visitor'}; - }]); - </script> - <div ng-controller="ExampleController"> - <form name="myForm"> - User name: <input type="text" name="userName" ng-model="user.name" required> - <span class="error" ng-show="myForm.userName.$error.required"> - Required!</span><br> - Last name: <input type="text" name="lastName" ng-model="user.last" - ng-minlength="3" ng-maxlength="10"> - <span class="error" ng-show="myForm.lastName.$error.minlength"> - Too short!</span> - <span class="error" ng-show="myForm.lastName.$error.maxlength"> - Too long!</span><br> - </form> - <hr> - <tt>user = {{user}}</tt><br/> - <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br> - <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br> - <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br> - <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> - <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br> - <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br> - </div> - </file> - <file name="protractor.js" type="protractor"> - var user = element(by.exactBinding('user')); - var userNameValid = element(by.binding('myForm.userName.$valid')); - var lastNameValid = element(by.binding('myForm.lastName.$valid')); - var lastNameError = element(by.binding('myForm.lastName.$error')); - var formValid = element(by.binding('myForm.$valid')); - var userNameInput = element(by.model('user.name')); - var userLastInput = element(by.model('user.last')); - - it('should initialize to model', function() { - expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); - expect(userNameValid.getText()).toContain('true'); - expect(formValid.getText()).toContain('true'); - }); - - it('should be invalid if empty when required', function() { - userNameInput.clear(); - userNameInput.sendKeys(''); - - expect(user.getText()).toContain('{"last":"visitor"}'); - expect(userNameValid.getText()).toContain('false'); - expect(formValid.getText()).toContain('false'); - }); - - it('should be valid if empty when min length is set', function() { - userLastInput.clear(); - userLastInput.sendKeys(''); - - expect(user.getText()).toContain('{"name":"guest","last":""}'); - expect(lastNameValid.getText()).toContain('true'); - expect(formValid.getText()).toContain('true'); - }); - - it('should be invalid if less than required min length', function() { - userLastInput.clear(); - userLastInput.sendKeys('xx'); - - expect(user.getText()).toContain('{"name":"guest"}'); - expect(lastNameValid.getText()).toContain('false'); - expect(lastNameError.getText()).toContain('minlength'); - expect(formValid.getText()).toContain('false'); - }); - - it('should be invalid if longer than max length', function() { - userLastInput.clear(); - userLastInput.sendKeys('some ridiculously long name'); - - expect(user.getText()).toContain('{"name":"guest"}'); - expect(lastNameValid.getText()).toContain('false'); - expect(lastNameError.getText()).toContain('maxlength'); - expect(formValid.getText()).toContain('false'); - }); - </file> - </example> - */ -var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', - function($browser, $sniffer, $filter, $parse) { - return { - restrict: 'E', - require: ['?ngModel'], - link: function(scope, element, attr, ctrls) { - if (ctrls[0]) { - (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, - $browser, $filter, $parse); - } - } - }; -}]; - -var VALID_CLASS = 'ng-valid', - INVALID_CLASS = 'ng-invalid', - PRISTINE_CLASS = 'ng-pristine', - DIRTY_CLASS = 'ng-dirty', - UNTOUCHED_CLASS = 'ng-untouched', - TOUCHED_CLASS = 'ng-touched', - PENDING_CLASS = 'ng-pending'; - -/** - * @ngdoc type - * @name ngModel.NgModelController - * - * @property {string} $viewValue Actual string value in the view. - * @property {*} $modelValue The value in the model, that the control is bound to. - * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever - the control reads value from the DOM. Each function is called, in turn, passing the value - through to the next. The last return value is used to populate the model. - Used to sanitize / convert the value as well as validation. For validation, - the parsers should update the validity state using - {@link ngModel.NgModelController#$setValidity $setValidity()}, - and return `undefined` for invalid values. - - * - * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever - the model value changes. Each function is called, in turn, passing the value through to the - next. Used to format / convert values for display in the control and validation. - * ```js - * function formatter(value) { - * if (value) { - * return value.toUpperCase(); - * } - * } - * ngModel.$formatters.push(formatter); - * ``` - * - * @property {Object.<string, function>} $validators A collection of validators that are applied - * whenever the model value changes. The key value within the object refers to the name of the - * validator while the function refers to the validation operation. The validation operation is - * provided with the model value as an argument and must return a true or false value depending - * on the response of that validation. - * - * ```js - * ngModel.$validators.validCharacters = function(modelValue, viewValue) { - * var value = modelValue || viewValue; - * return /[0-9]+/.test(value) && - * /[a-z]+/.test(value) && - * /[A-Z]+/.test(value) && - * /\W+/.test(value); - * }; - * ``` - * - * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to - * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided - * is expected to return a promise when it is run during the model validation process. Once the promise - * is delivered then the validation status will be set to true when fulfilled and false when rejected. - * When the asynchronous validators are triggered, each of the validators will run in parallel and the model - * value will only be updated once all validators have been fulfilled. Also, keep in mind that all - * asynchronous validators will only run once all synchronous validators have passed. - * - * Please note that if $http is used then it is important that the server returns a success HTTP response code - * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. - * - * ```js - * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { - * var value = modelValue || viewValue; - * - * // Lookup user by username - * return $http.get('/api/users/' + value). - * then(function resolved() { - * //username exists, this means validation fails - * return $q.reject('exists'); - * }, function rejected() { - * //username does not exist, therefore this validation passes - * return true; - * }); - * }; - * ``` - * - * @param {string} name The name of the validator. - * @param {Function} validationFn The validation function that will be run. - * - * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the - * view value has changed. It is called with no arguments, and its return value is ignored. - * This can be used in place of additional $watches against the model value. - * - * @property {Object} $error An object hash with all failing validator ids as keys. - * @property {Object} $pending An object hash with all pending validator ids as keys. - * - * @property {boolean} $untouched True if control has not lost focus yet. - * @property {boolean} $touched True if control has lost focus. - * @property {boolean} $pristine True if user has not interacted with the control yet. - * @property {boolean} $dirty True if user has already interacted with the control. - * @property {boolean} $valid True if there is no error. - * @property {boolean} $invalid True if at least one error on the control. - * - * @description - * - * `NgModelController` provides API for the `ng-model` directive. The controller contains - * services for data-binding, validation, CSS updates, and value formatting and parsing. It - * purposefully does not contain any logic which deals with DOM rendering or listening to - * DOM events. Such DOM related logic should be provided by other directives which make use of - * `NgModelController` for data-binding. - * - * ## Custom Control Example - * This example shows how to use `NgModelController` with a custom control to achieve - * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) - * collaborate together to achieve the desired result. - * - * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element - * contents be edited in place by the user. This will not work on older browsers. - * - * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} - * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). - * However, as we are using `$sce` the model can still decide to to provide unsafe content if it marks - * that content using the `$sce` service. - * - * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> - <file name="style.css"> - [contenteditable] { - border: 1px solid black; - background-color: white; - min-height: 20px; - } - - .ng-invalid { - border: 1px solid red; - } - - </file> - <file name="script.js"> - angular.module('customControl', ['ngSanitize']). - directive('contenteditable', ['$sce', function($sce) { - return { - restrict: 'A', // only activate on element attribute - require: '?ngModel', // get a hold of NgModelController - link: function(scope, element, attrs, ngModel) { - if (!ngModel) return; // do nothing if no ng-model - - // Specify how UI should be updated - ngModel.$render = function() { - element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); - }; - - // Listen for change events to enable binding - element.on('blur keyup change', function() { - scope.$apply(read); - }); - read(); // initialize - - // Write data to the model - function read() { - var html = element.html(); - // When we clear the content editable the browser leaves a <br> behind - // If strip-br attribute is provided then we strip this out - if ( attrs.stripBr && html == '<br>' ) { - html = ''; - } - ngModel.$setViewValue(html); - } - } - }; - }]); - </file> - <file name="index.html"> - <form name="myForm"> - <div contenteditable - name="myWidget" ng-model="userContent" - strip-br="true" - required>Change me!</div> - <span ng-show="myForm.myWidget.$error.required">Required!</span> - <hr> - <textarea ng-model="userContent"></textarea> - </form> - </file> - <file name="protractor.js" type="protractor"> - it('should data-bind and become invalid', function() { - if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { - // SafariDriver can't handle contenteditable - // and Firefox driver can't clear contenteditables very well - return; - } - var contentEditable = element(by.css('[contenteditable]')); - var content = 'Change me!'; - - expect(contentEditable.getText()).toEqual(content); - - contentEditable.clear(); - contentEditable.sendKeys(protractor.Key.BACK_SPACE); - expect(contentEditable.getText()).toEqual(''); - expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); - }); - </file> - * </example> - * - * - */ -var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', - function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q) { - this.$viewValue = Number.NaN; - this.$modelValue = Number.NaN; - this.$validators = {}; - this.$asyncValidators = {}; - this.$parsers = []; - this.$formatters = []; - this.$viewChangeListeners = []; - this.$untouched = true; - this.$touched = false; - this.$pristine = true; - this.$dirty = false; - this.$valid = true; - this.$invalid = false; - this.$error = {}; // keep invalid keys here - this.$$success = {}; // keep valid keys here - this.$pending = undefined; // keep pending keys here - this.$name = $attr.name; - - - var parsedNgModel = $parse($attr.ngModel), - pendingDebounce = null, - ctrl = this; - - var ngModelGet = function ngModelGet() { - var modelValue = parsedNgModel($scope); - if (ctrl.$options && ctrl.$options.getterSetter && isFunction(modelValue)) { - modelValue = modelValue(); - } - return modelValue; - }; - - var ngModelSet = function ngModelSet(newValue) { - var getterSetter; - if (ctrl.$options && ctrl.$options.getterSetter && - isFunction(getterSetter = parsedNgModel($scope))) { - - getterSetter(ctrl.$modelValue); - } else { - parsedNgModel.assign($scope, ctrl.$modelValue); - } - }; - - this.$$setOptions = function(options) { - ctrl.$options = options; - - if (!parsedNgModel.assign && (!options || !options.getterSetter)) { - throw $ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", - $attr.ngModel, startingTag($element)); - } - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$render - * - * @description - * Called when the view needs to be updated. It is expected that the user of the ng-model - * directive will implement this method. - * - * The `$render()` method is invoked in the following situations: - * - * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last - * committed value then `$render()` is called to update the input control. - * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and - * the `$viewValue` are different to last time. - * - * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of - * `$modelValue` and `$viewValue` are actually different to their previous value. If `$modelValue` - * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be - * invoked if you only change a property on the objects. - */ - this.$render = noop; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$isEmpty - * - * @description - * This is called when we need to determine if the value of the input is empty. - * - * For instance, the required directive does this to work out if the input has data or not. - * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. - * - * You can override this for input directives whose concept of being empty is different to the - * default. The `checkboxInputType` directive does this because in its case a value of `false` - * implies empty. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is empty. - */ - this.$isEmpty = function(value) { - return isUndefined(value) || value === '' || value === null || value !== value; - }; - - var parentForm = $element.inheritedData('$formController') || nullFormCtrl, - currentValidationRunId = 0; - - // Setup initial state of the control - $element - .addClass(PRISTINE_CLASS) - .addClass(UNTOUCHED_CLASS); - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setValidity - * - * @description - * Change the validity state, and notifies the form. - * - * This method can be called within $parsers/$formatters. However, if possible, please use the - * `ngModel.$validators` pipeline which is designed to call this method automatically. - * - * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign - * to `$error[validationErrorKey]` and `$pending[validationErrorKey]` - * so that it is available for data-binding. - * The `validationErrorKey` should be in camelCase and will get converted into dash-case - * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` - * class and can be bound to as `{{someForm.someControl.$error.myError}}` . - * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), - * or skipped (null). - */ - addSetValidityMethod({ - ctrl: this, - $element: $element, - set: function(object, property) { - object[property] = true; - }, - unset: function(object, property) { - delete object[property]; - }, - parentForm: parentForm, - $animate: $animate - }); - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setPristine - * - * @description - * Sets the control to its pristine state. - * - * This method can be called to remove the 'ng-dirty' class and set the control to its pristine - * state (ng-pristine class). A model is considered to be pristine when the model has not been changed - * from when first compiled within then form. - */ - this.$setPristine = function () { - ctrl.$dirty = false; - ctrl.$pristine = true; - $animate.removeClass($element, DIRTY_CLASS); - $animate.addClass($element, PRISTINE_CLASS); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setUntouched - * - * @description - * Sets the control to its untouched state. - * - * This method can be called to remove the 'ng-touched' class and set the control to its - * untouched state (ng-untouched class). Upon compilation, a model is set as untouched - * by default, however this function can be used to restore that state if the model has - * already been touched by the user. - */ - this.$setUntouched = function() { - ctrl.$touched = false; - ctrl.$untouched = true; - $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setTouched - * - * @description - * Sets the control to its touched state. - * - * This method can be called to remove the 'ng-untouched' class and set the control to its - * touched state (ng-touched class). A model is considered to be touched when the user has - * first interacted (focussed) on the model input element and then shifted focus away (blurred) - * from the input element. - */ - this.$setTouched = function() { - ctrl.$touched = true; - ctrl.$untouched = false; - $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$rollbackViewValue - * - * @description - * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, - * which may be caused by a pending debounced event or because the input is waiting for a some - * future event. - * - * If you have an input that uses `ng-model-options` to set up debounced events or events such - * as blur you can have a situation where there is a period when the `$viewValue` - * is out of synch with the ngModel's `$modelValue`. - * - * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue` - * programmatically before these debounced/future events have resolved/occurred, because Angular's - * dirty checking mechanism is not able to tell whether the model has actually changed or not. - * - * The `$rollbackViewValue()` method should be called before programmatically changing the model of an - * input which may have such events pending. This is important in order to make sure that the - * input field will be updated with the new model value and any pending operations are cancelled. - * - * <example name="ng-model-cancel-update" module="cancel-update-example"> - * <file name="app.js"> - * angular.module('cancel-update-example', []) - * - * .controller('CancelUpdateController', ['$scope', function($scope) { - * $scope.resetWithCancel = function (e) { - * if (e.keyCode == 27) { - * $scope.myForm.myInput1.$rollbackViewValue(); - * $scope.myValue = ''; - * } - * }; - * $scope.resetWithoutCancel = function (e) { - * if (e.keyCode == 27) { - * $scope.myValue = ''; - * } - * }; - * }]); - * </file> - * <file name="index.html"> - * <div ng-controller="CancelUpdateController"> - * <p>Try typing something in each input. See that the model only updates when you - * blur off the input. - * </p> - * <p>Now see what happens if you start typing then press the Escape key</p> - * - * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> - * <p>With $rollbackViewValue()</p> - * <input name="myInput1" ng-model="myValue" ng-keydown="resetWithCancel($event)"><br/> - * myValue: "{{ myValue }}" - * - * <p>Without $rollbackViewValue()</p> - * <input name="myInput2" ng-model="myValue" ng-keydown="resetWithoutCancel($event)"><br/> - * myValue: "{{ myValue }}" - * </form> - * </div> - * </file> - * </example> - */ - this.$rollbackViewValue = function() { - $timeout.cancel(pendingDebounce); - ctrl.$viewValue = ctrl.$$lastCommittedViewValue; - ctrl.$render(); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$validate - * - * @description - * Runs each of the registered validators (first synchronous validators and then asynchronous validators). - */ - this.$validate = function() { - // ignore $validate before model is initialized - if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { - return; - } - this.$$parseAndValidate(); - }; - - this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) { - currentValidationRunId++; - var localValidationRunId = currentValidationRunId; - - // check parser error - if (!processParseErrors(parseValid)) { - return; - } - if (!processSyncValidators()) { - return; - } - processAsyncValidators(); - - function processParseErrors(parseValid) { - var errorKey = ctrl.$$parserName || 'parse'; - if (parseValid === undefined) { - setValidity(errorKey, null); - } else { - setValidity(errorKey, parseValid); - if (!parseValid) { - forEach(ctrl.$validators, function(v, name) { - setValidity(name, null); - }); - forEach(ctrl.$asyncValidators, function(v, name) { - setValidity(name, null); - }); - validationDone(); - return false; - } - } - return true; - } - - function processSyncValidators() { - var syncValidatorsValid = true; - forEach(ctrl.$validators, function(validator, name) { - var result = validator(modelValue, viewValue); - syncValidatorsValid = syncValidatorsValid && result; - setValidity(name, result); - }); - if (!syncValidatorsValid) { - forEach(ctrl.$asyncValidators, function(v, name) { - setValidity(name, null); - }); - validationDone(); - return false; - } - return true; - } - - function processAsyncValidators() { - var validatorPromises = []; - forEach(ctrl.$asyncValidators, function(validator, name) { - var promise = validator(modelValue, viewValue); - if (!isPromiseLike(promise)) { - throw $ngModelMinErr("$asyncValidators", - "Expected asynchronous validator to return a promise but got '{0}' instead.", promise); - } - setValidity(name, undefined); - validatorPromises.push(promise.then(function() { - setValidity(name, true); - }, function(error) { - setValidity(name, false); - })); - }); - if (!validatorPromises.length) { - validationDone(); - } else { - $q.all(validatorPromises).then(validationDone); - } - } - - function setValidity(name, isValid) { - if (localValidationRunId === currentValidationRunId) { - ctrl.$setValidity(name, isValid); - } - } - - function validationDone() { - if (localValidationRunId === currentValidationRunId) { - - doneCallback(); - } - } - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$commitViewValue - * - * @description - * Commit a pending update to the `$modelValue`. - * - * Updates may be pending by a debounced event or because the input is waiting for a some future - * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` - * usually handles calling this in response to input events. - */ - this.$commitViewValue = function() { - var viewValue = ctrl.$viewValue; - - $timeout.cancel(pendingDebounce); - - // If the view value has not changed then we should just exit, except in the case where there is - // a native validator on the element. In this case the validation state may have changed even though - // the viewValue has stayed empty. - if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { - return; - } - ctrl.$$lastCommittedViewValue = viewValue; - - // change to dirty - if (ctrl.$pristine) { - ctrl.$dirty = true; - ctrl.$pristine = false; - $animate.removeClass($element, PRISTINE_CLASS); - $animate.addClass($element, DIRTY_CLASS); - parentForm.$setDirty(); - } - this.$$parseAndValidate(); - }; - - this.$$parseAndValidate = function() { - var parserValid = true, - viewValue = ctrl.$$lastCommittedViewValue, - modelValue = viewValue; - for(var i = 0; i < ctrl.$parsers.length; i++) { - modelValue = ctrl.$parsers[i](modelValue); - if (isUndefined(modelValue)) { - parserValid = false; - break; - } - } - if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { - // ctrl.$modelValue has not been touched yet... - ctrl.$modelValue = ngModelGet(); - } - var prevModelValue = ctrl.$modelValue; - var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; - if (allowInvalid) { - ctrl.$modelValue = modelValue; - writeToModelIfNeeded(); - } - ctrl.$$runValidators(parserValid, modelValue, viewValue, function() { - if (!allowInvalid) { - ctrl.$modelValue = ctrl.$valid ? modelValue : undefined; - writeToModelIfNeeded(); - } - }); - - function writeToModelIfNeeded() { - if (ctrl.$modelValue !== prevModelValue) { - ctrl.$$writeModelToScope(); - } - } - }; - - this.$$writeModelToScope = function() { - ngModelSet(ctrl.$modelValue); - forEach(ctrl.$viewChangeListeners, function(listener) { - try { - listener(); - } catch(e) { - $exceptionHandler(e); - } - }); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setViewValue - * - * @description - * Update the view value. - * - * This method should be called when an input directive want to change the view value; typically, - * this is done from within a DOM event handler. - * - * For example {@link ng.directive:input input} calls it when the value of the input changes and - * {@link ng.directive:select select} calls it when an option is selected. - * - * If the new `value` is an object (rather than a string or a number), we should make a copy of the - * object before passing it to `$setViewValue`. This is because `ngModel` does not perform a deep - * watch of objects, it only looks for a change of identity. If you only change the property of - * the object then ngModel will not realise that the object has changed and will not invoke the - * `$parsers` and `$validators` pipelines. - * - * For this reason, you should not change properties of the copy once it has been passed to - * `$setViewValue`. Otherwise you may cause the model value on the scope to change incorrectly. - * - * When this method is called, the new `value` will be staged for committing through the `$parsers` - * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged - * value sent directly for processing, finally to be applied to `$modelValue` and then the - * **expression** specified in the `ng-model` attribute. - * - * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. - * - * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` - * and the `default` trigger is not listed, all those actions will remain pending until one of the - * `updateOn` events is triggered on the DOM element. - * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} - * directive is used with a custom debounce for this particular event. - * - * Note that calling this function does not trigger a `$digest`. - * - * @param {string} value Value from the view. - * @param {string} trigger Event that triggered the update. - */ - this.$setViewValue = function(value, trigger) { - ctrl.$viewValue = value; - if (!ctrl.$options || ctrl.$options.updateOnDefault) { - ctrl.$$debounceViewValueCommit(trigger); - } - }; - - this.$$debounceViewValueCommit = function(trigger) { - var debounceDelay = 0, - options = ctrl.$options, - debounce; - - if (options && isDefined(options.debounce)) { - debounce = options.debounce; - if (isNumber(debounce)) { - debounceDelay = debounce; - } else if (isNumber(debounce[trigger])) { - debounceDelay = debounce[trigger]; - } else if (isNumber(debounce['default'])) { - debounceDelay = debounce['default']; - } - } - - $timeout.cancel(pendingDebounce); - if (debounceDelay) { - pendingDebounce = $timeout(function() { - ctrl.$commitViewValue(); - }, debounceDelay); - } else if ($rootScope.$$phase) { - ctrl.$commitViewValue(); - } else { - $scope.$apply(function() { - ctrl.$commitViewValue(); - }); - } - }; - - // model -> value - // Note: we cannot use a normal scope.$watch as we want to detect the following: - // 1. scope value is 'a' - // 2. user enters 'b' - // 3. ng-change kicks in and reverts scope value to 'a' - // -> scope value did not change since the last digest as - // ng-change executes in apply phase - // 4. view should be changed back to 'a' - $scope.$watch(function ngModelWatch() { - var modelValue = ngModelGet(); - - // if scope model value and ngModel value are out of sync - // TODO(perf): why not move this to the action fn? - if (modelValue !== ctrl.$modelValue) { - ctrl.$modelValue = modelValue; - - var formatters = ctrl.$formatters, - idx = formatters.length; - - var viewValue = modelValue; - while(idx--) { - viewValue = formatters[idx](viewValue); - } - if (ctrl.$viewValue !== viewValue) { - ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; - ctrl.$render(); - - ctrl.$$runValidators(undefined, modelValue, viewValue, noop); - } - } - - return modelValue; - }); -}]; - - -/** - * @ngdoc directive - * @name ngModel - * - * @element input - * - * @description - * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a - * property on the scope using {@link ngModel.NgModelController NgModelController}, - * which is created and exposed by this directive. - * - * `ngModel` is responsible for: - * - * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` - * require. - * - Providing validation behavior (i.e. required, number, email, url). - * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). - * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations. - * - Registering the control with its parent {@link ng.directive:form form}. - * - * Note: `ngModel` will try to bind to the property given by evaluating the expression on the - * current scope. If the property doesn't already exist on this scope, it will be created - * implicitly and added to the scope. - * - * For best practices on using `ngModel`, see: - * - * - [https://github.com/angular/angular.js/wiki/Understanding-Scopes] - * - * For basic examples, how to use `ngModel`, see: - * - * - {@link ng.directive:input input} - * - {@link input[text] text} - * - {@link input[checkbox] checkbox} - * - {@link input[radio] radio} - * - {@link input[number] number} - * - {@link input[email] email} - * - {@link input[url] url} - * - {@link input[date] date} - * - {@link input[dateTimeLocal] dateTimeLocal} - * - {@link input[time] time} - * - {@link input[month] month} - * - {@link input[week] week} - * - {@link ng.directive:select select} - * - {@link ng.directive:textarea textarea} - * - * # CSS classes - * The following CSS classes are added and removed on the associated input/select/textarea element - * depending on the validity of the model. - * - * - `ng-valid` is set if the model is valid. - * - `ng-invalid` is set if the model is invalid. - * - `ng-pristine` is set if the model is pristine. - * - `ng-dirty` is set if the model is dirty. - * - * Keep in mind that ngAnimate can detect each of these classes when added and removed. - * - * ## Animation Hooks - * - * Animations within models are triggered when any of the associated CSS classes are added and removed - * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, - * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. - * The animations that are triggered within ngModel are similar to how they work in ngClass and - * animations can be hooked into using CSS transitions, keyframes as well as JS animations. - * - * The following example shows a simple way to utilize CSS transitions to style an input element - * that has been rendered as invalid after it has been validated: - * - * <pre> - * //be sure to include ngAnimate as a module to hook into more - * //advanced animations - * .my-input { - * transition:0.5s linear all; - * background: white; - * } - * .my-input.ng-invalid { - * background: red; - * color:white; - * } - * </pre> - * - * @example - * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> - <file name="index.html"> - <script> - angular.module('inputExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.val = '1'; - }]); - </script> - <style> - .my-input { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; - background: transparent; - } - .my-input.ng-invalid { - color:white; - background: red; - } - </style> - Update input to see transitions when valid/invalid. - Integer is a valid value. - <form name="testForm" ng-controller="ExampleController"> - <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" /> - </form> - </file> - * </example> - * - * ## Binding to a getter/setter - * - * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a - * function that returns a representation of the model when called with zero arguments, and sets - * the internal state of a model when called with an argument. It's sometimes useful to use this - * for models that have an internal representation that's different than what the model exposes - * to the view. - * - * <div class="alert alert-success"> - * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more - * frequently than other parts of your code. - * </div> - * - * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that - * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to - * a `<form>`, which will enable this behavior for all `<input>`s within it. See - * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. - * - * The following example shows how to use `ngModel` with a getter/setter: - * - * @example - * <example name="ngModel-getter-setter" module="getterSetterExample"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <form name="userForm"> - Name: - <input type="text" name="userName" - ng-model="user.name" - ng-model-options="{ getterSetter: true }" /> - </form> - <pre>user.name = <span ng-bind="user.name()"></span></pre> - </div> - </file> - <file name="app.js"> - angular.module('getterSetterExample', []) - .controller('ExampleController', ['$scope', function($scope) { - var _name = 'Brian'; - $scope.user = { - name: function (newName) { - if (angular.isDefined(newName)) { - _name = newName; - } - return _name; - } - }; - }]); - </file> - * </example> - */ -var ngModelDirective = function() { - return { - restrict: 'A', - require: ['ngModel', '^?form', '^?ngModelOptions'], - controller: NgModelController, - link: { - pre: function(scope, element, attr, ctrls) { - var modelCtrl = ctrls[0], - formCtrl = ctrls[1] || nullFormCtrl; - - modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); - - // notify others, especially parent forms - formCtrl.$addControl(modelCtrl); - - scope.$on('$destroy', function() { - formCtrl.$removeControl(modelCtrl); - }); - }, - post: function(scope, element, attr, ctrls) { - var modelCtrl = ctrls[0]; - if (modelCtrl.$options && modelCtrl.$options.updateOn) { - element.on(modelCtrl.$options.updateOn, function(ev) { - modelCtrl.$$debounceViewValueCommit(ev && ev.type); - }); - } - - element.on('blur', function(ev) { - if (modelCtrl.$touched) return; - - scope.$apply(function() { - modelCtrl.$setTouched(); - }); - }); - } - } - }; -}; - - -/** - * @ngdoc directive - * @name ngChange - * - * @description - * Evaluate the given expression when the user changes the input. - * The expression is evaluated immediately, unlike the JavaScript onchange event - * which only triggers at the end of a change (usually, when the user leaves the - * form element or presses the return key). - * - * The `ngChange` expression is only evaluated when a change in the input value causes - * a new value to be committed to the model. - * - * It will not be evaluated: - * * if the value returned from the `$parsers` transformation pipeline has not changed - * * if the input has continued to be invalid since the model will stay `null` - * * if the model is changed programmatically and not by a change to the input value - * - * - * Note, this directive requires `ngModel` to be present. - * - * @element input - * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change - * in input value. - * - * @example - * <example name="ngChange-directive" module="changeExample"> - * <file name="index.html"> - * <script> - * angular.module('changeExample', []) - * .controller('ExampleController', ['$scope', function($scope) { - * $scope.counter = 0; - * $scope.change = function() { - * $scope.counter++; - * }; - * }]); - * </script> - * <div ng-controller="ExampleController"> - * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> - * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> - * <label for="ng-change-example2">Confirmed</label><br /> - * <tt>debug = {{confirmed}}</tt><br/> - * <tt>counter = {{counter}}</tt><br/> - * </div> - * </file> - * <file name="protractor.js" type="protractor"> - * var counter = element(by.binding('counter')); - * var debug = element(by.binding('confirmed')); - * - * it('should evaluate the expression if changing from view', function() { - * expect(counter.getText()).toContain('0'); - * - * element(by.id('ng-change-example1')).click(); - * - * expect(counter.getText()).toContain('1'); - * expect(debug.getText()).toContain('true'); - * }); - * - * it('should not evaluate the expression if changing from model', function() { - * element(by.id('ng-change-example2')).click(); - - * expect(counter.getText()).toContain('0'); - * expect(debug.getText()).toContain('true'); - * }); - * </file> - * </example> - */ -var ngChangeDirective = valueFn({ - restrict: 'A', - require: 'ngModel', - link: function(scope, element, attr, ctrl) { - ctrl.$viewChangeListeners.push(function() { - scope.$eval(attr.ngChange); - }); - } -}); - - -var requiredDirective = function() { - return { - restrict: 'A', - require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; - attr.required = true; // force truthy in case we are on non input element - - ctrl.$validators.required = function(modelValue, viewValue) { - return !attr.required || !ctrl.$isEmpty(viewValue); - }; - - attr.$observe('required', function() { - ctrl.$validate(); - }); - } - }; -}; - - -var patternDirective = function() { - return { - restrict: 'A', - require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; - - var regexp, patternExp = attr.ngPattern || attr.pattern; - attr.$observe('pattern', function(regex) { - if (isString(regex) && regex.length > 0) { - regex = new RegExp(regex); - } - - if (regex && !regex.test) { - throw minErr('ngPattern')('noregexp', - 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, - regex, startingTag(elm)); - } - - regexp = regex || undefined; - ctrl.$validate(); - }); - - ctrl.$validators.pattern = function(value) { - return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value); - }; - } - }; -}; - - -var maxlengthDirective = function() { - return { - restrict: 'A', - require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; - - var maxlength = 0; - attr.$observe('maxlength', function(value) { - maxlength = int(value) || 0; - ctrl.$validate(); - }); - ctrl.$validators.maxlength = function(modelValue, viewValue) { - return ctrl.$isEmpty(viewValue) || viewValue.length <= maxlength; - }; - } - }; -}; - -var minlengthDirective = function() { - return { - restrict: 'A', - require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; - - var minlength = 0; - attr.$observe('minlength', function(value) { - minlength = int(value) || 0; - ctrl.$validate(); - }); - ctrl.$validators.minlength = function(modelValue, viewValue) { - return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; - }; - } - }; -}; - - -/** - * @ngdoc directive - * @name ngList - * - * @description - * Text input that converts between a delimited string and an array of strings. The default - * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom - * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. - * - * The behaviour of the directive is affected by the use of the `ngTrim` attribute. - * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each - * list item is respected. This implies that the user of the directive is responsible for - * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a - * tab or newline character. - * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected - * when joining the list items back together) and whitespace around each list item is stripped - * before it is added to the model. - * - * ### Example with Validation - * - * <example name="ngList-directive" module="listExample"> - * <file name="app.js"> - * angular.module('listExample', []) - * .controller('ExampleController', ['$scope', function($scope) { - * $scope.names = ['morpheus', 'neo', 'trinity']; - * }]); - * </file> - * <file name="index.html"> - * <form name="myForm" ng-controller="ExampleController"> - * List: <input name="namesInput" ng-model="names" ng-list required> - * <span class="error" ng-show="myForm.namesInput.$error.required"> - * Required!</span> - * <br> - * <tt>names = {{names}}</tt><br/> - * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> - * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> - * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - * </form> - * </file> - * <file name="protractor.js" type="protractor"> - * var listInput = element(by.model('names')); - * var names = element(by.exactBinding('names')); - * var valid = element(by.binding('myForm.namesInput.$valid')); - * var error = element(by.css('span.error')); - * - * it('should initialize to model', function() { - * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); - * expect(valid.getText()).toContain('true'); - * expect(error.getCssValue('display')).toBe('none'); - * }); - * - * it('should be invalid if empty', function() { - * listInput.clear(); - * listInput.sendKeys(''); - * - * expect(names.getText()).toContain(''); - * expect(valid.getText()).toContain('false'); - * expect(error.getCssValue('display')).not.toBe('none'); - * }); - * </file> - * </example> - * - * ### Example - splitting on whitespace - * <example name="ngList-directive-newlines"> - * <file name="index.html"> - * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> - * <pre>{{ list | json }}</pre> - * </file> - * <file name="protractor.js" type="protractor"> - * it("should split the text by newlines", function() { - * var listInput = element(by.model('list')); - * var output = element(by.binding('list | json')); - * listInput.sendKeys('abc\ndef\nghi'); - * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); - * }); - * </file> - * </example> - * - * @element input - * @param {string=} ngList optional delimiter that should be used to split the value. - */ -var ngListDirective = function() { - return { - restrict: 'A', - priority: 100, - require: 'ngModel', - link: function(scope, element, attr, ctrl) { - // We want to control whitespace trimming so we use this convoluted approach - // to access the ngList attribute, which doesn't pre-trim the attribute - var ngList = element.attr(attr.$attr.ngList) || ', '; - var trimValues = attr.ngTrim !== 'false'; - var separator = trimValues ? trim(ngList) : ngList; - - var parse = function(viewValue) { - // If the viewValue is invalid (say required but empty) it will be `undefined` - if (isUndefined(viewValue)) return; - - var list = []; - - if (viewValue) { - forEach(viewValue.split(separator), function(value) { - if (value) list.push(trimValues ? trim(value) : value); - }); - } - - return list; - }; - - ctrl.$parsers.push(parse); - ctrl.$formatters.push(function(value) { - if (isArray(value)) { - return value.join(ngList); - } - - return undefined; - }); - - // Override the standard $isEmpty because an empty array means the input is empty. - ctrl.$isEmpty = function(value) { - return !value || !value.length; - }; - } - }; -}; - - -var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; -/** - * @ngdoc directive - * @name ngValue - * - * @description - * Binds the given expression to the value of `input[select]` or `input[radio]`, so - * that when the element is selected, the `ngModel` of that element is set to the - * bound value. - * - * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as - * shown below. - * - * @element input - * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute - * of the `input` element - * - * @example - <example name="ngValue-directive" module="valueExample"> - <file name="index.html"> - <script> - angular.module('valueExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.names = ['pizza', 'unicorns', 'robots']; - $scope.my = { favorite: 'unicorns' }; - }]); - </script> - <form ng-controller="ExampleController"> - <h2>Which is your favorite?</h2> - <label ng-repeat="name in names" for="{{name}}"> - {{name}} - <input type="radio" - ng-model="my.favorite" - ng-value="name" - id="{{name}}" - name="favorite"> - </label> - <div>You chose {{my.favorite}}</div> - </form> - </file> - <file name="protractor.js" type="protractor"> - var favorite = element(by.binding('my.favorite')); - - it('should initialize to model', function() { - expect(favorite.getText()).toContain('unicorns'); - }); - it('should bind the values to the inputs', function() { - element.all(by.model('my.favorite')).get(0).click(); - expect(favorite.getText()).toContain('pizza'); - }); - </file> - </example> - */ -var ngValueDirective = function() { - return { - restrict: 'A', - priority: 100, - compile: function(tpl, tplAttr) { - if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { - return function ngValueConstantLink(scope, elm, attr) { - attr.$set('value', scope.$eval(attr.ngValue)); - }; - } else { - return function ngValueLink(scope, elm, attr) { - scope.$watch(attr.ngValue, function valueWatchAction(value) { - attr.$set('value', value); - }); - }; - } - } - }; -}; - -/** - * @ngdoc directive - * @name ngModelOptions - * - * @description - * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of - * events that will trigger a model update and/or a debouncing delay so that the actual update only - * takes place when a timer expires; this timer will be reset after another change takes place. - * - * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might - * be different than the value in the actual model. This means that if you update the model you - * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in - * order to make sure it is synchronized with the model and that any debounced action is canceled. - * - * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} - * method is by making sure the input is placed inside a form that has a `name` attribute. This is - * important because `form` controllers are published to the related scope under the name in their - * `name` attribute. - * - * Any pending changes will take place immediately when an enclosing form is submitted via the - * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` - * to have access to the updated model. - * - * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: - * - `updateOn`: string specifying which event should be the input bound to. You can set several - * events using an space delimited list. There is a special event called `default` that - * matches the default events belonging of the control. - * - `debounce`: integer value which contains the debounce model update value in milliseconds. A - * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a - * custom value for each event. For example: - * `ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"` - * - `allowInvalid`: boolean value which indicates that the model can be set with values that did - * not validate correctly instead of the default behavior of setting the model to undefined. - * - `getterSetter`: boolean value which determines whether or not to treat functions bound to - `ngModel` as getters/setters. - * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for - * `<input type="date">`, `<input type="time">`, ... . Right now, the only supported value is `'UTC'`, - * otherwise the default timezone of the browser will be used. - * - * @example - - The following example shows how to override immediate updates. Changes on the inputs within the - form will update the model only when the control loses focus (blur event). If `escape` key is - pressed while the input field is focused, the value is reset to the value in the current model. - - <example name="ngModelOptions-directive-blur" module="optionsExample"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <form name="userForm"> - Name: - <input type="text" name="userName" - ng-model="user.name" - ng-model-options="{ updateOn: 'blur' }" - ng-keyup="cancel($event)" /><br /> - - Other data: - <input type="text" ng-model="user.data" /><br /> - </form> - <pre>user.name = <span ng-bind="user.name"></span></pre> - </div> - </file> - <file name="app.js"> - angular.module('optionsExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.user = { name: 'say', data: '' }; - - $scope.cancel = function (e) { - if (e.keyCode == 27) { - $scope.userForm.userName.$rollbackViewValue(); - } - }; - }]); - </file> - <file name="protractor.js" type="protractor"> - var model = element(by.binding('user.name')); - var input = element(by.model('user.name')); - var other = element(by.model('user.data')); - - it('should allow custom events', function() { - input.sendKeys(' hello'); - input.click(); - expect(model.getText()).toEqual('say'); - other.click(); - expect(model.getText()).toEqual('say hello'); - }); - - it('should $rollbackViewValue when model changes', function() { - input.sendKeys(' hello'); - expect(input.getAttribute('value')).toEqual('say hello'); - input.sendKeys(protractor.Key.ESCAPE); - expect(input.getAttribute('value')).toEqual('say'); - other.click(); - expect(model.getText()).toEqual('say'); - }); - </file> - </example> - - This one shows how to debounce model changes. Model will be updated only 1 sec after last change. - If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. - - <example name="ngModelOptions-directive-debounce" module="optionsExample"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <form name="userForm"> - Name: - <input type="text" name="userName" - ng-model="user.name" - ng-model-options="{ debounce: 1000 }" /> - <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br /> - </form> - <pre>user.name = <span ng-bind="user.name"></span></pre> - </div> - </file> - <file name="app.js"> - angular.module('optionsExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.user = { name: 'say' }; - }]); - </file> - </example> - - This one shows how to bind to getter/setters: - - <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <form name="userForm"> - Name: - <input type="text" name="userName" - ng-model="user.name" - ng-model-options="{ getterSetter: true }" /> - </form> - <pre>user.name = <span ng-bind="user.name()"></span></pre> - </div> - </file> - <file name="app.js"> - angular.module('getterSetterExample', []) - .controller('ExampleController', ['$scope', function($scope) { - var _name = 'Brian'; - $scope.user = { - name: function (newName) { - return angular.isDefined(newName) ? (_name = newName) : _name; - } - }; - }]); - </file> - </example> - */ -var ngModelOptionsDirective = function() { - return { - restrict: 'A', - controller: ['$scope', '$attrs', function($scope, $attrs) { - var that = this; - this.$options = $scope.$eval($attrs.ngModelOptions); - // Allow adding/overriding bound events - if (this.$options.updateOn !== undefined) { - this.$options.updateOnDefault = false; - // extract "default" pseudo-event from list of events that can trigger a model update - this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { - that.$options.updateOnDefault = true; - return ' '; - })); - } else { - this.$options.updateOnDefault = true; - } - }] - }; -}; - -// helper methods -function addSetValidityMethod(context) { - var ctrl = context.ctrl, - $element = context.$element, - classCache = {}, - set = context.set, - unset = context.unset, - parentForm = context.parentForm, - $animate = context.$animate; - - ctrl.$setValidity = setValidity; - toggleValidationCss('', true); - - function setValidity(validationErrorKey, state, options) { - if (state === undefined) { - createAndSet('$pending', validationErrorKey, options); - } else { - unsetAndCleanup('$pending', validationErrorKey, options); - } - if (!isBoolean(state)) { - unset(ctrl.$error, validationErrorKey, options); - unset(ctrl.$$success, validationErrorKey, options); - } else { - if (state) { - unset(ctrl.$error, validationErrorKey, options); - set(ctrl.$$success, validationErrorKey, options); - } else { - set(ctrl.$error, validationErrorKey, options); - unset(ctrl.$$success, validationErrorKey, options); - } - } - if (ctrl.$pending) { - cachedToggleClass(PENDING_CLASS, true); - ctrl.$valid = ctrl.$invalid = undefined; - toggleValidationCss('', null); - } else { - cachedToggleClass(PENDING_CLASS, false); - ctrl.$valid = isObjectEmpty(ctrl.$error); - ctrl.$invalid = !ctrl.$valid; - toggleValidationCss('', ctrl.$valid); - } - - // re-read the state as the set/unset methods could have - // combined state in ctrl.$error[validationError] (used for forms), - // where setting/unsetting only increments/decrements the value, - // and does not replace it. - var combinedState; - if (ctrl.$pending && ctrl.$pending[validationErrorKey]) { - combinedState = undefined; - } else if (ctrl.$error[validationErrorKey]) { - combinedState = false; - } else if (ctrl.$$success[validationErrorKey]) { - combinedState = true; - } else { - combinedState = null; - } - toggleValidationCss(validationErrorKey, combinedState); - parentForm.$setValidity(validationErrorKey, combinedState, ctrl); - } - - function createAndSet(name, value, options) { - if (!ctrl[name]) { - ctrl[name] = {}; - } - set(ctrl[name], value, options); - } - - function unsetAndCleanup(name, value, options) { - if (ctrl[name]) { - unset(ctrl[name], value, options); - } - if (isObjectEmpty(ctrl[name])) { - ctrl[name] = undefined; - } - } - - function cachedToggleClass(className, switchValue) { - if (switchValue && !classCache[className]) { - $animate.addClass($element, className); - classCache[className] = true; - } else if (!switchValue && classCache[className]) { - $animate.removeClass($element, className); - classCache[className] = false; - } - } - - function toggleValidationCss(validationErrorKey, isValid) { - validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; - - cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true); - cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false); - } -} - -function isObjectEmpty(obj) { - if (obj) { - for (var prop in obj) { - return false; - } - } - return true; -} - -/** - * @ngdoc directive - * @name ngBind - * @restrict AC - * - * @description - * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element - * with the value of a given expression, and to update the text content when the value of that - * expression changes. - * - * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like - * `{{ expression }}` which is similar but less verbose. - * - * It is preferable to use `ngBind` instead of `{{ expression }}` if a template 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. - * - * An alternative solution to this problem would be using the - * {@link ng.directive:ngCloak ngCloak} directive. - * - * - * @element ANY - * @param {expression} ngBind {@link guide/expression Expression} to evaluate. - * - * @example - * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. - <example module="bindExample"> - <file name="index.html"> - <script> - angular.module('bindExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.name = 'Whirled'; - }]); - </script> - <div ng-controller="ExampleController"> - Enter name: <input type="text" ng-model="name"><br> - Hello <span ng-bind="name"></span>! - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-bind', function() { - var nameInput = element(by.model('name')); - - expect(element(by.binding('name')).getText()).toBe('Whirled'); - nameInput.clear(); - nameInput.sendKeys('world'); - expect(element(by.binding('name')).getText()).toBe('world'); - }); - </file> - </example> - */ -var ngBindDirective = ['$compile', function($compile) { - return { - restrict: 'AC', - compile: function ngBindCompile(templateElement) { - $compile.$$addBindingClass(templateElement); - return function ngBindLink(scope, element, attr) { - $compile.$$addBindingInfo(element, attr.ngBind); - scope.$watch(attr.ngBind, function ngBindWatchAction(value) { - // We are purposefully using == here rather than === because we want to - // catch when value is "null or undefined" - // jshint -W041 - element.text(value == undefined ? '' : value); - }); - }; - } - }; -}]; - - -/** - * @ngdoc directive - * @name ngBindTemplate - * - * @description - * The `ngBindTemplate` directive specifies that the element - * text content should be replaced with the interpolation of the template - * in the `ngBindTemplate` attribute. - * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` - * expressions. This directive is needed since some HTML elements - * (such as TITLE and OPTION) cannot contain SPAN elements. - * - * @element ANY - * @param {string} ngBindTemplate template of form - * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. - * - * @example - * Try it here: enter text in text box and watch the greeting change. - <example module="bindExample"> - <file name="index.html"> - <script> - angular.module('bindExample', []) - .controller('ExampleController', ['$scope', function ($scope) { - $scope.salutation = 'Hello'; - $scope.name = 'World'; - }]); - </script> - <div ng-controller="ExampleController"> - Salutation: <input type="text" ng-model="salutation"><br> - Name: <input type="text" ng-model="name"><br> - <pre ng-bind-template="{{salutation}} {{name}}!"></pre> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-bind', function() { - var salutationElem = element(by.binding('salutation')); - var salutationInput = element(by.model('salutation')); - var nameInput = element(by.model('name')); - - expect(salutationElem.getText()).toBe('Hello World!'); - - salutationInput.clear(); - salutationInput.sendKeys('Greetings'); - nameInput.clear(); - nameInput.sendKeys('user'); - - expect(salutationElem.getText()).toBe('Greetings user!'); - }); - </file> - </example> - */ -var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { - return { - compile: function ngBindTemplateCompile(templateElement) { - $compile.$$addBindingClass(templateElement); - return function ngBindTemplateLink(scope, element, attr) { - var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); - $compile.$$addBindingInfo(element, interpolateFn.expressions); - attr.$observe('ngBindTemplate', function(value) { - element.text(value); - }); - }; - } - }; -}]; - - -/** - * @ngdoc directive - * @name ngBindHtml - * - * @description - * Creates a binding that will innerHTML the result of evaluating the `expression` into the current - * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link - * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` - * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in - * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to - * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example - * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. - * - * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you - * will have an exception (instead of an exploit.) - * - * @element ANY - * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. - * - * @example - - <example module="bindHtmlExample" deps="angular-sanitize.js"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <p ng-bind-html="myHTML"></p> - </div> - </file> - - <file name="script.js"> - angular.module('bindHtmlExample', ['ngSanitize']) - .controller('ExampleController', ['$scope', function($scope) { - $scope.myHTML = - 'I am an <code>HTML</code>string with ' + - '<a href="#">links!</a> and other <em>stuff</em>'; - }]); - </file> - - <file name="protractor.js" type="protractor"> - it('should check ng-bind-html', function() { - expect(element(by.binding('myHTML')).getText()).toBe( - 'I am an HTMLstring with links! and other stuff'); - }); - </file> - </example> - */ -var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { - return { - restrict: 'A', - compile: function ngBindHtmlCompile(tElement, tAttrs) { - var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); - var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) { - return (value || '').toString(); - }); - $compile.$$addBindingClass(tElement); - - return function ngBindHtmlLink(scope, element, attr) { - $compile.$$addBindingInfo(element, attr.ngBindHtml); - - scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { - // we re-evaluate the expr because we want a TrustedValueHolderType - // for $sce, not a string - element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || ''); - }); - }; - } - }; -}]; - -function classDirective(name, selector) { - name = 'ngClass' + name; - return ['$animate', function($animate) { - return { - restrict: 'AC', - link: function(scope, element, attr) { - var oldVal; - - scope.$watch(attr[name], ngClassWatchAction, true); - - attr.$observe('class', function(value) { - ngClassWatchAction(scope.$eval(attr[name])); - }); - - - if (name !== 'ngClass') { - scope.$watch('$index', function($index, old$index) { - // jshint bitwise: false - var mod = $index & 1; - if (mod !== (old$index & 1)) { - var classes = arrayClasses(scope.$eval(attr[name])); - mod === selector ? - addClasses(classes) : - removeClasses(classes); - } - }); - } - - function addClasses(classes) { - var newClasses = digestClassCounts(classes, 1); - attr.$addClass(newClasses); - } - - function removeClasses(classes) { - var newClasses = digestClassCounts(classes, -1); - attr.$removeClass(newClasses); - } - - function digestClassCounts (classes, count) { - var classCounts = element.data('$classCounts') || {}; - var classesToUpdate = []; - forEach(classes, function (className) { - if (count > 0 || classCounts[className]) { - classCounts[className] = (classCounts[className] || 0) + count; - if (classCounts[className] === +(count > 0)) { - classesToUpdate.push(className); - } - } - }); - element.data('$classCounts', classCounts); - return classesToUpdate.join(' '); - } - - function updateClasses (oldClasses, newClasses) { - var toAdd = arrayDifference(newClasses, oldClasses); - var toRemove = arrayDifference(oldClasses, newClasses); - toAdd = digestClassCounts(toAdd, 1); - toRemove = digestClassCounts(toRemove, -1); - if (toAdd && toAdd.length) { - $animate.addClass(element, toAdd); - } - if (toRemove && toRemove.length) { - $animate.removeClass(element, toRemove); - } - } - - function ngClassWatchAction(newVal) { - if (selector === true || scope.$index % 2 === selector) { - var newClasses = arrayClasses(newVal || []); - if (!oldVal) { - addClasses(newClasses); - } else if (!equals(newVal,oldVal)) { - var oldClasses = arrayClasses(oldVal); - updateClasses(oldClasses, newClasses); - } - } - oldVal = shallowCopy(newVal); - } - } - }; - - function arrayDifference(tokens1, tokens2) { - var values = []; - - outer: - for(var i = 0; i < tokens1.length; i++) { - var token = tokens1[i]; - for(var j = 0; j < tokens2.length; j++) { - if(token == tokens2[j]) continue outer; - } - values.push(token); - } - return values; - } - - function arrayClasses (classVal) { - if (isArray(classVal)) { - return classVal; - } else if (isString(classVal)) { - return classVal.split(' '); - } else if (isObject(classVal)) { - var classes = [], i = 0; - forEach(classVal, function(v, k) { - if (v) { - classes = classes.concat(k.split(' ')); - } - }); - return classes; - } - return classVal; - } - }]; -} - -/** - * @ngdoc directive - * @name ngClass - * @restrict AC - * - * @description - * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding - * an expression that represents all classes to be added. - * - * The directive operates in three different ways, depending on which of three types the expression - * evaluates to: - * - * 1. If the expression evaluates to a string, the string should be one or more space-delimited class - * names. - * - * 2. If the expression evaluates to an array, each element of the array should be a string that is - * one or more space-delimited class names. - * - * 3. If the expression evaluates to an object, then for each key-value pair of the - * object with a truthy value the corresponding key is used as a class name. - * - * The directive won't add duplicate classes if a particular class was already set. - * - * When the expression changes, the previously added classes are removed and only then the - * new classes are added. - * - * @animations - * add - happens just before the class is applied to the element - * remove - happens just before the class is removed from the element - * - * @element ANY - * @param {expression} ngClass {@link guide/expression Expression} to eval. The result - * of the evaluation can be a string representing space delimited class - * names, an array, or a map of class names to boolean values. In the case of a map, the - * names of the properties whose values are truthy will be added as css classes to the - * element. - * - * @example Example that demonstrates basic bindings via ngClass directive. - <example> - <file name="index.html"> - <p ng-class="{strike: deleted, bold: important, red: error}">Map Syntax Example</p> - <input type="checkbox" ng-model="deleted"> deleted (apply "strike" class)<br> - <input type="checkbox" ng-model="important"> important (apply "bold" class)<br> - <input type="checkbox" ng-model="error"> error (apply "red" class) - <hr> - <p ng-class="style">Using String Syntax</p> - <input type="text" ng-model="style" placeholder="Type: bold strike red"> - <hr> - <p ng-class="[style1, style2, style3]">Using Array Syntax</p> - <input ng-model="style1" placeholder="Type: bold, strike or red"><br> - <input ng-model="style2" placeholder="Type: bold, strike or red"><br> - <input ng-model="style3" placeholder="Type: bold, strike or red"><br> - </file> - <file name="style.css"> - .strike { - text-decoration: line-through; - } - .bold { - font-weight: bold; - } - .red { - color: red; - } - </file> - <file name="protractor.js" type="protractor"> - var ps = element.all(by.css('p')); - - it('should let you toggle the class', function() { - - expect(ps.first().getAttribute('class')).not.toMatch(/bold/); - expect(ps.first().getAttribute('class')).not.toMatch(/red/); - - element(by.model('important')).click(); - expect(ps.first().getAttribute('class')).toMatch(/bold/); - - element(by.model('error')).click(); - expect(ps.first().getAttribute('class')).toMatch(/red/); - }); - - it('should let you toggle string example', function() { - expect(ps.get(1).getAttribute('class')).toBe(''); - element(by.model('style')).clear(); - element(by.model('style')).sendKeys('red'); - expect(ps.get(1).getAttribute('class')).toBe('red'); - }); - - it('array example should have 3 classes', function() { - expect(ps.last().getAttribute('class')).toBe(''); - element(by.model('style1')).sendKeys('bold'); - element(by.model('style2')).sendKeys('strike'); - element(by.model('style3')).sendKeys('red'); - expect(ps.last().getAttribute('class')).toBe('bold strike red'); - }); - </file> - </example> - - ## Animations - - The example below demonstrates how to perform animations using ngClass. - - <example module="ngAnimate" deps="angular-animate.js" animations="true"> - <file name="index.html"> - <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> - <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> - <br> - <span class="base-class" ng-class="myVar">Sample Text</span> - </file> - <file name="style.css"> - .base-class { - -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - } - - .base-class.my-class { - color: red; - font-size:3em; - } - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-class', function() { - expect(element(by.css('.base-class')).getAttribute('class')).not. - toMatch(/my-class/); - - element(by.id('setbtn')).click(); - - expect(element(by.css('.base-class')).getAttribute('class')). - toMatch(/my-class/); - - element(by.id('clearbtn')).click(); - - expect(element(by.css('.base-class')).getAttribute('class')).not. - toMatch(/my-class/); - }); - </file> - </example> - - - ## ngClass and pre-existing CSS3 Transitions/Animations - The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. - Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder - any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure - to view the step by step details of {@link ngAnimate.$animate#addclass $animate.addClass} and - {@link ngAnimate.$animate#removeclass $animate.removeClass}. - */ -var ngClassDirective = classDirective('', true); - -/** - * @ngdoc directive - * @name ngClassOdd - * @restrict AC - * - * @description - * The `ngClassOdd` and `ngClassEven` directives work exactly as - * {@link ng.directive:ngClass ngClass}, except they work in - * conjunction with `ngRepeat` and take effect only on odd (even) rows. - * - * This directive can be applied only within the scope of an - * {@link ng.directive:ngRepeat ngRepeat}. - * - * @element ANY - * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result - * of the evaluation can be a string representing space delimited class names or an array. - * - * @example - <example> - <file name="index.html"> - <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> - <li ng-repeat="name in names"> - <span ng-class-odd="'odd'" ng-class-even="'even'"> - {{name}} - </span> - </li> - </ol> - </file> - <file name="style.css"> - .odd { - color: red; - } - .even { - color: blue; - } - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-class-odd and ng-class-even', function() { - expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). - toMatch(/odd/); - expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). - toMatch(/even/); - }); - </file> - </example> - */ -var ngClassOddDirective = classDirective('Odd', 0); - -/** - * @ngdoc directive - * @name ngClassEven - * @restrict AC - * - * @description - * The `ngClassOdd` and `ngClassEven` directives work exactly as - * {@link ng.directive:ngClass ngClass}, except they work in - * conjunction with `ngRepeat` and take effect only on odd (even) rows. - * - * This directive can be applied only within the scope of an - * {@link ng.directive:ngRepeat ngRepeat}. - * - * @element ANY - * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The - * result of the evaluation can be a string representing space delimited class names or an array. - * - * @example - <example> - <file name="index.html"> - <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> - <li ng-repeat="name in names"> - <span ng-class-odd="'odd'" ng-class-even="'even'"> - {{name}} - </span> - </li> - </ol> - </file> - <file name="style.css"> - .odd { - color: red; - } - .even { - color: blue; - } - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-class-odd and ng-class-even', function() { - expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). - toMatch(/odd/); - expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). - toMatch(/even/); - }); - </file> - </example> - */ -var ngClassEvenDirective = classDirective('Even', 1); - -/** - * @ngdoc directive - * @name ngCloak - * @restrict AC - * - * @description - * The `ngCloak` directive is used to prevent the Angular html template from being briefly - * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this - * directive to avoid the undesirable flicker effect caused by the html template display. - * - * The directive can be applied to the `<body>` element, but the preferred usage is to apply - * multiple `ngCloak` directives to small portions of the page to permit progressive rendering - * of the browser view. - * - * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and - * `angular.min.js`. - * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). - * - * ```css - * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { - * display: none !important; - * } - * ``` - * - * When this css rule is loaded by the browser, all html elements (including their children) that - * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive - * during the compilation of the template it deletes the `ngCloak` element attribute, making - * the compiled element visible. - * - * For the best result, the `angular.js` script must be loaded in the head section of the html - * document; alternatively, the css rule above must be included in the external stylesheet of the - * application. - * - * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they - * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css - * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below. - * - * @element ANY - * - * @example - <example> - <file name="index.html"> - <div id="template1" ng-cloak>{{ 'hello' }}</div> - <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div> - </file> - <file name="protractor.js" type="protractor"> - it('should remove the template directive and css class', function() { - expect($('#template1').getAttribute('ng-cloak')). - toBeNull(); - expect($('#template2').getAttribute('ng-cloak')). - toBeNull(); - }); - </file> - </example> - * - */ -var ngCloakDirective = ngDirective({ - compile: function(element, attr) { - attr.$set('ngCloak', undefined); - element.removeClass('ng-cloak'); - } -}); - -/** - * @ngdoc directive - * @name ngController - * - * @description - * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular - * supports the principles behind the Model-View-Controller design pattern. - * - * MVC components in angular: - * - * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties - * are accessed through bindings. - * * View — The template (HTML with data bindings) that is rendered into the View. - * * Controller — The `ngController` directive specifies a Controller class; the class contains business - * logic behind the application to decorate the scope with functions and values - * - * Note that you can also attach controllers to the DOM by declaring it in a route definition - * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller - * again using `ng-controller` in the template itself. This will cause the controller to be attached - * and executed twice. - * - * @element ANY - * @scope - * @param {expression} ngController Name of a constructor function registered with the current - * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} - * that on the current scope evaluates to a constructor function. - * - * The controller instance can be published into a scope property by specifying - * `ng-controller="as propertyName"`. - * - * If the current `$controllerProvider` is configured to use globals (via - * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may - * also be the name of a globally accessible constructor function (not recommended). - * - * @example - * Here is a simple form for editing user contact information. Adding, removing, clearing, and - * greeting are methods declared on the controller (see source tab). These methods can - * easily be called from the angular markup. Any changes to the data are automatically reflected - * in the View without the need for a manual update. - * - * Two different declaration styles are included below: - * - * * one binds methods and properties directly onto the controller using `this`: - * `ng-controller="SettingsController1 as settings"` - * * one injects `$scope` into the controller: - * `ng-controller="SettingsController2"` - * - * The second option is more common in the Angular community, and is generally used in boilerplates - * and in this guide. However, there are advantages to binding properties directly to the controller - * and avoiding scope. - * - * * Using `controller as` makes it obvious which controller you are accessing in the template when - * multiple controllers apply to an element. - * * If you are writing your controllers as classes you have easier access to the properties and - * methods, which will appear on the scope, from inside the controller code. - * * Since there is always a `.` in the bindings, you don't have to worry about prototypal - * inheritance masking primitives. - * - * This example demonstrates the `controller as` syntax. - * - * <example name="ngControllerAs" module="controllerAsExample"> - * <file name="index.html"> - * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> - * Name: <input type="text" ng-model="settings.name"/> - * [ <a href="" ng-click="settings.greet()">greet</a> ]<br/> - * Contact: - * <ul> - * <li ng-repeat="contact in settings.contacts"> - * <select ng-model="contact.type"> - * <option>phone</option> - * <option>email</option> - * </select> - * <input type="text" ng-model="contact.value"/> - * [ <a href="" ng-click="settings.clearContact(contact)">clear</a> - * | <a href="" ng-click="settings.removeContact(contact)">X</a> ] - * </li> - * <li>[ <a href="" ng-click="settings.addContact()">add</a> ]</li> - * </ul> - * </div> - * </file> - * <file name="app.js"> - * angular.module('controllerAsExample', []) - * .controller('SettingsController1', SettingsController1); - * - * function SettingsController1() { - * this.name = "John Smith"; - * this.contacts = [ - * {type: 'phone', value: '408 555 1212'}, - * {type: 'email', value: 'john.smith@example.org'} ]; - * } - * - * SettingsController1.prototype.greet = function() { - * alert(this.name); - * }; - * - * SettingsController1.prototype.addContact = function() { - * this.contacts.push({type: 'email', value: 'yourname@example.org'}); - * }; - * - * SettingsController1.prototype.removeContact = function(contactToRemove) { - * var index = this.contacts.indexOf(contactToRemove); - * this.contacts.splice(index, 1); - * }; - * - * SettingsController1.prototype.clearContact = function(contact) { - * contact.type = 'phone'; - * contact.value = ''; - * }; - * </file> - * <file name="protractor.js" type="protractor"> - * it('should check controller as', function() { - * var container = element(by.id('ctrl-as-exmpl')); - * expect(container.element(by.model('settings.name')) - * .getAttribute('value')).toBe('John Smith'); - * - * var firstRepeat = - * container.element(by.repeater('contact in settings.contacts').row(0)); - * var secondRepeat = - * container.element(by.repeater('contact in settings.contacts').row(1)); - * - * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) - * .toBe('408 555 1212'); - * - * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) - * .toBe('john.smith@example.org'); - * - * firstRepeat.element(by.linkText('clear')).click(); - * - * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) - * .toBe(''); - * - * container.element(by.linkText('add')).click(); - * - * expect(container.element(by.repeater('contact in settings.contacts').row(2)) - * .element(by.model('contact.value')) - * .getAttribute('value')) - * .toBe('yourname@example.org'); - * }); - * </file> - * </example> - * - * This example demonstrates the "attach to `$scope`" style of controller. - * - * <example name="ngController" module="controllerExample"> - * <file name="index.html"> - * <div id="ctrl-exmpl" ng-controller="SettingsController2"> - * Name: <input type="text" ng-model="name"/> - * [ <a href="" ng-click="greet()">greet</a> ]<br/> - * Contact: - * <ul> - * <li ng-repeat="contact in contacts"> - * <select ng-model="contact.type"> - * <option>phone</option> - * <option>email</option> - * </select> - * <input type="text" ng-model="contact.value"/> - * [ <a href="" ng-click="clearContact(contact)">clear</a> - * | <a href="" ng-click="removeContact(contact)">X</a> ] - * </li> - * <li>[ <a href="" ng-click="addContact()">add</a> ]</li> - * </ul> - * </div> - * </file> - * <file name="app.js"> - * angular.module('controllerExample', []) - * .controller('SettingsController2', ['$scope', SettingsController2]); - * - * function SettingsController2($scope) { - * $scope.name = "John Smith"; - * $scope.contacts = [ - * {type:'phone', value:'408 555 1212'}, - * {type:'email', value:'john.smith@example.org'} ]; - * - * $scope.greet = function() { - * alert($scope.name); - * }; - * - * $scope.addContact = function() { - * $scope.contacts.push({type:'email', value:'yourname@example.org'}); - * }; - * - * $scope.removeContact = function(contactToRemove) { - * var index = $scope.contacts.indexOf(contactToRemove); - * $scope.contacts.splice(index, 1); - * }; - * - * $scope.clearContact = function(contact) { - * contact.type = 'phone'; - * contact.value = ''; - * }; - * } - * </file> - * <file name="protractor.js" type="protractor"> - * it('should check controller', function() { - * var container = element(by.id('ctrl-exmpl')); - * - * expect(container.element(by.model('name')) - * .getAttribute('value')).toBe('John Smith'); - * - * var firstRepeat = - * container.element(by.repeater('contact in contacts').row(0)); - * var secondRepeat = - * container.element(by.repeater('contact in contacts').row(1)); - * - * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) - * .toBe('408 555 1212'); - * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) - * .toBe('john.smith@example.org'); - * - * firstRepeat.element(by.linkText('clear')).click(); - * - * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) - * .toBe(''); - * - * container.element(by.linkText('add')).click(); - * - * expect(container.element(by.repeater('contact in contacts').row(2)) - * .element(by.model('contact.value')) - * .getAttribute('value')) - * .toBe('yourname@example.org'); - * }); - * </file> - *</example> - - */ -var ngControllerDirective = [function() { - return { - restrict: 'A', - scope: true, - controller: '@', - priority: 500 - }; -}]; - -/** - * @ngdoc directive - * @name ngCsp - * - * @element html - * @description - * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. - * - * 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 Angular to be CSP compatible there are only two things that we need to do differently: - * - * - don't use `Function` constructor to generate optimized value getters - * - don't inject custom stylesheet into the document - * - * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` - * directive will cause Angular to use CSP compatibility 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. - * - * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically - * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). - * To make those directives work in CSP mode, include the `angular-csp.css` manually. - * - * Angular tries to autodetect if CSP is active and automatically turn on the CSP-safe mode. This - * autodetection however triggers a CSP error to be logged in the console: - * - * ``` - * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of - * script in the following Content Security Policy directive: "default-src 'self'". Note that - * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. - * ``` - * - * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` - * directive on the root element of the application or on the `angular.js` script tag, whichever - * appears first in the html document. - * - * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* - * - * @example - * This example shows how to apply the `ngCsp` directive to the `html` tag. - ```html - <!doctype html> - <html ng-app ng-csp> - ... - ... - </html> - ``` - */ - -// ngCsp is not implemented as a proper directive any more, because we need it be processed while we -// bootstrap the system (before $parse is instantiated), for this reason we just have -// the csp.isActive() fn that looks for ng-csp attribute anywhere in the current doc - -/** - * @ngdoc directive - * @name ngClick - * - * @description - * The ngClick directive allows you to specify custom behavior when - * an element is clicked. - * - * @element ANY - * @priority 0 - * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon - * click. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <button ng-click="count = count + 1" ng-init="count=0"> - Increment - </button> - <span> - count: {{count}} - </span> - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-click', function() { - expect(element(by.binding('count')).getText()).toMatch('0'); - element(by.css('button')).click(); - expect(element(by.binding('count')).getText()).toMatch('1'); - }); - </file> - </example> - */ -/* - * A directive that allows creation of custom onclick handlers that are defined as angular - * expressions and are compiled and executed within the current scope. - * - * Events that are handled via these handler are always configured not to propagate further. - */ -var ngEventDirectives = {}; - -// For events that might fire synchronously during DOM manipulation -// we need to execute their event handlers asynchronously using $evalAsync, -// so that they are not executed in an inconsistent state. -var forceAsyncEvents = { - 'blur': true, - 'focus': true -}; -forEach( - 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), - function(name) { - var directiveName = directiveNormalize('ng-' + name); - ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { - return { - restrict: 'A', - compile: function($element, attr) { - var fn = $parse(attr[directiveName]); - return function ngEventHandler(scope, element) { - var eventName = lowercase(name); - element.on(eventName, function(event) { - var callback = function() { - fn(scope, {$event:event}); - }; - if (forceAsyncEvents[eventName] && $rootScope.$$phase) { - scope.$evalAsync(callback); - } else { - scope.$apply(callback); - } - }); - }; - } - }; - }]; - } -); - -/** - * @ngdoc directive - * @name ngDblclick - * - * @description - * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. - * - * @element ANY - * @priority 0 - * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon - * a dblclick. (The Event object is available as `$event`) - * - * @example - <example> - <file name="index.html"> - <button ng-dblclick="count = count + 1" ng-init="count=0"> - Increment (on double click) - </button> - count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngMousedown - * - * @description - * The ngMousedown directive allows you to specify custom behavior on mousedown event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon - * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <button ng-mousedown="count = count + 1" ng-init="count=0"> - Increment (on mouse down) - </button> - count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngMouseup - * - * @description - * Specify custom behavior on mouseup event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon - * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <button ng-mouseup="count = count + 1" ng-init="count=0"> - Increment (on mouse up) - </button> - count: {{count}} - </file> - </example> - */ - -/** - * @ngdoc directive - * @name ngMouseover - * - * @description - * Specify custom behavior on mouseover event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon - * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <button ng-mouseover="count = count + 1" ng-init="count=0"> - Increment (when mouse is over) - </button> - count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngMouseenter - * - * @description - * Specify custom behavior on mouseenter event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon - * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <button ng-mouseenter="count = count + 1" ng-init="count=0"> - Increment (when mouse enters) - </button> - count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngMouseleave - * - * @description - * Specify custom behavior on mouseleave event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon - * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <button ng-mouseleave="count = count + 1" ng-init="count=0"> - Increment (when mouse leaves) - </button> - count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngMousemove - * - * @description - * Specify custom behavior on mousemove event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon - * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <button ng-mousemove="count = count + 1" ng-init="count=0"> - Increment (when mouse moves) - </button> - count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngKeydown - * - * @description - * Specify custom behavior on keydown event. - * - * @element ANY - * @priority 0 - * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon - * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) - * - * @example - <example> - <file name="index.html"> - <input ng-keydown="count = count + 1" ng-init="count=0"> - key down count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngKeyup - * - * @description - * Specify custom behavior on keyup event. - * - * @element ANY - * @priority 0 - * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon - * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) - * - * @example - <example> - <file name="index.html"> - <p>Typing in the input box below updates the key count</p> - <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} - - <p>Typing in the input box below updates the keycode</p> - <input ng-keyup="event=$event"> - <p>event keyCode: {{ event.keyCode }}</p> - <p>event altKey: {{ event.altKey }}</p> - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngKeypress - * - * @description - * Specify custom behavior on keypress event. - * - * @element ANY - * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon - * keypress. ({@link guide/expression#-event- Event object is available as `$event`} - * and can be interrogated for keyCode, altKey, etc.) - * - * @example - <example> - <file name="index.html"> - <input ng-keypress="count = count + 1" ng-init="count=0"> - key press count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngSubmit - * - * @description - * Enables binding angular expressions to onsubmit events. - * - * Additionally it prevents the default action (which for form means sending the request to the - * server and reloading the current page), but only if the form does not contain `action`, - * `data-action`, or `x-action` attributes. - * - * <div class="alert alert-warning"> - * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and - * `ngSubmit` handlers together. See the - * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} - * for a detailed discussion of when `ngSubmit` may be triggered. - * </div> - * - * @element form - * @priority 0 - * @param {expression} ngSubmit {@link guide/expression Expression} to eval. - * ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example module="submitExample"> - <file name="index.html"> - <script> - angular.module('submitExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.list = []; - $scope.text = 'hello'; - $scope.submit = function() { - if ($scope.text) { - $scope.list.push(this.text); - $scope.text = ''; - } - }; - }]); - </script> - <form ng-submit="submit()" ng-controller="ExampleController"> - Enter text and hit enter: - <input type="text" ng-model="text" name="text" /> - <input type="submit" id="submit" value="Submit" /> - <pre>list={{list}}</pre> - </form> - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-submit', function() { - expect(element(by.binding('list')).getText()).toBe('list=[]'); - element(by.css('#submit')).click(); - expect(element(by.binding('list')).getText()).toContain('hello'); - expect(element(by.model('text')).getAttribute('value')).toBe(''); - }); - it('should ignore empty strings', function() { - expect(element(by.binding('list')).getText()).toBe('list=[]'); - element(by.css('#submit')).click(); - element(by.css('#submit')).click(); - expect(element(by.binding('list')).getText()).toContain('hello'); - }); - </file> - </example> - */ - -/** - * @ngdoc directive - * @name ngFocus - * - * @description - * Specify custom behavior on focus event. - * - * Note: As the `focus` event is executed synchronously when calling `input.focus()` - * AngularJS executes the expression using `scope.$evalAsync` if the event is fired - * during an `$apply` to ensure a consistent state. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon - * focus. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - * See {@link ng.directive:ngClick ngClick} - */ - -/** - * @ngdoc directive - * @name ngBlur - * - * @description - * Specify custom behavior on blur event. - * - * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when - * an element has lost focus. - * - * Note: As the `blur` event is executed synchronously also during DOM manipulations - * (e.g. removing a focussed input), - * AngularJS executes the expression using `scope.$evalAsync` if the event is fired - * during an `$apply` to ensure a consistent state. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon - * blur. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - * See {@link ng.directive:ngClick ngClick} - */ - -/** - * @ngdoc directive - * @name ngCopy - * - * @description - * Specify custom behavior on copy event. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon - * copy. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> - copied: {{copied}} - </file> - </example> - */ - -/** - * @ngdoc directive - * @name ngCut - * - * @description - * Specify custom behavior on cut event. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon - * cut. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> - cut: {{cut}} - </file> - </example> - */ - -/** - * @ngdoc directive - * @name ngPaste - * - * @description - * Specify custom behavior on paste event. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon - * paste. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> - pasted: {{paste}} - </file> - </example> - */ - -/** - * @ngdoc directive - * @name ngIf - * @restrict A - * - * @description - * The `ngIf` directive removes or recreates a portion of the DOM tree based on an - * {expression}. If the expression assigned to `ngIf` evaluates to a false - * value then the element is removed from the DOM, otherwise a clone of the - * element is reinserted into the DOM. - * - * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the - * element in the DOM rather than changing its visibility via the `display` css property. A common - * case when this difference is significant is when using css selectors that rely on an element's - * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes. - * - * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope - * is created when the element is restored. The scope created within `ngIf` inherits from - * its parent scope using - * [prototypal inheritance](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance). - * An important implication of this is if `ngModel` is used within `ngIf` to bind to - * a javascript primitive defined in the parent scope. In this case any modifications made to the - * variable within the child scope will override (hide) the value in the parent scope. - * - * Also, `ngIf` recreates elements using their compiled state. An example of this behavior - * is if an element's class attribute is directly modified after it's compiled, using something like - * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element - * the added class will be lost because the original compiled state is used to regenerate the element. - * - * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` - * and `leave` effects. - * - * @animations - * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container - * leave - happens just before the ngIf contents are removed from the DOM - * - * @element ANY - * @scope - * @priority 600 - * @param {expression} ngIf If the {@link guide/expression expression} is falsy then - * the element is removed from the DOM tree. If it is truthy a copy of the compiled - * element is added to the DOM tree. - * - * @example - <example module="ngAnimate" deps="angular-animate.js" animations="true"> - <file name="index.html"> - Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/> - Show when checked: - <span ng-if="checked" class="animate-if"> - I'm removed when the checkbox is unchecked. - </span> - </file> - <file name="animations.css"> - .animate-if { - background:white; - border:1px solid black; - padding:10px; - } - - .animate-if.ng-enter, .animate-if.ng-leave { - -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - } - - .animate-if.ng-enter, - .animate-if.ng-leave.ng-leave-active { - opacity:0; - } - - .animate-if.ng-leave, - .animate-if.ng-enter.ng-enter-active { - opacity:1; - } - </file> - </example> - */ -var ngIfDirective = ['$animate', function($animate) { - return { - multiElement: true, - transclude: 'element', - priority: 600, - terminal: true, - restrict: 'A', - $$tlb: true, - link: function ($scope, $element, $attr, ctrl, $transclude) { - var block, childScope, previousElements; - $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { - - if (value) { - if (!childScope) { - $transclude(function (clone, newScope) { - childScope = newScope; - clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); - // Note: We only need the first/last node of the cloned nodes. - // However, we need to keep the reference to the jqlite wrapper as it might be changed later - // by a directive with templateUrl when its template arrives. - block = { - clone: clone - }; - $animate.enter(clone, $element.parent(), $element); - }); - } - } else { - if(previousElements) { - previousElements.remove(); - previousElements = null; - } - if(childScope) { - childScope.$destroy(); - childScope = null; - } - if(block) { - previousElements = getBlockNodes(block.clone); - $animate.leave(previousElements).then(function() { - previousElements = null; - }); - block = null; - } - } - }); - } - }; -}]; - -/** - * @ngdoc directive - * @name ngInclude - * @restrict ECA - * - * @description - * Fetches, compiles and includes an external HTML fragment. - * - * By default, the template URL is restricted to the same domain and protocol as the - * application document. This is done by calling {@link ng.$sce#getTrustedResourceUrl - * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols - * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or - * [wrap them](ng.$sce#trustAsResourceUrl) as trusted values. Refer to Angular's {@link - * ng.$sce Strict Contextual Escaping}. - * - * In addition, the browser's - * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) - * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) - * policy may further restrict whether the template is successfully loaded. - * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` - * access on some browsers. - * - * @animations - * enter - animation is used to bring new content into the browser. - * leave - animation is used to animate existing content away. - * - * The enter and leave animation occur concurrently. - * - * @scope - * @priority 400 - * - * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, - * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. - * @param {string=} onload Expression to evaluate when a new partial is loaded. - * - * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll - * $anchorScroll} to scroll the viewport after the content is loaded. - * - * - If the attribute is not set, disable scrolling. - * - If the attribute is set without value, enable scrolling. - * - Otherwise enable scrolling only if the expression evaluates to truthy value. - * - * @example - <example module="includeExample" deps="angular-animate.js" animations="true"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <select ng-model="template" ng-options="t.name for t in templates"> - <option value="">(blank)</option> - </select> - url of the template: <tt>{{template.url}}</tt> - <hr/> - <div class="slide-animate-container"> - <div class="slide-animate" ng-include="template.url"></div> - </div> - </div> - </file> - <file name="script.js"> - angular.module('includeExample', ['ngAnimate']) - .controller('ExampleController', ['$scope', function($scope) { - $scope.templates = - [ { name: 'template1.html', url: 'template1.html'}, - { name: 'template2.html', url: 'template2.html'} ]; - $scope.template = $scope.templates[0]; - }]); - </file> - <file name="template1.html"> - Content of template1.html - </file> - <file name="template2.html"> - Content of template2.html - </file> - <file name="animations.css"> - .slide-animate-container { - position:relative; - background:white; - border:1px solid black; - height:40px; - overflow:hidden; - } - - .slide-animate { - padding:10px; - } - - .slide-animate.ng-enter, .slide-animate.ng-leave { - -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - - position:absolute; - top:0; - left:0; - right:0; - bottom:0; - display:block; - padding:10px; - } - - .slide-animate.ng-enter { - top:-50px; - } - .slide-animate.ng-enter.ng-enter-active { - top:0; - } - - .slide-animate.ng-leave { - top:0; - } - .slide-animate.ng-leave.ng-leave-active { - top:50px; - } - </file> - <file name="protractor.js" type="protractor"> - var templateSelect = element(by.model('template')); - var includeElem = element(by.css('[ng-include]')); - - it('should load template1.html', function() { - expect(includeElem.getText()).toMatch(/Content of template1.html/); - }); - - it('should load template2.html', function() { - if (browser.params.browser == 'firefox') { - // Firefox can't handle using selects - // See https://github.com/angular/protractor/issues/480 - return; - } - templateSelect.click(); - templateSelect.all(by.css('option')).get(2).click(); - expect(includeElem.getText()).toMatch(/Content of template2.html/); - }); - - it('should change to blank', function() { - if (browser.params.browser == 'firefox') { - // Firefox can't handle using selects - return; - } - templateSelect.click(); - templateSelect.all(by.css('option')).get(0).click(); - expect(includeElem.isPresent()).toBe(false); - }); - </file> - </example> - */ - - -/** - * @ngdoc event - * @name ngInclude#$includeContentRequested - * @eventType emit on the scope ngInclude was declared in - * @description - * Emitted every time the ngInclude content is requested. - */ - - -/** - * @ngdoc event - * @name ngInclude#$includeContentLoaded - * @eventType emit on the current ngInclude scope - * @description - * Emitted every time the ngInclude content is reloaded. - */ - - -/** - * @ngdoc event - * @name ngInclude#$includeContentError - * @eventType emit on the scope ngInclude was declared in - * @description - * Emitted when a template HTTP request yields an erronous response (status < 200 || status > 299) - */ -var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', '$sce', - function($templateRequest, $anchorScroll, $animate, $sce) { - return { - restrict: 'ECA', - priority: 400, - terminal: true, - transclude: 'element', - controller: angular.noop, - compile: function(element, attr) { - var srcExp = attr.ngInclude || attr.src, - onloadExp = attr.onload || '', - autoScrollExp = attr.autoscroll; - - return function(scope, $element, $attr, ctrl, $transclude) { - var changeCounter = 0, - currentScope, - previousElement, - currentElement; - - var cleanupLastIncludeContent = function() { - if(previousElement) { - previousElement.remove(); - previousElement = null; - } - if(currentScope) { - currentScope.$destroy(); - currentScope = null; - } - if(currentElement) { - $animate.leave(currentElement).then(function() { - previousElement = null; - }); - previousElement = currentElement; - currentElement = null; - } - }; - - scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { - var afterAnimation = function() { - if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { - $anchorScroll(); - } - }; - var thisChangeId = ++changeCounter; - - if (src) { - //set the 2nd param to true to ignore the template request error so that the inner - //contents and scope can be cleaned up. - $templateRequest(src, true).then(function(response) { - if (thisChangeId !== changeCounter) return; - var newScope = scope.$new(); - ctrl.template = response; - - // Note: This will also link all children of ng-include that were contained in the original - // html. If that content contains controllers, ... they could pollute/change the scope. - // However, using ng-include on an element with additional content does not make sense... - // Note: We can't remove them in the cloneAttchFn of $transclude as that - // function is called before linking the content, which would apply child - // directives to non existing elements. - var clone = $transclude(newScope, function(clone) { - cleanupLastIncludeContent(); - $animate.enter(clone, null, $element).then(afterAnimation); - }); - - currentScope = newScope; - currentElement = clone; - - currentScope.$emit('$includeContentLoaded'); - scope.$eval(onloadExp); - }, function() { - if (thisChangeId === changeCounter) { - cleanupLastIncludeContent(); - scope.$emit('$includeContentError'); - } - }); - scope.$emit('$includeContentRequested'); - } else { - cleanupLastIncludeContent(); - ctrl.template = null; - } - }); - }; - } - }; -}]; - -// This directive is called during the $transclude call of the first `ngInclude` directive. -// It will replace and compile the content of the element with the loaded template. -// We need this directive so that the element content is already filled when -// the link function of another directive on the same element as ngInclude -// is called. -var ngIncludeFillContentDirective = ['$compile', - function($compile) { - return { - restrict: 'ECA', - priority: -400, - require: 'ngInclude', - link: function(scope, $element, $attr, ctrl) { - if (/SVG/.test($element[0].toString())) { - // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not - // support innerHTML, so detect this here and try to generate the contents - // specially. - $element.empty(); - $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope, - function namespaceAdaptedClone(clone) { - $element.append(clone); - }, undefined, undefined, $element); - return; - } - - $element.html(ctrl.template); - $compile($element.contents())(scope); - } - }; - }]; - -/** - * @ngdoc directive - * @name ngInit - * @restrict AC - * - * @description - * The `ngInit` directive allows you to evaluate an expression in the - * current scope. - * - * <div class="alert alert-error"> - * The only appropriate use of `ngInit` is for aliasing special properties of - * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you - * should use {@link guide/controller controllers} rather than `ngInit` - * to initialize values on a scope. - * </div> - * <div class="alert alert-warning"> - * **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make - * sure you have parenthesis for correct precedence: - * <pre class="prettyprint"> - * <div ng-init="test1 = (data | orderBy:'name')"></div> - * </pre> - * </div> - * - * @priority 450 - * - * @element ANY - * @param {expression} ngInit {@link guide/expression Expression} to eval. - * - * @example - <example module="initExample"> - <file name="index.html"> - <script> - angular.module('initExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.list = [['a', 'b'], ['c', 'd']]; - }]); - </script> - <div ng-controller="ExampleController"> - <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> - <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> - <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> - </div> - </div> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should alias index positions', function() { - var elements = element.all(by.css('.example-init')); - expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); - expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); - expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); - expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); - }); - </file> - </example> - */ -var ngInitDirective = ngDirective({ - priority: 450, - compile: function() { - return { - pre: function(scope, element, attrs) { - scope.$eval(attrs.ngInit); - } - }; - } -}); - -/** - * @ngdoc directive - * @name ngNonBindable - * @restrict AC - * @priority 1000 - * - * @description - * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current - * DOM element. This is useful if the element contains what appears to be Angular directives and - * bindings but which should be ignored by Angular. This could be the case if you have a site that - * displays snippets of code, for instance. - * - * @element ANY - * - * @example - * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, - * but the one wrapped in `ngNonBindable` is left alone. - * - * @example - <example> - <file name="index.html"> - <div>Normal: {{1 + 2}}</div> - <div ng-non-bindable>Ignored: {{1 + 2}}</div> - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-non-bindable', function() { - expect(element(by.binding('1 + 2')).getText()).toContain('3'); - expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); - }); - </file> - </example> - */ -var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); - -/** - * @ngdoc directive - * @name ngPluralize - * @restrict EA - * - * @description - * `ngPluralize` is a directive that displays messages according to en-US localization rules. - * These rules are bundled with angular.js, but can be overridden - * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive - * by specifying the mappings between - * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) - * and the strings to be displayed. - * - * # Plural categories and explicit number rules - * There are two - * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) - * in Angular's default en-US locale: "one" and "other". - * - * While a plural category may match many numbers (for example, in en-US locale, "other" can match - * any number that is not 1), an explicit number rule can only match one number. For example, the - * explicit number rule for "3" matches the number 3. There are examples of plural categories - * and explicit number rules throughout the rest of this documentation. - * - * # Configuring ngPluralize - * You configure ngPluralize by providing 2 attributes: `count` and `when`. - * You can also provide an optional attribute, `offset`. - * - * The value of the `count` attribute can be either a string or an {@link guide/expression - * Angular expression}; these are evaluated on the current scope for its bound value. - * - * The `when` attribute specifies the mappings between plural categories and the actual - * string to be displayed. The value of the attribute should be a JSON object. - * - * The following example shows how to configure ngPluralize: - * - * ```html - * <ng-pluralize count="personCount" - when="{'0': 'Nobody is viewing.', - * 'one': '1 person is viewing.', - * 'other': '{} people are viewing.'}"> - * </ng-pluralize> - *``` - * - * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not - * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" - * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for - * other numbers, for example 12, so that instead of showing "12 people are viewing", you can - * show "a dozen people are viewing". - * - * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted - * into pluralized strings. In the previous example, Angular will replace `{}` with - * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder - * for <span ng-non-bindable>{{numberExpression}}</span>. - * - * # Configuring ngPluralize with offset - * The `offset` attribute allows further customization of pluralized text, which can result in - * a better user experience. For example, instead of the message "4 people are viewing this document", - * you might display "John, Kate and 2 others are viewing this document". - * The offset attribute allows you to offset a number by any desired value. - * Let's take a look at an example: - * - * ```html - * <ng-pluralize count="personCount" offset=2 - * when="{'0': 'Nobody is viewing.', - * '1': '{{person1}} is viewing.', - * '2': '{{person1}} and {{person2}} are viewing.', - * 'one': '{{person1}}, {{person2}} and one other person are viewing.', - * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> - * </ng-pluralize> - * ``` - * - * Notice that we are still using two plural categories(one, other), but we added - * three explicit number rules 0, 1 and 2. - * When one person, perhaps John, views the document, "John is viewing" will be shown. - * When three people view the document, no explicit number rule is found, so - * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. - * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" - * is shown. - * - * Note that when you specify offsets, you must provide explicit number rules for - * numbers from 0 up to and including the offset. If you use an offset of 3, for example, - * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for - * plural categories "one" and "other". - * - * @param {string|expression} count The variable to be bound to. - * @param {string} when The mapping between plural category to its corresponding strings. - * @param {number=} offset Offset to deduct from the total number. - * - * @example - <example module="pluralizeExample"> - <file name="index.html"> - <script> - angular.module('pluralizeExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.person1 = 'Igor'; - $scope.person2 = 'Misko'; - $scope.personCount = 1; - }]); - </script> - <div ng-controller="ExampleController"> - Person 1:<input type="text" ng-model="person1" value="Igor" /><br/> - Person 2:<input type="text" ng-model="person2" value="Misko" /><br/> - Number of People:<input type="text" ng-model="personCount" value="1" /><br/> - - <!--- Example with simple pluralization rules for en locale ---> - Without Offset: - <ng-pluralize count="personCount" - when="{'0': 'Nobody is viewing.', - 'one': '1 person is viewing.', - 'other': '{} people are viewing.'}"> - </ng-pluralize><br> - - <!--- Example with offset ---> - With Offset(2): - <ng-pluralize count="personCount" offset=2 - when="{'0': 'Nobody is viewing.', - '1': '{{person1}} is viewing.', - '2': '{{person1}} and {{person2}} are viewing.', - 'one': '{{person1}}, {{person2}} and one other person are viewing.', - 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> - </ng-pluralize> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should show correct pluralized string', function() { - var withoutOffset = element.all(by.css('ng-pluralize')).get(0); - var withOffset = element.all(by.css('ng-pluralize')).get(1); - var countInput = element(by.model('personCount')); - - expect(withoutOffset.getText()).toEqual('1 person is viewing.'); - expect(withOffset.getText()).toEqual('Igor is viewing.'); - - countInput.clear(); - countInput.sendKeys('0'); - - expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); - expect(withOffset.getText()).toEqual('Nobody is viewing.'); - - countInput.clear(); - countInput.sendKeys('2'); - - expect(withoutOffset.getText()).toEqual('2 people are viewing.'); - expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); - - countInput.clear(); - countInput.sendKeys('3'); - - expect(withoutOffset.getText()).toEqual('3 people are viewing.'); - expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); - - countInput.clear(); - countInput.sendKeys('4'); - - expect(withoutOffset.getText()).toEqual('4 people are viewing.'); - expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); - }); - it('should show data-bound names', function() { - var withOffset = element.all(by.css('ng-pluralize')).get(1); - var personCount = element(by.model('personCount')); - var person1 = element(by.model('person1')); - var person2 = element(by.model('person2')); - personCount.clear(); - personCount.sendKeys('4'); - person1.clear(); - person1.sendKeys('Di'); - person2.clear(); - person2.sendKeys('Vojta'); - expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); - }); - </file> - </example> - */ -var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { - var BRACE = /{}/g; - return { - restrict: 'EA', - link: function(scope, element, attr) { - var numberExp = attr.count, - whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs - offset = attr.offset || 0, - whens = scope.$eval(whenExp) || {}, - whensExpFns = {}, - startSymbol = $interpolate.startSymbol(), - endSymbol = $interpolate.endSymbol(), - isWhen = /^when(Minus)?(.+)$/; - - forEach(attr, function(expression, attributeName) { - if (isWhen.test(attributeName)) { - whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = - element.attr(attr.$attr[attributeName]); - } - }); - forEach(whens, function(expression, key) { - whensExpFns[key] = - $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + - offset + endSymbol)); - }); - - scope.$watch(function ngPluralizeWatch() { - var value = parseFloat(scope.$eval(numberExp)); - - 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 (!(value in whens)) value = $locale.pluralCat(value - offset); - return whensExpFns[value](scope); - } else { - return ''; - } - }, function ngPluralizeWatchAction(newVal) { - element.text(newVal); - }); - } - }; -}]; - -/** - * @ngdoc directive - * @name ngRepeat - * - * @description - * The `ngRepeat` directive instantiates a template once per item from a collection. Each template - * instance gets its own scope, where the given loop variable is set to the current collection item, - * and `$index` is set to the item index or key. - * - * Special properties are exposed on the local scope of each template instance, including: - * - * | Variable | Type | Details | - * |-----------|-----------------|-----------------------------------------------------------------------------| - * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | - * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | - * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | - * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | - * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | - * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | - * - * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. - * This may be useful when, for instance, nesting ngRepeats. - * - * # Special repeat start and end points - * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending - * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. - * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on) - * up to and including the ending HTML tag where **ng-repeat-end** is placed. - * - * The example below makes use of this feature: - * ```html - * <header ng-repeat-start="item in items"> - * Header {{ item }} - * </header> - * <div class="body"> - * Body {{ item }} - * </div> - * <footer ng-repeat-end> - * Footer {{ item }} - * </footer> - * ``` - * - * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: - * ```html - * <header> - * Header A - * </header> - * <div class="body"> - * Body A - * </div> - * <footer> - * Footer A - * </footer> - * <header> - * Header B - * </header> - * <div class="body"> - * Body B - * </div> - * <footer> - * Footer B - * </footer> - * ``` - * - * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such - * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). - * - * @animations - * **.enter** - when a new item is added to the list or when an item is revealed after a filter - * - * **.leave** - when an item is removed from the list or when an item is filtered out - * - * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered - * - * @element ANY - * @scope - * @priority 1000 - * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These - * formats are currently supported: - * - * * `variable in expression` – where variable is the user defined loop variable and `expression` - * is a scope expression giving the collection to enumerate. - * - * For example: `album in artist.albums`. - * - * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, - * and `expression` is the scope expression giving the collection to enumerate. - * - * For example: `(name, age) in {'adam':10, 'amalie':12}`. - * - * * `variable in expression track by tracking_expression` – You can also provide an optional tracking function - * which can be used to associate the objects in the collection with the DOM elements. If no tracking function - * is specified the ng-repeat associates elements by identity in the collection. It is an error to have - * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are - * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, - * before specifying a tracking expression. - * - * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements - * will be associated by item identity in the array. - * - * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the - * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message - * when a filter is active on the repeater, but the filtered result set is empty. - * - * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after - * the items have been processed through the filter. - * - * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique - * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements - * with the corresponding item in the array by identity. Moving the same object in array would move the DOM - * element in the same way in the DOM. - * - * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this - * case the object identity does not matter. Two objects are considered equivalent as long as their `id` - * property is same. - * - * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter - * to items in conjunction with a tracking expression. - * - * @example - * This example initializes the scope to a list of names and - * then uses `ngRepeat` to display every person: - <example module="ngAnimate" deps="angular-animate.js" animations="true"> - <file name="index.html"> - <div ng-init="friends = [ - {name:'John', age:25, gender:'boy'}, - {name:'Jessie', age:30, gender:'girl'}, - {name:'Johanna', age:28, gender:'girl'}, - {name:'Joy', age:15, gender:'girl'}, - {name:'Mary', age:28, gender:'girl'}, - {name:'Peter', age:95, gender:'boy'}, - {name:'Sebastian', age:50, gender:'boy'}, - {name:'Erika', age:27, gender:'girl'}, - {name:'Patrick', age:40, gender:'boy'}, - {name:'Samantha', age:60, gender:'girl'} - ]"> - I have {{friends.length}} friends. They are: - <input type="search" ng-model="q" placeholder="filter friends..." /> - <ul class="example-animate-container"> - <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results"> - [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. - </li> - <li class="animate-repeat" ng-if="results.length == 0"> - <strong>No results found...</strong> - </li> - </ul> - </div> - </file> - <file name="animations.css"> - .example-animate-container { - background:white; - border:1px solid black; - list-style:none; - margin:0; - padding:0 10px; - } - - .animate-repeat { - line-height:40px; - list-style:none; - box-sizing:border-box; - } - - .animate-repeat.ng-move, - .animate-repeat.ng-enter, - .animate-repeat.ng-leave { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; - } - - .animate-repeat.ng-leave.ng-leave-active, - .animate-repeat.ng-move, - .animate-repeat.ng-enter { - opacity:0; - max-height:0; - } - - .animate-repeat.ng-leave, - .animate-repeat.ng-move.ng-move-active, - .animate-repeat.ng-enter.ng-enter-active { - opacity:1; - max-height:40px; - } - </file> - <file name="protractor.js" type="protractor"> - var friends = element.all(by.repeater('friend in friends')); - - it('should render initial data set', function() { - expect(friends.count()).toBe(10); - expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); - expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); - expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); - expect(element(by.binding('friends.length')).getText()) - .toMatch("I have 10 friends. They are:"); - }); - - it('should update repeater when filter predicate changes', function() { - expect(friends.count()).toBe(10); - - element(by.model('q')).sendKeys('ma'); - - expect(friends.count()).toBe(2); - expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); - expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); - }); - </file> - </example> - */ -var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { - var NG_REMOVED = '$$NG_REMOVED'; - var ngRepeatMinErr = minErr('ngRepeat'); - - var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { - // TODO(perf): generate setters to shave off ~40ms or 1-1.5% - scope[valueIdentifier] = value; - if (keyIdentifier) scope[keyIdentifier] = key; - scope.$index = index; - scope.$first = (index === 0); - scope.$last = (index === (arrayLength - 1)); - scope.$middle = !(scope.$first || scope.$last); - // jshint bitwise: false - scope.$odd = !(scope.$even = (index&1) === 0); - // jshint bitwise: true - }; - - var getBlockStart = function(block) { - return block.clone[0]; - }; - - var getBlockEnd = function(block) { - return block.clone[block.clone.length - 1]; - }; - - - return { - restrict: 'A', - multiElement: true, - transclude: 'element', - priority: 1000, - terminal: true, - $$tlb: true, - compile: function ngRepeatCompile($element, $attr) { - var expression = $attr.ngRepeat; - var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' '); - - var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); - - if (!match) { - throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", - expression); - } - - var lhs = match[1]; - var rhs = match[2]; - var aliasAs = match[3]; - var trackByExp = match[4]; - - match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); - - if (!match) { - throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", - lhs); - } - var valueIdentifier = match[3] || match[1]; - var keyIdentifier = match[2]; - - if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || - /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent)$/.test(aliasAs))) { - throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.", - aliasAs); - } - - var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; - var hashFnLocals = {$id: hashKey}; - - if (trackByExp) { - trackByExpGetter = $parse(trackByExp); - } else { - trackByIdArrayFn = function (key, value) { - return hashKey(value); - }; - trackByIdObjFn = function (key) { - return key; - }; - } - - return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { - - if (trackByExpGetter) { - trackByIdExpFn = function(key, value, index) { - // assign key, value, and $index to the locals so that they can be used in hash functions - if (keyIdentifier) hashFnLocals[keyIdentifier] = key; - hashFnLocals[valueIdentifier] = value; - hashFnLocals.$index = index; - return trackByExpGetter($scope, hashFnLocals); - }; - } - - // Store a list of elements from previous run. This is a hash where key is the item from the - // iterator, and the value is objects with following properties. - // - scope: bound scope - // - element: previous element. - // - index: position - // - // We are using no-proto object so that we don't need to guard against inherited props via - // hasOwnProperty. - var lastBlockMap = createMap(); - - //watch props - $scope.$watchCollection(rhs, function ngRepeatAction(collection) { - var index, length, - previousNode = $element[0], // node that cloned nodes should be inserted after - // initialized to the comment node anchor - nextNode, - // Same as lastBlockMap but it has the current state. It will become the - // lastBlockMap on the next iteration. - nextBlockMap = createMap(), - collectionLength, - key, value, // key/value of iteration - trackById, - trackByIdFn, - collectionKeys, - block, // last object information {scope, element, id} - nextBlockOrder, - elementsToRemove; - - if (aliasAs) { - $scope[aliasAs] = collection; - } - - if (isArrayLike(collection)) { - collectionKeys = collection; - trackByIdFn = trackByIdExpFn || trackByIdArrayFn; - } else { - trackByIdFn = trackByIdExpFn || trackByIdObjFn; - // if object, extract keys, sort them and use to determine order of iteration over obj props - collectionKeys = []; - for (var itemKey in collection) { - if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) != '$') { - collectionKeys.push(itemKey); - } - } - collectionKeys.sort(); - } - - collectionLength = collectionKeys.length; - nextBlockOrder = new Array(collectionLength); - - // locate existing items - for (index = 0; index < collectionLength; index++) { - key = (collection === collectionKeys) ? index : collectionKeys[index]; - value = collection[key]; - trackById = trackByIdFn(key, value, index); - if (lastBlockMap[trackById]) { - // found previously seen block - block = lastBlockMap[trackById]; - delete lastBlockMap[trackById]; - nextBlockMap[trackById] = block; - nextBlockOrder[index] = block; - } else if (nextBlockMap[trackById]) { - // if collision detected. restore lastBlockMap and throw an error - forEach(nextBlockOrder, function (block) { - if (block && block.scope) lastBlockMap[block.id] = block; - }); - throw ngRepeatMinErr('dupes', - "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", - expression, trackById, toJson(value)); - } else { - // new never before seen block - nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; - nextBlockMap[trackById] = true; - } - } - - // remove leftover items - for (var blockKey in lastBlockMap) { - block = lastBlockMap[blockKey]; - elementsToRemove = getBlockNodes(block.clone); - $animate.leave(elementsToRemove); - if (elementsToRemove[0].parentNode) { - // if the element was not removed yet because of pending animation, mark it as deleted - // so that we can ignore it later - for (index = 0, length = elementsToRemove.length; index < length; index++) { - elementsToRemove[index][NG_REMOVED] = true; - } - } - block.scope.$destroy(); - } - - // we are not using forEach for perf reasons (trying to avoid #call) - for (index = 0; index < collectionLength; index++) { - key = (collection === collectionKeys) ? index : collectionKeys[index]; - value = collection[key]; - block = nextBlockOrder[index]; - - if (block.scope) { - // if we have already seen this object, then we need to reuse the - // associated scope/element - - nextNode = previousNode; - - // skip nodes that are already pending removal via leave animation - do { - nextNode = nextNode.nextSibling; - } while (nextNode && nextNode[NG_REMOVED]); - - if (getBlockStart(block) != nextNode) { - // existing item which got moved - $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode)); - } - previousNode = getBlockEnd(block); - updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); - } else { - // new item which we don't know about - $transclude(function ngRepeatTransclude(clone, scope) { - block.scope = scope; - // http://jsperf.com/clone-vs-createcomment - var endNode = ngRepeatEndComment.cloneNode(false); - clone[clone.length++] = endNode; - - // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper? - $animate.enter(clone, null, jqLite(previousNode)); - previousNode = endNode; - // Note: We only need the first/last node of the cloned nodes. - // However, we need to keep the reference to the jqlite wrapper as it might be changed later - // by a directive with templateUrl when its template arrives. - block.clone = clone; - nextBlockMap[block.id] = block; - updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); - }); - } - } - lastBlockMap = nextBlockMap; - }); - }; - } - }; -}]; - -/** - * @ngdoc directive - * @name ngShow - * - * @description - * The `ngShow` directive shows or hides the given HTML element based on the expression - * provided to the ngShow attribute. The element is shown or hidden by removing or adding - * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined - * in AngularJS and sets the display style to none (using an !important flag). - * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). - * - * ```html - * <!-- when $scope.myValue is truthy (element is visible) --> - * <div ng-show="myValue"></div> - * - * <!-- when $scope.myValue is falsy (element is hidden) --> - * <div ng-show="myValue" class="ng-hide"></div> - * ``` - * - * When the ngShow expression evaluates to a falsy value then the ng-hide CSS class is added to the class - * attribute on the element causing it to become hidden. When truthy, the ng-hide CSS class is removed - * from the element causing the element not to appear hidden. - * - * ## Why is !important used? - * - * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector - * can be easily overridden by heavier selectors. For example, something as simple - * as changing the display style on a HTML list item would make hidden elements appear visible. - * This also becomes a bigger issue when dealing with CSS frameworks. - * - * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector - * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the - * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. - * - * ### Overriding .ng-hide - * - * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change - * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` - * class in CSS: - * - * ```css - * .ng-hide { - * /* this is just another form of hiding an element */ - * display:block!important; - * position:absolute; - * top:-9999px; - * left:-9999px; - * } - * ``` - * - * By default you don't need to override in CSS anything and the animations will work around the display style. - * - * ## A note about animations with ngShow - * - * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression - * is true and false. This system works like the animation system present with ngClass except that - * you must also include the !important flag to override the display property - * so that you can perform an animation when the element is hidden during the time of the animation. - * - * ```css - * // - * //a working example can be found at the bottom of this page - * // - * .my-element.ng-hide-add, .my-element.ng-hide-remove { - * /* this is required as of 1.3x to properly - * apply all styling in a show/hide animation */ - * transition:0s linear all; - * } - * - * .my-element.ng-hide-add-active, - * .my-element.ng-hide-remove-active { - * /* the transition is defined in the active class */ - * transition:1s linear all; - * } - * - * .my-element.ng-hide-add { ... } - * .my-element.ng-hide-add.ng-hide-add-active { ... } - * .my-element.ng-hide-remove { ... } - * .my-element.ng-hide-remove.ng-hide-remove-active { ... } - * ``` - * - * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display - * property to block during animation states--ngAnimate will handle the style toggling automatically for you. - * - * @animations - * addClass: .ng-hide - happens after the ngShow expression evaluates to a truthy value and the just before contents are set to visible - * removeClass: .ng-hide - happens after the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden - * - * @element ANY - * @param {expression} ngShow If the {@link guide/expression expression} is truthy - * then the element is shown or hidden respectively. - * - * @example - <example module="ngAnimate" deps="angular-animate.js" animations="true"> - <file name="index.html"> - Click me: <input type="checkbox" ng-model="checked"><br/> - <div> - Show: - <div class="check-element animate-show" ng-show="checked"> - <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. - </div> - </div> - <div> - Hide: - <div class="check-element animate-show" ng-hide="checked"> - <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. - </div> - </div> - </file> - <file name="glyphicons.css"> - @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); - </file> - <file name="animations.css"> - .animate-show { - line-height:20px; - opacity:1; - padding:10px; - border:1px solid black; - background:white; - } - - .animate-show.ng-hide-add.ng-hide-add-active, - .animate-show.ng-hide-remove.ng-hide-remove-active { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; - } - - .animate-show.ng-hide { - line-height:0; - opacity:0; - padding:0 10px; - } - - .check-element { - padding:10px; - border:1px solid black; - background:white; - } - </file> - <file name="protractor.js" type="protractor"> - var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); - var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); - - it('should check ng-show / ng-hide', function() { - expect(thumbsUp.isDisplayed()).toBeFalsy(); - expect(thumbsDown.isDisplayed()).toBeTruthy(); - - element(by.model('checked')).click(); - - expect(thumbsUp.isDisplayed()).toBeTruthy(); - expect(thumbsDown.isDisplayed()).toBeFalsy(); - }); - </file> - </example> - */ -var ngShowDirective = ['$animate', function($animate) { - return { - restrict: 'A', - multiElement: true, - link: function(scope, element, attr) { - scope.$watch(attr.ngShow, function ngShowWatchAction(value){ - $animate[value ? 'removeClass' : 'addClass'](element, 'ng-hide'); - }); - } - }; -}]; - - -/** - * @ngdoc directive - * @name ngHide - * - * @description - * The `ngHide` directive shows or hides the given HTML element based on the expression - * provided to the ngHide attribute. The element is shown or hidden by removing or adding - * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined - * in AngularJS and sets the display style to none (using an !important flag). - * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). - * - * ```html - * <!-- when $scope.myValue is truthy (element is hidden) --> - * <div ng-hide="myValue" class="ng-hide"></div> - * - * <!-- when $scope.myValue is falsy (element is visible) --> - * <div ng-hide="myValue"></div> - * ``` - * - * When the ngHide expression evaluates to a truthy value then the .ng-hide CSS class is added to the class - * attribute on the element causing it to become hidden. When falsy, the ng-hide CSS class is removed - * from the element causing the element not to appear hidden. - * - * ## Why is !important used? - * - * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector - * can be easily overridden by heavier selectors. For example, something as simple - * as changing the display style on a HTML list item would make hidden elements appear visible. - * This also becomes a bigger issue when dealing with CSS frameworks. - * - * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector - * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the - * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. - * - * ### Overriding .ng-hide - * - * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change - * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` - * class in CSS: - * - * ```css - * .ng-hide { - * /* this is just another form of hiding an element */ - * display:block!important; - * position:absolute; - * top:-9999px; - * left:-9999px; - * } - * ``` - * - * By default you don't need to override in CSS anything and the animations will work around the display style. - * - * ## A note about animations with ngHide - * - * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression - * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` - * CSS class is added and removed for you instead of your own CSS class. - * - * ```css - * // - * //a working example can be found at the bottom of this page - * // - * .my-element.ng-hide-add, .my-element.ng-hide-remove { - * transition:0.5s linear all; - * } - * - * .my-element.ng-hide-add { ... } - * .my-element.ng-hide-add.ng-hide-add-active { ... } - * .my-element.ng-hide-remove { ... } - * .my-element.ng-hide-remove.ng-hide-remove-active { ... } - * ``` - * - * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display - * property to block during animation states--ngAnimate will handle the style toggling automatically for you. - * - * @animations - * removeClass: .ng-hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden - * addClass: .ng-hide - happens after the ngHide expression evaluates to a non truthy value and just before the contents are set to visible - * - * @element ANY - * @param {expression} ngHide If the {@link guide/expression expression} is truthy then - * the element is shown or hidden respectively. - * - * @example - <example module="ngAnimate" deps="angular-animate.js" animations="true"> - <file name="index.html"> - Click me: <input type="checkbox" ng-model="checked"><br/> - <div> - Show: - <div class="check-element animate-hide" ng-show="checked"> - <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. - </div> - </div> - <div> - Hide: - <div class="check-element animate-hide" ng-hide="checked"> - <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. - </div> - </div> - </file> - <file name="glyphicons.css"> - @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); - </file> - <file name="animations.css"> - .animate-hide { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; - line-height:20px; - opacity:1; - padding:10px; - border:1px solid black; - background:white; - } - - .animate-hide.ng-hide { - line-height:0; - opacity:0; - padding:0 10px; - } - - .check-element { - padding:10px; - border:1px solid black; - background:white; - } - </file> - <file name="protractor.js" type="protractor"> - var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); - var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); - - it('should check ng-show / ng-hide', function() { - expect(thumbsUp.isDisplayed()).toBeFalsy(); - expect(thumbsDown.isDisplayed()).toBeTruthy(); - - element(by.model('checked')).click(); - - expect(thumbsUp.isDisplayed()).toBeTruthy(); - expect(thumbsDown.isDisplayed()).toBeFalsy(); - }); - </file> - </example> - */ -var ngHideDirective = ['$animate', function($animate) { - return { - restrict: 'A', - multiElement: true, - link: function(scope, element, attr) { - scope.$watch(attr.ngHide, function ngHideWatchAction(value){ - $animate[value ? 'addClass' : 'removeClass'](element, 'ng-hide'); - }); - } - }; -}]; - -/** - * @ngdoc directive - * @name ngStyle - * @restrict AC - * - * @description - * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. - * - * @element ANY - * @param {expression} ngStyle - * - * {@link guide/expression Expression} which evals to an - * object whose keys are CSS style names and values are corresponding values for those CSS - * keys. - * - * Since some CSS style names are not valid keys for an object, they must be quoted. - * See the 'background-color' style in the example below. - * - * @example - <example> - <file name="index.html"> - <input type="button" value="set color" ng-click="myStyle={color:'red'}"> - <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> - <input type="button" value="clear" ng-click="myStyle={}"> - <br/> - <span ng-style="myStyle">Sample Text</span> - <pre>myStyle={{myStyle}}</pre> - </file> - <file name="style.css"> - span { - color: black; - } - </file> - <file name="protractor.js" type="protractor"> - var colorSpan = element(by.css('span')); - - it('should check ng-style', function() { - expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); - element(by.css('input[value=\'set color\']')).click(); - expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); - element(by.css('input[value=clear]')).click(); - expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); - }); - </file> - </example> - */ -var ngStyleDirective = ngDirective(function(scope, element, attr) { - scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { - if (oldStyles && (newStyles !== oldStyles)) { - forEach(oldStyles, function(val, style) { element.css(style, '');}); - } - if (newStyles) element.css(newStyles); - }, true); -}); - -/** - * @ngdoc directive - * @name ngSwitch - * @restrict EA - * - * @description - * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. - * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location - * as specified in the template. - * - * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it - * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element - * matches the value obtained from the evaluated expression. In other words, you define a container element - * (where you place the directive), place an expression on the **`on="..."` attribute** - * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place - * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on - * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default - * attribute is displayed. - * - * <div class="alert alert-info"> - * Be aware that the attribute values to match against cannot be expressions. They are interpreted - * as literal string values to match against. - * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the - * value of the expression `$scope.someVal`. - * </div> - - * @animations - * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container - * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM - * - * @usage - * - * ``` - * <ANY ng-switch="expression"> - * <ANY ng-switch-when="matchValue1">...</ANY> - * <ANY ng-switch-when="matchValue2">...</ANY> - * <ANY ng-switch-default>...</ANY> - * </ANY> - * ``` - * - * - * @scope - * @priority 1200 - * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>. - * On child elements add: - * - * * `ngSwitchWhen`: the case statement to match against. If match then this - * case will be displayed. If the same match appears multiple times, all the - * elements will be displayed. - * * `ngSwitchDefault`: the default case when no other case match. If there - * are multiple default cases, all of them will be displayed when no other - * case match. - * - * - * @example - <example module="switchExample" deps="angular-animate.js" animations="true"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <select ng-model="selection" ng-options="item for item in items"> - </select> - <tt>selection={{selection}}</tt> - <hr/> - <div class="animate-switch-container" - ng-switch on="selection"> - <div class="animate-switch" ng-switch-when="settings">Settings Div</div> - <div class="animate-switch" ng-switch-when="home">Home Span</div> - <div class="animate-switch" ng-switch-default>default</div> - </div> - </div> - </file> - <file name="script.js"> - angular.module('switchExample', ['ngAnimate']) - .controller('ExampleController', ['$scope', function($scope) { - $scope.items = ['settings', 'home', 'other']; - $scope.selection = $scope.items[0]; - }]); - </file> - <file name="animations.css"> - .animate-switch-container { - position:relative; - background:white; - border:1px solid black; - height:40px; - overflow:hidden; - } - - .animate-switch { - padding:10px; - } - - .animate-switch.ng-animate { - -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - - position:absolute; - top:0; - left:0; - right:0; - bottom:0; - } - - .animate-switch.ng-leave.ng-leave-active, - .animate-switch.ng-enter { - top:-50px; - } - .animate-switch.ng-leave, - .animate-switch.ng-enter.ng-enter-active { - top:0; - } - </file> - <file name="protractor.js" type="protractor"> - var switchElem = element(by.css('[ng-switch]')); - var select = element(by.model('selection')); - - it('should start in settings', function() { - expect(switchElem.getText()).toMatch(/Settings Div/); - }); - it('should change to home', function() { - select.all(by.css('option')).get(1).click(); - expect(switchElem.getText()).toMatch(/Home Span/); - }); - it('should select default', function() { - select.all(by.css('option')).get(2).click(); - expect(switchElem.getText()).toMatch(/default/); - }); - </file> - </example> - */ -var ngSwitchDirective = ['$animate', function($animate) { - return { - restrict: 'EA', - require: 'ngSwitch', - - // asks for $scope to fool the BC controller module - controller: ['$scope', function ngSwitchController() { - this.cases = {}; - }], - link: function(scope, element, attr, ngSwitchController) { - var watchExpr = attr.ngSwitch || attr.on, - selectedTranscludes = [], - selectedElements = [], - previousLeaveAnimations = [], - selectedScopes = []; - - var spliceFactory = function(array, index) { - return function() { array.splice(index, 1); }; - }; - - scope.$watch(watchExpr, function ngSwitchWatchAction(value) { - var i, ii; - for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) { - $animate.cancel(previousLeaveAnimations[i]); - } - previousLeaveAnimations.length = 0; - - for (i = 0, ii = selectedScopes.length; i < ii; ++i) { - var selected = getBlockNodes(selectedElements[i].clone); - selectedScopes[i].$destroy(); - var promise = previousLeaveAnimations[i] = $animate.leave(selected); - promise.then(spliceFactory(previousLeaveAnimations, i)); - } - - selectedElements.length = 0; - selectedScopes.length = 0; - - if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { - forEach(selectedTranscludes, function(selectedTransclude) { - selectedTransclude.transclude(function(caseElement, selectedScope) { - selectedScopes.push(selectedScope); - var anchor = selectedTransclude.element; - caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: '); - var block = { clone: caseElement }; - - selectedElements.push(block); - $animate.enter(caseElement, anchor.parent(), anchor); - }); - }); - } - }); - } - }; -}]; - -var ngSwitchWhenDirective = ngDirective({ - transclude: 'element', - priority: 1200, - require: '^ngSwitch', - multiElement: true, - link: function(scope, element, attrs, ctrl, $transclude) { - ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); - ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); - } -}); - -var ngSwitchDefaultDirective = ngDirective({ - transclude: 'element', - priority: 1200, - require: '^ngSwitch', - multiElement: true, - link: function(scope, element, attr, ctrl, $transclude) { - ctrl.cases['?'] = (ctrl.cases['?'] || []); - ctrl.cases['?'].push({ transclude: $transclude, element: element }); - } -}); - -/** - * @ngdoc directive - * @name ngTransclude - * @restrict EAC - * - * @description - * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. - * - * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. - * - * @element ANY - * - * @example - <example module="transcludeExample"> - <file name="index.html"> - <script> - angular.module('transcludeExample', []) - .directive('pane', function(){ - return { - restrict: 'E', - transclude: true, - scope: { title:'@' }, - template: '<div style="border: 1px solid black;">' + - '<div style="background-color: gray">{{title}}</div>' + - '<ng-transclude></ng-transclude>' + - '</div>' - }; - }) - .controller('ExampleController', ['$scope', function($scope) { - $scope.title = 'Lorem Ipsum'; - $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; - }]); - </script> - <div ng-controller="ExampleController"> - <input ng-model="title"><br> - <textarea ng-model="text"></textarea> <br/> - <pane title="{{title}}">{{text}}</pane> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should have transcluded', function() { - var titleElement = element(by.model('title')); - titleElement.clear(); - titleElement.sendKeys('TITLE'); - var textElement = element(by.model('text')); - textElement.clear(); - textElement.sendKeys('TEXT'); - expect(element(by.binding('title')).getText()).toEqual('TITLE'); - expect(element(by.binding('text')).getText()).toEqual('TEXT'); - }); - </file> - </example> - * - */ -var ngTranscludeDirective = ngDirective({ - restrict: 'EAC', - link: function($scope, $element, $attrs, controller, $transclude) { - if (!$transclude) { - throw minErr('ngTransclude')('orphan', - 'Illegal use of ngTransclude directive in the template! ' + - 'No parent directive that requires a transclusion found. ' + - 'Element: {0}', - startingTag($element)); - } - - $transclude(function(clone) { - $element.empty(); - $element.append(clone); - }); - } -}); - -/** - * @ngdoc directive - * @name script - * @restrict E - * - * @description - * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the - * template can be used by {@link ng.directive:ngInclude `ngInclude`}, - * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the - * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be - * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. - * - * @param {string} type Must be set to `'text/ng-template'`. - * @param {string} id Cache name of the template. - * - * @example - <example> - <file name="index.html"> - <script type="text/ng-template" id="/tpl.html"> - Content of the template. - </script> - - <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> - <div id="tpl-content" ng-include src="currentTpl"></div> - </file> - <file name="protractor.js" type="protractor"> - it('should load template defined inside script tag', function() { - element(by.css('#tpl-link')).click(); - expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); - }); - </file> - </example> - */ -var scriptDirective = ['$templateCache', function($templateCache) { - return { - restrict: 'E', - terminal: true, - compile: function(element, attr) { - if (attr.type == 'text/ng-template') { - var templateUrl = attr.id, - // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent - text = element[0].text; - - $templateCache.put(templateUrl, text); - } - } - }; -}]; - -var ngOptionsMinErr = minErr('ngOptions'); -/** - * @ngdoc directive - * @name select - * @restrict E - * - * @description - * HTML `SELECT` element with angular data-binding. - * - * # `ngOptions` - * - * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` - * elements for the `<select>` element using the array or object obtained by evaluating the - * `ngOptions` comprehension_expression. - * - * When an item in the `<select>` menu is selected, the array element or object property - * represented by the selected option will be bound to the model identified by the `ngModel` - * directive. - * - * <div class="alert alert-warning"> - * **Note:** `ngModel` compares by reference, not value. This is important when binding to an - * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). - * </div> - * - * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can - * be nested into the `<select>` element. This element will then represent the `null` or "not selected" - * option. See example below for demonstration. - * - * <div class="alert alert-warning"> - * **Note:** `ngOptions` provides an iterator facility for the `<option>` element which should be used instead - * of {@link ng.directive:ngRepeat ngRepeat} when you want the - * `select` model to be bound to a non-string value. This is because an option element can only - * be bound to string values at present. - * </div> - * - * @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 - * `required` when you want to data-bind to the `required` attribute. - * @param {comprehension_expression=} ngOptions in one of the following forms: - * - * * for array data sources: - * * `label` **`for`** `value` **`in`** `array` - * * `select` **`as`** `label` **`for`** `value` **`in`** `array` - * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` - * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` - * * for object data sources: - * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` - * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` - * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` - * * `select` **`as`** `label` **`group by`** `group` - * **`for` `(`**`key`**`,`** `value`**`) in`** `object` - * - * Where: - * - * * `array` / `object`: an expression which evaluates to an array / object to iterate over. - * * `value`: local variable which will refer to each item in the `array` or each property value - * of `object` during iteration. - * * `key`: local variable which will refer to a property name in `object` during iteration. - * * `label`: The result of this expression will be the label for `<option>` element. The - * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). - * * `select`: The result of this expression will be bound to the model of the parent `<select>` - * element. If not specified, `select` expression will default to `value`. - * * `group`: The result of this expression will be used to group options using the `<optgroup>` - * DOM element. - * * `trackexpr`: Used when working with an array of objects. The result of this expression will be - * used to identify the objects in the array. The `trackexpr` will most likely refer to the - * `value` variable (e.g. `value.propertyName`). - * - * @example - <example module="selectExample"> - <file name="index.html"> - <script> - angular.module('selectExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.colors = [ - {name:'black', shade:'dark'}, - {name:'white', shade:'light'}, - {name:'red', shade:'dark'}, - {name:'blue', shade:'dark'}, - {name:'yellow', shade:'light'} - ]; - $scope.myColor = $scope.colors[2]; // red - }]); - </script> - <div ng-controller="ExampleController"> - <ul> - <li ng-repeat="color in colors"> - Name: <input ng-model="color.name"> - [<a href ng-click="colors.splice($index, 1)">X</a>] - </li> - <li> - [<a href ng-click="colors.push({})">add</a>] - </li> - </ul> - <hr/> - Color (null not allowed): - <select ng-model="myColor" ng-options="color.name for color in colors"></select><br> - - Color (null allowed): - <span class="nullable"> - <select ng-model="myColor" ng-options="color.name for color in colors"> - <option value="">-- choose color --</option> - </select> - </span><br/> - - Color grouped by shade: - <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> - </select><br/> - - - Select <a href ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</a>.<br> - <hr/> - Currently selected: {{ {selected_color:myColor} }} - <div style="border:solid 1px black; height:20px" - ng-style="{'background-color':myColor.name}"> - </div> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-options', function() { - expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); - element.all(by.model('myColor')).first().click(); - element.all(by.css('select[ng-model="myColor"] option')).first().click(); - expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); - element(by.css('.nullable select[ng-model="myColor"]')).click(); - element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); - expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); - }); - </file> - </example> - */ - -var ngOptionsDirective = valueFn({ - restrict: 'A', - terminal: true -}); - -// jshint maxlen: false -var selectDirective = ['$compile', '$parse', function($compile, $parse) { - //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888 - var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/, - nullModelCtrl = {$setViewValue: noop}; -// jshint maxlen: 100 - - return { - restrict: 'E', - require: ['select', '?ngModel'], - controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { - var self = this, - optionsMap = {}, - ngModelCtrl = nullModelCtrl, - nullOption, - unknownOption; - - - self.databound = $attrs.ngModel; - - - self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { - ngModelCtrl = ngModelCtrl_; - nullOption = nullOption_; - unknownOption = unknownOption_; - }; - - - self.addOption = function(value, element) { - assertNotHasOwnProperty(value, '"option value"'); - optionsMap[value] = true; - - if (ngModelCtrl.$viewValue == value) { - $element.val(value); - if (unknownOption.parent()) unknownOption.remove(); - } - // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 - // Adding an <option selected="selected"> element to a <select required="required"> should - // automatically select the new element - if (element[0].hasAttribute('selected')) { - element[0].selected = true; - } - }; - - - self.removeOption = function(value) { - if (this.hasOption(value)) { - delete optionsMap[value]; - if (ngModelCtrl.$viewValue == value) { - this.renderUnknownOption(value); - } - } - }; - - - self.renderUnknownOption = function(val) { - var unknownVal = '? ' + hashKey(val) + ' ?'; - unknownOption.val(unknownVal); - $element.prepend(unknownOption); - $element.val(unknownVal); - unknownOption.prop('selected', true); // needed for IE - }; - - - self.hasOption = function(value) { - return optionsMap.hasOwnProperty(value); - }; - - $scope.$on('$destroy', function() { - // disable unknown option so that we don't do work when the whole select is being destroyed - self.renderUnknownOption = noop; - }); - }], - - link: function(scope, element, attr, ctrls) { - // if ngModel is not defined, we don't need to do anything - if (!ctrls[1]) return; - - var selectCtrl = ctrls[0], - ngModelCtrl = ctrls[1], - multiple = attr.multiple, - optionsExp = attr.ngOptions, - nullOption = false, // if false, user will not be able to select it (used by ngOptions) - emptyOption, - renderScheduled = false, - // we can't just jqLite('<option>') since jqLite is not smart enough - // to create it in <select> and IE barfs otherwise. - optionTemplate = jqLite(document.createElement('option')), - optGroupTemplate =jqLite(document.createElement('optgroup')), - unknownOption = optionTemplate.clone(); - - // find "null" option - for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) { - if (children[i].value === '') { - emptyOption = nullOption = children.eq(i); - break; - } - } - - selectCtrl.init(ngModelCtrl, nullOption, unknownOption); - - // required validator - if (multiple) { - ngModelCtrl.$isEmpty = function(value) { - return !value || value.length === 0; - }; - } - - if (optionsExp) setupAsOptions(scope, element, ngModelCtrl); - else if (multiple) setupAsMultiple(scope, element, ngModelCtrl); - else setupAsSingle(scope, element, ngModelCtrl, selectCtrl); - - - //////////////////////////// - - - - function setupAsSingle(scope, selectElement, ngModelCtrl, selectCtrl) { - ngModelCtrl.$render = function() { - var viewValue = ngModelCtrl.$viewValue; - - if (selectCtrl.hasOption(viewValue)) { - if (unknownOption.parent()) unknownOption.remove(); - selectElement.val(viewValue); - if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy - } else { - if (isUndefined(viewValue) && emptyOption) { - selectElement.val(''); - } else { - selectCtrl.renderUnknownOption(viewValue); - } - } - }; - - selectElement.on('change', function() { - scope.$apply(function() { - if (unknownOption.parent()) unknownOption.remove(); - ngModelCtrl.$setViewValue(selectElement.val()); - }); - }); - } - - function setupAsMultiple(scope, selectElement, ctrl) { - var lastView; - ctrl.$render = function() { - var items = new HashMap(ctrl.$viewValue); - forEach(selectElement.find('option'), function(option) { - option.selected = isDefined(items.get(option.value)); - }); - }; - - // we have to do it on each watch since ngModel watches reference, but - // we need to work of an array, so we need to see if anything was inserted/removed - scope.$watch(function selectMultipleWatch() { - if (!equals(lastView, ctrl.$viewValue)) { - lastView = shallowCopy(ctrl.$viewValue); - ctrl.$render(); - } - }); - - selectElement.on('change', function() { - scope.$apply(function() { - var array = []; - forEach(selectElement.find('option'), function(option) { - if (option.selected) { - array.push(option.value); - } - }); - ctrl.$setViewValue(array); - }); - }); - } - - function setupAsOptions(scope, selectElement, ctrl) { - var match; - - if (!(match = optionsExp.match(NG_OPTIONS_REGEXP))) { - throw ngOptionsMinErr('iexp', - "Expected expression in form of " + - "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + - " but got '{0}'. Element: {1}", - optionsExp, startingTag(selectElement)); - } - - var displayFn = $parse(match[2] || match[1]), - valueName = match[4] || match[6], - keyName = match[5], - groupByFn = $parse(match[3] || ''), - valueFn = $parse(match[2] ? match[1] : valueName), - valuesFn = $parse(match[7]), - track = match[8], - trackFn = track ? $parse(match[8]) : null, - // This is an array of array of existing option groups in DOM. - // We try to reuse these if possible - // - optionGroupsCache[0] is the options with no option group - // - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element - optionGroupsCache = [[{element: selectElement, label:''}]]; - - if (nullOption) { - // compile the element since there might be bindings in it - $compile(nullOption)(scope); - - // remove the class, which is added automatically because we recompile the element and it - // becomes the compilation root - nullOption.removeClass('ng-scope'); - - // we need to remove it before calling selectElement.empty() because otherwise IE will - // remove the label from the element. wtf? - nullOption.remove(); - } - - // clear contents, we'll add what's needed based on the model - selectElement.empty(); - - selectElement.on('change', function() { - scope.$apply(function() { - var optionGroup, - collection = valuesFn(scope) || [], - locals = {}, - key, value, optionElement, index, groupIndex, length, groupLength, trackIndex; - - if (multiple) { - value = []; - for (groupIndex = 0, groupLength = optionGroupsCache.length; - groupIndex < groupLength; - groupIndex++) { - // list of options for that group. (first item has the parent) - optionGroup = optionGroupsCache[groupIndex]; - - for(index = 1, length = optionGroup.length; index < length; index++) { - if ((optionElement = optionGroup[index].element)[0].selected) { - key = optionElement.val(); - if (keyName) locals[keyName] = key; - if (trackFn) { - for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { - locals[valueName] = collection[trackIndex]; - if (trackFn(scope, locals) == key) break; - } - } else { - locals[valueName] = collection[key]; - } - value.push(valueFn(scope, locals)); - } - } - } - } else { - key = selectElement.val(); - if (key == '?') { - value = undefined; - } else if (key === ''){ - value = null; - } else { - if (trackFn) { - for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { - locals[valueName] = collection[trackIndex]; - if (trackFn(scope, locals) == key) { - value = valueFn(scope, locals); - break; - } - } - } else { - locals[valueName] = collection[key]; - if (keyName) locals[keyName] = key; - value = valueFn(scope, locals); - } - } - } - ctrl.$setViewValue(value); - render(); - }); - }); - - ctrl.$render = render; - - scope.$watchCollection(valuesFn, scheduleRendering); - - if (multiple) { - scope.$watchCollection(function() { return ctrl.$modelValue; }, scheduleRendering); - } - - - function getSelectedSet() { - var selectedSet = false; - if (multiple) { - var modelValue = ctrl.$modelValue; - if (trackFn && isArray(modelValue)) { - selectedSet = new HashMap([]); - var locals = {}; - for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) { - locals[valueName] = modelValue[trackIndex]; - selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]); - } - } else { - selectedSet = new HashMap(modelValue); - } - } - return selectedSet; - } - - - function scheduleRendering() { - if (!renderScheduled) { - scope.$$postDigest(render); - renderScheduled = true; - } - } - - - function render() { - renderScheduled = false; - - // Temporary location for the option groups before we render them - var optionGroups = {'':[]}, - optionGroupNames = [''], - optionGroupName, - optionGroup, - option, - existingParent, existingOptions, existingOption, - modelValue = ctrl.$modelValue, - values = valuesFn(scope) || [], - keys = keyName ? sortedKeys(values) : values, - key, - groupLength, length, - groupIndex, index, - locals = {}, - selected, - selectedSet = getSelectedSet(), - lastElement, - element, - label; - - - // We now build up the list of options we need (we merge later) - for (index = 0; length = keys.length, index < length; index++) { - - key = index; - if (keyName) { - key = keys[index]; - if ( key.charAt(0) === '$' ) continue; - locals[keyName] = key; - } - - locals[valueName] = values[key]; - - optionGroupName = groupByFn(scope, locals) || ''; - if (!(optionGroup = optionGroups[optionGroupName])) { - optionGroup = optionGroups[optionGroupName] = []; - optionGroupNames.push(optionGroupName); - } - if (multiple) { - selected = isDefined( - selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals)) - ); - } else { - if (trackFn) { - var modelCast = {}; - modelCast[valueName] = modelValue; - selected = trackFn(scope, modelCast) === trackFn(scope, locals); - } else { - selected = modelValue === valueFn(scope, locals); - } - selectedSet = selectedSet || selected; // see if at least one item is selected - } - label = displayFn(scope, locals); // what will be seen by the user - - // doing displayFn(scope, locals) || '' overwrites zero values - label = isDefined(label) ? label : ''; - optionGroup.push({ - // either the index into array or key from object - id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index), - label: label, - selected: selected // determine if we should be selected - }); - } - 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 - for (groupIndex = 0, groupLength = optionGroupNames.length; - groupIndex < groupLength; - groupIndex++) { - // current option group name or '' if no group - optionGroupName = optionGroupNames[groupIndex]; - - // list of options for that group. (first item has the parent) - optionGroup = optionGroups[optionGroupName]; - - if (optionGroupsCache.length <= groupIndex) { - // we need to grow the optionGroups - existingParent = { - element: optGroupTemplate.clone().attr('label', optionGroupName), - label: optionGroup.label - }; - existingOptions = [existingParent]; - optionGroupsCache.push(existingOptions); - selectElement.append(existingParent.element); - } else { - existingOptions = optionGroupsCache[groupIndex]; - existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element - - // update the OPTGROUP label if not the same. - if (existingParent.label != optionGroupName) { - existingParent.element.attr('label', existingParent.label = optionGroupName); - } - } - - lastElement = null; // start at the beginning - for(index = 0, length = optionGroup.length; index < length; index++) { - option = optionGroup[index]; - if ((existingOption = existingOptions[index+1])) { - // reuse elements - lastElement = existingOption.element; - if (existingOption.label !== option.label) { - lastElement.text(existingOption.label = option.label); - } - if (existingOption.id !== option.id) { - lastElement.val(existingOption.id = option.id); - } - // lastElement.prop('selected') provided by jQuery has side-effects - if (lastElement[0].selected !== option.selected) { - lastElement.prop('selected', (existingOption.selected = option.selected)); - if (msie) { - // See #7692 - // The selected item wouldn't visually update on IE without this. - // Tested on Win7: IE9, IE10 and IE11. Future IEs should be tested as well - lastElement.prop('selected', existingOption.selected); - } - } - } else { - // grow elements - - // if it's a null option - if (option.id === '' && nullOption) { - // put back the pre-compiled element - element = nullOption; - } else { - // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but - // in this version of jQuery on some browser the .text() returns a string - // rather then the element. - (element = optionTemplate.clone()) - .val(option.id) - .prop('selected', option.selected) - .attr('selected', option.selected) - .text(option.label); - } - - existingOptions.push(existingOption = { - element: element, - label: option.label, - id: option.id, - selected: option.selected - }); - if (lastElement) { - lastElement.after(element); - } else { - existingParent.element.append(element); - } - lastElement = element; - } - } - // remove any excessive OPTIONs in a group - index++; // increment since the existingOptions[0] is parent element not OPTION - while(existingOptions.length > index) { - existingOptions.pop().element.remove(); - } - } - // remove any excessive OPTGROUPs from select - while(optionGroupsCache.length > groupIndex) { - optionGroupsCache.pop()[0].element.remove(); - } - } - } - } - }; -}]; - -var optionDirective = ['$interpolate', function($interpolate) { - var nullSelectCtrl = { - addOption: noop, - removeOption: noop - }; - - return { - restrict: 'E', - priority: 100, - compile: function(element, attr) { - if (isUndefined(attr.value)) { - var interpolateFn = $interpolate(element.text(), true); - if (!interpolateFn) { - attr.$set('value', element.text()); - } - } - - return function (scope, element, attr) { - var selectCtrlName = '$selectController', - parent = element.parent(), - selectCtrl = parent.data(selectCtrlName) || - parent.parent().data(selectCtrlName); // in case we are in optgroup - - if (selectCtrl && selectCtrl.databound) { - // For some reason Opera defaults to true and if not overridden this messes up the repeater. - // We don't want the view to drive the initialization of the model anyway. - element.prop('selected', false); - } else { - selectCtrl = nullSelectCtrl; - } - - if (interpolateFn) { - scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { - attr.$set('value', newVal); - if (oldVal !== newVal) { - selectCtrl.removeOption(oldVal); - } - selectCtrl.addOption(newVal, element); - }); - } else { - selectCtrl.addOption(attr.value, element); - } - - element.on('$destroy', function() { - selectCtrl.removeOption(attr.value); - }); - }; - } - }; -}]; - -var styleDirective = valueFn({ - restrict: 'E', - terminal: false -}); - - if (window.angular.bootstrap) { - //AngularJS is already loaded, so we can return here... - console.log('WARNING: Tried to load angular more than once.'); - return; - } - - //try to bind to jquery now so that one can write jqLite(document).ready() - //but we will rebind on bootstrap again. - bindJQuery(); - - publishExternalAPI(angular); - - jqLite(document).ready(function() { - angularInit(document, bootstrap); - }); - -})(window, document); - -!window.angular.$$csp() && window.angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-animate){display:none !important;}ng\\:form{display:block;}</style>'); \ No newline at end of file diff --git a/syweb/webclient/js/angular.min.js b/syweb/webclient/js/angular.min.js deleted file mode 100644 index 5475589e2f..0000000000 --- a/syweb/webclient/js/angular.min.js +++ /dev/null @@ -1,237 +0,0 @@ -/* - AngularJS v1.3.0-rc.1 - (c) 2010-2014 Google, Inc. http://angularjs.org - License: MIT -*/ -(function(t,Y,s){'use strict';function K(b){return function(){var a=arguments[0],c;c="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.3.0-rc.1/"+(b?b+"/":"")+a;for(a=1;a<arguments.length;a++){c=c+(1==a?"?":"&")+"p"+(a-1)+"=";var d=encodeURIComponent,e;e=arguments[a];e="function"==typeof e?e.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof e?"undefined":"string"!=typeof e?JSON.stringify(e):e;c+=d(e)}return Error(c)}}function Na(b){if(null==b||Oa(b))return!1;var a=b.length;return 1===b.nodeType&& -a?!0:C(b)||L(b)||0===a||"number"===typeof a&&0<a&&a-1 in b}function r(b,a,c){var d,e;if(b)if(D(b))for(d in b)"prototype"==d||"length"==d||"name"==d||b.hasOwnProperty&&!b.hasOwnProperty(d)||a.call(c,b[d],d,b);else if(L(b)||Na(b)){var f="object"!==typeof b;d=0;for(e=b.length;d<e;d++)(f||d in b)&&a.call(c,b[d],d,b)}else if(b.forEach&&b.forEach!==r)b.forEach(a,c,b);else for(d in b)b.hasOwnProperty(d)&&a.call(c,b[d],d,b);return b}function Zb(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a.sort()} -function od(b,a,c){for(var d=Zb(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function $b(b){return function(a,c){b(c,a)}}function pd(){return++bb}function ac(b,a){a?b.$$hashKey=a:delete b.$$hashKey}function E(b){for(var a=b.$$hashKey,c=1,d=arguments.length;c<d;c++){var e=arguments[c];if(e)for(var f=Object.keys(e),g=0,h=f.length;g<h;g++){var m=f[g];b[m]=e[m]}}ac(b,a);return b}function U(b){return parseInt(b,10)}function bc(b,a){return E(new (E(function(){},{prototype:b})),a)}function w(){} -function Pa(b){return b}function da(b){return function(){return b}}function F(b){return"undefined"===typeof b}function B(b){return"undefined"!==typeof b}function S(b){return null!==b&&"object"===typeof b}function C(b){return"string"===typeof b}function ea(b){return"number"===typeof b}function fa(b){return"[object Date]"===Ga.call(b)}function D(b){return"function"===typeof b}function cb(b){return"[object RegExp]"===Ga.call(b)}function Oa(b){return b&&b.window===b}function Qa(b){return b&&b.$evalAsync&& -b.$watch}function qd(b){return!(!b||!(b.nodeName||b.prop&&b.attr&&b.find))}function rd(b){var a={};b=b.split(",");var c;for(c=0;c<b.length;c++)a[b[c]]=!0;return a}function pa(b){return P(b.nodeName||b[0].nodeName)}function sd(b,a,c){var d=[];r(b,function(b,f,g){d.push(a.call(c,b,f,g))});return d}function Ra(b,a){var c=b.indexOf(a);0<=c&&b.splice(c,1);return a}function Ha(b,a,c,d){if(Oa(b)||Qa(b))throw Sa("cpws");if(a){if(b===a)throw Sa("cpi");c=c||[];d=d||[];if(S(b)){var e=c.indexOf(b);if(-1!==e)return d[e]; -c.push(b);d.push(a)}if(L(b))for(var f=a.length=0;f<b.length;f++)e=Ha(b[f],null,c,d),S(b[f])&&(c.push(b[f]),d.push(e)),a.push(e);else{var g=a.$$hashKey;L(a)?a.length=0:r(a,function(c,b){delete a[b]});for(f in b)b.hasOwnProperty(f)&&(e=Ha(b[f],null,c,d),S(b[f])&&(c.push(b[f]),d.push(e)),a[f]=e);ac(a,g)}}else if(a=b)L(b)?a=Ha(b,[],c,d):fa(b)?a=new Date(b.getTime()):cb(b)?(a=new RegExp(b.source,b.toString().match(/[^\/]*$/)[0]),a.lastIndex=b.lastIndex):S(b)&&(e=Object.create(Object.getPrototypeOf(b)), -a=Ha(b,e,c,d));return a}function qa(b,a){if(L(b)){a=a||[];for(var c=0,d=b.length;c<d;c++)a[c]=b[c]}else if(S(b))for(c in a=a||{},b)if("$"!==c.charAt(0)||"$"!==c.charAt(1))a[c]=b[c];return a||b}function ra(b,a){if(b===a)return!0;if(null===b||null===a)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,d;if(c==typeof a&&"object"==c)if(L(b)){if(!L(a))return!1;if((c=b.length)==a.length){for(d=0;d<c;d++)if(!ra(b[d],a[d]))return!1;return!0}}else{if(fa(b))return fa(a)?ra(b.getTime(),a.getTime()):!1;if(cb(b)&& -cb(a))return b.toString()==a.toString();if(Qa(b)||Qa(a)||Oa(b)||Oa(a)||L(a))return!1;c={};for(d in b)if("$"!==d.charAt(0)&&!D(b[d])){if(!ra(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!c.hasOwnProperty(d)&&"$"!==d.charAt(0)&&a[d]!==s&&!D(a[d]))return!1;return!0}return!1}function db(b,a,c){return b.concat(Ta.call(a,c))}function cc(b,a){var c=2<arguments.length?Ta.call(arguments,2):[];return!D(a)||a instanceof RegExp?a:c.length?function(){return arguments.length?a.apply(b,c.concat(Ta.call(arguments,0))): -a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}}function td(b,a){var c=a;"string"===typeof b&&"$"===b.charAt(0)&&"$"===b.charAt(1)?c=s:Oa(a)?c="$WINDOW":a&&Y===a?c="$DOCUMENT":Qa(a)&&(c="$SCOPE");return c}function sa(b,a){return"undefined"===typeof b?s:JSON.stringify(b,td,a?" ":null)}function dc(b){return C(b)?JSON.parse(b):b}function ta(b){b=G(b).clone();try{b.empty()}catch(a){}var c=G("<div>").append(b).html();try{return 3===b[0].nodeType?P(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/, -function(a,c){return"<"+P(c)})}catch(d){return P(c)}}function ec(b){try{return decodeURIComponent(b)}catch(a){}}function fc(b){var a={},c,d;r((b||"").split("&"),function(b){b&&(c=b.replace(/\+/g,"%20").split("="),d=ec(c[0]),B(d)&&(b=B(c[1])?ec(c[1]):!0,Ab.call(a,d)?L(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function Bb(b){var a=[];r(b,function(b,d){L(b)?r(b,function(b){a.push(Da(d,!0)+(!0===b?"":"="+Da(b,!0)))}):a.push(Da(d,!0)+(!0===b?"":"="+Da(b,!0)))});return a.length?a.join("&"):""} -function eb(b){return Da(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Da(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,a?"%20":"+")}function ud(b,a){var c,d,e=fb.length;b=G(b);for(d=0;d<e;++d)if(c=fb[d]+a,C(c=b.attr(c)))return c;return null}function vd(b,a){var c,d,e={};r(fb,function(a){a+="app";!c&&b.hasAttribute&&b.hasAttribute(a)&&(c=b,d=b.getAttribute(a))}); -r(fb,function(a){a+="app";var e;!c&&(e=b.querySelector("["+a.replace(":","\\:")+"]"))&&(c=e,d=e.getAttribute(a))});c&&(e.strictDi=null!==ud(c,"strict-di"),a(c,d?[d]:[],e))}function gc(b,a,c){S(c)||(c={});c=E({strictDi:!1},c);var d=function(){b=G(b);if(b.injector()){var d=b[0]===Y?"document":ta(b);throw Sa("btstrpd",d.replace(/</,"<").replace(/>/,">"));}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);c.debugInfoEnabled&&a.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]); -a.unshift("ng");d=Cb(a,c.strictDi);d.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return d},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;t&&e.test(t.name)&&(c.debugInfoEnabled=!0,t.name=t.name.replace(e,""));if(t&&!f.test(t.name))return d();t.name=t.name.replace(f,"");Ea.resumeBootstrap=function(b){r(b,function(b){a.push(b)});d()}}function wd(){t.name="NG_ENABLE_DEBUG_INFO!"+t.name;t.location.reload()}function xd(b){return Ea.element(b).injector().get("$$testability")} -function Db(b,a){a=a||"_";return b.replace(yd,function(b,d){return(d?a:"")+b.toLowerCase()})}function zd(){var b;hc||((la=t.jQuery)&&la.fn.on?(G=la,E(la.fn,{scope:Ia.scope,isolateScope:Ia.isolateScope,controller:Ia.controller,injector:Ia.injector,inheritedData:Ia.inheritedData}),b=la.cleanData,la.cleanData=function(a){var c;if(Eb)Eb=!1;else for(var d=0,e;null!=(e=a[d]);d++)(c=la._data(e,"events"))&&c.$destroy&&la(e).triggerHandler("$destroy");b(a)}):G=V,Ea.element=G,hc=!0)}function Fb(b,a,c){if(!b)throw Sa("areq", -a||"?",c||"required");return b}function gb(b,a,c){c&&L(b)&&(b=b[b.length-1]);Fb(D(b),a,"not a function, got "+(b&&"object"===typeof b?b.constructor.name||"Object":typeof b));return b}function Ja(b,a){if("hasOwnProperty"===b)throw Sa("badname",a);}function ic(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,f=a.length,g=0;g<f;g++)d=a[g],b&&(b=(e=b)[d]);return!c&&D(b)?cc(e,b):b}function hb(b){var a=b[0];b=b[b.length-1];var c=[a];do{a=a.nextSibling;if(!a)break;c.push(a)}while(a!==b);return G(c)}function Ad(b){function a(a, -b,c){return a[b]||(a[b]=c())}var c=K("$injector"),d=K("ng");b=a(b,"angular",Object);b.$$minErr=b.$$minErr||K;return a(b,"module",function(){var b={};return function(f,g,h){if("hasOwnProperty"===f)throw d("badname","module");g&&b.hasOwnProperty(f)&&(b[f]=null);return a(b,f,function(){function a(c,d,e,f){f||(f=b);return function(){f[e||"push"]([c,d,arguments]);return q}}if(!g)throw c("nomod",f);var b=[],d=[],e=[],l=a("$injector","invoke","push",d),q={_invokeQueue:b,_configBlocks:d,_runBlocks:e,requires:g, -name:f,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),animation:a("$animateProvider","register"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:l,run:function(a){e.push(a);return this}};h&&l(h);return q})}})}function Bd(b){E(b,{bootstrap:gc,copy:Ha,extend:E,equals:ra,element:G,forEach:r, -injector:Cb,noop:w,bind:cc,toJson:sa,fromJson:dc,identity:Pa,isUndefined:F,isDefined:B,isString:C,isFunction:D,isObject:S,isNumber:ea,isElement:qd,isArray:L,version:Cd,isDate:fa,lowercase:P,uppercase:ib,callbacks:{counter:0},getTestability:xd,$$minErr:K,$$csp:Ua,reloadWithDebugInfo:wd,$$hasClass:jb});Va=Ad(t);try{Va("ngLocale")}catch(a){Va("ngLocale",[]).provider("$locale",Dd)}Va("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:Ed});a.provider("$compile",jc).directive({a:Fd,input:kc, -textarea:kc,form:Gd,script:Hd,select:Id,style:Jd,option:Kd,ngBind:Ld,ngBindHtml:Md,ngBindTemplate:Nd,ngClass:Od,ngClassEven:Pd,ngClassOdd:Qd,ngCloak:Rd,ngController:Sd,ngForm:Td,ngHide:Ud,ngIf:Vd,ngInclude:Wd,ngInit:Xd,ngNonBindable:Yd,ngPluralize:Zd,ngRepeat:$d,ngShow:ae,ngStyle:be,ngSwitch:ce,ngSwitchWhen:de,ngSwitchDefault:ee,ngOptions:fe,ngTransclude:ge,ngModel:he,ngList:ie,ngChange:je,pattern:lc,ngPattern:lc,required:mc,ngRequired:mc,minlength:nc,ngMinlength:nc,maxlength:oc,ngMaxlength:oc,ngValue:ke, -ngModelOptions:le}).directive({ngInclude:me}).directive(kb).directive(pc);a.provider({$anchorScroll:ne,$animate:oe,$browser:pe,$cacheFactory:qe,$controller:re,$document:se,$exceptionHandler:te,$filter:qc,$interpolate:ue,$interval:ve,$http:we,$httpBackend:xe,$location:ye,$log:ze,$parse:Ae,$rootScope:Be,$q:Ce,$$q:De,$sce:Ee,$sceDelegate:Fe,$sniffer:Ge,$templateCache:He,$templateRequest:Ie,$$testability:Je,$timeout:Ke,$window:Le,$$rAF:Me,$$asyncCallback:Ne})}])}function Wa(b){return b.replace(Oe,function(a, -b,d,e){return e?d.toUpperCase():d}).replace(Pe,"Moz$1")}function rc(b){b=b.nodeType;return 1===b||!b||9===b}function sc(b,a){var c,d,e=a.createDocumentFragment(),f=[];if(Gb.test(b)){c=c||e.appendChild(a.createElement("div"));d=(Qe.exec(b)||["",""])[1].toLowerCase();d=ia[d]||ia._default;c.innerHTML=d[1]+b.replace(Re,"<$1></$2>")+d[2];for(d=d[0];d--;)c=c.lastChild;f=db(f,c.childNodes);c=e.firstChild;c.textContent=""}else f.push(a.createTextNode(b));e.textContent="";e.innerHTML="";r(f,function(a){e.appendChild(a)}); -return e}function V(b){if(b instanceof V)return b;var a;C(b)&&(b=ba(b),a=!0);if(!(this instanceof V)){if(a&&"<"!=b.charAt(0))throw Hb("nosel");return new V(b)}if(a){a=Y;var c;b=(c=Se.exec(b))?[a.createElement(c[1])]:(c=sc(b,a))?c.childNodes:[]}tc(this,b)}function Ib(b){return b.cloneNode(!0)}function lb(b,a){a||mb(b);if(b.querySelectorAll)for(var c=b.querySelectorAll("*"),d=0,e=c.length;d<e;d++)mb(c[d])}function uc(b,a,c,d){if(B(d))throw Hb("offargs");var e=(d=nb(b))&&d.events;if(d&&d.handle)if(a)r(a.split(" "), -function(a){F(c)?(b.removeEventListener(a,e[a],!1),delete e[a]):Ra(e[a]||[],c)});else for(a in e)"$destroy"!==a&&b.removeEventListener(a,e[a],!1),delete e[a]}function mb(b,a){var c=b.ng339,d=c&&ob[c];d&&(a?delete d.data[a]:(d.handle&&(d.events.$destroy&&d.handle({},"$destroy"),uc(b)),delete ob[c],b.ng339=s))}function nb(b,a){var c=b.ng339,c=c&&ob[c];a&&!c&&(b.ng339=c=++Te,c=ob[c]={events:{},data:{},handle:s});return c}function Jb(b,a,c){if(rc(b)){var d=B(c),e=!d&&a&&!S(a),f=!a;b=(b=nb(b,!e))&&b.data; -if(d)b[a]=c;else{if(f)return b;if(e)return b&&b[a];E(b,a)}}}function jb(b,a){return b.getAttribute?-1<(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+a+" "):!1}function Kb(b,a){a&&b.setAttribute&&r(a.split(" "),function(a){b.setAttribute("class",ba((" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").replace(" "+ba(a)+" "," ")))})}function Lb(b,a){if(a&&b.setAttribute){var c=(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");r(a.split(" "),function(a){a= -ba(a);-1===c.indexOf(" "+a+" ")&&(c+=a+" ")});b.setAttribute("class",ba(c))}}function tc(b,a){if(a)if(a.nodeType)b[b.length++]=a;else{var c=a.length;if("number"===typeof c&&a.window!==a){if(c)for(var d=0;d<c;d++)b[b.length++]=a[d]}else b[b.length++]=a}}function vc(b,a){return pb(b,"$"+(a||"ngController")+"Controller")}function pb(b,a,c){9==b.nodeType&&(b=b.documentElement);for(a=L(a)?a:[a];b;){for(var d=0,e=a.length;d<e;d++)if((c=G.data(b,a[d]))!==s)return c;b=b.parentNode||11===b.nodeType&&b.host}} -function wc(b){for(lb(b,!0);b.firstChild;)b.removeChild(b.firstChild)}function xc(b,a){a||lb(b);var c=b.parentNode;c&&c.removeChild(b)}function yc(b,a){var c=qb[a.toLowerCase()];return c&&zc[pa(b)]&&c}function Ue(b,a){var c=b.nodeName;return("INPUT"===c||"TEXTAREA"===c)&&Ac[a]}function Ve(b,a){var c=function(c,e){c.isDefaultPrevented=function(){return c.defaultPrevented};var f=a[e||c.type],g=f?f.length:0;if(g){1<g&&(f=qa(f));for(var h=0;h<g;h++)f[h].call(b,c)}};c.elem=b;return c}function Ka(b,a){var c= -b&&b.$$hashKey;if(c)return"function"===typeof c&&(c=b.$$hashKey()),c;c=typeof b;return c="function"==c||"object"==c&&null!==b?b.$$hashKey=c+":"+(a||pd)():c+":"+b}function Xa(b,a){if(a){var c=0;this.nextUid=function(){return++c}}r(b,this.put,this)}function We(b){return(b=b.toString().replace(Bc,"").match(Cc))?"function("+(b[1]||"").replace(/[\s\r\n]+/," ")+")":"fn"}function Mb(b,a,c){var d;if("function"===typeof b){if(!(d=b.$inject)){d=[];if(b.length){if(a)throw C(c)&&c||(c=b.name||We(b)),La("strictdi", -c);a=b.toString().replace(Bc,"");a=a.match(Cc);r(a[1].split(Xe),function(a){a.replace(Ye,function(a,b,c){d.push(c)})})}b.$inject=d}}else L(b)?(a=b.length-1,gb(b[a],"fn"),d=b.slice(0,a)):gb(b,"fn",!0);return d}function Cb(b,a){function c(a){return function(b,c){if(S(b))r(b,$b(a));else return a(b,c)}}function d(a,b){Ja(a,"service");if(D(b)||L(b))b=p.instantiate(b);if(!b.$get)throw La("pget",a);return n[a+"Provider"]=b}function e(a,b){return d(a,{$get:b})}function f(a){var b=[],c;r(a,function(a){function d(a){var b, -c;b=0;for(c=a.length;b<c;b++){var e=a[b],f=p.get(e[0]);f[e[1]].apply(f,e[2])}}if(!k.get(a)){k.put(a,!0);try{C(a)?(c=Va(a),b=b.concat(f(c.requires)).concat(c._runBlocks),d(c._invokeQueue),d(c._configBlocks)):D(a)?b.push(p.invoke(a)):L(a)?b.push(p.invoke(a)):gb(a,"module")}catch(e){throw L(a)&&(a=a[a.length-1]),e.message&&e.stack&&-1==e.stack.indexOf(e.message)&&(e=e.message+"\n"+e.stack),La("modulerr",a,e.stack||e.message||e);}}});return b}function g(b,c){function d(a){if(b.hasOwnProperty(a)){if(b[a]=== -h)throw La("cdep",a+" <- "+m.join(" <- "));return b[a]}try{return m.unshift(a),b[a]=h,b[a]=c(a)}catch(e){throw b[a]===h&&delete b[a],e;}finally{m.shift()}}function e(b,c,f,h){"string"===typeof f&&(h=f,f=null);var g=[];h=Mb(b,a,h);var k,m,l;m=0;for(k=h.length;m<k;m++){l=h[m];if("string"!==typeof l)throw La("itkn",l);g.push(f&&f.hasOwnProperty(l)?f[l]:d(l))}L(b)&&(b=b[k]);return b.apply(c,g)}return{invoke:e,instantiate:function(a,b,c){var d=function(){};d.prototype=(L(a)?a[a.length-1]:a).prototype; -d=new d;a=e(a,d,b,c);return S(a)||D(a)?a:d},get:d,annotate:Mb,has:function(a){return n.hasOwnProperty(a+"Provider")||b.hasOwnProperty(a)}}}a=!0===a;var h={},m=[],k=new Xa([],!0),n={$provide:{provider:c(d),factory:c(e),service:c(function(a,b){return e(a,["$injector",function(a){return a.instantiate(b)}])}),value:c(function(a,b){return e(a,da(b))}),constant:c(function(a,b){Ja(a,"constant");n[a]=b;l[a]=b}),decorator:function(a,b){var c=p.get(a+"Provider"),d=c.$get;c.$get=function(){var a=q.invoke(d, -c);return q.invoke(b,null,{$delegate:a})}}}},p=n.$injector=g(n,function(){throw La("unpr",m.join(" <- "));}),l={},q=l.$injector=g(l,function(a){var b=p.get(a+"Provider");return q.invoke(b.$get,b,s,a)});r(f(b),function(a){q.invoke(a||w)});return q}function ne(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$get=["$window","$location","$rootScope",function(a,c,d){function e(a){var b=null;r(a,function(a){b||"a"!==pa(a)||(b=a)});return b}function f(){var b=c.hash(),d;b?(d=g.getElementById(b))? -d.scrollIntoView():(d=e(g.getElementsByName(b)))?d.scrollIntoView():"top"===b&&a.scrollTo(0,0):a.scrollTo(0,0)}var g=a.document;b&&d.$watch(function(){return c.hash()},function(){d.$evalAsync(f)});return f}]}function Ne(){this.$get=["$$rAF","$timeout",function(b,a){return b.supported?function(a){return b(a)}:function(b){return a(b,0,!1)}}]}function Ze(b,a,c,d){function e(a){try{a.apply(null,Ta.call(arguments,1))}finally{if(A--,0===A)for(;u.length;)try{u.pop()()}catch(b){c.error(b)}}}function f(a, -b){(function R(){r(x,function(a){a()});z=b(R,a)})()}function g(){y=null;T!=h.url()&&(T=h.url(),r(Q,function(a){a(h.url())}))}var h=this,m=a[0],k=b.location,n=b.history,p=b.setTimeout,l=b.clearTimeout,q={};h.isMock=!1;var A=0,u=[];h.$$completeOutstandingRequest=e;h.$$incOutstandingRequestCount=function(){A++};h.notifyWhenNoOutstandingRequests=function(a){r(x,function(a){a()});0===A?a():u.push(a)};var x=[],z;h.addPollFn=function(a){F(z)&&f(100,p);x.push(a);return a};var T=k.href,v=a.find("base"),y= -null;h.url=function(a,c){k!==b.location&&(k=b.location);n!==b.history&&(n=b.history);if(a){if(T!=a)return T=a,d.history?c?n.replaceState(null,"",a):(n.pushState(null,"",a),v.attr("href",v.attr("href"))):(y=a,c?k.replace(a):k.href=a),h}else return y||k.href.replace(/%27/g,"'")};var Q=[],ca=!1;h.onUrlChange=function(a){if(!ca){if(d.history)G(b).on("popstate",g);if(d.hashchange)G(b).on("hashchange",g);else h.addPollFn(g);ca=!0}Q.push(a);return a};h.$$checkUrlChange=g;h.baseHref=function(){var a=v.attr("href"); -return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};var J={},N="",M=h.baseHref();h.cookies=function(a,b){var d,e,f,h;if(a)b===s?m.cookie=encodeURIComponent(a)+"=;path="+M+";expires=Thu, 01 Jan 1970 00:00:00 GMT":C(b)&&(d=(m.cookie=encodeURIComponent(a)+"="+encodeURIComponent(b)+";path="+M).length+1,4096<d&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!"));else{if(m.cookie!==N)for(N=m.cookie,d=N.split("; "),J={},f=0;f<d.length;f++)e=d[f],h=e.indexOf("="), -0<h&&(a=decodeURIComponent(e.substring(0,h)),J[a]===s&&(J[a]=decodeURIComponent(e.substring(h+1))));return J}};h.defer=function(a,b){var c;A++;c=p(function(){delete q[c];e(a)},b||0);q[c]=!0;return c};h.defer.cancel=function(a){return q[a]?(delete q[a],l(a),e(w),!0):!1}}function pe(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new Ze(b,d,a,c)}]}function qe(){this.$get=function(){function b(b,d){function e(a){a!=p&&(l?l==a&&(l=a.n):l=a,f(a.n,a.p),f(a,p),p=a,p.n=null)} -function f(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(b in a)throw K("$cacheFactory")("iid",b);var g=0,h=E({},d,{id:b}),m={},k=d&&d.capacity||Number.MAX_VALUE,n={},p=null,l=null;return a[b]={put:function(a,b){if(k<Number.MAX_VALUE){var c=n[a]||(n[a]={key:a});e(c)}if(!F(b))return a in m||g++,m[a]=b,g>k&&this.remove(l.key),b},get:function(a){if(k<Number.MAX_VALUE){var b=n[a];if(!b)return;e(b)}return m[a]},remove:function(a){if(k<Number.MAX_VALUE){var b=n[a];if(!b)return;b==p&&(p=b.p);b==l&&(l=b.n);f(b.n, -b.p);delete n[a]}delete m[a];g--},removeAll:function(){m={};g=0;n={};p=l=null},destroy:function(){n=h=m=null;delete a[b]},info:function(){return E({},h,{size:g})}}}var a={};b.info=function(){var b={};r(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function He(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function jc(b,a){var c={},d=/^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/,e=/(([\d\w_\-]+)(?:\:([^;]+))?;?)/,f=rd("ngSrc,ngSrcset,src,srcset"),g= -/^(on[a-z]+|formaction)$/;this.directive=function k(a,d){Ja(a,"directive");C(a)?(Fb(d,"directiveFactory"),c.hasOwnProperty(a)||(c[a]=[],b.factory(a+"Directive",["$injector","$exceptionHandler",function(b,d){var e=[];r(c[a],function(c,f){try{var h=b.invoke(c);D(h)?h={compile:da(h)}:!h.compile&&h.link&&(h.compile=da(h.link));h.priority=h.priority||0;h.index=f;h.name=h.name||a;h.require=h.require||h.controller&&h.name;h.restrict=h.restrict||"EA";e.push(h)}catch(g){d(g)}});return e}])),c[a].push(d)): -r(a,$b(k));return this};this.aHrefSanitizationWhitelist=function(b){return B(b)?(a.aHrefSanitizationWhitelist(b),this):a.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=function(b){return B(b)?(a.imgSrcSanitizationWhitelist(b),this):a.imgSrcSanitizationWhitelist()};var h=!0;this.debugInfoEnabled=function(a){return B(a)?(h=a,this):h};this.$get=["$injector","$interpolate","$exceptionHandler","$templateRequest","$parse","$controller","$rootScope","$document","$sce","$animate","$$sanitizeUri", -function(a,b,p,l,q,A,u,x,z,T,v){function y(a,b){try{a.addClass(b)}catch(c){}}function Q(a,b,c,d,e){a instanceof G||(a=G(a));r(a,function(b,c){3==b.nodeType&&b.nodeValue.match(/\S+/)&&(a[c]=G(b).wrap("<span></span>").parent()[0])});var f=ca(a,b,a,c,d,e);Q.$$addScopeClass(a);var h=null,g=a,k;return function(b,c,d,e,l){Fb(b,"scope");h||(h=(l=l&&l[0])?"foreignobject"!==pa(l)&&l.toString().match(/SVG/)?"svg":"html":"html");"html"!==h&&a[0]!==k&&(g=G(Nb(h,G("<div>").append(a).html())));k=a[0];l=c?Ia.clone.call(g): -g;if(d)for(var n in d)l.data("$"+n+"Controller",d[n].instance);Q.$$addScopeInfo(l,b);c&&c(l,b);f&&f(b,l,l,e);return l}}function ca(a,b,c,d,e,f){function h(a,c,d,e){var f,k,l,n,u,q,ua;if(p)for(ua=Array(c.length),n=0;n<g.length;n+=3)f=g[n],ua[f]=c[f];else ua=c;n=0;for(u=g.length;n<u;)k=ua[g[n++]],c=g[n++],f=g[n++],c?(c.scope?(l=a.$new(),Q.$$addScopeInfo(G(k),l)):l=a,q=c.transcludeOnThisElement?J(a,c.transclude,e,c.elementTranscludeOnThisElement):!c.templateOnThisElement&&e?e:!e&&b?J(a,b):null,c(f,l, -k,d,q)):f&&f(a,k.childNodes,s,e)}for(var g=[],k,l,n,u,p,q=0;q<a.length;q++){k=new Ob;l=N(a[q],[],k,0===q?d:s,e);(f=l.length?H(l,a[q],k,b,c,null,[],[],f):null)&&f.scope&&Q.$$addScopeClass(k.$$element);k=f&&f.terminal||!(n=a[q].childNodes)||!n.length?null:ca(n,f?(f.transcludeOnThisElement||!f.templateOnThisElement)&&f.transclude:b);if(f||k)g.push(q,f,k),u=!0,p=p||f;f=null}return u?h:null}function J(a,b,c,d){return function(e,f,h,g){var k=!1;e||(e=a.$new(),k=e.$$transcluded=!0);f=b(e,f,h,c,g);if(k&& -!d)f.on("$destroy",function(){e.$destroy()});return f}}function N(b,f,h,g,l){var n=h.$attr,u;switch(b.nodeType){case 1:R(f,va(pa(b)),"E",g,l);for(var p,q,T,A=b.attributes,z=0,v=A&&A.length;z<v;z++){var x=!1,J=!1;p=A[z];if(!X||8<=X||p.specified){u=p.name;p=ba(p.value);q=va(u);if(T=U.test(q))u=Db(q.substr(6),"-");var W=q.replace(/(Start|End)$/,""),r;a:{var H=W;if(c.hasOwnProperty(H)){r=void 0;for(var H=a.get(H+"Directive"),O=0,Q=H.length;O<Q;O++)if(r=H[O],r.multiElement){r=!0;break a}}r=!1}r&&q===W+ -"Start"&&(x=u,J=u.substr(0,u.length-5)+"end",u=u.substr(0,u.length-6));q=va(u.toLowerCase());n[q]=u;if(T||!h.hasOwnProperty(q))h[q]=p,yc(b,q)&&(h[q]=!0);ya(b,f,p,q,T);R(f,q,"A",g,l,x,J)}}b=b.className;if(C(b)&&""!==b)for(;u=e.exec(b);)q=va(u[2]),R(f,q,"C",g,l)&&(h[q]=ba(u[3])),b=b.substr(u.index+u[0].length);break;case 3:t(f,b.nodeValue);break;case 8:try{if(u=d.exec(b.nodeValue))q=va(u[1]),R(f,q,"M",g,l)&&(h[q]=ba(u[2]))}catch(M){}}f.sort(F);return f}function M(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&& -a.hasAttribute(b)){do{if(!a)throw ja("uterdir",b,c);1==a.nodeType&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return G(d)}function O(a,b,c){return function(d,e,f,h,g){e=M(e[0],b,c);return a(d,e,f,h,g)}}function H(a,c,d,e,f,h,g,k,l){function u(a,b,c,d){if(a){c&&(a=O(a,c,d));a.require=I.require;a.directiveName=ha;if(y===I||I.$$isolateScope)a=ga(a,{isolateScope:!0});g.push(a)}if(b){c&&(b=O(b,c,d));b.require=I.require;b.directiveName=ha;if(y=== -I||I.$$isolateScope)b=ga(b,{isolateScope:!0});k.push(b)}}function T(a,b,c,d){var e,f="data",h=!1;if(C(b)){for(;"^"==(e=b.charAt(0))||"?"==e;)b=b.substr(1),"^"==e&&(f="inheritedData"),h=h||"?"==e;e=null;d&&"data"===f&&(e=d[b])&&(e=e.instance);e=e||c[f]("$"+b+"Controller");if(!e&&!h)throw ja("ctreq",b,a);}else L(b)&&(e=[],r(b,function(b){e.push(T(a,b,c,d))}));return e}function z(a,e,f,h,l){function u(a,b,c){var d;Qa(a)||(c=b,b=a,a=s);E&&(d=W);c||(c=E?M.parent():M);return l(a,b,d,c)}var p,v,ua,x,W,O, -M,R;c===f?(R=d,M=d.$$element):(M=G(f),R=new Ob(M,d));y&&(x=e.$new(!0));O=l&&u;J&&(H={},W={},r(J,function(a){var b={$scope:a===y||a.$$isolateScope?x:e,$element:M,$attrs:R,$transclude:O};ua=a.controller;"@"==ua&&(ua=R[a.name]);b=A(ua,b,!0,a.controllerAs);W[a.name]=b;E||M.data("$"+a.name+"Controller",b.instance);H[a.name]=b}));if(y){var N=/^\s*([@=&])(\??)\s*(\w*)\s*$/;Q.$$addScopeInfo(M,x,!0,!(ca&&(ca===y||ca===y.$$originalDirective)));Q.$$addScopeClass(M,!0);h=H&&H[y.name];var xa=x;h&&h.identifier&& -!0===y.bindToController&&(xa=h.instance);r(y.scope,function(a,c){var d=a.match(N)||[],f=d[3]||c,h="?"==d[2],d=d[1],g,k,l,u;x.$$isolateBindings[c]=d+f;switch(d){case "@":R.$observe(f,function(a){x[c]=a});R.$$observers[f].$$scope=e;R[f]&&(xa[c]=b(R[f])(e));break;case "=":if(h&&!R[f])break;k=q(R[f]);u=k.literal?ra:function(a,b){return a===b||a!==a&&b!==b};l=k.assign||function(){g=xa[c]=k(e);throw ja("nonassign",R[f],y.name);};g=xa[c]=k(e);h=e.$watch(q(R[f],function(a){u(a,xa[c])||(u(a,g)?l(e,a=xa[c]): -xa[c]=a);return g=a}),null,k.literal);x.$on("$destroy",h);break;case "&":k=q(R[f]);xa[c]=function(a){return k(e,a)};break;default:throw ja("iscp",y.name,c,a);}})}H&&(r(H,function(a){a()}),H=null);h=0;for(p=g.length;h<p;h++)v=g[h],Dc(v,v.isolateScope?x:e,M,R,v.require&&T(v.directiveName,v.require,M,W),O);h=e;y&&(y.template||null===y.templateUrl)&&(h=x);a&&a(h,f.childNodes,s,l);for(h=k.length-1;0<=h;h--)v=k[h],Dc(v,v.isolateScope?x:e,M,R,v.require&&T(v.directiveName,v.require,M,W),O)}l=l||{};for(var v= --Number.MAX_VALUE,x,J=l.controllerDirectives,H,y=l.newIsolateScopeDirective,ca=l.templateDirective,R=l.nonTlbTranscludeDirective,w=!1,F=!1,E=l.hasElementTranscludeDirective,aa=d.$$element=G(c),I,ha,t,P=e,za,ma=0,ya=a.length;ma<ya;ma++){I=a[ma];var U=I.$$start,X=I.$$end;U&&(aa=M(c,U,X));t=s;if(v>I.priority)break;if(t=I.scope)I.templateUrl||(S(t)?(K("new/isolated scope",y||x,I,aa),y=I):K("new/isolated scope",y,I,aa)),x=x||I;ha=I.name;!I.templateUrl&&I.controller&&(t=I.controller,J=J||{},K("'"+ha+"' controller", -J[ha],I,aa),J[ha]=I);if(t=I.transclude)w=!0,I.$$tlb||(K("transclusion",R,I,aa),R=I),"element"==t?(E=!0,v=I.priority,t=aa,aa=d.$$element=G(Y.createComment(" "+ha+": "+d[ha]+" ")),c=aa[0],rb(f,Ta.call(t,0),c),P=Q(t,e,v,h&&h.name,{nonTlbTranscludeDirective:R})):(t=G(Ib(c)).contents(),aa.empty(),P=Q(t,e));if(I.template)if(F=!0,K("template",ca,I,aa),ca=I,t=D(I.template)?I.template(aa,d):I.template,t=V(t),I.replace){h=I;t=Gb.test(t)?G(Nb(I.templateNamespace,ba(t))):[];c=t[0];if(1!=t.length||1!==c.nodeType)throw ja("tplrt", -ha,"");rb(f,aa,c);ya={$attr:{}};t=N(c,[],ya);var Z=a.splice(ma+1,a.length-(ma+1));y&&W(t);a=a.concat(t).concat(Z);B(d,ya);ya=a.length}else aa.html(t);if(I.templateUrl)F=!0,K("template",ca,I,aa),ca=I,I.replace&&(h=I),z=$e(a.splice(ma,a.length-ma),aa,d,f,w&&P,g,k,{controllerDirectives:J,newIsolateScopeDirective:y,templateDirective:ca,nonTlbTranscludeDirective:R}),ya=a.length;else if(I.compile)try{za=I.compile(aa,d,P),D(za)?u(null,za,U,X):za&&u(za.pre,za.post,U,X)}catch($){p($,ta(aa))}I.terminal&&(z.terminal= -!0,v=Math.max(v,I.priority))}z.scope=x&&!0===x.scope;z.transcludeOnThisElement=w;z.elementTranscludeOnThisElement=E;z.templateOnThisElement=F;z.transclude=P;l.hasElementTranscludeDirective=E;return z}function W(a){for(var b=0,c=a.length;b<c;b++)a[b]=bc(a[b],{$$isolateScope:!0})}function R(b,d,e,f,h,g,l){if(d===h)return null;h=null;if(c.hasOwnProperty(d)){var n;d=a.get(d+"Directive");for(var u=0,q=d.length;u<q;u++)try{n=d[u],(f===s||f>n.priority)&&-1!=n.restrict.indexOf(e)&&(g&&(n=bc(n,{$$start:g, -$$end:l})),b.push(n),h=n)}catch(T){p(T)}}return h}function B(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;r(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&b[e]!==d&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});r(b,function(b,f){"class"==f?(y(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):"style"==f?(e.attr("style",e.attr("style")+";"+b),a.style=(a.style?a.style+";":"")+b):"$"==f.charAt(0)||a.hasOwnProperty(f)||(a[f]=b,d[f]=c[f])})}function $e(a,b,c,d,e,f,h,g){var k=[],n,u,p=b[0],q=a.shift(), -T=E({},q,{templateUrl:null,transclude:null,replace:null,$$originalDirective:q}),v=D(q.templateUrl)?q.templateUrl(b,c):q.templateUrl,A=q.templateNamespace;b.empty();l(z.getTrustedResourceUrl(v)).then(function(l){var z,x;l=V(l);if(q.replace){l=Gb.test(l)?G(Nb(A,ba(l))):[];z=l[0];if(1!=l.length||1!==z.nodeType)throw ja("tplrt",q.name,v);l={$attr:{}};rb(d,b,z);var O=N(z,[],l);S(q.scope)&&W(O);a=O.concat(a);B(c,l)}else z=p,b.html(l);a.unshift(T);n=H(a,z,c,e,b,q,f,h,g);r(d,function(a,c){a==z&&(d[c]=b[0])}); -for(u=ca(b[0].childNodes,e);k.length;){l=k.shift();x=k.shift();var M=k.shift(),Q=k.shift(),O=b[0];if(x!==p){var R=x.className;g.hasElementTranscludeDirective&&q.replace||(O=Ib(z));rb(M,G(x),O);y(G(O),R)}x=n.transcludeOnThisElement?J(l,n.transclude,Q):Q;n(u,l,O,d,x)}k=null});return function(a,b,c,d,e){a=e;k?(k.push(b),k.push(c),k.push(d),k.push(a)):(n.transcludeOnThisElement&&(a=J(b,n.transclude,e)),n(u,b,c,d,a))}}function F(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name? --1:1:a.index-b.index}function K(a,b,c,d){if(b)throw ja("multidir",b.name,c.name,a,ta(d));}function t(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:function(a){a=a.parent();var b=!!a.length;b&&Q.$$addBindingClass(a);return function(a,c){var e=c.parent();b||Q.$$addBindingClass(e);Q.$$addBindingInfo(e,d.expressions);a.$watch(d,function(a){c[0].nodeValue=a})}}})}function Nb(a,b){a=P(a||"html");switch(a){case "svg":case "math":var c=Y.createElement("div");c.innerHTML="<"+a+">"+b+"</"+a+">";return c.childNodes[0].childNodes; -default:return b}}function za(a,b){if("srcdoc"==b)return z.HTML;var c=pa(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return z.RESOURCE_URL}function ya(a,c,d,e,h){var k=b(d,!0);if(k){if("multiple"===e&&"select"===pa(a))throw ja("selmulti",ta(a));c.push({priority:100,compile:function(){return{pre:function(c,d,l){d=l.$$observers||(l.$$observers={});if(g.test(e))throw ja("nodomevents");if(k=b(l[e],!0,za(a,e),f[e]||h))l[e]=k(c),(d[e]||(d[e]=[])).$$inter=!0,(l.$$observers&& -l.$$observers[e].$$scope||c).$watch(k,function(a,b){"class"===e&&a!=b?l.$updateClass(a,b):l.$set(e,a)})}}}})}}function rb(a,b,c){var d=b[0],e=b.length,f=d.parentNode,h,g;if(a)for(h=0,g=a.length;h<g;h++)if(a[h]==d){a[h++]=c;g=h+e-1;for(var k=a.length;h<k;h++,g++)g<k?a[h]=a[g]:delete a[h];a.length-=e-1;a.context===d&&(a.context=c);break}f&&f.replaceChild(c,d);a=Y.createDocumentFragment();a.appendChild(d);G(c).data(G(d).data());la?(Eb=!0,la.cleanData([d])):delete G.cache[d[G.expando]];d=1;for(e=b.length;d< -e;d++)f=b[d],G(f).remove(),a.appendChild(f),delete b[d];b[0]=c;b.length=1}function ga(a,b){return E(function(){return a.apply(null,arguments)},a,b)}function Dc(a,b,c,d,e,f){try{a(b,c,d,e,f)}catch(h){p(h,ta(c))}}var Ob=function(a,b){if(b){var c=Object.keys(b),d,e,f;d=0;for(e=c.length;d<e;d++)f=c[d],this[f]=b[f]}else this.$attr={};this.$$element=a};Ob.prototype={$normalize:va,$addClass:function(a){a&&0<a.length&&T.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&T.removeClass(this.$$element, -a)},$updateClass:function(a,b){var c=Ec(a,b);c&&c.length&&T.addClass(this.$$element,c);(c=Ec(b,a))&&c.length&&T.removeClass(this.$$element,c)},$set:function(a,b,c,d){var e=this.$$element[0],f=yc(e,a),h=Ue(e,a),e=a;f?(this.$$element.prop(a,b),d=f):h&&(this[h]=b,e=h);this[a]=b;d?this.$attr[a]=d:(d=this.$attr[a])||(this.$attr[a]=d=Db(a,"-"));f=pa(this.$$element);if("a"===f&&"href"===a||"img"===f&&"src"===a)this[a]=b=v(b,"src"===a);!1!==c&&(null===b||b===s?this.$$element.removeAttr(d):this.$$element.attr(d, -b));(a=this.$$observers)&&r(a[e],function(a){try{a(b)}catch(c){p(c)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers={}),e=d[a]||(d[a]=[]);e.push(b);u.$evalAsync(function(){e.$$inter||b(c[a])});return function(){Ra(e,b)}}};var ma=b.startSymbol(),ha=b.endSymbol(),V="{{"==ma||"}}"==ha?Pa:function(a){return a.replace(/\{\{/g,ma).replace(/}}/g,ha)},U=/^ngAttr[A-Z]/;Q.$$addBindingInfo=h?function(a,b){var c=a.data("$binding")||[];L(b)?c=c.concat(b):c.push(b);a.data("$binding",c)}:w; -Q.$$addBindingClass=h?function(a){y(a,"ng-binding")}:w;Q.$$addScopeInfo=h?function(a,b,c,d){a.data(c?d?"$isolateScopeNoTemplate":"$isolateScope":"$scope",b)}:w;Q.$$addScopeClass=h?function(a,b){y(a,b?"ng-isolate-scope":"ng-scope")}:w;return Q}]}function va(b){return Wa(b.replace(af,""))}function Ec(b,a){var c="",d=b.split(/\s+/),e=a.split(/\s+/),f=0;a:for(;f<d.length;f++){for(var g=d[f],h=0;h<e.length;h++)if(g==e[h])continue a;c+=(0<c.length?" ":"")+g}return c}function re(){var b={},a=!1,c=/^(\S+)(\s+as\s+(\w+))?$/; -this.register=function(a,c){Ja(a,"controller");S(a)?E(b,a):b[a]=c};this.allowGlobals=function(){a=!0};this.$get=["$injector","$window",function(d,e){function f(a,b,c,d){if(!a||!S(a.$scope))throw K("$controller")("noscp",d,b);a.$scope[b]=c}return function(g,h,m,k){var n,p,l;m=!0===m;k&&C(k)&&(l=k);C(g)&&(k=g.match(c),p=k[1],l=l||k[3],g=b.hasOwnProperty(p)?b[p]:ic(h.$scope,p,!0)||(a?ic(e,p,!0):s),gb(g,p,!0));if(m)return m=function(){},m.prototype=(L(g)?g[g.length-1]:g).prototype,n=new m,l&&f(h,l,n, -p||g.name),E(function(){d.invoke(g,n,h,p);return n},{instance:n,identifier:l});n=d.instantiate(g,h,p);l&&f(h,l,n,p||g.name);return n}}]}function se(){this.$get=["$window",function(b){return G(b.document)}]}function te(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function Fc(b){var a={},c,d,e;if(!b)return a;r(b.split("\n"),function(b){e=b.indexOf(":");c=P(ba(b.substr(0,e)));d=ba(b.substr(e+1));c&&(a[c]=a[c]?a[c]+", "+d:d)});return a}function Gc(b){var a=S(b)?b: -s;return function(c){a||(a=Fc(b));return c?a[P(c)]||null:a}}function Hc(b,a,c){if(D(c))return c(b,a);r(c,function(c){b=c(b,a)});return b}function we(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d={"Content-Type":"application/json;charset=utf-8"},e=this.defaults={transformResponse:[function(d){C(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=dc(d)));return d}],transformRequest:[function(a){return S(a)&&"[object File]"!==Ga.call(a)&&"[object Blob]"!==Ga.call(a)?sa(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"}, -post:qa(d),put:qa(d),patch:qa(d)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},f=!1;this.useApplyAsync=function(a){return B(a)?(f=!!a,this):f};var g=this.interceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,d,p,l){function q(a){function b(a){var d=E({},a,{data:Hc(a.data,a.headers,c.transformResponse)});a=a.status;return 200<=a&&300>a?d:p.reject(d)}var c={method:"get",transformRequest:e.transformRequest,transformResponse:e.transformResponse}, -d=function(a){var b=e.headers,c=E({},a.headers),d,f,b=E({},b.common,b[P(a.method)]);a:for(d in b){a=P(d);for(f in c)if(P(f)===a)continue a;c[d]=b[d]}(function(a){var b;r(a,function(c,d){D(c)&&(b=c(),null!=b?a[d]=b:delete a[d])})})(c);return c}(a);E(c,a);c.headers=d;c.method=ib(c.method);var f=[function(a){d=a.headers;var c=Hc(a.data,Gc(d),a.transformRequest);F(c)&&r(d,function(a,b){"content-type"===P(b)&&delete d[b]});F(a.withCredentials)&&!F(e.withCredentials)&&(a.withCredentials=e.withCredentials); -return A(a,c,d).then(b,b)},s],h=p.when(c);for(r(z,function(a){(a.request||a.requestError)&&f.unshift(a.request,a.requestError);(a.response||a.responseError)&&f.push(a.response,a.responseError)});f.length;){a=f.shift();var g=f.shift(),h=h.then(a,g)}h.success=function(a){h.then(function(b){a(b.data,b.status,b.headers,c)});return h};h.error=function(a){h.then(null,function(b){a(b.data,b.status,b.headers,c)});return h};return h}function A(c,g,k){function l(a,b,c,e){function h(){z(b,a,c,e)}O&&(200<=a&& -300>a?O.put(W,[a,b,Fc(c),e]):O.remove(W));f?d.$applyAsync(h):(h(),d.$$phase||d.$apply())}function z(a,b,d,e){b=Math.max(b,0);(200<=b&&300>b?r.resolve:r.reject)({data:a,status:b,headers:Gc(d),config:c,statusText:e})}function A(){var a=q.pendingRequests.indexOf(c);-1!==a&&q.pendingRequests.splice(a,1)}var r=p.defer(),M=r.promise,O,H,W=u(c.url,c.params);q.pendingRequests.push(c);M.then(A,A);!c.cache&&!e.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(O=S(c.cache)?c.cache:S(e.cache)?e.cache: -x);if(O)if(H=O.get(W),B(H)){if(H&&D(H.then))return H.then(A,A),H;L(H)?z(H[1],H[0],qa(H[2]),H[3]):z(H,200,{},"OK")}else O.put(W,M);F(H)&&((H=Ic(c.url)?b.cookies()[c.xsrfCookieName||e.xsrfCookieName]:s)&&(k[c.xsrfHeaderName||e.xsrfHeaderName]=H),a(c.method,W,g,l,k,c.timeout,c.withCredentials,c.responseType));return M}function u(a,b){if(!b)return a;var c=[];od(b,function(a,b){null===a||F(a)||(L(a)||(a=[a]),r(a,function(a){S(a)&&(a=fa(a)?a.toISOString():sa(a));c.push(Da(b)+"="+Da(a))}))});0<c.length&& -(a+=(-1==a.indexOf("?")?"?":"&")+c.join("&"));return a}var x=c("$http"),z=[];r(g,function(a){z.unshift(C(a)?l.get(a):l.invoke(a))});q.pendingRequests=[];(function(a){r(arguments,function(a){q[a]=function(b,c){return q(E(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){r(arguments,function(a){q[a]=function(b,c,d){return q(E(d||{},{method:a,url:b,data:c}))}})})("post","put","patch");q.defaults=e;return q}]}function bf(b){if(8>=X&&(!b.match(/^(get|post|head|put|delete|options)$/i)|| -!t.XMLHttpRequest))return new t.ActiveXObject("Microsoft.XMLHTTP");if(t.XMLHttpRequest)return new t.XMLHttpRequest;throw K("$httpBackend")("noxhr");}function xe(){this.$get=["$browser","$window","$document",function(b,a,c){return cf(b,bf,b.defer,a.angular.callbacks,c[0])}]}function cf(b,a,c,d,e){function f(a,b,c){var f=e.createElement("script"),n=null;f.type="text/javascript";f.src=a;f.async=!0;n=function(a){f.removeEventListener("load",n,!1);f.removeEventListener("error",n,!1);e.body.removeChild(f); -f=null;var g=-1,q="unknown";a&&("load"!==a.type||d[b].called||(a={type:"error"}),q=a.type,g="error"===a.type?404:200);c&&c(g,q)};f.addEventListener("load",n,!1);f.addEventListener("error",n,!1);e.body.appendChild(f);return n}return function(e,h,m,k,n,p,l,q){function A(){x=-1;T&&T();v&&v.abort()}function u(a,d,e,f,g){Q&&c.cancel(Q);T=v=null;0===d&&(d=e?200:"file"==Aa(h).protocol?404:0);a(1223===d?204:d,e,f,g||"");b.$$completeOutstandingRequest(w)}var x;b.$$incOutstandingRequestCount();h=h||b.url(); -if("jsonp"==P(e)){var z="_"+(d.counter++).toString(36);d[z]=function(a){d[z].data=a;d[z].called=!0};var T=f(h.replace("JSON_CALLBACK","angular.callbacks."+z),z,function(a,b){u(k,a,d[z].data,"",b);d[z]=w})}else{var v=a(e);v.open(e,h,!0);r(n,function(a,b){B(a)&&v.setRequestHeader(b,a)});v.onreadystatechange=function(){if(v&&4==v.readyState){var a=null,b=null,c="";-1!==x&&(a=v.getAllResponseHeaders(),b="response"in v?v.response:v.responseText);-1===x&&10>X||(c=v.statusText);u(k,x||v.status,b,a,c)}}; -l&&(v.withCredentials=!0);if(q)try{v.responseType=q}catch(y){if("json"!==q)throw y;}v.send(m||null)}if(0<p)var Q=c(A,p);else p&&D(p.then)&&p.then(A)}}function ue(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse","$exceptionHandler","$sce",function(c,d,e){function f(a){return"\\\\\\"+a}function g(f,g,q,A){function u(c){return c.replace(k,b).replace(n,a)}function x(a){try{var b;var c=q?e.getTrusted(q,a):e.valueOf(a); -if(null==c)b="";else{switch(typeof c){case "string":break;case "number":c=""+c;break;default:c=sa(c)}b=c}return b}catch(h){a=Pb("interr",f,h.toString()),d(a)}}A=!!A;for(var z,T,v=0,r=[],Q=[],s=f.length,J=[],N=[];v<s;)if(-1!=(z=f.indexOf(b,v))&&-1!=(T=f.indexOf(a,z+h)))v!==z&&J.push(u(f.substring(v,z))),v=f.substring(z+h,T),r.push(v),Q.push(c(v,x)),v=T+m,N.push(J.length),J.push("");else{v!==s&&J.push(u(f.substring(v)));break}if(q&&1<J.length)throw Pb("noconcat",f);if(!g||r.length){var M=function(a){for(var b= -0,c=r.length;b<c;b++){if(A&&F(a[b]))return;J[N[b]]=a[b]}return J.join("")};return E(function(a){var b=0,c=r.length,e=Array(c);try{for(;b<c;b++)e[b]=Q[b](a);return M(e)}catch(h){a=Pb("interr",f,h.toString()),d(a)}},{exp:f,expressions:r,$$watchDelegate:function(a,b,c){var d;return a.$watchGroup(Q,function(c,e){var f=M(c);D(b)&&b.call(this,f,c!==e?d:f,a);d=f},c)}})}}var h=b.length,m=a.length,k=new RegExp(b.replace(/./g,f),"g"),n=new RegExp(a.replace(/./g,f),"g");g.startSymbol=function(){return b};g.endSymbol= -function(){return a};return g}]}function ve(){this.$get=["$rootScope","$window","$q","$$q",function(b,a,c,d){function e(e,h,m,k){var n=a.setInterval,p=a.clearInterval,l=0,q=B(k)&&!k,A=(q?d:c).defer(),u=A.promise;m=B(m)?m:0;u.then(null,null,e);u.$$intervalId=n(function(){A.notify(l++);0<m&&l>=m&&(A.resolve(l),p(u.$$intervalId),delete f[u.$$intervalId]);q||b.$apply()},h);f[u.$$intervalId]=A;return u}var f={};e.cancel=function(b){return b&&b.$$intervalId in f?(f[b.$$intervalId].reject("canceled"),a.clearInterval(b.$$intervalId), -delete f[b.$$intervalId],!0):!1};return e}]}function Dd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "), -DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a",short:"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return 1===b?"one":"other"}}}}function Qb(b){b=b.split("/");for(var a=b.length;a--;)b[a]=eb(b[a]);return b.join("/")}function Jc(b,a,c){b=Aa(b,c);a.$$protocol= -b.protocol;a.$$host=b.hostname;a.$$port=U(b.port)||df[b.protocol]||null}function Kc(b,a,c){var d="/"!==b.charAt(0);d&&(b="/"+b);b=Aa(b,c);a.$$path=decodeURIComponent(d&&"/"===b.pathname.charAt(0)?b.pathname.substring(1):b.pathname);a.$$search=fc(b.search);a.$$hash=decodeURIComponent(b.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function wa(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Ya(b){var a=b.indexOf("#");return-1==a?b:b.substr(0,a)}function Rb(b){return b.substr(0, -Ya(b).lastIndexOf("/")+1)}function Lc(b,a){this.$$html5=!0;a=a||"";var c=Rb(b);Jc(b,this,b);this.$$parse=function(a){var e=wa(c,a);if(!C(e))throw sb("ipthprfx",a,c);Kc(e,this,b);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Bb(this.$$search),b=this.$$hash?"#"+eb(this.$$hash):"";this.$$url=Qb(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;(f=wa(b,d))!==s? -(g=f,g=(f=wa(a,f))!==s?c+(wa("/",f)||f):b+g):(f=wa(c,d))!==s?g=c+f:c==d+"/"&&(g=c);g&&this.$$parse(g);return!!g}}function Sb(b,a){var c=Rb(b);Jc(b,this,b);this.$$parse=function(d){var e=wa(b,d)||wa(c,d),e="#"==e.charAt(0)?wa(a,e):this.$$html5?e:"";if(!C(e))throw sb("ihshprfx",d,a);Kc(e,this,b);d=this.$$path;var f=/^\/[A-Z]:(\/.*)/;0===e.indexOf(b)&&(e=e.replace(b,""));f.exec(e)||(d=(e=f.exec(d))?e[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var c=Bb(this.$$search),e=this.$$hash? -"#"+eb(this.$$hash):"";this.$$url=Qb(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+(this.$$url?a+this.$$url:"")};this.$$parseLinkUrl=function(a,c){return Ya(b)==Ya(a)?(this.$$parse(a),!0):!1}}function Mc(b,a){this.$$html5=!0;Sb.apply(this,arguments);var c=Rb(b);this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;b==Ya(d)?f=d:(g=wa(c,d))?f=b+a+g:c===d+"/"&&(f=c);f&&this.$$parse(f);return!!f};this.$$compose=function(){var c=Bb(this.$$search),e=this.$$hash?"#"+eb(this.$$hash): -"";this.$$url=Qb(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+a+this.$$url}}function tb(b){return function(){return this[b]}}function Nc(b,a){return function(c){if(F(c))return this[b];this[b]=a(c);this.$$compose();return this}}function ye(){var b="",a=!1;this.hashPrefix=function(a){return B(a)?(b=a,this):b};this.html5Mode=function(b){return B(b)?(a=b,this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement",function(c,d,e,f){function g(a){c.$broadcast("$locationChangeSuccess",h.absUrl(), -a)}var h,m=d.baseHref(),k=d.url();if(a){if(!m)throw sb("nobase");m=k.substring(0,k.indexOf("/",k.indexOf("//")+2))+(m||"/");e=e.history?Lc:Mc}else m=Ya(k),e=Sb;h=new e(m,"#"+b);h.$$parseLinkUrl(k,k);var n=/^\s*(javascript|mailto):/i;f.on("click",function(a){if(!a.ctrlKey&&!a.metaKey&&2!=a.which){for(var b=G(a.target);"a"!==pa(b[0]);)if(b[0]===f[0]||!(b=b.parent())[0])return;var e=b.prop("href"),g=b.attr("href")||b.attr("xlink:href");S(e)&&"[object SVGAnimatedString]"===e.toString()&&(e=Aa(e.animVal).href); -n.test(e)||!e||b.attr("target")||a.isDefaultPrevented()||!h.$$parseLinkUrl(e,g)||(a.preventDefault(),h.absUrl()!=d.url()&&(c.$apply(),t.angular["ff-684208-preventDefault"]=!0))}});h.absUrl()!=k&&d.url(h.absUrl(),!0);d.onUrlChange(function(a){h.absUrl()!=a&&(c.$evalAsync(function(){var b=h.absUrl();h.$$parse(a);c.$broadcast("$locationChangeStart",a,b).defaultPrevented?(h.$$parse(b),d.url(b)):g(b)}),c.$$phase||c.$digest())});var p=0;c.$watch(function(){var a=d.url(),b=h.$$replace;p&&a==h.absUrl()|| -(p++,c.$evalAsync(function(){c.$broadcast("$locationChangeStart",h.absUrl(),a).defaultPrevented?h.$$parse(a):(d.url(h.absUrl(),b),g(a))}));h.$$replace=!1;return p});return h}]}function ze(){var b=!0,a=this;this.debugEnabled=function(a){return B(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b= -c.console||{},e=b[a]||b.log||w;a=!1;try{a=!!e.apply}catch(m){}return a?function(){var a=[];r(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function na(b,a){if("__defineGetter__"===b||"__defineSetter__"===b||"__lookupGetter__"===b||"__lookupSetter__"===b||"__proto__"===b)throw oa("isecfld",a);return b}function Ba(b, -a){if(b){if(b.constructor===b)throw oa("isecfn",a);if(b.window===b)throw oa("isecwindow",a);if(b.children&&(b.nodeName||b.prop&&b.attr&&b.find))throw oa("isecdom",a);if(b===Object)throw oa("isecobj",a);}return b}function ub(b,a,c,d){Ba(b,d);a=a.split(".");for(var e,f=0;1<a.length;f++){e=na(a.shift(),d);var g=Ba(b[e],d);g||(g={},b[e]=g);b=g}e=na(a.shift(),d);Ba(b[e],d);return b[e]=c}function Oc(b,a,c,d,e,f){na(b,f);na(a,f);na(c,f);na(d,f);na(e,f);return function(f,h){var m=h&&h.hasOwnProperty(b)?h: -f;if(null==m)return m;m=m[b];if(!a)return m;if(null==m)return s;m=m[a];if(!c)return m;if(null==m)return s;m=m[c];if(!d)return m;if(null==m)return s;m=m[d];return e?null==m?s:m=m[e]:m}}function Pc(b,a,c){var d=Qc[b];if(d)return d;var e=b.split("."),f=e.length;if(a.csp)d=6>f?Oc(e[0],e[1],e[2],e[3],e[4],c):function(a,b){var d=0,g;do g=Oc(e[d++],e[d++],e[d++],e[d++],e[d++],c)(a,b),b=s,a=g;while(d<f);return g};else{var g="";r(e,function(a,b){na(a,c);g+="if(s == null) return undefined;\ns="+(b?"s":'((l&&l.hasOwnProperty("'+ -a+'"))?l:s)')+"."+a+";\n"});g+="return s;";a=new Function("s","l",g);a.toString=da(g);a.assign=function(a,c){return ub(a,b,c,b)};d=a}d.sharedGetter=!0;return Qc[b]=d}function Ae(){var b=Object.create(null),a={csp:!1};this.$get=["$filter","$sniffer",function(c,d){function e(a){var b=a;a.sharedGetter&&(b=function(b,c){return a(b,c)},b.literal=a.literal,b.constant=a.constant,b.assign=a.assign);return b}function f(a,b,c,d){var e,f;return e=a.$watch(function(a){return d(a)},function(a,c,d){f=a;D(b)&&b.apply(this, -arguments);B(a)&&d.$$postDigest(function(){B(f)&&e()})},c)}function g(a,b,c,d){function e(a){var b=!0;r(a,function(a){B(a)||(b=!1)});return b}var f;return f=a.$watch(function(a){return d(a)},function(a,c,d){D(b)&&b.call(this,a,c,d);e(a)&&d.$$postDigest(function(){e(a)&&f()})},c)}function h(a,b,c,d){var e;return e=a.$watch(function(a){return d(a)},function(a,c,d){D(b)&&b.apply(this,arguments);e()},c)}function m(a,b){if(!b)return a;var c=function(c,d){var e=a(c,d),f=b(e,c,d);return B(e)?f:e};c.$$watchDelegate= -a.$$watchDelegate;return c}a.csp=d.csp;return function(d,n){var p,l,q;switch(typeof d){case "string":return q=d=d.trim(),p=b[q],p||(":"===d.charAt(0)&&":"===d.charAt(1)&&(l=!0,d=d.substring(2)),p=new Tb(a),p=(new Za(p,c,a)).parse(d),p.constant?p.$$watchDelegate=h:l&&(p=e(p),p.$$watchDelegate=p.literal?g:f),b[q]=p),m(p,n);case "function":return m(d,n);default:return m(w,n)}}}]}function Ce(){this.$get=["$rootScope","$exceptionHandler",function(b,a){return Rc(function(a){b.$evalAsync(a)},a)}]}function De(){this.$get= -["$browser","$exceptionHandler",function(b,a){return Rc(function(a){b.defer(a)},a)}]}function Rc(b,a){function c(a,b,c){function d(b){return function(c){e||(e=!0,b.call(a,c))}}var e=!1;return[d(b),d(c)]}function d(){this.$$state={status:0}}function e(a,b){return function(c){b.call(a,c)}}function f(c){!c.processScheduled&&c.pending&&(c.processScheduled=!0,b(function(){var b,d,e;e=c.pending;c.processScheduled=!1;c.pending=s;for(var f=0,h=e.length;f<h;++f){d=e[f][0];b=e[f][c.status];try{D(b)?d.resolve(b(c.value)): -1===c.status?d.resolve(c.value):d.reject(c.value)}catch(g){d.reject(g),a(g)}}}))}function g(){this.promise=new d;this.resolve=e(this,this.resolve);this.reject=e(this,this.reject);this.notify=e(this,this.notify)}var h=K("$q",TypeError);d.prototype={then:function(a,b,c){var d=new g;this.$$state.pending=this.$$state.pending||[];this.$$state.pending.push([d,a,b,c]);0<this.$$state.status&&f(this.$$state);return d.promise},"catch":function(a){return this.then(null,a)},"finally":function(a,b){return this.then(function(b){return k(b, -!0,a)},function(b){return k(b,!1,a)},b)}};g.prototype={resolve:function(a){this.promise.$$state.status||(a===this.promise?this.$$reject(h("qcycle",a)):this.$$resolve(a))},$$resolve:function(b){var d,e;e=c(this,this.$$resolve,this.$$reject);try{if(S(b)||D(b))d=b&&b.then;D(d)?(this.promise.$$state.status=-1,d.call(b,e[0],e[1],this.notify)):(this.promise.$$state.value=b,this.promise.$$state.status=1,f(this.promise.$$state))}catch(h){e[1](h),a(h)}},reject:function(a){this.promise.$$state.status||this.$$reject(a)}, -$$reject:function(a){this.promise.$$state.value=a;this.promise.$$state.status=2;f(this.promise.$$state)},notify:function(c){var d=this.promise.$$state.pending;0>=this.promise.$$state.status&&d&&d.length&&b(function(){for(var b,e,f=0,h=d.length;f<h;f++){e=d[f][0];b=d[f][3];try{e.notify(D(b)?b(c):c)}catch(g){a(g)}}})}};var m=function(a,b){var c=new g;b?c.resolve(a):c.reject(a);return c.promise},k=function(a,b,c){var d=null;try{D(c)&&(d=c())}catch(e){return m(e,!1)}return d&&D(d.then)?d.then(function(){return m(a, -b)},function(a){return m(a,!1)}):m(a,b)},n=function(a,b,c,d){var e=new g;e.resolve(a);return e.promise.then(b,c,d)},p=function q(a){if(!D(a))throw h("norslvr",a);if(!(this instanceof q))return new q(a);var b=new g;a(function(a){b.resolve(a)},function(a){b.reject(a)});return b.promise};p.defer=function(){return new g};p.reject=function(a){var b=new g;b.reject(a);return b.promise};p.when=n;p.all=function(a){var b=new g,c=0,d=L(a)?[]:{};r(a,function(a,e){c++;n(a).then(function(a){d.hasOwnProperty(e)|| -(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});0===c&&b.resolve(d);return b.promise};return p}function Me(){this.$get=["$window","$timeout",function(b,a){var c=b.requestAnimationFrame||b.webkitRequestAnimationFrame||b.mozRequestAnimationFrame,d=b.cancelAnimationFrame||b.webkitCancelAnimationFrame||b.mozCancelAnimationFrame||b.webkitCancelRequestAnimationFrame,e=!!c,f=e?function(a){var b=c(a);return function(){d(b)}}:function(b){var c=a(b,16.66,!1);return function(){a.cancel(c)}}; -f.supported=e;return f}]}function Be(){var b=10,a=K("$rootScope"),c=null,d=null;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=["$injector","$exceptionHandler","$parse","$browser",function(e,f,g,h){function m(){this.$id=++bb;this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this["this"]=this.$root=this;this.$$destroyed=!1;this.$$asyncQueue=[];this.$$postDigestQueue=[];this.$$listeners={};this.$$listenerCount= -{};this.$$isolateBindings={};this.$$applyAsyncQueue=[]}function k(b){if(A.$$phase)throw a("inprog",A.$$phase);A.$$phase=b}function n(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function p(){}function l(){for(var a=A.$$applyAsyncQueue;a.length;)try{a.shift()()}catch(b){f(b)}d=null}function q(){null===d&&(d=h.defer(function(){A.$apply(l)}))}m.prototype={constructor:m,$new:function(a){a?(a=new m,a.$root=this.$root,a.$$asyncQueue=this.$$asyncQueue, -a.$$postDigestQueue=this.$$postDigestQueue):(this.$$ChildScope||(this.$$ChildScope=function(){this.$$watchers=this.$$nextSibling=this.$$childHead=this.$$childTail=null;this.$$listeners={};this.$$listenerCount={};this.$id=++bb;this.$$ChildScope=null},this.$$ChildScope.prototype=this),a=new this.$$ChildScope);a["this"]=a;a.$parent=this;a.$$prevSibling=this.$$childTail;this.$$childHead?this.$$childTail=this.$$childTail.$$nextSibling=a:this.$$childHead=this.$$childTail=a;return a},$watch:function(a,b, -d){var e=g(a);if(e.$$watchDelegate)return e.$$watchDelegate(this,b,d,e);var f=this.$$watchers,h={fn:b,last:p,get:e,exp:a,eq:!!d};c=null;D(b)||(h.fn=w);f||(f=this.$$watchers=[]);f.unshift(h);return function(){Ra(f,h);c=null}},$watchGroup:function(a,b){function c(){g=!1;k?(k=!1,b(e,e,h)):b(e,d,h)}var d=Array(a.length),e=Array(a.length),f=[],h=this,g=!1,k=!0;if(!a.length){var m=!0;h.$evalAsync(function(){m&&b(e,e,h)});return function(){m=!1}}if(1===a.length)return this.$watch(a[0],function(a,c,f){e[0]= -a;d[0]=c;b(e,a===c?e:d,f)});r(a,function(a,b){var k=h.$watch(a,function(a,f){e[b]=a;d[b]=f;g||(g=!0,h.$evalAsync(c))});f.push(k)});return function(){for(;f.length;)f.shift()()}},$watchCollection:function(a,b){var c=this,d,e,f,h=1<b.length,k=0,m=g(a,function(a){d=a;var b,c,f,h;if(S(d))if(Na(d))for(e!==l&&(e=l,q=e.length=0,k++),a=d.length,q!==a&&(k++,e.length=q=a),b=0;b<a;b++)h=e[b],f=d[b],c=h!==h&&f!==f,c||h===f||(k++,e[b]=f);else{e!==n&&(e=n={},q=0,k++);a=0;for(b in d)d.hasOwnProperty(b)&&(a++,f= -d[b],h=e[b],b in e?(c=h!==h&&f!==f,c||h===f||(k++,e[b]=f)):(q++,e[b]=f,k++));if(q>a)for(b in k++,e)d.hasOwnProperty(b)||(q--,delete e[b])}else e!==d&&(e=d,k++);return k}),l=[],n={},p=!0,q=0;return this.$watch(m,function(){p?(p=!1,b(d,d,c)):b(d,f,c);if(h)if(S(d))if(Na(d)){f=Array(d.length);for(var a=0;a<d.length;a++)f[a]=d[a]}else for(a in f={},d)Ab.call(d,a)&&(f[a]=d[a]);else f=d})},$digest:function(){var e,g,m,n,q=this.$$asyncQueue,r=this.$$postDigestQueue,s,B,J=b,N,M=[],O,H,W;k("$digest");h.$$checkUrlChange(); -this===A&&null!==d&&(h.defer.cancel(d),l());c=null;do{B=!1;for(N=this;q.length;){try{W=q.shift(),W.scope.$eval(W.expression)}catch(R){f(R)}c=null}a:do{if(n=N.$$watchers)for(s=n.length;s--;)try{if(e=n[s])if((g=e.get(N))!==(m=e.last)&&!(e.eq?ra(g,m):"number"===typeof g&&"number"===typeof m&&isNaN(g)&&isNaN(m)))B=!0,c=e,e.last=e.eq?Ha(g,null):g,e.fn(g,m===p?g:m,N),5>J&&(O=4-J,M[O]||(M[O]=[]),H=D(e.exp)?"fn: "+(e.exp.name||e.exp.toString()):e.exp,H+="; newVal: "+sa(g)+"; oldVal: "+sa(m),M[O].push(H)); -else if(e===c){B=!1;break a}}catch(t){f(t)}if(!(n=N.$$childHead||N!==this&&N.$$nextSibling))for(;N!==this&&!(n=N.$$nextSibling);)N=N.$parent}while(N=n);if((B||q.length)&&!J--)throw A.$$phase=null,a("infdig",b,sa(M));}while(B||q.length);for(A.$$phase=null;r.length;)try{r.shift()()}catch(G){f(G)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;if(this!==A){for(var b in this.$$listenerCount)n(this,this.$$listenerCount[b],b);a.$$childHead== -this&&(a.$$childHead=this.$$nextSibling);a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=this.$root=null;this.$$listeners={};this.$$watchers=this.$$asyncQueue=this.$$postDigestQueue=[];this.$destroy=this.$digest=this.$apply=w;this.$on=this.$watch=this.$watchGroup= -function(){return w}}}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a){A.$$phase||A.$$asyncQueue.length||h.defer(function(){A.$$asyncQueue.length&&A.$digest()});this.$$asyncQueue.push({scope:this,expression:a})},$$postDigest:function(a){this.$$postDigestQueue.push(a)},$apply:function(a){try{return k("$apply"),this.$eval(a)}catch(b){f(b)}finally{A.$$phase=null;try{A.$digest()}catch(c){throw f(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&A.$$applyAsyncQueue.push(b); -q()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){c[c.indexOf(b)]=null;n(e,1,a)}},$emit:function(a,b){var c=[],d,e=this,h=!1,g={name:a,targetScope:e,stopPropagation:function(){h=!0},preventDefault:function(){g.defaultPrevented=!0},defaultPrevented:!1},k=db([g],arguments,1),m,l;do{d=e.$$listeners[a]||c;g.currentScope=e;m=0;for(l= -d.length;m<l;m++)if(d[m])try{d[m].apply(null,k)}catch(n){f(n)}else d.splice(m,1),m--,l--;if(h)return g.currentScope=null,g;e=e.$parent}while(e);g.currentScope=null;return g},$broadcast:function(a,b){var c=this,d=this,e={name:a,targetScope:this,preventDefault:function(){e.defaultPrevented=!0},defaultPrevented:!1};if(!this.$$listenerCount[a])return e;for(var h=db([e],arguments,1),g,k;c=d;){e.currentScope=c;d=c.$$listeners[a]||[];g=0;for(k=d.length;g<k;g++)if(d[g])try{d[g].apply(null,h)}catch(m){f(m)}else d.splice(g, -1),g--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}e.currentScope=null;return e}};var A=new m;return A}]}function Ed(){var b=/^\s*(https?|ftp|mailto|tel|file):/,a=/^\s*((https?|ftp|file|blob):|data:image\/)/;this.aHrefSanitizationWhitelist=function(a){return B(a)?(b=a,this):b};this.imgSrcSanitizationWhitelist=function(b){return B(b)?(a=b,this):a};this.$get=function(){return function(c,d){var e=d?a:b,f;if(!X||8<=X)if(f= -Aa(c).href,""!==f&&!f.match(e))return"unsafe:"+f;return c}}}function ef(b){if("self"===b)return b;if(C(b)){if(-1<b.indexOf("***"))throw Ca("iwcard",b);b=b.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08").replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return new RegExp("^"+b+"$")}if(cb(b))return new RegExp("^"+b.source+"$");throw Ca("imatcher");}function Sc(b){var a=[];B(b)&&r(b,function(b){a.push(ef(b))});return a}function Fe(){this.SCE_CONTEXTS=ka;var b=["self"],a=[]; -this.resourceUrlWhitelist=function(a){arguments.length&&(b=Sc(a));return b};this.resourceUrlBlacklist=function(b){arguments.length&&(a=Sc(b));return a};this.$get=["$injector",function(c){function d(a,b){return"self"===a?Ic(b):!!a.exec(b.href)}function e(a){var b=function(a){this.$$unwrapTrustedValue=function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};return b} -var f=function(a){throw Ca("unsafe");};c.has("$sanitize")&&(f=c.get("$sanitize"));var g=e(),h={};h[ka.HTML]=e(g);h[ka.CSS]=e(g);h[ka.URL]=e(g);h[ka.JS]=e(g);h[ka.RESOURCE_URL]=e(h[ka.URL]);return{trustAs:function(a,b){var c=h.hasOwnProperty(a)?h[a]:null;if(!c)throw Ca("icontext",a,b);if(null===b||b===s||""===b)return b;if("string"!==typeof b)throw Ca("itype",a);return new c(b)},getTrusted:function(c,e){if(null===e||e===s||""===e)return e;var g=h.hasOwnProperty(c)?h[c]:null;if(g&&e instanceof g)return e.$$unwrapTrustedValue(); -if(c===ka.RESOURCE_URL){var g=Aa(e.toString()),p,l,q=!1;p=0;for(l=b.length;p<l;p++)if(d(b[p],g)){q=!0;break}if(q)for(p=0,l=a.length;p<l;p++)if(d(a[p],g)){q=!1;break}if(q)return e;throw Ca("insecurl",e.toString());}if(c===ka.HTML)return f(e);throw Ca("unsafe");},valueOf:function(a){return a instanceof g?a.$$unwrapTrustedValue():a}}}]}function Ee(){var b=!0;this.enabled=function(a){arguments.length&&(b=!!a);return b};this.$get=["$parse","$sniffer","$sceDelegate",function(a,c,d){if(b&&c.msie&&8>c.msieDocumentMode)throw Ca("iequirks"); -var e=qa(ka);e.isEnabled=function(){return b};e.trustAs=d.trustAs;e.getTrusted=d.getTrusted;e.valueOf=d.valueOf;b||(e.trustAs=e.getTrusted=function(a,b){return b},e.valueOf=Pa);e.parseAs=function(b,c){var d=a(c);return d.literal&&d.constant?d:a(c,function(a){return e.getTrusted(b,a)})};var f=e.parseAs,g=e.getTrusted,h=e.trustAs;r(ka,function(a,b){var c=P(b);e[Wa("parse_as_"+c)]=function(b){return f(a,b)};e[Wa("get_trusted_"+c)]=function(b){return g(a,b)};e[Wa("trust_as_"+c)]=function(b){return h(a, -b)}});return e}]}function Ge(){this.$get=["$window","$document",function(b,a){var c={},d=U((/android (\d+)/.exec(P((b.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),f=a[0]||{},g=f.documentMode,h,m=/^(Moz|webkit|O|ms)(?=[A-Z])/,k=f.body&&f.body.style,n=!1,p=!1;if(k){for(var l in k)if(n=m.exec(l)){h=n[0];h=h.substr(0,1).toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in k&&"webkit");n=!!("transition"in k||h+"Transition"in k);p=!!("animation"in k||h+"Animation"in -k);!d||n&&p||(n=C(f.body.style.webkitTransition),p=C(f.body.style.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hashchange:"onhashchange"in b&&(!g||7<g),hasEvent:function(a){if("input"==a&&9==X)return!1;if(F(c[a])){var b=f.createElement("div");c[a]="on"+a in b}return c[a]},csp:Ua(),vendorPrefix:h,transitions:n,animations:p,android:d,msie:X,msieDocumentMode:g}}]}function Ie(){this.$get=["$templateCache","$http","$q",function(b,a,c){function d(e,f){function g(){h.totalPendingRequests--; -if(!f)throw ja("tpload",e);return c.reject()}var h=d;h.totalPendingRequests++;return a.get(e,{cache:b}).then(function(a){a=a.data;if(!a||0===a.length)return g();h.totalPendingRequests--;b.put(e,a);return a},g)}d.totalPendingRequests=0;return d}]}function Je(){this.$get=["$rootScope","$browser","$location",function(b,a,c){return{findBindings:function(a,b,c){a=a.getElementsByClassName("ng-binding");var g=[];r(a,function(a){var d=Ea.element(a).data("$binding");d&&r(d,function(d){c?(new RegExp("(^|\\s)"+ -b+"(\\s|\\||$)")).test(d)&&g.push(a):-1!=d.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,c){for(var g=["ng-","data-ng-","ng\\:"],h=0;h<g.length;++h){var m=a.querySelectorAll("["+g[h]+"model"+(c?"=":"*=")+'"'+b+'"]');if(m.length)return m}},getLocation:function(){return c.url()},setLocation:function(a){a!==c.url()&&(c.url(a),b.$digest())},whenStable:function(b){a.notifyWhenNoOutstandingRequests(b)}}}]}function Ke(){this.$get=["$rootScope","$browser","$q","$$q","$exceptionHandler",function(b, -a,c,d,e){function f(f,m,k){var n=B(k)&&!k,p=(n?d:c).defer(),l=p.promise;m=a.defer(function(){try{p.resolve(f())}catch(a){p.reject(a),e(a)}finally{delete g[l.$$timeoutId]}n||b.$apply()},m);l.$$timeoutId=m;g[m]=p;return l}var g={};f.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),delete g[b.$$timeoutId],a.defer.cancel(b.$$timeoutId)):!1};return f}]}function Aa(b,a){var c=b;X&&(Z.setAttribute("href",c),c=Z.href);Z.setAttribute("href",c);return{href:Z.href,protocol:Z.protocol? -Z.protocol.replace(/:$/,""):"",host:Z.host,search:Z.search?Z.search.replace(/^\?/,""):"",hash:Z.hash?Z.hash.replace(/^#/,""):"",hostname:Z.hostname,port:Z.port,pathname:"/"===Z.pathname.charAt(0)?Z.pathname:"/"+Z.pathname}}function Ic(b){b=C(b)?Aa(b):b;return b.protocol===Tc.protocol&&b.host===Tc.host}function Le(){this.$get=da(t)}function qc(b){function a(c,d){if(S(c)){var e={};r(c,function(b,c){e[c]=a(c,b)});return e}return b.factory(c+"Filter",d)}this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+ -"Filter")}}];a("currency",Uc);a("date",Vc);a("filter",ff);a("json",gf);a("limitTo",hf);a("lowercase",jf);a("number",Wc);a("orderBy",Xc);a("uppercase",kf)}function ff(){return function(b,a,c){if(!L(b))return b;var d=typeof c,e=[];e.check=function(a,b){for(var c=0;c<e.length;c++)if(!e[c](a,b))return!1;return!0};"function"!==d&&(c="boolean"===d&&c?function(a,b){return Ea.equals(a,b)}:function(a,b){if(a&&b&&"object"===typeof a&&"object"===typeof b){for(var d in a)if("$"!==d.charAt(0)&&Ab.call(a,d)&&c(a[d], -b[d]))return!0;return!1}b=(""+b).toLowerCase();return-1<(""+a).toLowerCase().indexOf(b)});var f=function(a,b){if("string"==typeof b&&"!"===b.charAt(0))return!f(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return c(a,b);case "object":switch(typeof b){case "object":return c(a,b);default:for(var d in a)if("$"!==d.charAt(0)&&f(a[d],b))return!0}return!1;case "array":for(d=0;d<a.length;d++)if(f(a[d],b))return!0;return!1;default:return!1}};switch(typeof a){case "boolean":case "number":case "string":a= -{$:a};case "object":for(var g in a)(function(b){"undefined"!==typeof a[b]&&e.push(function(c){return f("$"==b?c:c&&c[b],a[b])})})(g);break;case "function":e.push(a);break;default:return b}d=[];for(g=0;g<b.length;g++){var h=b[g];e.check(h,g)&&d.push(h)}return d}}function Uc(b){var a=b.NUMBER_FORMATS;return function(b,d){F(d)&&(d=a.CURRENCY_SYM);return null==b?b:Yc(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,2).replace(/\u00A4/g,d)}}function Wc(b){var a=b.NUMBER_FORMATS;return function(b,d){return null== -b?b:Yc(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}function Yc(b,a,c,d,e){if(!isFinite(b)||S(b))return"";var f=0>b;b=Math.abs(b);var g=b+"",h="",m=[],k=!1;if(-1!==g.indexOf("e")){var n=g.match(/([\d\.]+)e(-?)(\d+)/);n&&"-"==n[2]&&n[3]>e+1?(g="0",b=0):(h=g,k=!0)}if(k)0<e&&-1<b&&1>b&&(h=b.toFixed(e));else{g=(g.split(Zc)[1]||"").length;F(e)&&(e=Math.min(Math.max(a.minFrac,g),a.maxFrac));b=+(Math.round(+(b.toString()+"e"+e)).toString()+"e"+-e);0===b&&(f=!1);b=(""+b).split(Zc);g=b[0];b=b[1]||"";var n= -0,p=a.lgSize,l=a.gSize;if(g.length>=p+l)for(n=g.length-p,k=0;k<n;k++)0===(n-k)%l&&0!==k&&(h+=c),h+=g.charAt(k);for(k=n;k<g.length;k++)0===(g.length-k)%p&&0!==k&&(h+=c),h+=g.charAt(k);for(;b.length<e;)b+="0";e&&"0"!==e&&(h+=d+b.substr(0,e))}m.push(f?a.negPre:a.posPre);m.push(h);m.push(f?a.negSuf:a.posSuf);return m.join("")}function vb(b,a,c){var d="";0>b&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=b.substr(b.length-a));return d+b}function $(b,a,c,d){c=c||0;return function(e){e=e["get"+b](); -if(0<c||e>-c)e+=c;0===e&&-12==c&&(e=12);return vb(e,a,d)}}function wb(b,a){return function(c,d){var e=c["get"+b](),f=ib(a?"SHORT"+b:b);return d[f][e]}}function $c(b){var a=(new Date(b,0,1)).getDay();return new Date(b,0,(4>=a?5:12)-a)}function ad(b){return function(a){var c=$c(a.getFullYear());a=+new Date(a.getFullYear(),a.getMonth(),a.getDate()+(4-a.getDay()))-+c;a=1+Math.round(a/6048E5);return vb(a,b)}}function Vc(b){function a(a){var b;if(b=a.match(c)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear: -a.setFullYear,m=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=U(b[9]+b[10]),g=U(b[9]+b[11]));h.call(a,U(b[1]),U(b[2])-1,U(b[3]));f=U(b[4]||0)-f;g=U(b[5]||0)-g;h=U(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));m.call(a,f,g,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e,f){var g="",h=[],m,k;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;C(c)&&(c=lf.test(c)?U(c):a(c));ea(c)&&(c=new Date(c));if(!fa(c))return c; -for(;e;)(k=mf.exec(e))?(h=db(h,k,1),e=h.pop()):(h.push(e),e=null);f&&"UTC"===f&&(c=new Date(c.getTime()),c.setMinutes(c.getMinutes()+c.getTimezoneOffset()));r(h,function(a){m=nf[a];g+=m?m(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function gf(){return function(b){return sa(b,!0)}}function hf(){return function(b,a){if(!L(b)&&!C(b))return b;a=Infinity===Math.abs(Number(a))?Number(a):U(a);if(C(b))return a?0<=a?b.slice(0,a):b.slice(a,b.length):"";var c=[],d,e;a>b.length? -a=b.length:a<-b.length&&(a=-b.length);0<a?(d=0,e=a):(d=b.length+a,e=b.length);for(;d<e;d++)c.push(b[d]);return c}}function Xc(b){return function(a,c,d){function e(a,b){return b?function(b,c){return a(c,b)}:a}function f(a,b){var c=typeof a,d=typeof b;return c==d?(fa(a)&&fa(b)&&(a=a.valueOf(),b=b.valueOf()),"string"==c&&(a=a.toLowerCase(),b=b.toLowerCase()),a===b?0:a<b?-1:1):c<d?-1:1}if(!Na(a)||!c)return a;c=L(c)?c:[c];c=sd(c,function(a){var c=!1,d=a||Pa;if(C(a)){if("+"==a.charAt(0)||"-"==a.charAt(0))c= -"-"==a.charAt(0),a=a.substring(1);d=b(a);if(d.constant){var h=d();return e(function(a,b){return f(a[h],b[h])},c)}}return e(function(a,b){return f(d(a),d(b))},c)});for(var g=[],h=0;h<a.length;h++)g.push(a[h]);return g.sort(e(function(a,b){for(var d=0;d<c.length;d++){var e=c[d](a,b);if(0!==e)return e}return 0},d))}}function Fa(b){D(b)&&(b={link:b});b.restrict=b.restrict||"AC";return da(b)}function bd(b,a,c,d){var e=this,f=b.parent().controller("form")||xb,g=[];e.$error={};e.$$success={};e.$pending= -s;e.$name=a.name||a.ngForm;e.$dirty=!1;e.$pristine=!0;e.$valid=!0;e.$invalid=!1;e.$submitted=!1;f.$addControl(e);b.addClass(Ma);e.$rollbackViewValue=function(){r(g,function(a){a.$rollbackViewValue()})};e.$commitViewValue=function(){r(g,function(a){a.$commitViewValue()})};e.$addControl=function(a){Ja(a.$name,"input");g.push(a);a.$name&&(e[a.$name]=a)};e.$removeControl=function(a){a.$name&&e[a.$name]===a&&delete e[a.$name];r(e.$pending,function(b,c){e.$setValidity(c,null,a)});r(e.$error,function(b, -c){e.$setValidity(c,null,a)});Ra(g,a)};cd({ctrl:this,$element:b,set:function(a,b,c){var d=a[b];d?-1===d.indexOf(c)&&d.push(c):a[b]=[c]},unset:function(a,b,c){var d=a[b];d&&(Ra(d,c),0===d.length&&delete a[b])},parentForm:f,$animate:d});e.$setDirty=function(){d.removeClass(b,Ma);d.addClass(b,yb);e.$dirty=!0;e.$pristine=!1;f.$setDirty()};e.$setPristine=function(){d.setClass(b,Ma,yb+" ng-submitted");e.$dirty=!1;e.$pristine=!0;e.$submitted=!1;r(g,function(a){a.$setPristine()})};e.$setSubmitted=function(){d.addClass(b, -"ng-submitted");e.$submitted=!0;f.$setSubmitted()}}function Ub(b){b.$formatters.push(function(a){return b.$isEmpty(a)?a:a.toString()})}function $a(b,a,c,d,e,f){a.prop("validity");var g=a[0].placeholder,h={},m=P(a[0].type);if(!e.android){var k=!1;a.on("compositionstart",function(a){k=!0});a.on("compositionend",function(){k=!1;n()})}var n=function(b){if(!k){var e=a.val(),f=b&&b.type;X&&"input"===(b||h).type&&a[0].placeholder!==g?g=a[0].placeholder:("password"===m||c.ngTrim&&"false"===c.ngTrim||(e=ba(e)), -(d.$viewValue!==e||""===e&&d.$$hasNativeValidators)&&d.$setViewValue(e,f))}};if(e.hasEvent("input"))a.on("input",n);else{var p,l=function(a){p||(p=f.defer(function(){n(a);p=null}))};a.on("keydown",function(a){var b=a.keyCode;91===b||15<b&&19>b||37<=b&&40>=b||l(a)});if(e.hasEvent("paste"))a.on("paste cut",l)}a.on("change",n);d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)}}function zb(b,a){return function(c){var d;if(fa(c))return c;if(C(c)){'"'==c.charAt(0)&&'"'==c.charAt(c.length- -1)&&(c=c.substring(1,c.length-1));if(of.test(c))return new Date(c);b.lastIndex=0;if(c=b.exec(c))return c.shift(),d={yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0},r(c,function(b,c){c<a.length&&(d[a[c]]=+b)}),new Date(d.yyyy,d.MM-1,d.dd,d.HH,d.mm,d.ss||0)}return NaN}}function ab(b,a,c,d){return function(e,f,g,h,m,k,n){function p(a){return B(a)?fa(a)?a:c(a):s}dd(e,f,g,h);$a(e,f,g,h,m,k);var l=h&&h.$options&&h.$options.timezone;h.$$parserName=b;h.$parsers.push(function(b){return h.$isEmpty(b)?null:a.test(b)?(b= -c(b),"UTC"===l&&b.setMinutes(b.getMinutes()-b.getTimezoneOffset()),b):s});h.$formatters.push(function(a){return fa(a)?n("date")(a,d,l):""});if(B(g.min)||g.ngMin){var q;h.$validators.min=function(a){return h.$isEmpty(a)||F(q)||c(a)>=q};g.$observe("min",function(a){q=p(a);h.$validate()})}if(B(g.max)||g.ngMax){var r;h.$validators.max=function(a){return h.$isEmpty(a)||F(r)||c(a)<=r};g.$observe("max",function(a){r=p(a);h.$validate()})}}}function dd(b,a,c,d){(d.$$hasNativeValidators=S(a[0].validity))&& -d.$parsers.push(function(b){var c=a.prop("validity")||{};return c.badInput&&!c.typeMismatch?s:b})}function ed(b,a,c,d,e){if(B(d)){b=b(d);if(!b.constant)throw K("ngModel")("constexpr",c,d);return b(a)}return e}function cd(b){function a(a,b){b&&!f[a]?(k.addClass(e,a),f[a]=!0):!b&&f[a]&&(k.removeClass(e,a),f[a]=!1)}function c(b,c){b=b?"-"+Db(b,"-"):"";a(pf+b,!0===c);a(qf+b,!1===c)}var d=b.ctrl,e=b.$element,f={},g=b.set,h=b.unset,m=b.parentForm,k=b.$animate;d.$setValidity=function(b,e,f){e===s?(d.$pending|| -(d.$pending={}),g(d.$pending,b,f)):(d.$pending&&h(d.$pending,b,f),fd(d.$pending)&&(d.$pending=s));"boolean"!==typeof e?(h(d.$error,b,f),h(d.$$success,b,f)):e?(h(d.$error,b,f),g(d.$$success,b,f)):(g(d.$error,b,f),h(d.$$success,b,f));d.$pending?(a(gd,!0),d.$valid=d.$invalid=s,c("",null)):(a(gd,!1),d.$valid=fd(d.$error),d.$invalid=!d.$valid,c("",d.$valid));e=d.$pending&&d.$pending[b]?s:d.$error[b]?!1:d.$$success[b]?!0:null;c(b,e);m.$setValidity(b,e,d)};c("",!0)}function fd(b){if(b)for(var a in b)return!1; -return!0}function Vb(b,a){b="ngClass"+b;return["$animate",function(c){function d(a,b){var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],n=0;n<b.length;n++)if(e==b[n])continue a;c.push(e)}return c}function e(a){if(!L(a)){if(C(a))return a.split(" ");if(S(a)){var b=[];r(a,function(a,c){a&&(b=b.concat(c.split(" ")))});return b}}return a}return{restrict:"AC",link:function(f,g,h){function m(a,b){var c=g.data("$classCounts")||{},d=[];r(a,function(a){if(0<b||c[a])c[a]=(c[a]||0)+b,c[a]===+(0<b)&&d.push(a)}); -g.data("$classCounts",c);return d.join(" ")}function k(b){if(!0===a||f.$index%2===a){var k=e(b||[]);if(!n){var q=m(k,1);h.$addClass(q)}else if(!ra(b,n)){var r=e(n),q=d(k,r),k=d(r,k),q=m(q,1),k=m(k,-1);q&&q.length&&c.addClass(g,q);k&&k.length&&c.removeClass(g,k)}}n=qa(b)}var n;f.$watch(h[b],k,!0);h.$observe("class",function(a){k(f.$eval(h[b]))});"ngClass"!==b&&f.$watch("$index",function(c,d){var g=c&1;if(g!==(d&1)){var k=e(f.$eval(h[b]));g===a?(g=m(k,1),h.$addClass(g)):(g=m(k,-1),h.$removeClass(g))}})}}}]} -var rf=/^\/(.+)\/([a-z]*)$/,P=function(b){return C(b)?b.toLowerCase():b},Ab=Object.prototype.hasOwnProperty,ib=function(b){return C(b)?b.toUpperCase():b},X,G,la,Ta=[].slice,sf=[].push,Ga=Object.prototype.toString,Sa=K("ng"),Ea=t.angular||(t.angular={}),Va,bb=0;X=U((/msie (\d+)/.exec(P(navigator.userAgent))||[])[1]);isNaN(X)&&(X=U((/trident\/.*; rv:(\d+)/.exec(P(navigator.userAgent))||[])[1]));w.$inject=[];Pa.$inject=[];var L=Array.isArray,ba=function(b){return C(b)?b.trim():b},Ua=function(){if(B(Ua.isActive_))return Ua.isActive_; -var b=!(!Y.querySelector("[ng-csp]")&&!Y.querySelector("[data-ng-csp]"));if(!b)try{new Function("")}catch(a){b=!0}return Ua.isActive_=b},fb=["ng-","data-ng-","ng:","x-ng-"],yd=/[A-Z]/g,hc=!1,Eb,Cd={full:"1.3.0-rc.1",major:1,minor:3,dot:0,codeName:"backyard-atomicity"};V.expando="ng339";var ob=V.cache={},Te=1;V._data=function(b){return this.cache[b[this.expando]]||{}};var Oe=/([\:\-\_]+(.))/g,Pe=/^moz([A-Z])/,tf={mouseleave:"mouseout",mouseenter:"mouseover"},Hb=K("jqLite"),Se=/^<(\w+)\s*\/?>(?:<\/\1>|)$/, -Gb=/<|&#?\w+;/,Qe=/<([\w:]+)/,Re=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ia={option:[1,'<select multiple="multiple">',"</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ia.optgroup=ia.option;ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead;ia.th=ia.td;var Ia=V.prototype={ready:function(b){function a(){c||(c= -!0,b())}var c=!1;"complete"===Y.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),V(t).on("load",a),this.on("DOMContentLoaded",a))},toString:function(){var b=[];r(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?G(this[b]):G(this[this.length+b])},length:0,push:sf,sort:[].sort,splice:[].splice},qb={};r("multiple selected checked disabled readOnly required open".split(" "),function(b){qb[P(b)]=b});var zc={};r("input select option textarea button form details".split(" "), -function(b){zc[b]=!0});var Ac={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};r({data:Jb,removeData:mb},function(b,a){V[a]=b});r({data:Jb,inheritedData:pb,scope:function(b){return G.data(b,"$scope")||pb(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return G.data(b,"$isolateScope")||G.data(b,"$isolateScopeNoTemplate")},controller:vc,injector:function(b){return pb(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:jb, -css:function(b,a,c){a=Wa(a);if(B(c))b.style[a]=c;else return b.style[a]},attr:function(b,a,c){var d=P(a);if(qb[d])if(B(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||w).specified?d:s;else if(B(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?s:b},prop:function(b,a,c){if(B(c))b[a]=c;else return b[a]},text:function(){function b(a,b){if(F(b)){var d=a.nodeType;return 1===d||3===d?a.textContent:""}a.textContent= -b}b.$dv="";return b}(),val:function(b,a){if(F(a)){if(b.multiple&&"select"===pa(b)){var c=[];r(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(F(a))return b.innerHTML;lb(b,!0);b.innerHTML=a},empty:wc},function(b,a){V.prototype[a]=function(a,d){var e,f,g=this.length;if(b!==wc&&(2==b.length&&b!==jb&&b!==vc?a:d)===s){if(S(a)){for(e=0;e<g;e++)if(b===Jb)b(this[e],a);else for(f in a)b(this[e],f,a[f]);return this}e=b.$dv; -g=e===s?Math.min(g,1):g;for(f=0;f<g;f++){var h=b(this[f],a,d);e=e?e+h:h}return e}for(e=0;e<g;e++)b(this[e],a,d);return this}});r({removeData:mb,on:function a(c,d,e,f){if(B(f))throw Hb("onargs");if(rc(c)){var g=nb(c,!0);f=g.events;var h=g.handle;h||(h=g.handle=Ve(c,f));for(var g=0<=d.indexOf(" ")?d.split(" "):[d],m=g.length;m--;){d=g[m];var k=f[d];k||(f[d]=[],"mouseenter"===d||"mouseleave"===d?a(c,tf[d],function(a){var c=a.relatedTarget;c&&(c===this||this.contains(c))||h(a,d)}):"$destroy"!==d&&c.addEventListener(d, -h,!1),k=f[d]);k.push(e)}}},off:uc,one:function(a,c,d){a=G(a);a.on(c,function f(){a.off(c,d);a.off(c,f)});a.on(c,d)},replaceWith:function(a,c){var d,e=a.parentNode;lb(a);r(new V(c),function(c){d?e.insertBefore(c,d.nextSibling):e.replaceChild(c,a);d=c})},children:function(a){var c=[];r(a.childNodes,function(a){1===a.nodeType&&c.push(a)});return c},contents:function(a){return a.contentDocument||a.childNodes||[]},append:function(a,c){var d=a.nodeType;if(1===d||11===d){c=new V(c);for(var d=0,e=c.length;d< -e;d++)a.appendChild(c[d])}},prepend:function(a,c){if(1===a.nodeType){var d=a.firstChild;r(new V(c),function(c){a.insertBefore(c,d)})}},wrap:function(a,c){c=G(c).eq(0).clone()[0];var d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:xc,detach:function(a){xc(a,!0)},after:function(a,c){var d=a,e=a.parentNode;c=new V(c);for(var f=0,g=c.length;f<g;f++){var h=c[f];e.insertBefore(h,d.nextSibling);d=h}},addClass:Lb,removeClass:Kb,toggleClass:function(a,c,d){c&&r(c.split(" "),function(c){var f= -d;F(f)&&(f=!jb(a,c));(f?Lb:Kb)(a,c)})},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){return a.nextElementSibling},find:function(a,c){return a.getElementsByTagName?a.getElementsByTagName(c):[]},clone:Ib,triggerHandler:function(a,c,d){var e,f;e=c.type||c;var g=nb(a);if(g=(g=g&&g.events)&&g[e])e={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return!0===this.defaultPrevented},stopPropagation:w,type:e,target:a},c.type&&(e=E(e,c)), -c=qa(g),f=d?[e].concat(d):[e],r(c,function(c){c.apply(a,f)})}},function(a,c){V.prototype[c]=function(c,e,f){for(var g,h=0,m=this.length;h<m;h++)F(g)?(g=a(this[h],c,e,f),B(g)&&(g=G(g))):tc(g,a(this[h],c,e,f));return B(g)?g:this};V.prototype.bind=V.prototype.on;V.prototype.unbind=V.prototype.off});Xa.prototype={put:function(a,c){this[Ka(a,this.nextUid)]=c},get:function(a){return this[Ka(a,this.nextUid)]},remove:function(a){var c=this[a=Ka(a,this.nextUid)];delete this[a];return c}};var Cc=/^function\s*[^\(]*\(\s*([^\)]*)\)/m, -Xe=/,/,Ye=/^\s*(_?)(\S+?)\1\s*$/,Bc=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,La=K("$injector");Cb.$$annotate=Mb;var uf=K("$animate"),oe=["$provide",function(a){this.$$selectors={};this.register=function(c,d){var e=c+"-animation";if(c&&"."!=c.charAt(0))throw uf("notcsel",c);this.$$selectors[c.substr(1)]=e;a.factory(e,d)};this.classNameFilter=function(a){1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null);return this.$$classNameFilter};this.$get=["$$q","$$asyncCallback",function(a,d){function e(){f|| -(f=a.defer(),d(function(){f.resolve();f=null}));return f.promise}var f;return{enter:function(a,c,d){d?d.after(a):c.prepend(a);return e()},leave:function(a){a.remove();return e()},move:function(a,c,d){return this.enter(a,c,d)},addClass:function(a,c){c=C(c)?c:L(c)?c.join(" "):"";r(a,function(a){Lb(a,c)});return e()},removeClass:function(a,c){c=C(c)?c:L(c)?c.join(" "):"";r(a,function(a){Kb(a,c)});return e()},setClass:function(a,c,d){this.addClass(a,c);this.removeClass(a,d);return e()},enabled:w,cancel:w}}]}], -ja=K("$compile");jc.$inject=["$provide","$$sanitizeUriProvider"];var af=/^(x[\:\-_]|data[\:\-_])/i,Pb=K("$interpolate"),vf=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,df={http:80,https:443,ftp:21},sb=K("$location");Mc.prototype=Sb.prototype=Lc.prototype={$$html5:!1,$$replace:!1,absUrl:tb("$$absUrl"),url:function(a){if(F(a))return this.$$url;a=vf.exec(a);a[1]&&this.path(decodeURIComponent(a[1]));(a[2]||a[1])&&this.search(a[3]||"");this.hash(a[5]||"");return this},protocol:tb("$$protocol"),host:tb("$$host"), -port:tb("$$port"),path:Nc("$$path",function(a){a=a?a.toString():"";return"/"==a.charAt(0)?a:"/"+a}),search:function(a,c){switch(arguments.length){case 0:return this.$$search;case 1:if(C(a)||ea(a))a=a.toString(),this.$$search=fc(a);else if(S(a))r(a,function(c,e){null==c&&delete a[e]}),this.$$search=a;else throw sb("isrcharg");break;default:F(c)||null===c?delete this.$$search[a]:this.$$search[a]=c}this.$$compose();return this},hash:Nc("$$hash",function(a){return a?a.toString():""}),replace:function(){this.$$replace= -!0;return this}};var oa=K("$parse"),wf=Function.prototype.call,xf=Function.prototype.apply,yf=Function.prototype.bind,hd=Object.create(null);r({"null":function(){return null},"true":function(){return!0},"false":function(){return!1},undefined:function(){}},function(a,c){a.constant=a.literal=a.sharedGetter=!0;hd[c]=a});var Wb=E(Object.create(null),{"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return B(d)?B(e)?d+e:d:B(e)?e:s},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(B(d)?d:0)-(B(e)?e:0)},"*":function(a, -c,d,e){return d(a,c)*e(a,c)},"/":function(a,c,d,e){return d(a,c)/e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^e(a,c)},"=":w,"===":function(a,c,d,e){return d(a,c)===e(a,c)},"!==":function(a,c,d,e){return d(a,c)!==e(a,c)},"==":function(a,c,d,e){return d(a,c)==e(a,c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,d,e){return d(a,c)<e(a,c)},">":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a, -c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}}),zf={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},Tb=function(a){this.options=a};Tb.prototype={constructor:Tb,lex:function(a){this.text=a;this.index=0;this.ch=s;for(this.tokens=[];this.index<this.text.length;)if(this.ch=this.text.charAt(this.index), -this.is("\"'"))this.readString(this.ch);else if(this.isNumber(this.ch)||this.is(".")&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdent(this.ch))this.readIdent();else if(this.is("(){}[].,;:?"))this.tokens.push({index:this.index,text:this.ch}),this.index++;else if(this.isWhitespace(this.ch))this.index++;else{a=this.ch+this.peek();var c=a+this.peek(2),d=Wb[this.ch],e=Wb[a],f=Wb[c];f?(this.tokens.push({index:this.index,text:c,fn:f}),this.index+=3):e?(this.tokens.push({index:this.index, -text:a,fn:e}),this.index+=2):d?(this.tokens.push({index:this.index,text:this.ch,fn:d}),this.index+=1):this.throwError("Unexpected next character ",this.index,this.index+1)}return this.tokens},is:function(a){return-1!==a.indexOf(this.ch)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<= -a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=d||this.index;c=B(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw oa("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index<this.text.length;){var d=P(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var e=this.peek();if("e"==d&&this.isExpOperator(e))a+=d;else if(this.isExpOperator(d)&& -e&&this.isNumber(e)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||e&&this.isNumber(e)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}a*=1;this.tokens.push({index:c,text:a,constant:!0,fn:function(){return a}})},readIdent:function(){for(var a=this.text,c="",d=this.index,e,f,g,h;this.index<this.text.length;){h=this.text.charAt(this.index);if("."===h||this.isIdent(h)||this.isNumber(h))"."===h&&(e=this.index),c+=h;else break;this.index++}e&&"."=== -c[c.length-1]&&(this.index--,c=c.slice(0,-1),e=c.lastIndexOf("."),-1===e&&(e=s));if(e)for(f=this.index;f<this.text.length;){h=this.text.charAt(f);if("("===h){g=c.substr(e-d+1);c=c.substr(0,e-d);this.index=f;break}if(this.isWhitespace(h))f++;else break}this.tokens.push({index:d,text:c,fn:hd[c]||Pc(c,this.options,a)});g&&(this.tokens.push({index:e,text:"."}),this.tokens.push({index:e+1,text:g}))},readString:function(a){var c=this.index;this.index++;for(var d="",e=a,f=!1;this.index<this.text.length;){var g= -this.text.charAt(this.index),e=e+g;if(f)"u"===g?(f=this.text.substring(this.index+1,this.index+5),f.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+f+"]"),this.index+=4,d+=String.fromCharCode(parseInt(f,16))):d+=zf[g]||g,f=!1;else if("\\"===g)f=!0;else{if(g===a){this.index++;this.tokens.push({index:c,text:e,string:d,constant:!0,fn:function(){return d}});return}d+=g}this.index++}this.throwError("Unterminated quote",c)}};var Za=function(a,c,d){this.lexer=a;this.$filter=c;this.options= -d};Za.ZERO=E(function(){return 0},{sharedGetter:!0,constant:!0});Za.prototype={constructor:Za,parse:function(a){this.text=a;this.tokens=this.lexer.lex(a);a=this.statements();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);a.literal=!!a.literal;a.constant=!!a.constant;return a},primary:function(){var a;if(this.expect("("))a=this.filterChain(),this.consume(")");else if(this.expect("["))a=this.arrayDeclaration();else if(this.expect("{"))a=this.object();else{var c=this.expect(); -(a=c.fn)||this.throwError("not a primary expression",c);c.constant&&(a.constant=!0,a.literal=!0)}for(var d;c=this.expect("(","[",".");)"("===c.text?(a=this.functionCall(a,d),d=null):"["===c.text?(d=a,a=this.objectIndex(a)):"."===c.text?(d=a,a=this.fieldAccess(a)):this.throwError("IMPOSSIBLE");return a},throwError:function(a,c){throw oa("syntax",c.text,a,c.index+1,this.text,this.text.substring(c.index));},peekToken:function(){if(0===this.tokens.length)throw oa("ueoe",this.text);return this.tokens[0]}, -peek:function(a,c,d,e){if(0<this.tokens.length){var f=this.tokens[0],g=f.text;if(g===a||g===c||g===d||g===e||!(a||c||d||e))return f}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,e))?(this.tokens.shift(),a):!1},consume:function(a){this.expect(a)||this.throwError("is unexpected, expecting ["+a+"]",this.peek())},unaryFn:function(a,c){return E(function(d,e){return a(d,e,c)},{constant:c.constant})},ternaryFn:function(a,c,d){return E(function(e,f){return a(e,f)?c(e,f):d(e,f)},{constant:a.constant&& -c.constant&&d.constant})},binaryFn:function(a,c,d){return E(function(e,f){return c(e,f,a,d)},{constant:a.constant&&d.constant})},statements:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.filterChain()),!this.expect(";"))return 1===a.length?a[0]:function(c,d){for(var e,f=0,g=a.length;f<g;f++)e=a[f](c,d);return e}},filterChain:function(){for(var a=this.expression(),c;c=this.expect("|");)a=this.binaryFn(a,c.fn,this.filter());return a},filter:function(){var a= -this.expect(),c=this.$filter(a.text),d,e;if(this.peek(":"))for(d=[],e=[];this.expect(":");)d.push(this.expression());return da(function(a,g,h){if(e){e[0]=h;for(h=d.length;h--;)e[h+1]=d[h](a,g);return c.apply(s,e)}return c(h)})},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary(),c,d;return(d=this.expect("="))?(a.assign||this.throwError("implies assignment but ["+this.text.substring(0,d.index)+"] can not be assigned to",d),c=this.ternary(),function(d,f){return a.assign(d, -c(d,f),f)}):a},ternary:function(){var a=this.logicalOR(),c,d;if(this.expect("?")){c=this.assignment();if(d=this.expect(":"))return this.ternaryFn(a,c,this.assignment());this.throwError("expected :",d)}else return a},logicalOR:function(){for(var a=this.logicalAND(),c;c=this.expect("||");)a=this.binaryFn(a,c.fn,this.logicalAND());return a},logicalAND:function(){var a=this.equality(),c;if(c=this.expect("&&"))a=this.binaryFn(a,c.fn,this.logicalAND());return a},equality:function(){var a=this.relational(), -c;if(c=this.expect("==","!=","===","!=="))a=this.binaryFn(a,c.fn,this.equality());return a},relational:function(){var a=this.additive(),c;if(c=this.expect("<",">","<=",">="))a=this.binaryFn(a,c.fn,this.relational());return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a=this.binaryFn(a,c.fn,this.multiplicative());return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a=this.binaryFn(a,c.fn,this.unary());return a},unary:function(){var a; -return this.expect("+")?this.primary():(a=this.expect("-"))?this.binaryFn(Za.ZERO,a.fn,this.unary()):(a=this.expect("!"))?this.unaryFn(a.fn,this.unary()):this.primary()},fieldAccess:function(a){var c=this.text,d=this.expect().text,e=Pc(d,this.options,c);return E(function(c,d,h){return e(h||a(c,d))},{assign:function(e,g,h){(h=a(e,h))||a.assign(e,h={});return ub(h,d,g,c)}})},objectIndex:function(a){var c=this.text,d=this.expression();this.consume("]");return E(function(e,f){var g=a(e,f),h=d(e,f);na(h, -c);return g?Ba(g[h],c):s},{assign:function(e,f,g){var h=na(d(e,g),c);(g=Ba(a(e,g),c))||a.assign(e,g={});return g[h]=f}})},functionCall:function(a,c){var d=[];if(")"!==this.peekToken().text){do d.push(this.expression());while(this.expect(","))}this.consume(")");var e=this.text,f=d.length?[]:null;return function(g,h){var m=c?c(g,h):g,k=a(g,h,m)||w;if(f)for(var n=d.length;n--;)f[n]=Ba(d[n](g,h),e);Ba(m,e);if(k){if(k.constructor===k)throw oa("isecfn",e);if(k===wf||k===xf||k===yf)throw oa("isecff",e); -}m=k.apply?k.apply(m,f):k(f[0],f[1],f[2],f[3],f[4]);return Ba(m,e)}},arrayDeclaration:function(){var a=[],c=!0;if("]"!==this.peekToken().text){do{if(this.peek("]"))break;var d=this.expression();a.push(d);d.constant||(c=!1)}while(this.expect(","))}this.consume("]");return E(function(c,d){for(var g=[],h=0,m=a.length;h<m;h++)g.push(a[h](c,d));return g},{literal:!0,constant:c})},object:function(){var a=[],c=!0;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;var d=this.expect(),d=d.string|| -d.text;this.consume(":");var e=this.expression();a.push({key:d,value:e});e.constant||(c=!1)}while(this.expect(","))}this.consume("}");return E(function(c,d){for(var e={},m=0,k=a.length;m<k;m++){var n=a[m];e[n.key]=n.value(c,d)}return e},{literal:!0,constant:c})}};var Qc=Object.create(null),Ca=K("$sce"),ka={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},ja=K("$compile"),Z=Y.createElement("a"),Tc=Aa(t.location.href,!0);qc.$inject=["$provide"];Uc.$inject=["$locale"];Wc.$inject=["$locale"]; -var Zc=".",nf={yyyy:$("FullYear",4),yy:$("FullYear",2,0,!0),y:$("FullYear",1),MMMM:wb("Month"),MMM:wb("Month",!0),MM:$("Month",2,1),M:$("Month",1,1),dd:$("Date",2),d:$("Date",1),HH:$("Hours",2),H:$("Hours",1),hh:$("Hours",2,-12),h:$("Hours",1,-12),mm:$("Minutes",2),m:$("Minutes",1),ss:$("Seconds",2),s:$("Seconds",1),sss:$("Milliseconds",3),EEEE:wb("Day"),EEE:wb("Day",!0),a:function(a,c){return 12>a.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=-1*a.getTimezoneOffset();return a=(0<=a?"+":"")+(vb(Math[0< -a?"floor":"ceil"](a/60),2)+vb(Math.abs(a%60),2))},ww:ad(2),w:ad(1)},mf=/((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/,lf=/^\-?\d+$/;Vc.$inject=["$locale"];var jf=da(P),kf=da(ib);Xc.$inject=["$parse"];var Fd=da({restrict:"E",compile:function(a,c){8>=X&&(c.href||c.name||c.$set("href",""),a.append(Y.createComment("IE fix")));if(!c.href&&!c.xlinkHref&&!c.name)return function(a,c){var f="[object SVGAnimatedString]"===Ga.call(c.prop("href"))?"xlink:href":"href";c.on("click", -function(a){c.attr(f)||a.preventDefault()})}}}),kb={};r(qb,function(a,c){if("multiple"!=a){var d=va("ng-"+c);kb[d]=function(){return{restrict:"A",priority:100,link:function(a,f,g){a.$watch(g[d],function(a){g.$set(c,!!a)})}}}}});r(Ac,function(a,c){kb[c]=function(){return{priority:100,link:function(a,e,f){if("ngPattern"===c&&"/"==f.ngPattern.charAt(0)&&(e=f.ngPattern.match(rf))){f.$set("ngPattern",new RegExp(e[1],e[2]));return}a.$watch(f[c],function(a){f.$set(c,a)})}}}});r(["src","srcset","href"],function(a){var c= -va("ng-"+a);kb[c]=function(){return{priority:99,link:function(d,e,f){var g=a,h=a;"href"===a&&"[object SVGAnimatedString]"===Ga.call(e.prop("href"))&&(h="xlinkHref",f.$attr[h]="xlink:href",g=null);f.$observe(c,function(c){c?(f.$set(h,c),X&&g&&e.prop(g,f[h])):"href"===a&&f.$set(h,null)})}}}});var xb={$addControl:w,$removeControl:w,$setValidity:w,$$setPending:w,$setDirty:w,$setPristine:w,$setSubmitted:w,$$clearControlValidity:w};bd.$inject=["$element","$attrs","$scope","$animate"];var id=function(a){return["$timeout", -function(c){return{name:"form",restrict:a?"EAC":"E",controller:bd,compile:function(){return{pre:function(a,e,f,g){if(!f.action){var h=function(c){a.$apply(function(){g.$commitViewValue();g.$setSubmitted()});c.preventDefault?c.preventDefault():c.returnValue=!1};e[0].addEventListener("submit",h,!1);e.on("$destroy",function(){c(function(){e[0].removeEventListener("submit",h,!1)},0,!1)})}var m=e.parent().controller("form"),k=f.name||f.ngForm;k&&ub(a,k,g,k);if(m)e.on("$destroy",function(){m.$removeControl(g); -k&&ub(a,k,s,k);E(g,xb)})}}}}}]},Gd=id(),Td=id(!0),of=/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/,Af=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,Bf=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,Cf=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,jd=/^(\d{4})-(\d{2})-(\d{2})$/,kd=/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d))?$/,Xb=/^(\d{4})-W(\d\d)$/,ld=/^(\d{4})-(\d\d)$/,md=/^(\d\d):(\d\d)(?::(\d\d))?$/, -Df=/(\s+|^)default(\s+|$)/,Yb=new K("ngModel"),nd={text:function(a,c,d,e,f,g){$a(a,c,d,e,f,g);Ub(e)},date:ab("date",jd,zb(jd,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":ab("datetimelocal",kd,zb(kd,"yyyy MM dd HH mm ss".split(" ")),"yyyy-MM-ddTHH:mm:ss"),time:ab("time",md,zb(md,["HH","mm","ss"]),"HH:mm:ss"),week:ab("week",Xb,function(a){if(fa(a))return a;if(C(a)){Xb.lastIndex=0;var c=Xb.exec(a);if(c){a=+c[1];var d=+c[2],c=$c(a),d=7*(d-1);return new Date(a,0,c.getDate()+d)}}return NaN},"yyyy-Www"), -month:ab("month",ld,zb(ld,["yyyy","MM"]),"yyyy-MM"),number:function(a,c,d,e,f,g){dd(a,c,d,e);$a(a,c,d,e,f,g);e.$$parserName="number";e.$parsers.push(function(a){return e.$isEmpty(a)?null:Cf.test(a)?parseFloat(a):s});e.$formatters.push(function(a){if(!e.$isEmpty(a)){if(!ea(a))throw Yb("numfmt",a);a=a.toString()}return a});if(d.min||d.ngMin){var h;e.$validators.min=function(a){return e.$isEmpty(a)||F(h)||a>=h};d.$observe("min",function(a){B(a)&&!ea(a)&&(a=parseFloat(a,10));h=ea(a)&&!isNaN(a)?a:s;e.$validate()})}if(d.max|| -d.ngMax){var m;e.$validators.max=function(a){return e.$isEmpty(a)||F(m)||a<=m};d.$observe("max",function(a){B(a)&&!ea(a)&&(a=parseFloat(a,10));m=ea(a)&&!isNaN(a)?a:s;e.$validate()})}},url:function(a,c,d,e,f,g){$a(a,c,d,e,f,g);Ub(e);e.$$parserName="url";e.$validators.url=function(a,c){var d=a||c;return e.$isEmpty(d)||Af.test(d)}},email:function(a,c,d,e,f,g){$a(a,c,d,e,f,g);Ub(e);e.$$parserName="email";e.$validators.email=function(a,c){var d=a||c;return e.$isEmpty(d)||Bf.test(d)}},radio:function(a, -c,d,e){F(d.name)&&c.attr("name",++bb);c.on("click",function(a){c[0].checked&&e.$setViewValue(d.value,a&&a.type)});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e,f,g,h,m){var k=ed(m,a,"ngTrueValue",d.ngTrueValue,!0),n=ed(m,a,"ngFalseValue",d.ngFalseValue,!1);c.on("click",function(a){e.$setViewValue(c[0].checked,a&&a.type)});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return a!==k};e.$formatters.push(function(a){return ra(a, -k)});e.$parsers.push(function(a){return a?k:n})},hidden:w,button:w,submit:w,reset:w,file:w},kc=["$browser","$sniffer","$filter","$parse",function(a,c,d,e){return{restrict:"E",require:["?ngModel"],link:function(f,g,h,m){m[0]&&(nd[P(h.type)]||nd.text)(f,g,h,m[0],c,a,d,e)}}}],pf="ng-valid",qf="ng-invalid",Ma="ng-pristine",yb="ng-dirty",gd="ng-pending",Ef=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q",function(a,c,d,e,f,g,h,m,k){this.$modelValue=this.$viewValue= -Number.NaN;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=s;this.$name=d.name;var n=f(d.ngModel),p=null,l=this,q=function(){var c=n(a);l.$options&&l.$options.getterSetter&&D(c)&&(c=c());return c},A=function(c){var d;l.$options&&l.$options.getterSetter&&D(d=n(a))?d(l.$modelValue):n.assign(a, -l.$modelValue)};this.$$setOptions=function(a){l.$options=a;if(!(n.assign||a&&a.getterSetter))throw Yb("nonassign",d.ngModel,ta(e));};this.$render=w;this.$isEmpty=function(a){return F(a)||""===a||null===a||a!==a};var u=e.inheritedData("$formController")||xb,x=0;e.addClass(Ma).addClass("ng-untouched");cd({ctrl:this,$element:e,set:function(a,c){a[c]=!0},unset:function(a,c){delete a[c]},parentForm:u,$animate:g});this.$setPristine=function(){l.$dirty=!1;l.$pristine=!0;g.removeClass(e,yb);g.addClass(e, -Ma)};this.$setUntouched=function(){l.$touched=!1;l.$untouched=!0;g.setClass(e,"ng-untouched","ng-touched")};this.$setTouched=function(){l.$touched=!0;l.$untouched=!1;g.setClass(e,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){h.cancel(p);l.$viewValue=l.$$lastCommittedViewValue;l.$render()};this.$validate=function(){ea(l.$modelValue)&&isNaN(l.$modelValue)||this.$$parseAndValidate()};this.$$runValidators=function(a,c,d,e){function f(){var a=!0;r(l.$validators,function(e,f){var h=e(c, -d);a=a&&h;g(f,h)});return a?!0:(r(l.$asyncValidators,function(a,c){g(c,null)}),m(),!1)}function h(){var a=[];r(l.$asyncValidators,function(e,f){var h=e(c,d);if(!h||!D(h.then))throw Yb("$asyncValidators",h);g(f,s);a.push(h.then(function(){g(f,!0)},function(a){g(f,!1)}))});a.length?k.all(a).then(m):m()}function g(a,c){n===x&&l.$setValidity(a,c)}function m(){n===x&&e()}x++;var n=x;(function(a){var c=l.$$parserName||"parse";if(a===s)g(c,null);else if(g(c,a),!a)return r(l.$validators,function(a,c){g(c, -null)}),r(l.$asyncValidators,function(a,c){g(c,null)}),m(),!1;return!0})(a)&&f()&&h()};this.$commitViewValue=function(){var a=l.$viewValue;h.cancel(p);if(l.$$lastCommittedViewValue!==a||""===a&&l.$$hasNativeValidators)l.$$lastCommittedViewValue=a,l.$pristine&&(l.$dirty=!0,l.$pristine=!1,g.removeClass(e,Ma),g.addClass(e,yb),u.$setDirty()),this.$$parseAndValidate()};this.$$parseAndValidate=function(){for(var a=!0,c=l.$$lastCommittedViewValue,d=c,e=0;e<l.$parsers.length;e++)if(d=l.$parsers[e](d),F(d)){a= -!1;break}ea(l.$modelValue)&&isNaN(l.$modelValue)&&(l.$modelValue=q());var f=l.$modelValue,h=l.$options&&l.$options.allowInvalid;h&&(l.$modelValue=d,l.$modelValue!==f&&l.$$writeModelToScope());l.$$runValidators(a,d,c,function(){h||(l.$modelValue=l.$valid?d:s,l.$modelValue!==f&&l.$$writeModelToScope())})};this.$$writeModelToScope=function(){A(l.$modelValue);r(l.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};this.$setViewValue=function(a,c){l.$viewValue=a;l.$options&&!l.$options.updateOnDefault|| -l.$$debounceViewValueCommit(c)};this.$$debounceViewValueCommit=function(c){var d=0,e=l.$options;e&&B(e.debounce)&&(e=e.debounce,ea(e)?d=e:ea(e[c])?d=e[c]:ea(e["default"])&&(d=e["default"]));h.cancel(p);d?p=h(function(){l.$commitViewValue()},d):m.$$phase?l.$commitViewValue():a.$apply(function(){l.$commitViewValue()})};a.$watch(function(){var a=q();if(a!==l.$modelValue){l.$modelValue=a;for(var c=l.$formatters,d=c.length,e=a;d--;)e=c[d](e);l.$viewValue!==e&&(l.$viewValue=l.$$lastCommittedViewValue=e, -l.$render(),l.$$runValidators(s,a,e,w))}return a})}],he=function(){return{restrict:"A",require:["ngModel","^?form","^?ngModelOptions"],controller:Ef,link:{pre:function(a,c,d,e){var f=e[0],g=e[1]||xb;f.$$setOptions(e[2]&&e[2].$options);g.$addControl(f);a.$on("$destroy",function(){g.$removeControl(f)})},post:function(a,c,d,e){var f=e[0];if(f.$options&&f.$options.updateOn)c.on(f.$options.updateOn,function(a){f.$$debounceViewValueCommit(a&&a.type)});c.on("blur",function(c){f.$touched||a.$apply(function(){f.$setTouched()})})}}}}, -je=da({restrict:"A",require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),mc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){e&&(d.required=!0,e.$validators.required=function(a,c){return!d.required||!e.$isEmpty(c)},d.$observe("required",function(){e.$validate()}))}}},lc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f,g=d.ngPattern||d.pattern;d.$observe("pattern",function(a){C(a)&&0<a.length&& -(a=new RegExp(a));if(a&&!a.test)throw K("ngPattern")("noregexp",g,a,ta(c));f=a||s;e.$validate()});e.$validators.pattern=function(a){return e.$isEmpty(a)||F(f)||f.test(a)}}}}},oc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=0;d.$observe("maxlength",function(a){f=U(a)||0;e.$validate()});e.$validators.maxlength=function(a,c){return e.$isEmpty(c)||c.length<=f}}}}},nc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=0;d.$observe("minlength", -function(a){f=U(a)||0;e.$validate()});e.$validators.minlength=function(a,c){return e.$isEmpty(c)||c.length>=f}}}}},ie=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,c,d,e){var f=c.attr(d.$attr.ngList)||", ",g="false"!==d.ngTrim,h=g?ba(f):f;e.$parsers.push(function(a){if(!F(a)){var c=[];a&&r(a.split(h),function(a){a&&c.push(g?ba(a):a)});return c}});e.$formatters.push(function(a){return L(a)?a.join(f):s});e.$isEmpty=function(a){return!a||!a.length}}}},Ff=/^(true|false|\d+)$/, -ke=function(){return{restrict:"A",priority:100,compile:function(a,c){return Ff.test(c.ngValue)?function(a,c,f){f.$set("value",a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value",a)})}}}},le=function(){return{restrict:"A",controller:["$scope","$attrs",function(a,c){var d=this;this.$options=a.$eval(c.ngModelOptions);this.$options.updateOn!==s?(this.$options.updateOnDefault=!1,this.$options.updateOn=ba(this.$options.updateOn.replace(Df,function(){d.$options.updateOnDefault= -!0;return" "}))):this.$options.updateOnDefault=!0}]}},Ld=["$compile",function(a){return{restrict:"AC",compile:function(c){a.$$addBindingClass(c);return function(c,e,f){a.$$addBindingInfo(e,f.ngBind);c.$watch(f.ngBind,function(a){e.text(a==s?"":a)})}}}}],Nd=["$interpolate","$compile",function(a,c){return{compile:function(d){c.$$addBindingClass(d);return function(d,f,g){d=a(f.attr(g.$attr.ngBindTemplate));c.$$addBindingInfo(f,d.expressions);g.$observe("ngBindTemplate",function(a){f.text(a)})}}}}],Md= -["$sce","$parse","$compile",function(a,c,d){return{restrict:"A",compile:function(e,f){var g=c(f.ngBindHtml),h=c(f.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(e);return function(c,e,f){d.$$addBindingInfo(e,f.ngBindHtml);c.$watch(h,function(){e.html(a.getTrustedHtml(g(c))||"")})}}}}],Od=Vb("",!0),Qd=Vb("Odd",0),Pd=Vb("Even",1),Rd=Fa({compile:function(a,c){c.$set("ngCloak",s);a.removeClass("ng-cloak")}}),Sd=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}], -pc={},Gf={blur:!0,focus:!0};r("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=va("ng-"+a);pc[c]=["$parse","$rootScope",function(d,e){return{restrict:"A",compile:function(f,g){var h=d(g[c]);return function(c,d){var f=P(a);d.on(f,function(a){var d=function(){h(c,{$event:a})};Gf[f]&&e.$$phase?c.$evalAsync(d):c.$apply(d)})}}}}]});var Vd=["$animate",function(a){return{multiElement:!0, -transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,f,g){var h,m,k;c.$watch(e.ngIf,function(c){c?m||g(function(c,f){m=f;c[c.length++]=Y.createComment(" end ngIf: "+e.ngIf+" ");h={clone:c};a.enter(c,d.parent(),d)}):(k&&(k.remove(),k=null),m&&(m.$destroy(),m=null),h&&(k=hb(h.clone),a.leave(k).then(function(){k=null}),h=null))})}}}],Wd=["$templateRequest","$anchorScroll","$animate","$sce",function(a,c,d,e){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element", -controller:Ea.noop,compile:function(f,g){var h=g.ngInclude||g.src,m=g.onload||"",k=g.autoscroll;return function(f,g,l,q,r){var u=0,s,z,t,v=function(){z&&(z.remove(),z=null);s&&(s.$destroy(),s=null);t&&(d.leave(t).then(function(){z=null}),z=t,t=null)};f.$watch(e.parseAsResourceUrl(h),function(e){var h=function(){!B(k)||k&&!f.$eval(k)||c()},l=++u;e?(a(e,!0).then(function(a){if(l===u){var c=f.$new();q.template=a;a=r(c,function(a){v();d.enter(a,null,g).then(h)});s=c;t=a;s.$emit("$includeContentLoaded"); -f.$eval(m)}},function(){l===u&&(v(),f.$emit("$includeContentError"))}),f.$emit("$includeContentRequested")):(v(),q.template=null)})}}}}],me=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(c,d,e,f){/SVG/.test(d[0].toString())?(d.empty(),a(sc(f.template,Y).childNodes)(c,function(a){d.append(a)},s,s,d)):(d.html(f.template),a(d.contents())(c))}}}],Xd=Fa({priority:450,compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Yd=Fa({terminal:!0,priority:1E3}), -Zd=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,f,g){var h=g.count,m=g.$attr.when&&f.attr(g.$attr.when),k=g.offset||0,n=e.$eval(m)||{},p={},l=c.startSymbol(),q=c.endSymbol(),s=/^when(Minus)?(.+)$/;r(g,function(a,c){s.test(c)&&(n[P(c.replace("when","").replace("Minus","-"))]=f.attr(g.$attr[c]))});r(n,function(a,e){p[e]=c(a.replace(d,l+h+"-"+k+q))});e.$watch(function(){var c=parseFloat(e.$eval(h));if(isNaN(c))return"";c in n||(c=a.pluralCat(c-k));return p[c](e)}, -function(a){f.text(a)})}}}],$d=["$parse","$animate",function(a,c){var d=K("ngRepeat"),e=function(a,c,d,e,k,n,p){a[d]=e;k&&(a[k]=n);a.$index=c;a.$first=0===c;a.$last=c===p-1;a.$middle=!(a.$first||a.$last);a.$odd=!(a.$even=0===(c&1))};return{restrict:"A",multiElement:!0,transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,compile:function(f,g){var h=g.ngRepeat,m=Y.createComment(" end ngRepeat: "+h+" "),k=h.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); -if(!k)throw d("iexp",h);var n=k[1],p=k[2],l=k[3],q=k[4],k=n.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!k)throw d("iidexp",n);var A=k[3]||k[1],u=k[2];if(l&&(!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(l)||/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent)$/.test(l)))throw d("badident",l);var t,z,B,v,w={$id:Ka};q?t=a(q):(B=function(a,c){return Ka(c)},v=function(a){return a});return function(a,f,g,k,n){t&&(z=function(c,d,e){u&&(w[u]=c);w[A]=d;w.$index=e;return t(a,w)}); -var q=Object.create(null);a.$watchCollection(p,function(g){var k,p,t=f[0],J,x=Object.create(null),w,N,E,F,y,C,ga;l&&(a[l]=g);if(Na(g))y=g,p=z||B;else{p=z||v;y=[];for(ga in g)g.hasOwnProperty(ga)&&"$"!=ga.charAt(0)&&y.push(ga);y.sort()}w=y.length;ga=Array(w);for(k=0;k<w;k++)if(N=g===y?k:y[k],E=g[N],F=p(N,E,k),q[F])C=q[F],delete q[F],x[F]=C,ga[k]=C;else{if(x[F])throw r(ga,function(a){a&&a.scope&&(q[a.id]=a)}),d("dupes",h,F,sa(E));ga[k]={id:F,scope:s,clone:s};x[F]=!0}for(J in q){C=q[J];F=hb(C.clone); -c.leave(F);if(F[0].parentNode)for(k=0,p=F.length;k<p;k++)F[k].$$NG_REMOVED=!0;C.scope.$destroy()}for(k=0;k<w;k++)if(N=g===y?k:y[k],E=g[N],C=ga[k],C.scope){J=t;do J=J.nextSibling;while(J&&J.$$NG_REMOVED);C.clone[0]!=J&&c.move(hb(C.clone),null,G(t));t=C.clone[C.clone.length-1];e(C.scope,k,A,E,u,N,w)}else n(function(a,d){C.scope=d;var f=m.cloneNode(!1);a[a.length++]=f;c.enter(a,null,G(t));t=f;C.clone=a;x[C.id]=C;e(C.scope,k,A,E,u,N,w)});q=x})}}}}],ae=["$animate",function(a){return{restrict:"A",multiElement:!0, -link:function(c,d,e){c.$watch(e.ngShow,function(c){a[c?"removeClass":"addClass"](d,"ng-hide")})}}}],Ud=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(c,d,e){c.$watch(e.ngHide,function(c){a[c?"addClass":"removeClass"](d,"ng-hide")})}}}],be=Fa(function(a,c,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&r(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),ce=["$animate",function(a){return{restrict:"EA",require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(c, -d,e,f){var g=[],h=[],m=[],k=[],n=function(a,c){return function(){a.splice(c,1)}};c.$watch(e.ngSwitch||e.on,function(c){var d,e;d=0;for(e=m.length;d<e;++d)a.cancel(m[d]);d=m.length=0;for(e=k.length;d<e;++d){var s=hb(h[d].clone);k[d].$destroy();(m[d]=a.leave(s)).then(n(m,d))}h.length=0;k.length=0;(g=f.cases["!"+c]||f.cases["?"])&&r(g,function(c){c.transclude(function(d,e){k.push(e);var f=c.element;d[d.length++]=Y.createComment(" end ngSwitchWhen: ");h.push({clone:d});a.enter(d,f.parent(),f)})})})}}}], -de=Fa({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,c,d,e,f){e.cases["!"+d.ngSwitchWhen]=e.cases["!"+d.ngSwitchWhen]||[];e.cases["!"+d.ngSwitchWhen].push({transclude:f,element:c})}}),ee=Fa({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,c,d,e,f){e.cases["?"]=e.cases["?"]||[];e.cases["?"].push({transclude:f,element:c})}}),ge=Fa({restrict:"EAC",link:function(a,c,d,e,f){if(!f)throw K("ngTransclude")("orphan",ta(c));f(function(a){c.empty(); -c.append(a)})}}),Hd=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(c,d){"text/ng-template"==d.type&&a.put(d.id,c[0].text)}}}],Hf=K("ngOptions"),fe=da({restrict:"A",terminal:!0}),Id=["$compile","$parse",function(a,c){var d=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,e={$setViewValue:w};return{restrict:"E",require:["select", -"?ngModel"],controller:["$element","$scope","$attrs",function(a,c,d){var m=this,k={},n=e,p;m.databound=d.ngModel;m.init=function(a,c,d){n=a;p=d};m.addOption=function(c,d){Ja(c,'"option value"');k[c]=!0;n.$viewValue==c&&(a.val(c),p.parent()&&p.remove());d[0].hasAttribute("selected")&&(d[0].selected=!0)};m.removeOption=function(a){this.hasOption(a)&&(delete k[a],n.$viewValue==a&&this.renderUnknownOption(a))};m.renderUnknownOption=function(c){c="? "+Ka(c)+" ?";p.val(c);a.prepend(p);a.val(c);p.prop("selected", -!0)};m.hasOption=function(a){return k.hasOwnProperty(a)};c.$on("$destroy",function(){m.renderUnknownOption=w})}],link:function(e,g,h,m){function k(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(a)?(y.parent()&&y.remove(),c.val(a),""===a&&w.prop("selected",!0)):F(a)&&w?c.val(""):e.renderUnknownOption(a)};c.on("change",function(){a.$apply(function(){y.parent()&&y.remove();d.$setViewValue(c.val())})})}function n(a,c,d){var e;d.$render=function(){var a=new Xa(d.$viewValue);r(c.find("option"), -function(c){c.selected=B(a.get(c.value))})};a.$watch(function(){ra(e,d.$viewValue)||(e=qa(d.$viewValue),d.$render())});c.on("change",function(){a.$apply(function(){var a=[];r(c.find("option"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function p(e,f,h){function g(){z||(e.$$postDigest(k),z=!0)}function k(){z=!1;var a={"":[]},c=[""],d,g,l,s,t;l=h.$modelValue;s=F(e)||[];var A=p?Zb(s):s,G,y,D;y={};D=!1;if(q)if(g=h.$modelValue,x&&L(g))for(D=new Xa([]),d={},t=0;t<g.length;t++)d[n]= -g[t],D.put(x(e,d),g[t]);else D=new Xa(g);t=D;var H,K;for(D=0;G=A.length,D<G;D++){g=D;if(p){g=A[D];if("$"===g.charAt(0))continue;y[p]=g}y[n]=s[g];d=r(e,y)||"";(g=a[d])||(g=a[d]=[],c.push(d));q?d=B(t.remove(x?x(e,y):w(e,y))):(x?(d={},d[n]=l,d=x(e,d)===x(e,y)):d=l===w(e,y),t=t||d);H=m(e,y);H=B(H)?H:"";g.push({id:x?x(e,y):p?A[D]:D,label:H,selected:d})}q||(u||null===l?a[""].unshift({id:"",label:"",selected:!t}):t||a[""].unshift({id:"?",label:"",selected:!0}));y=0;for(A=c.length;y<A;y++){d=c[y];g=a[d]; -E.length<=y?(l={element:v.clone().attr("label",d),label:g.label},s=[l],E.push(s),f.append(l.element)):(s=E[y],l=s[0],l.label!=d&&l.element.attr("label",l.label=d));H=null;D=0;for(G=g.length;D<G;D++)d=g[D],(t=s[D+1])?(H=t.element,t.label!==d.label&&H.text(t.label=d.label),t.id!==d.id&&H.val(t.id=d.id),H[0].selected!==d.selected&&(H.prop("selected",t.selected=d.selected),X&&H.prop("selected",t.selected))):(""===d.id&&u?K=u:(K=C.clone()).val(d.id).prop("selected",d.selected).attr("selected",d.selected).text(d.label), -s.push({element:K,label:d.label,id:d.id,selected:d.selected}),H?H.after(K):l.element.append(K),H=K);for(D++;s.length>D;)s.pop().element.remove()}for(;E.length>y;)E.pop()[0].element.remove()}var l;if(!(l=t.match(d)))throw Hf("iexp",t,ta(f));var m=c(l[2]||l[1]),n=l[4]||l[6],p=l[5],r=c(l[3]||""),w=c(l[2]?l[1]:n),F=c(l[7]),x=l[8]?c(l[8]):null,E=[[{element:f,label:""}]];u&&(a(u)(e),u.removeClass("ng-scope"),u.remove());f.empty();f.on("change",function(){e.$apply(function(){var a,c=F(e)||[],d={},g,l,m, -r,t,u,v;if(q)for(l=[],r=0,u=E.length;r<u;r++)for(a=E[r],m=1,t=a.length;m<t;m++){if((g=a[m].element)[0].selected){g=g.val();p&&(d[p]=g);if(x)for(v=0;v<c.length&&(d[n]=c[v],x(e,d)!=g);v++);else d[n]=c[g];l.push(w(e,d))}}else if(g=f.val(),"?"==g)l=s;else if(""===g)l=null;else if(x)for(v=0;v<c.length;v++){if(d[n]=c[v],x(e,d)==g){l=w(e,d);break}}else d[n]=c[g],p&&(d[p]=g),l=w(e,d);h.$setViewValue(l);k()})});h.$render=k;e.$watchCollection(F,g);q&&e.$watchCollection(function(){return h.$modelValue},g)}if(m[1]){var l= -m[0];m=m[1];var q=h.multiple,t=h.ngOptions,u=!1,w,z=!1,C=G(Y.createElement("option")),v=G(Y.createElement("optgroup")),y=C.clone();h=0;for(var E=g.children(),D=E.length;h<D;h++)if(""===E[h].value){w=u=E.eq(h);break}l.init(m,u,y);q&&(m.$isEmpty=function(a){return!a||0===a.length});t?p(e,g,m):q?n(e,g,m):k(e,g,m,l)}}}}],Kd=["$interpolate",function(a){var c={addOption:w,removeOption:w};return{restrict:"E",priority:100,compile:function(d,e){if(F(e.value)){var f=a(d.text(),!0);f||e.$set("value",d.text())}return function(a, -d,e){var k=d.parent(),n=k.data("$selectController")||k.parent().data("$selectController");n&&n.databound?d.prop("selected",!1):n=c;f?a.$watch(f,function(a,c){e.$set("value",a);c!==a&&n.removeOption(c);n.addOption(a,d)}):n.addOption(e.value,d);d.on("$destroy",function(){n.removeOption(e.value)})}}}}],Jd=da({restrict:"E",terminal:!1});t.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):(zd(),Bd(Ea),G(Y).ready(function(){vd(Y,gc)}))})(window,document); -!window.angular.$$csp()&&window.angular.element(document).find("head").prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-animate){display:none !important;}ng\\:form{display:block;}</style>'); -//# sourceMappingURL=angular.min.js.map diff --git a/syweb/webclient/js/autofill-event.js b/syweb/webclient/js/autofill-event.js deleted file mode 100755 index 006f83e1be..0000000000 --- a/syweb/webclient/js/autofill-event.js +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Autofill event polyfill ##version:1.0.0## - * (c) 2014 Google, Inc. - * License: MIT - */ -(function(window) { - var $ = window.jQuery || window.angular.element; - var rootElement = window.document.documentElement, - $rootElement = $(rootElement); - - addGlobalEventListener('change', markValue); - addValueChangeByJsListener(markValue); - - $.prototype.checkAndTriggerAutoFillEvent = jqCheckAndTriggerAutoFillEvent; - - // Need to use blur and not change event - // as Chrome does not fire change events in all cases an input is changed - // (e.g. when starting to type and then finish the input by auto filling a username) - addGlobalEventListener('blur', function(target) { - // setTimeout needed for Chrome as it fills other - // form fields a little later... - window.setTimeout(function() { - findParentForm(target).find('input').checkAndTriggerAutoFillEvent(); - }, 20); - }); - - window.document.addEventListener('DOMContentLoaded', function() { - // The timeout is needed for Chrome as it auto fills - // login forms some time after DOMContentLoaded! - window.setTimeout(function() { - $rootElement.find('input').checkAndTriggerAutoFillEvent(); - }, 200); - }, false); - - return; - - // ---------- - - function jqCheckAndTriggerAutoFillEvent() { - var i, el; - for (i=0; i<this.length; i++) { - el = this[i]; - if (!valueMarked(el)) { - markValue(el); - triggerChangeEvent(el); - } - } - } - - function valueMarked(el) { - var val = el.value, - $$currentValue = el.$$currentValue; - if (!val && !$$currentValue) { - return true; - } - return val === $$currentValue; - } - - function markValue(el) { - el.$$currentValue = el.value; - } - - function addValueChangeByJsListener(listener) { - var jq = window.jQuery || window.angular.element, - jqProto = jq.prototype; - var _val = jqProto.val; - jqProto.val = function(newValue) { - var res = _val.apply(this, arguments); - if (arguments.length > 0) { - forEach(this, function(el) { - listener(el, newValue); - }); - } - return res; - } - } - - function addGlobalEventListener(eventName, listener) { - // Use a capturing event listener so that - // we also get the event when it's stopped! - // Also, the blur event does not bubble. - rootElement.addEventListener(eventName, onEvent, true); - - function onEvent(event) { - var target = event.target; - listener(target); - } - } - - function findParentForm(el) { - while (el) { - if (el.nodeName === 'FORM') { - return $(el); - } - el = el.parentNode; - } - return $(); - } - - function forEach(arr, listener) { - if (arr.forEach) { - return arr.forEach(listener); - } - var i; - for (i=0; i<arr.length; i++) { - listener(arr[i]); - } - } - - function triggerChangeEvent(element) { - var doc = window.document; - var event = doc.createEvent("HTMLEvents"); - event.initEvent("change", true, true); - element.dispatchEvent(event); - } - -})(window); diff --git a/syweb/webclient/js/elastic.js b/syweb/webclient/js/elastic.js deleted file mode 100644 index d585d81109..0000000000 --- a/syweb/webclient/js/elastic.js +++ /dev/null @@ -1,216 +0,0 @@ -/* - * angular-elastic v2.4.0 - * (c) 2014 Monospaced http://monospaced.com - * License: MIT - */ - -angular.module('monospaced.elastic', []) - - .constant('msdElasticConfig', { - append: '' - }) - - .directive('msdElastic', [ - '$timeout', '$window', 'msdElasticConfig', - function($timeout, $window, config) { - 'use strict'; - - return { - require: 'ngModel', - restrict: 'A, C', - link: function(scope, element, attrs, ngModel) { - - // cache a reference to the DOM element - var ta = element[0], - $ta = element; - - // ensure the element is a textarea, and browser is capable - if (ta.nodeName !== 'TEXTAREA' || !$window.getComputedStyle) { - return; - } - - // set these properties before measuring dimensions - $ta.css({ - 'overflow': 'hidden', - 'overflow-y': 'hidden', - 'word-wrap': 'break-word' - }); - - // force text reflow - var text = ta.value; - ta.value = ''; - ta.value = text; - - var append = attrs.msdElastic ? attrs.msdElastic.replace(/\\n/g, '\n') : config.append, - $win = angular.element($window), - mirrorInitStyle = 'position: absolute; top: -999px; right: auto; bottom: auto;' + - 'left: 0; overflow: hidden; -webkit-box-sizing: content-box;' + - '-moz-box-sizing: content-box; box-sizing: content-box;' + - 'min-height: 0 !important; height: 0 !important; padding: 0;' + - 'word-wrap: break-word; border: 0;', - $mirror = angular.element('<textarea tabindex="-1" ' + - 'style="' + mirrorInitStyle + '"/>').data('elastic', true), - mirror = $mirror[0], - taStyle = getComputedStyle(ta), - resize = taStyle.getPropertyValue('resize'), - borderBox = taStyle.getPropertyValue('box-sizing') === 'border-box' || - taStyle.getPropertyValue('-moz-box-sizing') === 'border-box' || - taStyle.getPropertyValue('-webkit-box-sizing') === 'border-box', - boxOuter = !borderBox ? {width: 0, height: 0} : { - width: parseInt(taStyle.getPropertyValue('border-right-width'), 10) + - parseInt(taStyle.getPropertyValue('padding-right'), 10) + - parseInt(taStyle.getPropertyValue('padding-left'), 10) + - parseInt(taStyle.getPropertyValue('border-left-width'), 10), - height: parseInt(taStyle.getPropertyValue('border-top-width'), 10) + - parseInt(taStyle.getPropertyValue('padding-top'), 10) + - parseInt(taStyle.getPropertyValue('padding-bottom'), 10) + - parseInt(taStyle.getPropertyValue('border-bottom-width'), 10) - }, - minHeightValue = parseInt(taStyle.getPropertyValue('min-height'), 10), - heightValue = parseInt(taStyle.getPropertyValue('height'), 10), - minHeight = Math.max(minHeightValue, heightValue) - boxOuter.height, - maxHeight = parseInt(taStyle.getPropertyValue('max-height'), 10), - mirrored, - active, - copyStyle = ['font-family', - 'font-size', - 'font-weight', - 'font-style', - 'letter-spacing', - 'line-height', - 'text-transform', - 'word-spacing', - 'text-indent']; - - // exit if elastic already applied (or is the mirror element) - if ($ta.data('elastic')) { - return; - } - - // Opera returns max-height of -1 if not set - maxHeight = maxHeight && maxHeight > 0 ? maxHeight : 9e4; - - // append mirror to the DOM - if (mirror.parentNode !== document.body) { - angular.element(document.body).append(mirror); - } - - // set resize and apply elastic - $ta.css({ - 'resize': (resize === 'none' || resize === 'vertical') ? 'none' : 'horizontal' - }).data('elastic', true); - - /* - * methods - */ - - function initMirror() { - var mirrorStyle = mirrorInitStyle; - - mirrored = ta; - // copy the essential styles from the textarea to the mirror - taStyle = getComputedStyle(ta); - angular.forEach(copyStyle, function(val) { - mirrorStyle += val + ':' + taStyle.getPropertyValue(val) + ';'; - }); - mirror.setAttribute('style', mirrorStyle); - } - - function adjust() { - var taHeight, - taComputedStyleWidth, - mirrorHeight, - width, - overflow; - - if (mirrored !== ta) { - initMirror(); - } - - // active flag prevents actions in function from calling adjust again - if (!active) { - active = true; - - mirror.value = ta.value + append; // optional whitespace to improve animation - mirror.style.overflowY = ta.style.overflowY; - - taHeight = ta.style.height === '' ? 'auto' : parseInt(ta.style.height, 10); - - taComputedStyleWidth = getComputedStyle(ta).getPropertyValue('width'); - - // ensure getComputedStyle has returned a readable 'used value' pixel width - if (taComputedStyleWidth.substr(taComputedStyleWidth.length - 2, 2) === 'px') { - // update mirror width in case the textarea width has changed - width = parseInt(taComputedStyleWidth, 10) - boxOuter.width; - mirror.style.width = width + 'px'; - } - - mirrorHeight = mirror.scrollHeight; - - if (mirrorHeight > maxHeight) { - mirrorHeight = maxHeight; - overflow = 'scroll'; - } else if (mirrorHeight < minHeight) { - mirrorHeight = minHeight; - } - mirrorHeight += boxOuter.height; - - ta.style.overflowY = overflow || 'hidden'; - - if (taHeight !== mirrorHeight) { - ta.style.height = mirrorHeight + 'px'; - scope.$emit('elastic:resize', $ta); - } - - // small delay to prevent an infinite loop - $timeout(function() { - active = false; - }, 1); - - } - } - - function forceAdjust() { - active = false; - adjust(); - } - - /* - * initialise - */ - - // listen - if ('onpropertychange' in ta && 'oninput' in ta) { - // IE9 - ta['oninput'] = ta.onkeyup = adjust; - } else { - ta['oninput'] = adjust; - } - - $win.bind('resize', forceAdjust); - - scope.$watch(function() { - return ngModel.$modelValue; - }, function(newValue) { - forceAdjust(); - }); - - scope.$on('elastic:adjust', function() { - initMirror(); - forceAdjust(); - }); - - $timeout(adjust); - - /* - * destroy - */ - - scope.$on('$destroy', function() { - $mirror.remove(); - $win.unbind('resize', forceAdjust); - }); - } - }; - } - ]); diff --git a/syweb/webclient/js/jquery-1.8.3.min.js b/syweb/webclient/js/jquery-1.8.3.min.js deleted file mode 100644 index 3883779527..0000000000 --- a/syweb/webclient/js/jquery-1.8.3.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v1.8.3 jquery.com | jquery.org/license */ -(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r<i;r++)v.event.add(t,n,u[n][r])}o.data&&(o.data=v.extend({},o.data))}function Ot(e,t){var n;if(t.nodeType!==1)return;t.clearAttributes&&t.clearAttributes(),t.mergeAttributes&&t.mergeAttributes(e),n=t.nodeName.toLowerCase(),n==="object"?(t.parentNode&&(t.outerHTML=e.outerHTML),v.support.html5Clone&&e.innerHTML&&!v.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):n==="input"&&Et.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):n==="option"?t.selected=e.defaultSelected:n==="input"||n==="textarea"?t.defaultValue=e.defaultValue:n==="script"&&t.text!==e.text&&(t.text=e.text),t.removeAttribute(v.expando)}function Mt(e){return typeof e.getElementsByTagName!="undefined"?e.getElementsByTagName("*"):typeof e.querySelectorAll!="undefined"?e.querySelectorAll("*"):[]}function _t(e){Et.test(e.type)&&(e.defaultChecked=e.checked)}function Qt(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Jt.length;while(i--){t=Jt[i]+n;if(t in e)return t}return r}function Gt(e,t){return e=t||e,v.css(e,"display")==="none"||!v.contains(e.ownerDocument,e)}function Yt(e,t){var n,r,i=[],s=0,o=e.length;for(;s<o;s++){n=e[s];if(!n.style)continue;i[s]=v._data(n,"olddisplay"),t?(!i[s]&&n.style.display==="none"&&(n.style.display=""),n.style.display===""&&Gt(n)&&(i[s]=v._data(n,"olddisplay",nn(n.nodeName)))):(r=Dt(n,"display"),!i[s]&&r!=="none"&&v._data(n,"olddisplay",r))}for(s=0;s<o;s++){n=e[s];if(!n.style)continue;if(!t||n.style.display==="none"||n.style.display==="")n.style.display=t?i[s]||"":"none"}return e}function Zt(e,t,n){var r=Rt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function en(e,t,n,r){var i=n===(r?"border":"content")?4:t==="width"?1:0,s=0;for(;i<4;i+=2)n==="margin"&&(s+=v.css(e,n+$t[i],!0)),r?(n==="content"&&(s-=parseFloat(Dt(e,"padding"+$t[i]))||0),n!=="margin"&&(s-=parseFloat(Dt(e,"border"+$t[i]+"Width"))||0)):(s+=parseFloat(Dt(e,"padding"+$t[i]))||0,n!=="padding"&&(s+=parseFloat(Dt(e,"border"+$t[i]+"Width"))||0));return s}function tn(e,t,n){var r=t==="width"?e.offsetWidth:e.offsetHeight,i=!0,s=v.support.boxSizing&&v.css(e,"boxSizing")==="border-box";if(r<=0||r==null){r=Dt(e,t);if(r<0||r==null)r=e.style[t];if(Ut.test(r))return r;i=s&&(v.support.boxSizingReliable||r===e.style[t]),r=parseFloat(r)||0}return r+en(e,t,n||(s?"border":"content"),i)+"px"}function nn(e){if(Wt[e])return Wt[e];var t=v("<"+e+">").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write("<!doctype html><html><body>"),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u<a;u++)r=o[u],s=/^\+/.test(r),s&&(r=r.substr(1)||"*"),i=e[r]=e[r]||[],i[s?"unshift":"push"](n)}}function kn(e,n,r,i,s,o){s=s||n.dataTypes[0],o=o||{},o[s]=!0;var u,a=e[s],f=0,l=a?a.length:0,c=e===Sn;for(;f<l&&(c||!u);f++)u=a[f](n,r,i),typeof u=="string"&&(!c||o[u]?u=t:(n.dataTypes.unshift(u),u=kn(e,n,r,i,u,o)));return(c||!u)&&!o["*"]&&(u=kn(e,n,r,i,"*",o)),u}function Ln(e,n){var r,i,s=v.ajaxSettings.flatOptions||{};for(r in n)n[r]!==t&&((s[r]?e:i||(i={}))[r]=n[r]);i&&v.extend(!0,e,i)}function An(e,n,r){var i,s,o,u,a=e.contents,f=e.dataTypes,l=e.responseFields;for(s in l)s in r&&(n[l[s]]=r[s]);while(f[0]==="*")f.shift(),i===t&&(i=e.mimeType||n.getResponseHeader("content-type"));if(i)for(s in a)if(a[s]&&a[s].test(i)){f.unshift(s);break}if(f[0]in r)o=f[0];else{for(s in r){if(!f[0]||e.converters[s+" "+f[0]]){o=s;break}u||(u=s)}o=o||u}if(o)return o!==f[0]&&f.unshift(o),r[o]}function On(e,t){var n,r,i,s,o=e.dataTypes.slice(),u=o[0],a={},f=0;e.dataFilter&&(t=e.dataFilter(t,e.dataType));if(o[1])for(n in e.converters)a[n.toLowerCase()]=e.converters[n];for(;i=o[++f];)if(i!=="*"){if(u!=="*"&&u!==i){n=a[u+" "+i]||a["* "+i];if(!n)for(r in a){s=r.split(" ");if(s[1]===i){n=a[u+" "+s[0]]||a["* "+s[0]];if(n){n===!0?n=a[r]:a[r]!==!0&&(i=s[0],o.splice(f--,0,i));break}}}if(n!==!0)if(n&&e["throws"])t=n(t);else try{t=n(t)}catch(l){return{state:"parsererror",error:n?l:"No conversion from "+u+" to "+i}}}u=i}return{state:"success",data:t}}function Fn(){try{return new e.XMLHttpRequest}catch(t){}}function In(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}function $n(){return setTimeout(function(){qn=t},0),qn=v.now()}function Jn(e,t){v.each(t,function(t,n){var r=(Vn[t]||[]).concat(Vn["*"]),i=0,s=r.length;for(;i<s;i++)if(r[i].call(e,t,n))return})}function Kn(e,t,n){var r,i=0,s=0,o=Xn.length,u=v.Deferred().always(function(){delete a.elem}),a=function(){var t=qn||$n(),n=Math.max(0,f.startTime+f.duration-t),r=n/f.duration||0,i=1-r,s=0,o=f.tweens.length;for(;s<o;s++)f.tweens[s].run(i);return u.notifyWith(e,[f,i,n]),i<1&&o?n:(u.resolveWith(e,[f]),!1)},f=u.promise({elem:e,props:v.extend({},t),opts:v.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:qn||$n(),duration:n.duration,tweens:[],createTween:function(t,n,r){var i=v.Tween(e,f.opts,t,n,f.opts.specialEasing[t]||f.opts.easing);return f.tweens.push(i),i},stop:function(t){var n=0,r=t?f.tweens.length:0;for(;n<r;n++)f.tweens[n].run(1);return t?u.resolveWith(e,[f,t]):u.rejectWith(e,[f,t]),this}}),l=f.props;Qn(l,f.opts.specialEasing);for(;i<o;i++){r=Xn[i].call(f,e,l,f.opts);if(r)return r}return Jn(f,l),v.isFunction(f.opts.start)&&f.opts.start.call(e,f),v.fx.timer(v.extend(a,{anim:f,queue:f.opts.queue,elem:e})),f.progress(f.opts.progress).done(f.opts.done,f.opts.complete).fail(f.opts.fail).always(f.opts.always)}function Qn(e,t){var n,r,i,s,o;for(n in e){r=v.camelCase(n),i=t[r],s=e[n],v.isArray(s)&&(i=s[1],s=e[n]=s[0]),n!==r&&(e[r]=s,delete e[n]),o=v.cssHooks[r];if(o&&"expand"in o){s=o.expand(s),delete e[r];for(n in s)n in e||(e[n]=s[n],t[n]=i)}else t[r]=i}}function Gn(e,t,n){var r,i,s,o,u,a,f,l,c,h=this,p=e.style,d={},m=[],g=e.nodeType&&Gt(e);n.queue||(l=v._queueHooks(e,"fx"),l.unqueued==null&&(l.unqueued=0,c=l.empty.fire,l.empty.fire=function(){l.unqueued||c()}),l.unqueued++,h.always(function(){h.always(function(){l.unqueued--,v.queue(e,"fx").length||l.empty.fire()})})),e.nodeType===1&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],v.css(e,"display")==="inline"&&v.css(e,"float")==="none"&&(!v.support.inlineBlockNeedsLayout||nn(e.nodeName)==="inline"?p.display="inline-block":p.zoom=1)),n.overflow&&(p.overflow="hidden",v.support.shrinkWrapBlocks||h.done(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t){s=t[r];if(Un.exec(s)){delete t[r],a=a||s==="toggle";if(s===(g?"hide":"show"))continue;m.push(r)}}o=m.length;if(o){u=v._data(e,"fxshow")||v._data(e,"fxshow",{}),"hidden"in u&&(g=u.hidden),a&&(u.hidden=!g),g?v(e).show():h.done(function(){v(e).hide()}),h.done(function(){var t;v.removeData(e,"fxshow",!0);for(t in d)v.style(e,t,d[t])});for(r=0;r<o;r++)i=m[r],f=h.createTween(i,g?u[i]:0),d[i]=u[i]||v.style(e,i),i in u||(u[i]=f.start,g&&(f.end=f.start,f.start=i==="width"||i==="height"?1:0))}}function Yn(e,t,n,r,i){return new Yn.prototype.init(e,t,n,r,i)}function Zn(e,t){var n,r={height:e},i=0;t=t?1:0;for(;i<4;i+=2-t)n=$t[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}function tr(e){return v.isWindow(e)?e:e.nodeType===9?e.defaultView||e.parentWindow:!1}var n,r,i=e.document,s=e.location,o=e.navigator,u=e.jQuery,a=e.$,f=Array.prototype.push,l=Array.prototype.slice,c=Array.prototype.indexOf,h=Object.prototype.toString,p=Object.prototype.hasOwnProperty,d=String.prototype.trim,v=function(e,t){return new v.fn.init(e,t,n)},m=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,g=/\S/,y=/\s+/,b=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,w=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a<f;a++)if((e=arguments[a])!=null)for(n in e){r=u[n],i=e[n];if(u===i)continue;l&&i&&(v.isPlainObject(i)||(s=v.isArray(i)))?(s?(s=!1,o=r&&v.isArray(r)?r:[]):o=r&&v.isPlainObject(r)?r:{},u[n]=v.extend(l,o,i)):i!==t&&(u[n]=i)}return u},v.extend({noConflict:function(t){return e.$===v&&(e.$=a),t&&e.jQuery===v&&(e.jQuery=u),v},isReady:!1,readyWait:1,holdReady:function(e){e?v.readyWait++:v.ready(!0)},ready:function(e){if(e===!0?--v.readyWait:v.isReady)return;if(!i.body)return setTimeout(v.ready,1);v.isReady=!0;if(e!==!0&&--v.readyWait>0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s<o;)if(n.apply(e[s++],r)===!1)break}else if(u){for(i in e)if(n.call(e[i],i,e[i])===!1)break}else for(;s<o;)if(n.call(e[s],s,e[s++])===!1)break;return e},trim:d&&!d.call("\ufeff\u00a0")?function(e){return e==null?"":d.call(e)}:function(e){return e==null?"":(e+"").replace(b,"")},makeArray:function(e,t){var n,r=t||[];return e!=null&&(n=v.type(e),e.length==null||n==="string"||n==="function"||n==="regexp"||v.isWindow(e)?f.call(r,e):v.merge(r,e)),r},inArray:function(e,t,n){var r;if(t){if(c)return c.call(t,e,n);r=t.length,n=n?n<0?Math.max(0,r+n):n:0;for(;n<r;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,s=0;if(typeof r=="number")for(;s<r;s++)e[i++]=n[s];else while(n[s]!==t)e[i++]=n[s++];return e.length=i,e},grep:function(e,t,n){var r,i=[],s=0,o=e.length;n=!!n;for(;s<o;s++)r=!!t(e[s],s),n!==r&&i.push(e[s]);return i},map:function(e,n,r){var i,s,o=[],u=0,a=e.length,f=e instanceof v||a!==t&&typeof a=="number"&&(a>0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u<a;u++)i=n(e[u],u,r),i!=null&&(o[o.length]=i);else for(s in e)i=n(e[s],s,r),i!=null&&(o[o.length]=i);return o.concat.apply([],o)},guid:1,proxy:function(e,n){var r,i,s;return typeof n=="string"&&(r=e[n],n=e,e=r),v.isFunction(e)?(i=l.call(arguments,2),s=function(){return e.apply(n,i.concat(l.call(arguments)))},s.guid=e.guid=e.guid||v.guid++,s):t},access:function(e,n,r,i,s,o,u){var a,f=r==null,l=0,c=e.length;if(r&&typeof r=="object"){for(l in r)v.access(e,n,l,r[l],1,o,i);s=1}else if(i!==t){a=u===t&&v.isFunction(i),f&&(a?(a=n,n=function(e,t,n){return a.call(v(e),n)}):(n.call(e,i),n=null));if(n)for(;l<c;l++)n(e[l],r,a?i.call(e[l],l,n(e[l],r)):i,u);s=1}return s?e:f?n.call(e):c?n(e[0],r):o},now:function(){return(new Date).getTime()}}),v.ready.promise=function(t){if(!r){r=v.Deferred();if(i.readyState==="complete")setTimeout(v.ready,1);else if(i.addEventListener)i.addEventListener("DOMContentLoaded",A,!1),e.addEventListener("load",v.ready,!1);else{i.attachEvent("onreadystatechange",A),e.attachEvent("onload",v.ready);var n=!1;try{n=e.frameElement==null&&i.documentElement}catch(s){}n&&n.doScroll&&function o(){if(!v.isReady){try{n.doScroll("left")}catch(e){return setTimeout(o,50)}v.ready()}}()}}return r.promise(t)},v.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(e,t){O["[object "+t+"]"]=t.toLowerCase()}),n=v(i);var M={};v.Callbacks=function(e){e=typeof e=="string"?M[e]||_(e):v.extend({},e);var n,r,i,s,o,u,a=[],f=!e.once&&[],l=function(t){n=e.memory&&t,r=!0,u=s||0,s=0,o=a.length,i=!0;for(;a&&u<o;u++)if(a[u].apply(t[0],t[1])===!1&&e.stopOnFalse){n=!1;break}i=!1,a&&(f?f.length&&l(f.shift()):n?a=[]:c.disable())},c={add:function(){if(a){var t=a.length;(function r(t){v.each(t,function(t,n){var i=v.type(n);i==="function"?(!e.unique||!c.has(n))&&a.push(n):n&&n.length&&i!=="string"&&r(n)})})(arguments),i?o=a.length:n&&(s=t,l(n))}return this},remove:function(){return a&&v.each(arguments,function(e,t){var n;while((n=v.inArray(t,a,n))>-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t<r;t++)n[t]&&v.isFunction(n[t].promise)?n[t].promise().done(o(t,f,n)).fail(s.reject).progress(o(t,a,u)):--i}return i||s.resolveWith(f,n),s.promise()}}),v.support=function(){var t,n,r,s,o,u,a,f,l,c,h,p=i.createElement("div");p.setAttribute("className","t"),p.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="<table><tr><td></td><td>t</td></tr></table>",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="<div></div>",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i<s;i++)delete r[t[i]];if(!(n?B:v.isEmptyObject)(r))return}}if(!n){delete u[a].data;if(!B(u[a]))return}o?v.cleanData([e],!0):v.support.deleteExpando||u!=u.window?delete u[a]:u[a]=null},_data:function(e,t,n){return v.data(e,t,n,!0)},acceptData:function(e){var t=e.nodeName&&v.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),v.fn.extend({data:function(e,n){var r,i,s,o,u,a=this[0],f=0,l=null;if(e===t){if(this.length){l=v.data(a);if(a.nodeType===1&&!v._data(a,"parsedAttrs")){s=a.attributes;for(u=s.length;f<u;f++)o=s[f].name,o.indexOf("data-")||(o=v.camelCase(o.substring(5)),H(a,o,l[o]));v._data(a,"parsedAttrs",!0)}}return l}return typeof e=="object"?this.each(function(){v.data(this,e)}):(r=e.split(".",2),r[1]=r[1]?"."+r[1]:"",i=r[1]+"!",v.access(this,function(n){if(n===t)return l=this.triggerHandler("getData"+i,[r[0]]),l===t&&a&&(l=v.data(a,e),l=H(a,e,l)),l===t&&r[1]?this.data(r[0]):l;r[1]=n,this.each(function(){var t=v(this);t.triggerHandler("setData"+i,r),v.data(this,e,n),t.triggerHandler("changeData"+i,r)})},null,n,arguments.length>1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length<r?v.queue(this[0],e):n===t?this:this.each(function(){var t=v.queue(this,e,n);v._queueHooks(this,e),e==="fx"&&t[0]!=="inprogress"&&v.dequeue(this,e)})},dequeue:function(e){return this.each(function(){v.dequeue(this,e)})},delay:function(e,t){return e=v.fx?v.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,s=v.Deferred(),o=this,u=this.length,a=function(){--i||s.resolveWith(o,[o])};typeof e!="string"&&(n=e,e=t),e=e||"fx";while(u--)r=v._data(o[u],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(a));return a(),s.promise(n)}});var j,F,I,q=/[\t\r\n]/g,R=/\r/g,U=/^(?:button|input)$/i,z=/^(?:button|input|object|select|textarea)$/i,W=/^a(?:rea|)$/i,X=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,V=v.support.getSetAttribute;v.fn.extend({attr:function(e,t){return v.access(this,v.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n<r;n++){i=this[n];if(i.nodeType===1)if(!i.className&&t.length===1)i.className=e;else{s=" "+i.className+" ";for(o=0,u=t.length;o<u;o++)s.indexOf(" "+t[o]+" ")<0&&(s+=t[o]+" ");i.className=v.trim(s)}}}return this},removeClass:function(e){var n,r,i,s,o,u,a;if(v.isFunction(e))return this.each(function(t){v(this).removeClass(e.call(this,t,this.className))});if(e&&typeof e=="string"||e===t){n=(e||"").split(y);for(u=0,a=this.length;u<a;u++){i=this[u];if(i.nodeType===1&&i.className){r=(" "+i.className+" ").replace(q," ");for(s=0,o=n.length;s<o;s++)while(r.indexOf(" "+n[s]+" ")>=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n<r;n++)if(this[n].nodeType===1&&(" "+this[n].className+" ").replace(q," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a<u;a++){n=r[a];if((n.selected||a===i)&&(v.support.optDisabled?!n.disabled:n.getAttribute("disabled")===null)&&(!n.parentNode.disabled||!v.nodeName(n.parentNode,"optgroup"))){t=v(n).val();if(s)return t;o.push(t)}}return o},set:function(e,t){var n=v.makeArray(t);return v(e).find("option").each(function(){this.selected=v.inArray(v(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o<r.length;o++)i=r[o],i&&(n=v.propFix[i]||i,s=X.test(i),s||v.attr(e,i,""),e.removeAttribute(V?i:n),s&&n in e&&(e[n]=!1))}},attrHooks:{type:{set:function(e,t){if(U.test(e.nodeName)&&e.parentNode)v.error("type property can't be changed");else if(!v.support.radioValue&&t==="radio"&&v.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}},value:{get:function(e,t){return j&&v.nodeName(e,"button")?j.get(e,t):t in e?e.value:null},set:function(e,t,n){if(j&&v.nodeName(e,"button"))return j.set(e,t,n);e.value=t}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,s,o,u=e.nodeType;if(!e||u===3||u===8||u===2)return;return o=u!==1||!v.isXMLDoc(e),o&&(n=v.propFix[n]||n,s=v.propHooks[n]),r!==t?s&&"set"in s&&(i=s.set(e,r,n))!==t?i:e[n]=r:s&&"get"in s&&(i=s.get(e,n))!==null?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):z.test(e.nodeName)||W.test(e.nodeName)&&e.href?0:t}}}}),F={get:function(e,n){var r,i=v.prop(e,n);return i===!0||typeof i!="boolean"&&(r=e.getAttributeNode(n))&&r.nodeValue!==!1?n.toLowerCase():t},set:function(e,t,n){var r;return t===!1?v.removeAttr(e,n):(r=v.propFix[n]||n,r in e&&(e[r]=!0),e.setAttribute(n,n.toLowerCase())),n}},V||(I={name:!0,id:!0,coords:!0},j=v.valHooks.button={get:function(e,n){var r;return r=e.getAttributeNode(n),r&&(I[n]?r.value!=="":r.specified)?r.value:t},set:function(e,t,n){var r=e.getAttributeNode(n);return r||(r=i.createAttribute(n),e.setAttributeNode(r)),r.value=t+""}},v.each(["width","height"],function(e,t){v.attrHooks[t]=v.extend(v.attrHooks[t],{set:function(e,n){if(n==="")return e.setAttribute(t,"auto"),n}})}),v.attrHooks.contenteditable={get:j.get,set:function(e,t,n){t===""&&(t="false"),j.set(e,t,n)}}),v.support.hrefNormalized||v.each(["href","src","width","height"],function(e,n){v.attrHooks[n]=v.extend(v.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return r===null?t:r}})}),v.support.style||(v.attrHooks.style={get:function(e){return e.style.cssText.toLowerCase()||t},set:function(e,t){return e.style.cssText=t+""}}),v.support.optSelected||(v.propHooks.selected=v.extend(v.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),v.support.enctype||(v.propFix.enctype="encoding"),v.support.checkOn||v.each(["radio","checkbox"],function(){v.valHooks[this]={get:function(e){return e.getAttribute("value")===null?"on":e.value}}}),v.each(["radio","checkbox"],function(){v.valHooks[this]=v.extend(v.valHooks[this],{set:function(e,t){if(v.isArray(t))return e.checked=v.inArray(v(e).val(),t)>=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f<n.length;f++){l=J.exec(n[f])||[],c=l[1],h=(l[2]||"").split(".").sort(),g=v.event.special[c]||{},c=(s?g.delegateType:g.bindType)||c,g=v.event.special[c]||{},p=v.extend({type:c,origType:l[1],data:i,handler:r,guid:r.guid,selector:s,needsContext:s&&v.expr.match.needsContext.test(s),namespace:h.join(".")},d),m=a[c];if(!m){m=a[c]=[],m.delegateCount=0;if(!g.setup||g.setup.call(e,i,h,u)===!1)e.addEventListener?e.addEventListener(c,u,!1):e.attachEvent&&e.attachEvent("on"+c,u)}g.add&&(g.add.call(e,p),p.handler.guid||(p.handler.guid=r.guid)),s?m.splice(m.delegateCount++,0,p):m.push(p),v.event.global[c]=!0}e=null},global:{},remove:function(e,t,n,r,i){var s,o,u,a,f,l,c,h,p,d,m,g=v.hasData(e)&&v._data(e);if(!g||!(h=g.events))return;t=v.trim(Z(t||"")).split(" ");for(s=0;s<t.length;s++){o=J.exec(t[s])||[],u=a=o[1],f=o[2];if(!u){for(u in h)v.event.remove(e,u+t[s],n,r,!0);continue}p=v.event.special[u]||{},u=(r?p.delegateType:p.bindType)||u,d=h[u]||[],l=d.length,f=f?new RegExp("(^|\\.)"+f.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(c=0;c<d.length;c++)m=d[c],(i||a===m.origType)&&(!n||n.guid===m.guid)&&(!f||f.test(m.namespace))&&(!r||r===m.selector||r==="**"&&m.selector)&&(d.splice(c--,1),m.selector&&d.delegateCount--,p.remove&&p.remove.call(e,m));d.length===0&&l!==d.length&&((!p.teardown||p.teardown.call(e,f,g.handle)===!1)&&v.removeEvent(e,u,g.handle),delete h[u])}v.isEmptyObject(h)&&(delete g.handle,v.removeData(e,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(n,r,s,o){if(!s||s.nodeType!==3&&s.nodeType!==8){var u,a,f,l,c,h,p,d,m,g,y=n.type||n,b=[];if(Y.test(y+v.event.triggered))return;y.indexOf("!")>=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f<m.length&&!n.isPropagationStopped();f++)l=m[f][0],n.type=m[f][1],d=(v._data(l,"events")||{})[n.type]&&v._data(l,"handle"),d&&d.apply(l,r),d=h&&l[h],d&&v.acceptData(l)&&d.apply&&d.apply(l,r)===!1&&n.preventDefault();return n.type=y,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(s.ownerDocument,r)===!1)&&(y!=="click"||!v.nodeName(s,"a"))&&v.acceptData(s)&&h&&s[y]&&(y!=="focus"&&y!=="blur"||n.target.offsetWidth!==0)&&!v.isWindow(s)&&(c=s[h],c&&(s[h]=null),v.event.triggered=y,s[y](),v.event.triggered=t,c&&(s[h]=c)),n.result}return},dispatch:function(n){n=v.event.fix(n||e.event);var r,i,s,o,u,a,f,c,h,p,d=(v._data(this,"events")||{})[n.type]||[],m=d.delegateCount,g=l.call(arguments),y=!n.exclusive&&!n.namespace,b=v.event.special[n.type]||{},w=[];g[0]=n,n.delegateTarget=this;if(b.preDispatch&&b.preDispatch.call(this,n)===!1)return;if(m&&(!n.button||n.type!=="click"))for(s=n.target;s!=this;s=s.parentNode||this)if(s.disabled!==!0||n.type!=="click"){u={},f=[];for(r=0;r<m;r++)c=d[r],h=c.selector,u[h]===t&&(u[h]=c.needsContext?v(h,this).index(s)>=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r<w.length&&!n.isPropagationStopped();r++){a=w[r],n.currentTarget=a.elem;for(i=0;i<a.matches.length&&!n.isImmediatePropagationStopped();i++){c=a.matches[i];if(y||!n.namespace&&!c.namespace||n.namespace_re&&n.namespace_re.test(c.namespace))n.data=c.data,n.handleObj=c,o=((v.event.special[c.origType]||{}).handle||c.handler).apply(a.elem,g),o!==t&&(n.result=o,o===!1&&(n.preventDefault(),n.stopPropagation()))}}return b.postDispatch&&b.postDispatch.call(this,n),n.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return e.which==null&&(e.which=t.charCode!=null?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,s,o,u=n.button,a=n.fromElement;return e.pageX==null&&n.clientX!=null&&(r=e.target.ownerDocument||i,s=r.documentElement,o=r.body,e.pageX=n.clientX+(s&&s.scrollLeft||o&&o.scrollLeft||0)-(s&&s.clientLeft||o&&o.clientLeft||0),e.pageY=n.clientY+(s&&s.scrollTop||o&&o.scrollTop||0)-(s&&s.clientTop||o&&o.clientTop||0)),!e.relatedTarget&&a&&(e.relatedTarget=a===e.target?n.toElement:a),!e.which&&u!==t&&(e.which=u&1?1:u&2?3:u&4?2:0),e}},fix:function(e){if(e[v.expando])return e;var t,n,r=e,s=v.event.fixHooks[e.type]||{},o=s.props?this.props.concat(s.props):this.props;e=v.Event(r);for(t=o.length;t;)n=o[--t],e[n]=r[n];return e.target||(e.target=r.srcElement||i),e.target.nodeType===3&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,r):e},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(e,t,n){v.isWindow(this)&&(this.onbeforeunload=n)},teardown:function(e,t){this.onbeforeunload===t&&(this.onbeforeunload=null)}}},simulate:function(e,t,n,r){var i=v.extend(new v.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?v.event.trigger(i,null,t):v.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},v.event.handle=v.event.dispatch,v.removeEvent=i.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]=="undefined"&&(e[r]=null),e.detachEvent(r,n))},v.Event=function(e,t){if(!(this instanceof v.Event))return new v.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?tt:et):this.type=e,t&&v.extend(this,t),this.timeStamp=e&&e.timeStamp||v.now(),this[v.expando]=!0},v.Event.prototype={preventDefault:function(){this.isDefaultPrevented=tt;var e=this.originalEvent;if(!e)return;e.preventDefault?e.preventDefault():e.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=tt;var e=this.originalEvent;if(!e)return;e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=tt,this.stopPropagation()},isDefaultPrevented:et,isPropagationStopped:et,isImmediatePropagationStopped:et},v.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){v.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,s=e.handleObj,o=s.selector;if(!i||i!==r&&!v.contains(r,i))e.type=s.origType,n=s.handler.apply(this,arguments),e.type=t;return n}}}),v.support.submitBubbles||(v.event.special.submit={setup:function(){if(v.nodeName(this,"form"))return!1;v.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=v.nodeName(n,"input")||v.nodeName(n,"button")?n.form:t;r&&!v._data(r,"_submit_attached")&&(v.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),v._data(r,"_submit_attached",!0))})},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&v.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){if(v.nodeName(this,"form"))return!1;v.event.remove(this,"._submit")}}),v.support.changeBubbles||(v.event.special.change={setup:function(){if($.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")v.event.add(this,"propertychange._change",function(e){e.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),v.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),v.event.simulate("change",this,e,!0)});return!1}v.event.add(this,"beforeactivate._change",function(e){var t=e.target;$.test(t.nodeName)&&!v._data(t,"_change_attached")&&(v.event.add(t,"change._change",function(e){this.parentNode&&!e.isSimulated&&!e.isTrigger&&v.event.simulate("change",this.parentNode,e,!0)}),v._data(t,"_change_attached",!0))})},handle:function(e){var t=e.target;if(this!==t||e.isSimulated||e.isTrigger||t.type!=="radio"&&t.type!=="checkbox")return e.handleObj.handler.apply(this,arguments)},teardown:function(){return v.event.remove(this,"._change"),!$.test(this.nodeName)}}),v.support.focusinBubbles||v.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){v.event.simulate(t,e.target,v.event.fix(e),!0)};v.event.special[t]={setup:function(){n++===0&&i.addEventListener(e,r,!0)},teardown:function(){--n===0&&i.removeEventListener(e,r,!0)}}}),v.fn.extend({on:function(e,n,r,i,s){var o,u;if(typeof e=="object"){typeof n!="string"&&(r=r||n,n=t);for(u in e)this.on(u,n,r,e[u],s);return this}r==null&&i==null?(i=n,r=n=t):i==null&&(typeof n=="string"?(i=r,r=t):(i=r,r=n,n=t));if(i===!1)i=et;else if(!i)return this;return s===1&&(o=i,i=function(e){return v().off(e),o.apply(this,arguments)},i.guid=o.guid||(o.guid=v.guid++)),this.each(function(){v.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,s;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,v(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if(typeof e=="object"){for(s in e)this.off(s,n,e[s]);return this}if(n===!1||typeof n=="function")r=n,n=t;return r===!1&&(r=et),this.each(function(){v.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},live:function(e,t,n){return v(this.context).on(e,this.selector,t,n),this},die:function(e,t){return v(this.context).off(e,this.selector||"**",t),this},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return arguments.length===1?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){v.event.trigger(e,t,this)})},triggerHandler:function(e,t){if(this[0])return v.event.trigger(e,t,this[0],!0)},toggle:function(e){var t=arguments,n=e.guid||v.guid++,r=0,i=function(n){var i=(v._data(this,"lastToggle"+e.guid)||0)%r;return v._data(this,"lastToggle"+e.guid,i+1),n.preventDefault(),t[i].apply(this,arguments)||!1};i.guid=n;while(r<t.length)t[r++].guid=n;return this.click(i)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),v.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){v.fn[t]=function(e,n){return n==null&&(n=e,e=null),arguments.length>0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u<a;u++)if(s=e[u])if(!n||n(s,r,i))o.push(s),f&&t.push(u);return o}function ct(e,t,n,r,i,s){return r&&!r[d]&&(r=ct(r)),i&&!i[d]&&(i=ct(i,s)),N(function(s,o,u,a){var f,l,c,h=[],p=[],d=o.length,v=s||dt(t||"*",u.nodeType?[u]:u,[]),m=e&&(s||!t)?lt(v,h,e,u,a):v,g=n?i||(s?e:d||r)?[]:o:m;n&&n(m,g,u,a);if(r){f=lt(g,p),r(f,[],u,a),l=f.length;while(l--)if(c=f[l])g[p[l]]=!(m[p[l]]=c)}if(s){if(i||e){if(i){f=[],l=g.length;while(l--)(c=g[l])&&f.push(m[l]=c);i(null,g=[],f,a)}l=g.length;while(l--)(c=g[l])&&(f=i?T.call(s,c):h[l])>-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a<s;a++)if(n=i.relative[e[a].type])h=[at(ft(h),n)];else{n=i.filter[e[a].type].apply(null,e[a].matches);if(n[d]){r=++a;for(;r<s;r++)if(i.relative[e[r].type])break;return ct(a>1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a<r&&ht(e.slice(a,r)),r<s&&ht(e=e.slice(r)),r<s&&e.join(""))}h.push(n)}return ft(h)}function pt(e,t){var r=t.length>0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r<i;r++)nt(e,t[r],n);return n}function vt(e,t,n,r,s){var o,u,f,l,c,h=ut(e),p=h.length;if(!r&&h.length===1){u=h[0]=h[0].slice(0);if(u.length>2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;t<n;t++)if(this[t]===e)return t;return-1},N=function(e,t){return e[d]=t==null||t,e},C=function(){var e={},t=[];return N(function(n,r){return t.push(n)>i.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="<a href='#'></a>",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="<select></select>";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="<a name='"+d+"'></a><div name='"+d+"'></div>",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:st(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:st(function(e,t,n){for(var r=n<0?n+t:n;--r>=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}},f=y.compareDocumentPosition?function(e,t){return e===t?(l=!0,0):(!e.compareDocumentPosition||!t.compareDocumentPosition?e.compareDocumentPosition:e.compareDocumentPosition(t)&4)?-1:1}:function(e,t){if(e===t)return l=!0,0;if(e.sourceIndex&&t.sourceIndex)return e.sourceIndex-t.sourceIndex;var n,r,i=[],s=[],o=e.parentNode,u=t.parentNode,a=o;if(o===u)return ot(e,t);if(!o)return-1;if(!u)return 1;while(a)i.unshift(a),a=a.parentNode;a=u;while(a)s.unshift(a),a=a.parentNode;n=i.length,r=s.length;for(var f=0;f<n&&f<r;f++)if(i[f]!==s[f])return ot(i[f],s[f]);return f===n?ot(e,s[f],-1):ot(i[f],t,1)},[0,0].sort(f),h=!l,nt.uniqueSort=function(e){var t,n=[],r=1,i=0;l=h,e.sort(f);if(l){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e},nt.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},a=nt.compile=function(e,t){var n,r=[],i=[],s=A[d][e+" "];if(!s){t||(t=ut(e)),n=t.length;while(n--)s=ht(t[n]),s[d]?r.push(s):i.push(s);s=A(e,pt(i,r))}return s},g.querySelectorAll&&function(){var e,t=vt,n=/'|\\/g,r=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,i=[":focus"],s=[":active"],u=y.matchesSelector||y.mozMatchesSelector||y.webkitMatchesSelector||y.oMatchesSelector||y.msMatchesSelector;K(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="<p test=''></p>",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="<input type='hidden'/>",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t<n;t++)if(v.contains(u[t],this))return!0});o=this.pushStack("","find",e);for(t=0,n=this.length;t<n;t++){r=o.length,v.find(e,this[t],o);if(t>0)for(i=r;i<o.length;i++)for(s=0;s<r;s++)if(o[s]===o[i]){o.splice(i--,1);break}}return o},has:function(e){var t,n=v(e,this),r=n.length;return this.filter(function(){for(t=0;t<r;t++)if(v.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1),"not",e)},filter:function(e){return this.pushStack(ft(this,e,!0),"filter",e)},is:function(e){return!!e&&(typeof e=="string"?st.test(e)?v(e,this.context).index(this[0])>=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r<i;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&n.nodeType!==11){if(o?o.index(n)>-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/<tbody/i,gt=/<|&#?\w+;/,yt=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,wt=new RegExp("<(?:"+ct+")[\\s/>]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,Nt={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X<div>","</div>"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1></$2>");try{for(;r<i;r++)n=this[r]||{},n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),n.innerHTML=e);n=0}catch(s){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){return ut(this[0])?this.length?this.pushStack(v(v.isFunction(e)?e():e),"replaceWith",e):this:v.isFunction(e)?this.each(function(t){var n=v(this),r=n.html();n.replaceWith(e.call(this,t,r))}):(typeof e!="string"&&(e=v(e).detach()),this.each(function(){var t=this.nextSibling,n=this.parentNode;v(this).remove(),t?v(t).before(e):v(n).append(e)}))},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=[].concat.apply([],e);var i,s,o,u,a=0,f=e[0],l=[],c=this.length;if(!v.support.checkClone&&c>1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a<c;a++)r.call(n&&v.nodeName(this[a],"table")?Lt(this[a],"tbody"):this[a],a===u?o:v.clone(o,!0,!0))}o=s=null,l.length&&v.each(l,function(e,t){t.src?v.ajax?v.ajax({url:t.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):v.error("no ajax"):v.globalEval((t.text||t.textContent||t.innerHTML||"").replace(Tt,"")),t.parentNode&&t.parentNode.removeChild(t)})}return this}}),v.buildFragment=function(e,n,r){var s,o,u,a=e[0];return n=n||i,n=!n.nodeType&&n[0]||n,n=n.ownerDocument||n,e.length===1&&typeof a=="string"&&a.length<512&&n===i&&a.charAt(0)==="<"&&!bt.test(a)&&(v.support.checkClone||!St.test(a))&&(v.support.html5Clone||!wt.test(a))&&(o=!0,s=v.fragments[a],u=s!==t),s||(s=n.createDocumentFragment(),v.clean(e,n,s,r),o&&(v.fragments[a]=u&&s)),{fragment:s,cacheable:o}},v.fragments={},v.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){v.fn[e]=function(n){var r,i=0,s=[],o=v(n),u=o.length,a=this.length===1&&this[0].parentNode;if((a==null||a&&a.nodeType===11&&a.childNodes.length===1)&&u===1)return o[t](this[0]),this;for(;i<u;i++)r=(i>0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1></$2>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]==="<table>"&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("<div>").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r<i;r++)n=e[r],Vn[n]=Vn[n]||[],Vn[n].unshift(t)},prefilter:function(e,t){t?Xn.unshift(e):Xn.push(e)}}),v.Tween=Yn,Yn.prototype={constructor:Yn,init:function(e,t,n,r,i,s){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=s||(v.cssNumber[n]?"":"px")},cur:function(){var e=Yn.propHooks[this.prop];return e&&e.get?e.get(this):Yn.propHooks._default.get(this)},run:function(e){var t,n=Yn.propHooks[this.prop];return this.options.duration?this.pos=t=v.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Yn.propHooks._default.set(this),this}},Yn.prototype.init.prototype=Yn.prototype,Yn.propHooks={_default:{get:function(e){var t;return e.elem[e.prop]==null||!!e.elem.style&&e.elem.style[e.prop]!=null?(t=v.css(e.elem,e.prop,!1,""),!t||t==="auto"?0:t):e.elem[e.prop]},set:function(e){v.fx.step[e.prop]?v.fx.step[e.prop](e):e.elem.style&&(e.elem.style[v.cssProps[e.prop]]!=null||v.cssHooks[e.prop])?v.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},Yn.propHooks.scrollTop=Yn.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},v.each(["toggle","show","hide"],function(e,t){var n=v.fn[t];v.fn[t]=function(r,i,s){return r==null||typeof r=="boolean"||!e&&v.isFunction(r)&&v.isFunction(i)?n.apply(this,arguments):this.animate(Zn(t,!0),r,i,s)}}),v.fn.extend({fadeTo:function(e,t,n,r){return this.filter(Gt).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=v.isEmptyObject(e),s=v.speed(t,n,r),o=function(){var t=Kn(this,v.extend({},e),s);i&&t.stop(!0)};return i||s.queue===!1?this.each(o):this.queue(s.queue,o)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return typeof e!="string"&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=e!=null&&e+"queueHooks",s=v.timers,o=v._data(this);if(n)o[n]&&o[n].stop&&i(o[n]);else for(n in o)o[n]&&o[n].stop&&Wn.test(n)&&i(o[n]);for(n=s.length;n--;)s[n].elem===this&&(e==null||s[n].queue===e)&&(s[n].anim.stop(r),t=!1,s.splice(n,1));(t||!r)&&v.dequeue(this,e)})}}),v.each({slideDown:Zn("show"),slideUp:Zn("hide"),slideToggle:Zn("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){v.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),v.speed=function(e,t,n){var r=e&&typeof e=="object"?v.extend({},e):{complete:n||!n&&t||v.isFunction(e)&&e,duration:e,easing:n&&t||t&&!v.isFunction(t)&&t};r.duration=v.fx.off?0:typeof r.duration=="number"?r.duration:r.duration in v.fx.speeds?v.fx.speeds[r.duration]:v.fx.speeds._default;if(r.queue==null||r.queue===!0)r.queue="fx";return r.old=r.complete,r.complete=function(){v.isFunction(r.old)&&r.old.call(this),r.queue&&v.dequeue(this,r.queue)},r},v.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},v.timers=[],v.fx=Yn.prototype.init,v.fx.tick=function(){var e,n=v.timers,r=0;qn=v.now();for(;r<n.length;r++)e=n[r],!e()&&n[r]===e&&n.splice(r--,1);n.length||v.fx.stop(),qn=t},v.fx.timer=function(e){e()&&v.timers.push(e)&&!Rn&&(Rn=setInterval(v.fx.tick,v.fx.interval))},v.fx.interval=13,v.fx.stop=function(){clearInterval(Rn),Rn=null},v.fx.speeds={slow:600,fast:200,_default:400},v.fx.step={},v.expr&&v.expr.filters&&(v.expr.filters.animated=function(e){return v.grep(v.timers,function(t){return e===t.elem}).length});var er=/^(?:body|html)$/i;v.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){v.offset.setOffset(this,e,t)});var n,r,i,s,o,u,a,f={top:0,left:0},l=this[0],c=l&&l.ownerDocument;if(!c)return;return(r=c.body)===l?v.offset.bodyOffset(l):(n=c.documentElement,v.contains(n,l)?(typeof l.getBoundingClientRect!="undefined"&&(f=l.getBoundingClientRect()),i=tr(c),s=n.clientTop||r.clientTop||0,o=n.clientLeft||r.clientLeft||0,u=i.pageYOffset||n.scrollTop,a=i.pageXOffset||n.scrollLeft,{top:f.top+u-s,left:f.left+a-o}):f)},v.offset={bodyOffset:function(e){var t=e.offsetTop,n=e.offsetLeft;return v.support.doesNotIncludeMarginInBodyOffset&&(t+=parseFloat(v.css(e,"marginTop"))||0,n+=parseFloat(v.css(e,"marginLeft"))||0),{top:t,left:n}},setOffset:function(e,t,n){var r=v.css(e,"position");r==="static"&&(e.style.position="relative");var i=v(e),s=i.offset(),o=v.css(e,"top"),u=v.css(e,"left"),a=(r==="absolute"||r==="fixed")&&v.inArray("auto",[o,u])>-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); \ No newline at end of file diff --git a/syweb/webclient/js/jquery.peity.min.js b/syweb/webclient/js/jquery.peity.min.js deleted file mode 100644 index 054b83c5d8..0000000000 --- a/syweb/webclient/js/jquery.peity.min.js +++ /dev/null @@ -1,13 +0,0 @@ -// Peity jQuery plugin version 3.0.2 -// (c) 2014 Ben Pickles -// -// http://benpickles.github.io/peity -// -// Released under MIT license. -(function(h,w,i,v){var p=function(a,b){var d=w.createElementNS("http://www.w3.org/2000/svg",a);h(d).attr(b);return d},y="createElementNS"in w&&p("svg",{}).createSVGRect,e=h.fn.peity=function(a,b){y&&this.each(function(){var d=h(this),c=d.data("peity");c?(a&&(c.type=a),h.extend(c.opts,b)):(c=new x(d,a,h.extend({},e.defaults[a],b)),d.change(function(){c.draw()}).data("peity",c));c.draw()});return this},x=function(a,b,d){this.$el=a;this.type=b;this.opts=d},r=x.prototype;r.draw=function(){e.graphers[this.type].call(this, -this.opts)};r.fill=function(){var a=this.opts.fill;return h.isFunction(a)?a:function(b,d){return a[d%a.length]}};r.prepare=function(a,b){this.svg||this.$el.hide().after(this.svg=p("svg",{"class":"peity"}));return h(this.svg).empty().data("peity",this).attr({height:b,width:a})};r.values=function(){return h.map(this.$el.text().split(this.opts.delimiter),function(a){return parseFloat(a)})};e.defaults={};e.graphers={};e.register=function(a,b,d){this.defaults[a]=b;this.graphers[a]=d};e.register("pie", -{fill:["#ff9900","#fff4dd","#ffc66e"],radius:8},function(a){if(!a.delimiter){var b=this.$el.text().match(/[^0-9\.]/);a.delimiter=b?b[0]:","}b=this.values();if("/"==a.delimiter)var d=b[0],b=[d,i.max(0,b[1]-d)];for(var c=0,d=b.length,n=0;c<d;c++)n+=b[c];for(var c=2*a.radius,f=this.prepare(a.width||c,a.height||c),c=f.width(),f=f.height(),s=c/2,k=f/2,f=i.min(s,k),a=a.innerRadius,e=i.PI,q=this.fill(),g=this.scale=function(a,b){var c=2*a/n*e-e/2;return[b*i.cos(c)+s,b*i.sin(c)+k]},l=0,c=0;c<d;c++){var t= -b[c],j=t/n;if(0!=j){if(1==j)if(a)var j=s-0.01,o=k-f,m=k-a,j=p("path",{d:["M",s,o,"A",f,f,0,1,1,j,o,"L",j,m,"A",a,a,0,1,0,s,m].join(" ")});else j=p("circle",{cx:s,cy:k,r:f});else o=l+t,m=["M"].concat(g(l,f),"A",f,f,0,0.5<j?1:0,1,g(o,f),"L"),a?m=m.concat(g(o,a),"A",a,a,0,0.5<j?1:0,0,g(l,a)):m.push(s,k),l+=t,j=p("path",{d:m.join(" ")});h(j).attr("fill",q.call(this,t,c,b));this.svg.appendChild(j)}}});e.register("donut",h.extend(!0,{},e.defaults.pie),function(a){a.innerRadius||(a.innerRadius=0.5*a.radius); -e.graphers.pie.call(this,a)});e.register("line",{delimiter:",",fill:"#c6d9fd",height:16,min:0,stroke:"#4d89f9",strokeWidth:1,width:32},function(a){var b=this.values();1==b.length&&b.push(b[0]);for(var d=i.max.apply(i,a.max==v?b:b.concat(a.max)),c=i.min.apply(i,a.min==v?b:b.concat(a.min)),n=this.prepare(a.width,a.height),f=a.strokeWidth,e=n.width(),k=n.height()-f,h=d-c,d=this.x=function(a){return a*(e/(b.length-1))},n=this.y=function(a){var b=k;h&&(b-=(a-c)/h*k);return b+f/2},q=n(i.max(c,0)),g=[0, -q],l=0;l<b.length;l++)g.push(d(l),n(b[l]));g.push(e,q);this.svg.appendChild(p("polygon",{fill:a.fill,points:g.join(" ")}));f&&this.svg.appendChild(p("polyline",{fill:"transparent",points:g.slice(2,g.length-2).join(" "),stroke:a.stroke,"stroke-width":f,"stroke-linecap":"square"}))});e.register("bar",{delimiter:",",fill:["#4D89F9"],height:16,min:0,padding:0.1,width:32},function(a){for(var b=this.values(),d=i.max.apply(i,a.max==v?b:b.concat(a.max)),c=i.min.apply(i,a.min==v?b:b.concat(a.min)),e=this.prepare(a.width, -a.height),f=e.width(),h=e.height(),k=d-c,a=a.padding,e=this.fill(),r=this.x=function(a){return a*f/b.length},q=this.y=function(a){return h-(k?(a-c)/k*h:1)},g=0;g<b.length;g++){var l=r(g+a),t=r(g+1-a)-l,j=b[g],o=q(j),m=o,u;k?0>j?m=q(i.min(d,0)):o=q(i.max(c,0)):u=1;u=o-m;0==u&&(u=1,0<d&&k&&m--);this.svg.appendChild(p("rect",{fill:e.call(this,j,g,b),x:l,y:m,width:t,height:u}))}})})(jQuery,document,Math); diff --git a/syweb/webclient/js/ng-infinite-scroll-matrix.js b/syweb/webclient/js/ng-infinite-scroll-matrix.js deleted file mode 100644 index 045ec8d93e..0000000000 --- a/syweb/webclient/js/ng-infinite-scroll-matrix.js +++ /dev/null @@ -1,63 +0,0 @@ -/* ng-infinite-scroll - v1.0.0 - 2013-02-23 - Matrix: Modified to support scrolling UP to get infinite loading and to listen - to scroll events on the PARENT element, not the window. -*/ -var mod; - -mod = angular.module('infinite-scroll', []); - -mod.directive('infiniteScroll', [ - '$rootScope', '$window', '$timeout', function($rootScope, $window, $timeout) { - return { - link: function(scope, elem, attrs) { - var checkWhenEnabled, handler, scrollDistance, scrollEnabled; - $window = angular.element($window); - scrollDistance = 0; - if (attrs.infiniteScrollDistance != null) { - scope.$watch(attrs.infiniteScrollDistance, function(value) { - return scrollDistance = parseInt(value, 10); - }); - } - scrollEnabled = true; - checkWhenEnabled = false; - if (attrs.infiniteScrollDisabled != null) { - scope.$watch(attrs.infiniteScrollDisabled, function(value) { - scrollEnabled = !value; - if (scrollEnabled && checkWhenEnabled) { - checkWhenEnabled = false; - return handler(); - } - }); - } - handler = function() { - var elementTop, remaining, shouldScroll, windowTop; - windowTop = 0; - elementTop = elem.offset().top; - shouldScroll = elementTop >= 0; // top of list is at the top of the window or further down the page - if (shouldScroll && scrollEnabled) { - if ($rootScope.$$phase) { - return scope.$eval(attrs.infiniteScroll); - } else { - return scope.$apply(attrs.infiniteScroll); - } - } else if (shouldScroll) { - return checkWhenEnabled = true; - } - }; - elem.parent().on('scroll', handler); - scope.$on('$destroy', function() { - return elem.parent().off('scroll', handler); - }); - return $timeout((function() { - if (attrs.infiniteScrollImmediateCheck) { - if (scope.$eval(attrs.infiniteScrollImmediateCheck)) { - return handler(); - } - } else { - return handler(); - } - }), 0); - } - }; - } -]); diff --git a/syweb/webclient/js/recaptcha_ajax.js b/syweb/webclient/js/recaptcha_ajax.js deleted file mode 100644 index d0e71e5b88..0000000000 --- a/syweb/webclient/js/recaptcha_ajax.js +++ /dev/null @@ -1,195 +0,0 @@ -(function(){var h,k=this,l=function(a){return void 0!==a},ba=function(){},n=function(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&& -!a.propertyIsEnumerable("call"))return"function"}else return"null";else if("function"==b&&"undefined"==typeof a.call)return"object";return b},p=function(a){return"array"==n(a)},ca=function(a){var b=n(a);return"array"==b||"object"==b&&"number"==typeof a.length},q=function(a){return"string"==typeof a},r=function(a){return"function"==n(a)},da=function(a){var b=typeof a;return"object"==b&&null!=a||"function"==b},ea=function(a,b,c){return a.call.apply(a.bind,arguments)},fa=function(a,b,c){if(!a)throw Error(); -if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var c=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(c,d);return a.apply(b,c)}}return function(){return a.apply(b,arguments)}},s=function(a,b,c){s=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?ea:fa;return s.apply(null,arguments)},ga=function(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=c.slice();b.push.apply(b,arguments); -return a.apply(this,b)}},ha=Date.now||function(){return+new Date},ia=null,t=function(a,b){var c=a.split("."),d=k;c[0]in d||!d.execScript||d.execScript("var "+c[0]);for(var e;c.length&&(e=c.shift());)!c.length&&l(b)?d[e]=b:d=d[e]?d[e]:d[e]={}},u=function(a,b){function c(){}c.prototype=b.prototype;a.superClass_=b.prototype;a.prototype=new c;a.base=function(a,c,g){return b.prototype[c].apply(a,Array.prototype.slice.call(arguments,2))}}; -Function.prototype.bind=Function.prototype.bind||function(a,b){if(1<arguments.length){var c=Array.prototype.slice.call(arguments,1);c.unshift(this,a);return s.apply(null,c)}return s(this,a)};var v={};t("RecaptchaTemplates",v);v.VertHtml='<table id="recaptcha_table" class="recaptchatable" > <tr> <td colspan="6" class=\'recaptcha_r1_c1\'></td> </tr> <tr> <td class=\'recaptcha_r2_c1\'></td> <td colspan="4" class=\'recaptcha_image_cell\'><center><div id="recaptcha_image"></div></center></td> <td class=\'recaptcha_r2_c2\'></td> </tr> <tr> <td rowspan="6" class=\'recaptcha_r3_c1\'></td> <td colspan="4" class=\'recaptcha_r3_c2\'></td> <td rowspan="6" class=\'recaptcha_r3_c3\'></td> </tr> <tr> <td rowspan="3" class=\'recaptcha_r4_c1\' height="49"> <div class="recaptcha_input_area"> <input name="recaptcha_response_field" id="recaptcha_response_field" type="text" autocorrect="off" autocapitalize="off" placeholder="" /> <span id="recaptcha_privacy" class="recaptcha_only_if_privacy"></span> </div> </td> <td rowspan="4" class=\'recaptcha_r4_c2\'></td> <td><a id=\'recaptcha_reload_btn\'><img id=\'recaptcha_reload\' width="25" height="17" /></a></td> <td rowspan="4" class=\'recaptcha_r4_c4\'></td> </tr> <tr> <td><a id=\'recaptcha_switch_audio_btn\' class="recaptcha_only_if_image"><img id=\'recaptcha_switch_audio\' width="25" height="16" alt="" /></a><a id=\'recaptcha_switch_img_btn\' class="recaptcha_only_if_audio"><img id=\'recaptcha_switch_img\' width="25" height="16" alt=""/></a></td> </tr> <tr> <td><a id=\'recaptcha_whatsthis_btn\'><img id=\'recaptcha_whatsthis\' width="25" height="16" /></a></td> </tr> <tr> <td class=\'recaptcha_r7_c1\'></td> <td class=\'recaptcha_r8_c1\'></td> </tr> </table> ';v.CleanCss=".recaptchatable td img{display:block}.recaptchatable .recaptcha_image_cell center img{height:57px}.recaptchatable .recaptcha_image_cell center{height:57px}.recaptchatable .recaptcha_image_cell{background-color:white;height:57px;padding:7px!important}.recaptchatable,#recaptcha_area tr,#recaptcha_area td,#recaptcha_area th{margin:0!important;border:0!important;border-collapse:collapse!important;vertical-align:middle!important}.recaptchatable *{margin:0;padding:0;border:0;color:black;position:static;top:auto;left:auto;right:auto;bottom:auto}.recaptchatable #recaptcha_image{position:relative;margin:auto;border:1px solid #dfdfdf!important}.recaptchatable #recaptcha_image #recaptcha_challenge_image{display:block}.recaptchatable #recaptcha_image #recaptcha_ad_image{display:block;position:absolute;top:0}.recaptchatable a img{border:0}.recaptchatable a,.recaptchatable a:hover{cursor:pointer;outline:none;border:0!important;padding:0!important;text-decoration:none;color:blue;background:none!important;font-weight:normal}.recaptcha_input_area{position:relative!important;background:none!important}.recaptchatable label.recaptcha_input_area_text{border:1px solid #dfdfdf!important;margin:0!important;padding:0!important;position:static!important;top:auto!important;left:auto!important;right:auto!important;bottom:auto!important}.recaptcha_theme_red label.recaptcha_input_area_text,.recaptcha_theme_white label.recaptcha_input_area_text{color:black!important}.recaptcha_theme_blackglass label.recaptcha_input_area_text{color:white!important}.recaptchatable #recaptcha_response_field{font-size:11pt}.recaptcha_theme_blackglass #recaptcha_response_field,.recaptcha_theme_white #recaptcha_response_field{border:1px solid gray}.recaptcha_theme_red #recaptcha_response_field{border:1px solid #cca940}.recaptcha_audio_cant_hear_link{font-size:7pt;color:black}.recaptchatable{line-height:1em;border:1px solid #dfdfdf!important}.recaptcha_error_text{color:red}.recaptcha_only_if_privacy{float:right;text-align:right;margin-right:7px}#recaptcha-ad-choices{position:absolute;height:15px;top:0;right:0}#recaptcha-ad-choices img{height:15px}.recaptcha-ad-choices-collapsed{width:30px;height:15px;display:block}.recaptcha-ad-choices-expanded{width:75px;height:15px;display:none}#recaptcha-ad-choices:hover .recaptcha-ad-choices-collapsed{display:none}#recaptcha-ad-choices:hover .recaptcha-ad-choices-expanded{display:block}";v.CleanHtml='<table id="recaptcha_table" class="recaptchatable"> <tr height="73"> <td class=\'recaptcha_image_cell\' width="302"><center><div id="recaptcha_image"></div></center></td> <td style="padding: 10px 7px 7px 7px;"> <a id=\'recaptcha_reload_btn\'><img id=\'recaptcha_reload\' width="25" height="18" alt="" /></a> <a id=\'recaptcha_switch_audio_btn\' class="recaptcha_only_if_image"><img id=\'recaptcha_switch_audio\' width="25" height="15" alt="" /></a><a id=\'recaptcha_switch_img_btn\' class="recaptcha_only_if_audio"><img id=\'recaptcha_switch_img\' width="25" height="15" alt=""/></a> <a id=\'recaptcha_whatsthis_btn\'><img id=\'recaptcha_whatsthis\' width="25" height="16" /></a> </td> <td style="padding: 18px 7px 18px 7px;"> <img id=\'recaptcha_logo\' alt="" width="71" height="36" /> </td> </tr> <tr> <td style="padding-left: 7px;"> <div class="recaptcha_input_area" style="padding-top: 2px; padding-bottom: 7px;"> <input style="border: 1px solid #3c3c3c; width: 302px;" name="recaptcha_response_field" id="recaptcha_response_field" type="text" /> </div> </td> <td colspan=2><span id="recaptcha_privacy" class="recaptcha_only_if_privacy"></span></td> </tr> </table> ';v.VertCss=".recaptchatable td img{display:block}.recaptchatable .recaptcha_r1_c1{background:url('IMGROOT/sprite.png') 0 -63px no-repeat;width:318px;height:9px}.recaptchatable .recaptcha_r2_c1{background:url('IMGROOT/sprite.png') -18px 0 no-repeat;width:9px;height:57px}.recaptchatable .recaptcha_r2_c2{background:url('IMGROOT/sprite.png') -27px 0 no-repeat;width:9px;height:57px}.recaptchatable .recaptcha_r3_c1{background:url('IMGROOT/sprite.png') 0 0 no-repeat;width:9px;height:63px}.recaptchatable .recaptcha_r3_c2{background:url('IMGROOT/sprite.png') -18px -57px no-repeat;width:300px;height:6px}.recaptchatable .recaptcha_r3_c3{background:url('IMGROOT/sprite.png') -9px 0 no-repeat;width:9px;height:63px}.recaptchatable .recaptcha_r4_c1{background:url('IMGROOT/sprite.png') -43px 0 no-repeat;width:171px;height:49px}.recaptchatable .recaptcha_r4_c2{background:url('IMGROOT/sprite.png') -36px 0 no-repeat;width:7px;height:57px}.recaptchatable .recaptcha_r4_c4{background:url('IMGROOT/sprite.png') -214px 0 no-repeat;width:97px;height:57px}.recaptchatable .recaptcha_r7_c1{background:url('IMGROOT/sprite.png') -43px -49px no-repeat;width:171px;height:8px}.recaptchatable .recaptcha_r8_c1{background:url('IMGROOT/sprite.png') -43px -49px no-repeat;width:25px;height:8px}.recaptchatable .recaptcha_image_cell center img{height:57px}.recaptchatable .recaptcha_image_cell center{height:57px}.recaptchatable .recaptcha_image_cell{background-color:white;height:57px}#recaptcha_area,#recaptcha_table{width:318px!important}.recaptchatable,#recaptcha_area tr,#recaptcha_area td,#recaptcha_area th{margin:0!important;border:0!important;padding:0!important;border-collapse:collapse!important;vertical-align:middle!important}.recaptchatable *{margin:0;padding:0;border:0;font-family:helvetica,sans-serif;font-size:8pt;color:black;position:static;top:auto;left:auto;right:auto;bottom:auto}.recaptchatable #recaptcha_image{position:relative;margin:auto}.recaptchatable #recaptcha_image #recaptcha_challenge_image{display:block}.recaptchatable #recaptcha_image #recaptcha_ad_image{display:block;position:absolute;top:0}.recaptchatable img{border:0!important;margin:0!important;padding:0!important}.recaptchatable a,.recaptchatable a:hover{cursor:pointer;outline:none;border:0!important;padding:0!important;text-decoration:none;color:blue;background:none!important;font-weight:normal}.recaptcha_input_area{position:relative!important;width:153px!important;height:45px!important;margin-left:7px!important;margin-right:7px!important;background:none!important}.recaptchatable label.recaptcha_input_area_text{margin:0!important;padding:0!important;position:static!important;top:auto!important;left:auto!important;right:auto!important;bottom:auto!important;background:none!important;height:auto!important;width:auto!important}.recaptcha_theme_red label.recaptcha_input_area_text,.recaptcha_theme_white label.recaptcha_input_area_text{color:black!important}.recaptcha_theme_blackglass label.recaptcha_input_area_text{color:white!important}.recaptchatable #recaptcha_response_field{width:153px!important;position:relative!important;bottom:7px!important;padding:0!important;margin:15px 0 0 0!important;font-size:10pt}.recaptcha_theme_blackglass #recaptcha_response_field,.recaptcha_theme_white #recaptcha_response_field{border:1px solid gray}.recaptcha_theme_red #recaptcha_response_field{border:1px solid #cca940}.recaptcha_audio_cant_hear_link{font-size:7pt;color:black}.recaptchatable{line-height:1!important}#recaptcha_instructions_error{color:red!important}.recaptcha_only_if_privacy{float:right;text-align:right}#recaptcha-ad-choices{position:absolute;height:15px;top:0;right:0}#recaptcha-ad-choices img{height:15px}.recaptcha-ad-choices-collapsed{width:30px;height:15px;display:block}.recaptcha-ad-choices-expanded{width:75px;height:15px;display:none}#recaptcha-ad-choices:hover .recaptcha-ad-choices-collapsed{display:none}#recaptcha-ad-choices:hover .recaptcha-ad-choices-expanded{display:block}";var w={visual_challenge:"Get a visual challenge",audio_challenge:"Get an audio challenge",refresh_btn:"Get a new challenge",instructions_visual:"Type the text:",instructions_audio:"Type what you hear:",help_btn:"Help",play_again:"Play sound again",cant_hear_this:"Download sound as MP3",incorrect_try_again:"Incorrect. Try again.",image_alt_text:"reCAPTCHA challenge image",privacy_and_terms:"Privacy & Terms"},ja={visual_challenge:"\u0627\u0644\u062d\u0635\u0648\u0644 \u0639\u0644\u0649 \u062a\u062d\u062f\u064d \u0645\u0631\u0626\u064a", -audio_challenge:"\u0627\u0644\u062d\u0635\u0648\u0644 \u0639\u0644\u0649 \u062a\u062d\u062f\u064d \u0635\u0648\u062a\u064a",refresh_btn:"\u0627\u0644\u062d\u0635\u0648\u0644 \u0639\u0644\u0649 \u062a\u062d\u062f\u064d \u062c\u062f\u064a\u062f",instructions_visual:"\u064a\u0631\u062c\u0649 \u0643\u062a\u0627\u0628\u0629 \u0627\u0644\u0646\u0635:",instructions_audio:"\u0627\u0643\u062a\u0628 \u0645\u0627 \u062a\u0633\u0645\u0639\u0647:",help_btn:"\u0645\u0633\u0627\u0639\u062f\u0629",play_again:"\u062a\u0634\u063a\u064a\u0644 \u0627\u0644\u0635\u0648\u062a \u0645\u0631\u0629 \u0623\u062e\u0631\u0649", -cant_hear_this:"\u062a\u0646\u0632\u064a\u0644 \u0627\u0644\u0635\u0648\u062a \u0628\u062a\u0646\u0633\u064a\u0642 MP3",incorrect_try_again:"\u063a\u064a\u0631 \u0635\u062d\u064a\u062d. \u0623\u0639\u062f \u0627\u0644\u0645\u062d\u0627\u0648\u0644\u0629.",image_alt_text:"\u0635\u0648\u0631\u0629 \u0627\u0644\u062a\u062d\u062f\u064a \u0645\u0646 reCAPTCHA",privacy_and_terms:"\u0627\u0644\u062e\u0635\u0648\u0635\u064a\u0629 \u0648\u0627\u0644\u0628\u0646\u0648\u062f"},ka={visual_challenge:"Obtener una pista visual", -audio_challenge:"Obtener una pista sonora",refresh_btn:"Obtener una pista nueva",instructions_visual:"Introduzca el texto:",instructions_audio:"Escribe lo que oigas:",help_btn:"Ayuda",play_again:"Volver a reproducir el sonido",cant_hear_this:"Descargar el sonido en MP3",incorrect_try_again:"Incorrecto. Vu\u00e9lvelo a intentar.",image_alt_text:"Pista de imagen reCAPTCHA",privacy_and_terms:"Privacidad y condiciones"},la={visual_challenge:"Kumuha ng pagsubok na visual",audio_challenge:"Kumuha ng pagsubok na audio", -refresh_btn:"Kumuha ng bagong pagsubok",instructions_visual:"I-type ang teksto:",instructions_audio:"I-type ang iyong narinig",help_btn:"Tulong",play_again:"I-play muli ang tunog",cant_hear_this:"I-download ang tunog bilang MP3",incorrect_try_again:"Hindi wasto. Muling subukan.",image_alt_text:"larawang panghamon ng reCAPTCHA",privacy_and_terms:"Privacy at Mga Tuntunin"},ma={visual_challenge:"Test visuel",audio_challenge:"Test audio",refresh_btn:"Nouveau test",instructions_visual:"Saisissez le texte\u00a0:", -instructions_audio:"Qu'entendez-vous ?",help_btn:"Aide",play_again:"R\u00e9\u00e9couter",cant_hear_this:"T\u00e9l\u00e9charger l'audio au format MP3",incorrect_try_again:"Incorrect. Veuillez r\u00e9essayer.",image_alt_text:"Image reCAPTCHA",privacy_and_terms:"Confidentialit\u00e9 et conditions d'utilisation"},na={visual_challenge:"Dapatkan kata pengujian berbentuk visual",audio_challenge:"Dapatkan kata pengujian berbentuk audio",refresh_btn:"Dapatkan kata pengujian baru",instructions_visual:"Ketik teks:", -instructions_audio:"Ketik yang Anda dengar:",help_btn:"Bantuan",play_again:"Putar suara sekali lagi",cant_hear_this:"Unduh suara sebagai MP3",incorrect_try_again:"Salah. Coba lagi.",image_alt_text:"Gambar tantangan reCAPTCHA",privacy_and_terms:"Privasi & Persyaratan"},oa={visual_challenge:"\u05e7\u05d1\u05dc \u05d0\u05ea\u05d2\u05e8 \u05d7\u05d6\u05d5\u05ea\u05d9",audio_challenge:"\u05e7\u05d1\u05dc \u05d0\u05ea\u05d2\u05e8 \u05e9\u05de\u05e2",refresh_btn:"\u05e7\u05d1\u05dc \u05d0\u05ea\u05d2\u05e8 \u05d7\u05d3\u05e9", -instructions_visual:"\u05d4\u05e7\u05dc\u05d3 \u05d0\u05ea \u05d4\u05d8\u05e7\u05e1\u05d8:",instructions_audio:"\u05d4\u05e7\u05dc\u05d3 \u05d0\u05ea \u05de\u05d4 \u05e9\u05d0\u05ea\u05d4 \u05e9\u05d5\u05de\u05e2:",help_btn:"\u05e2\u05d6\u05e8\u05d4",play_again:"\u05d4\u05e4\u05e2\u05dc \u05e9\u05d5\u05d1 \u05d0\u05ea \u05d4\u05e9\u05de\u05e2",cant_hear_this:"\u05d4\u05d5\u05e8\u05d3 \u05e9\u05de\u05e2 \u05db-3MP",incorrect_try_again:"\u05e9\u05d2\u05d5\u05d9. \u05e0\u05e1\u05d4 \u05e9\u05d5\u05d1.", -image_alt_text:"\u05ea\u05de\u05d5\u05e0\u05ea \u05d0\u05ea\u05d2\u05e8 \u05e9\u05dc reCAPTCHA",privacy_and_terms:"\u05e4\u05e8\u05d8\u05d9\u05d5\u05ea \u05d5\u05ea\u05e0\u05d0\u05d9\u05dd"},pa={visual_challenge:"Obter um desafio visual",audio_challenge:"Obter um desafio de \u00e1udio",refresh_btn:"Obter um novo desafio",instructions_visual:"Digite o texto:",instructions_audio:"Digite o que voc\u00ea ouve:",help_btn:"Ajuda",play_again:"Reproduzir som novamente",cant_hear_this:"Fazer download do som no formato MP3", -incorrect_try_again:"Incorreto. Tente novamente.",image_alt_text:"Imagem de desafio reCAPTCHA",privacy_and_terms:"Privacidade e Termos"},qa={visual_challenge:"Ob\u0163ine\u0163i un cod captcha vizual",audio_challenge:"Ob\u0163ine\u0163i un cod captcha audio",refresh_btn:"Ob\u0163ine\u0163i un nou cod captcha",instructions_visual:"Introduce\u021bi textul:",instructions_audio:"Introduce\u0163i ceea ce auzi\u0163i:",help_btn:"Ajutor",play_again:"Reda\u0163i sunetul din nou",cant_hear_this:"Desc\u0103rca\u0163i fi\u015fierul audio ca MP3", -incorrect_try_again:"Incorect. \u00cencerca\u0163i din nou.",image_alt_text:"Imagine de verificare reCAPTCHA",privacy_and_terms:"Confiden\u0163ialitate \u015fi termeni"},ra={visual_challenge:"\u6536\u5230\u4e00\u4e2a\u89c6\u9891\u9080\u8bf7",audio_challenge:"\u6362\u4e00\u7ec4\u97f3\u9891\u9a8c\u8bc1\u7801",refresh_btn:"\u6362\u4e00\u7ec4\u9a8c\u8bc1\u7801",instructions_visual:"\u8f93\u5165\u6587\u5b57\uff1a",instructions_audio:"\u8bf7\u952e\u5165\u60a8\u542c\u5230\u7684\u5185\u5bb9\uff1a",help_btn:"\u5e2e\u52a9", -play_again:"\u91cd\u65b0\u64ad\u653e",cant_hear_this:"\u4ee5 MP3 \u683c\u5f0f\u4e0b\u8f7d\u58f0\u97f3",incorrect_try_again:"\u4e0d\u6b63\u786e\uff0c\u8bf7\u91cd\u8bd5\u3002",image_alt_text:"reCAPTCHA \u9a8c\u8bc1\u56fe\u7247",privacy_and_terms:"\u9690\u79c1\u6743\u548c\u4f7f\u7528\u6761\u6b3e"},sa={en:w,af:{visual_challenge:"Kry 'n visuele verifi\u00ebring",audio_challenge:"Kry 'n klankverifi\u00ebring",refresh_btn:"Kry 'n nuwe verifi\u00ebring",instructions_visual:"",instructions_audio:"Tik wat jy hoor:", -help_btn:"Hulp",play_again:"Speel geluid weer",cant_hear_this:"Laai die klank af as MP3",incorrect_try_again:"Verkeerd. Probeer weer.",image_alt_text:"reCAPTCHA-uitdagingprent",privacy_and_terms:"Privaatheid en bepalings"},am:{visual_challenge:"\u12e8\u12a5\u12ed\u1273 \u1270\u130b\u1323\u121a \u12a0\u130d\u129d",audio_challenge:"\u120c\u120b \u12a0\u12f2\u1235 \u12e8\u12f5\u121d\u133d \u1325\u12eb\u1244 \u12ed\u1245\u1228\u1265",refresh_btn:"\u120c\u120b \u12a0\u12f2\u1235 \u1325\u12eb\u1244 \u12ed\u1245\u1228\u1265", -instructions_visual:"",instructions_audio:"\u12e8\u121d\u1275\u1230\u121b\u12cd\u1295 \u1270\u12ed\u1265\u1361-",help_btn:"\u12a5\u1308\u12db",play_again:"\u12f5\u121d\u1339\u1295 \u12a5\u1295\u12f0\u1308\u1293 \u12a0\u132b\u12cd\u1275",cant_hear_this:"\u12f5\u121d\u1339\u1295 \u1260MP3 \u1245\u122d\u133d \u12a0\u12cd\u122d\u12f5",incorrect_try_again:"\u1275\u12ad\u12ad\u120d \u12a0\u12ed\u12f0\u1208\u121d\u1362 \u12a5\u1295\u12f0\u1308\u1293 \u121e\u12ad\u122d\u1362",image_alt_text:"reCAPTCHA \u121d\u1235\u120d \u130d\u1320\u121d", -privacy_and_terms:"\u130d\u120b\u12ca\u1290\u1275 \u12a5\u1293 \u12cd\u120d"},ar:ja,"ar-EG":ja,bg:{visual_challenge:"\u041f\u043e\u043b\u0443\u0447\u0430\u0432\u0430\u043d\u0435 \u043d\u0430 \u0432\u0438\u0437\u0443\u0430\u043b\u043d\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430",audio_challenge:"\u0417\u0430\u0440\u0435\u0436\u0434\u0430\u043d\u0435 \u043d\u0430 \u0430\u0443\u0434\u0438\u043e\u0442\u0435\u0441\u0442",refresh_btn:"\u0417\u0430\u0440\u0435\u0436\u0434\u0430\u043d\u0435 \u043d\u0430 \u043d\u043e\u0432 \u0442\u0435\u0441\u0442", -instructions_visual:"\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0442\u0435\u043a\u0441\u0442\u0430:",instructions_audio:"\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0447\u0443\u0442\u043e\u0442\u043e:",help_btn:"\u041f\u043e\u043c\u043e\u0449",play_again:"\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u043f\u0443\u0441\u043a\u0430\u043d\u0435 \u043d\u0430 \u0437\u0432\u0443\u043a\u0430",cant_hear_this:"\u0418\u0437\u0442\u0435\u0433\u043b\u044f\u043d\u0435 \u043d\u0430 \u0437\u0432\u0443\u043a\u0430 \u0432\u044a\u0432 \u0444\u043e\u0440\u043c\u0430\u0442 MP3", -incorrect_try_again:"\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u043d\u043e. \u041e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e.",image_alt_text:"\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\u0442\u0430 \u0441 reCAPTCHA",privacy_and_terms:"\u041f\u043e\u0432\u0435\u0440\u0438\u0442\u0435\u043b\u043d\u043e\u0441\u0442 \u0438 \u041e\u0431\u0449\u0438 \u0443\u0441\u043b\u043e\u0432\u0438\u044f"}, -bn:{visual_challenge:"\u098f\u0995\u099f\u09bf \u09a6\u09c3\u09b6\u09cd\u09af\u09ae\u09be\u09a8 \u09aa\u09cd\u09b0\u09a4\u09bf\u09a6\u09cd\u09ac\u09a8\u09cd\u09a6\u09cd\u09ac\u09bf\u09a4\u09be \u09aa\u09be\u09a8",audio_challenge:"\u098f\u0995\u099f\u09bf \u0985\u09a1\u09bf\u0993 \u09aa\u09cd\u09b0\u09a4\u09bf\u09a6\u09cd\u09ac\u09a8\u09cd\u09a6\u09cd\u09ac\u09bf\u09a4\u09be \u09aa\u09be\u09a8",refresh_btn:"\u098f\u0995\u099f\u09bf \u09a8\u09a4\u09c1\u09a8 \u09aa\u09cd\u09b0\u09a4\u09bf\u09a6\u09cd\u09ac\u09a8\u09cd\u09a6\u09cd\u09ac\u09bf\u09a4\u09be \u09aa\u09be\u09a8", -instructions_visual:"",instructions_audio:"\u0986\u09aa\u09a8\u09bf \u09af\u09be \u09b6\u09c1\u09a8\u099b\u09c7\u09a8 \u09a4\u09be \u09b2\u09bf\u0996\u09c1\u09a8:",help_btn:"\u09b8\u09b9\u09be\u09df\u09a4\u09be",play_again:"\u0986\u09ac\u09be\u09b0 \u09b8\u09be\u0989\u09a8\u09cd\u09a1 \u09aa\u09cd\u09b2\u09c7 \u0995\u09b0\u09c1\u09a8",cant_hear_this:"MP3 \u09b0\u09c2\u09aa\u09c7 \u09b6\u09ac\u09cd\u09a6 \u09a1\u09be\u0989\u09a8\u09b2\u09cb\u09a1 \u0995\u09b0\u09c1\u09a8",incorrect_try_again:"\u09ac\u09c7\u09a0\u09bf\u0995\u09f7 \u0986\u09ac\u09be\u09b0 \u099a\u09c7\u09b7\u09cd\u099f\u09be \u0995\u09b0\u09c1\u09a8\u09f7", -image_alt_text:"reCAPTCHA \u099a\u09cd\u09af\u09be\u09b2\u09c7\u099e\u09cd\u099c \u099a\u09bf\u09a4\u09cd\u09b0",privacy_and_terms:"\u0997\u09cb\u09aa\u09a8\u09c0\u09af\u09bc\u09a4\u09be \u0993 \u09b6\u09b0\u09cd\u09a4\u09be\u09ac\u09b2\u09c0"},ca:{visual_challenge:"Obt\u00e9n un repte visual",audio_challenge:"Obteniu una pista sonora",refresh_btn:"Obteniu una pista nova",instructions_visual:"Escriviu el text:",instructions_audio:"Escriviu el que escolteu:",help_btn:"Ajuda",play_again:"Torna a reproduir el so", -cant_hear_this:"Baixa el so com a MP3",incorrect_try_again:"No \u00e9s correcte. Torna-ho a provar.",image_alt_text:"Imatge del repte de reCAPTCHA",privacy_and_terms:"Privadesa i condicions"},cs:{visual_challenge:"Zobrazit vizu\u00e1ln\u00ed podobu v\u00fdrazu",audio_challenge:"P\u0159ehr\u00e1t zvukovou podobu v\u00fdrazu",refresh_btn:"Zobrazit nov\u00fd v\u00fdraz",instructions_visual:"Zadejte text:",instructions_audio:"Napi\u0161te, co jste sly\u0161eli:",help_btn:"N\u00e1pov\u011bda",play_again:"Znovu p\u0159ehr\u00e1t zvuk", -cant_hear_this:"St\u00e1hnout zvuk ve form\u00e1tu MP3",incorrect_try_again:"\u0160patn\u011b. Zkuste to znovu.",image_alt_text:"Obr\u00e1zek reCAPTCHA",privacy_and_terms:"Ochrana soukrom\u00ed a smluvn\u00ed podm\u00ednky"},da:{visual_challenge:"Hent en visuel udfordring",audio_challenge:"Hent en lydudfordring",refresh_btn:"Hent en ny udfordring",instructions_visual:"Indtast teksten:",instructions_audio:"Indtast det, du h\u00f8rer:",help_btn:"Hj\u00e6lp",play_again:"Afspil lyden igen",cant_hear_this:"Download lyd som MP3", -incorrect_try_again:"Forkert. Pr\u00f8v igen.",image_alt_text:"reCAPTCHA-udfordringsbillede",privacy_and_terms:"Privatliv og vilk\u00e5r"},de:{visual_challenge:"Captcha abrufen",audio_challenge:"Audio-Captcha abrufen",refresh_btn:"Neues Captcha abrufen",instructions_visual:"Geben Sie den angezeigten Text ein:",instructions_audio:"Geben Sie das Geh\u00f6rte ein:",help_btn:"Hilfe",play_again:"Wort erneut abspielen",cant_hear_this:"Wort als MP3 herunterladen",incorrect_try_again:"Falsch. Bitte versuchen Sie es erneut.", -image_alt_text:"reCAPTCHA-Bild",privacy_and_terms:"Datenschutzerkl\u00e4rung & Nutzungsbedingungen"},el:{visual_challenge:"\u039f\u03c0\u03c4\u03b9\u03ba\u03ae \u03c0\u03c1\u03cc\u03ba\u03bb\u03b7\u03c3\u03b7",audio_challenge:"\u0397\u03c7\u03b7\u03c4\u03b9\u03ba\u03ae \u03c0\u03c1\u03cc\u03ba\u03bb\u03b7\u03c3\u03b7",refresh_btn:"\u039d\u03ad\u03b1 \u03c0\u03c1\u03cc\u03ba\u03bb\u03b7\u03c3\u03b7",instructions_visual:"\u03a0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03af\u03bc\u03b5\u03bd\u03bf:", -instructions_audio:"\u03a0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03b1\u03ba\u03bf\u03cd\u03c4\u03b5:",help_btn:"\u0392\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1",play_again:"\u0391\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae \u03ae\u03c7\u03bf\u03c5 \u03be\u03b1\u03bd\u03ac",cant_hear_this:"\u039b\u03ae\u03c8\u03b7 \u03ae\u03c7\u03bf\u03c5 \u03c9\u03c2 \u039c\u03a13",incorrect_try_again:"\u039b\u03ac\u03b8\u03bf\u03c2. \u0394\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", -image_alt_text:"\u0395\u03b9\u03ba\u03cc\u03bd\u03b1 \u03c0\u03c1\u03cc\u03ba\u03bb\u03b7\u03c3\u03b7\u03c2 reCAPTCHA",privacy_and_terms:"\u0391\u03c0\u03cc\u03c1\u03c1\u03b7\u03c4\u03bf \u03ba\u03b1\u03b9 \u03cc\u03c1\u03bf\u03b9"},"en-GB":w,"en-US":w,es:ka,"es-419":{visual_challenge:"Enfrentar un desaf\u00edo visual",audio_challenge:"Enfrentar un desaf\u00edo de audio",refresh_btn:"Enfrentar un nuevo desaf\u00edo",instructions_visual:"Escriba el texto:",instructions_audio:"Escribe lo que escuchas:", -help_btn:"Ayuda",play_again:"Reproducir sonido de nuevo",cant_hear_this:"Descargar sonido en formato MP3",incorrect_try_again:"Incorrecto. Vuelve a intentarlo.",image_alt_text:"Imagen del desaf\u00edo de la reCAPTCHA",privacy_and_terms:"Privacidad y condiciones"},"es-ES":ka,et:{visual_challenge:"Kuva kuvap\u00f5hine robotil\u00f5ks",audio_challenge:"Kuva helip\u00f5hine robotil\u00f5ks",refresh_btn:"Kuva uus robotil\u00f5ks",instructions_visual:"Tippige tekst:",instructions_audio:"Tippige, mida kuulete.", -help_btn:"Abi",play_again:"Esita heli uuesti",cant_hear_this:"Laadi heli alla MP3-vormingus",incorrect_try_again:"Vale. Proovige uuesti.",image_alt_text:"reCAPTCHA robotil\u00f5ksu kujutis",privacy_and_terms:"Privaatsus ja tingimused"},eu:{visual_challenge:"Eskuratu ikusizko erronka",audio_challenge:"Eskuratu audio-erronka",refresh_btn:"Eskuratu erronka berria",instructions_visual:"",instructions_audio:"Idatzi entzuten duzuna:",help_btn:"Laguntza",play_again:"Erreproduzitu soinua berriro",cant_hear_this:"Deskargatu soinua MP3 gisa", -incorrect_try_again:"Ez da zuzena. Saiatu berriro.",image_alt_text:"reCAPTCHA erronkaren irudia",privacy_and_terms:"Pribatutasuna eta baldintzak"},fa:{visual_challenge:"\u062f\u0631\u06cc\u0627\u0641\u062a \u06cc\u06a9 \u0645\u0639\u0645\u0627\u06cc \u062f\u06cc\u062f\u0627\u0631\u06cc",audio_challenge:"\u062f\u0631\u06cc\u0627\u0641\u062a \u06cc\u06a9 \u0645\u0639\u0645\u0627\u06cc \u0635\u0648\u062a\u06cc",refresh_btn:"\u062f\u0631\u06cc\u0627\u0641\u062a \u06cc\u06a9 \u0645\u0639\u0645\u0627\u06cc \u062c\u062f\u06cc\u062f", -instructions_visual:"",instructions_audio:"\u0622\u0646\u0686\u0647 \u0631\u0627 \u06a9\u0647 \u0645\u06cc\u200c\u0634\u0646\u0648\u06cc\u062f \u062a\u0627\u06cc\u067e \u06a9\u0646\u06cc\u062f:",help_btn:"\u0631\u0627\u0647\u0646\u0645\u0627\u06cc\u06cc",play_again:"\u067e\u062e\u0634 \u0645\u062c\u062f\u062f \u0635\u062f\u0627",cant_hear_this:"\u062f\u0627\u0646\u0644\u0648\u062f \u0635\u062f\u0627 \u0628\u0647 \u0635\u0648\u0631\u062a MP3",incorrect_try_again:"\u0646\u0627\u062f\u0631\u0633\u062a. \u062f\u0648\u0628\u0627\u0631\u0647 \u0627\u0645\u062a\u062d\u0627\u0646 \u06a9\u0646\u06cc\u062f.", -image_alt_text:"\u062a\u0635\u0648\u06cc\u0631 \u0686\u0627\u0644\u0634\u06cc reCAPTCHA",privacy_and_terms:"\u062d\u0631\u06cc\u0645 \u062e\u0635\u0648\u0635\u06cc \u0648 \u0634\u0631\u0627\u06cc\u0637"},fi:{visual_challenge:"Kuvavahvistus",audio_challenge:"\u00c4\u00e4nivahvistus",refresh_btn:"Uusi kuva",instructions_visual:"Kirjoita teksti:",instructions_audio:"Kirjoita kuulemasi:",help_btn:"Ohje",play_again:"Toista \u00e4\u00e4ni uudelleen",cant_hear_this:"Lataa \u00e4\u00e4ni MP3-tiedostona", -incorrect_try_again:"V\u00e4\u00e4rin. Yrit\u00e4 uudelleen.",image_alt_text:"reCAPTCHA-kuva",privacy_and_terms:"Tietosuoja ja k\u00e4ytt\u00f6ehdot"},fil:la,fr:ma,"fr-CA":{visual_challenge:"Obtenir un test visuel",audio_challenge:"Obtenir un test audio",refresh_btn:"Obtenir un nouveau test",instructions_visual:"Saisissez le texte\u00a0:",instructions_audio:"Tapez ce que vous entendez\u00a0:",help_btn:"Aide",play_again:"Jouer le son de nouveau",cant_hear_this:"T\u00e9l\u00e9charger le son en format MP3", -incorrect_try_again:"Erreur, essayez \u00e0 nouveau",image_alt_text:"Image reCAPTCHA",privacy_and_terms:"Confidentialit\u00e9 et conditions d'utilisation"},"fr-FR":ma,gl:{visual_challenge:"Obter unha proba visual",audio_challenge:"Obter unha proba de audio",refresh_btn:"Obter unha proba nova",instructions_visual:"",instructions_audio:"Escribe o que escoitas:",help_btn:"Axuda",play_again:"Reproducir o son de novo",cant_hear_this:"Descargar son como MP3",incorrect_try_again:"Incorrecto. T\u00e9ntao de novo.", -image_alt_text:"Imaxe de proba de reCAPTCHA",privacy_and_terms:"Privacidade e condici\u00f3ns"},gu:{visual_challenge:"\u0a8f\u0a95 \u0aa6\u0ac3\u0ab6\u0acd\u0aaf\u0abe\u0aa4\u0acd\u0aae\u0a95 \u0aaa\u0aa1\u0a95\u0abe\u0ab0 \u0aae\u0ac7\u0ab3\u0ab5\u0acb",audio_challenge:"\u0a8f\u0a95 \u0a91\u0aa1\u0abf\u0a93 \u0aaa\u0aa1\u0a95\u0abe\u0ab0 \u0aae\u0ac7\u0ab3\u0ab5\u0acb",refresh_btn:"\u0a8f\u0a95 \u0aa8\u0ab5\u0acb \u0aaa\u0aa1\u0a95\u0abe\u0ab0 \u0aae\u0ac7\u0ab3\u0ab5\u0acb",instructions_visual:"", -instructions_audio:"\u0aa4\u0aae\u0ac7 \u0a9c\u0ac7 \u0ab8\u0abe\u0a82\u0aad\u0ab3\u0acb \u0a9b\u0acb \u0aa4\u0ac7 \u0ab2\u0a96\u0acb:",help_btn:"\u0ab8\u0ab9\u0abe\u0aaf",play_again:"\u0aa7\u0acd\u0ab5\u0aa8\u0abf \u0aab\u0ab0\u0ac0\u0aa5\u0ac0 \u0a9a\u0ab2\u0abe\u0ab5\u0acb",cant_hear_this:"MP3 \u0aa4\u0ab0\u0ac0\u0a95\u0ac7 \u0aa7\u0acd\u0ab5\u0aa8\u0abf\u0aa8\u0ac7 \u0aa1\u0abe\u0a89\u0aa8\u0ab2\u0acb\u0aa1 \u0a95\u0ab0\u0acb",incorrect_try_again:"\u0a96\u0acb\u0a9f\u0ac1\u0a82. \u0aab\u0ab0\u0ac0 \u0aaa\u0acd\u0ab0\u0aaf\u0abe\u0ab8 \u0a95\u0ab0\u0acb.", -image_alt_text:"reCAPTCHA \u0aaa\u0aa1\u0a95\u0abe\u0ab0 \u0a9b\u0aac\u0ac0",privacy_and_terms:"\u0a97\u0acb\u0aaa\u0aa8\u0ac0\u0aaf\u0aa4\u0abe \u0a85\u0aa8\u0ac7 \u0ab6\u0ab0\u0aa4\u0acb"},hi:{visual_challenge:"\u0915\u094b\u0908 \u0935\u093f\u091c\u0941\u0905\u0932 \u091a\u0941\u0928\u094c\u0924\u0940 \u0932\u0947\u0902",audio_challenge:"\u0915\u094b\u0908 \u0911\u0921\u093f\u092f\u094b \u091a\u0941\u0928\u094c\u0924\u0940 \u0932\u0947\u0902",refresh_btn:"\u0915\u094b\u0908 \u0928\u0908 \u091a\u0941\u0928\u094c\u0924\u0940 \u0932\u0947\u0902", -instructions_visual:"\u091f\u0947\u0915\u094d\u0938\u094d\u091f \u091f\u093e\u0907\u092a \u0915\u0930\u0947\u0902:",instructions_audio:"\u091c\u094b \u0906\u092a \u0938\u0941\u0928 \u0930\u0939\u0947 \u0939\u0948\u0902 \u0909\u0938\u0947 \u0932\u093f\u0916\u0947\u0902:",help_btn:"\u0938\u0939\u093e\u092f\u0924\u093e",play_again:"\u0927\u094d\u200d\u0935\u0928\u093f \u092a\u0941\u0928: \u091a\u0932\u093e\u090f\u0902",cant_hear_this:"\u0927\u094d\u200d\u0935\u0928\u093f \u0915\u094b MP3 \u0915\u0947 \u0930\u0942\u092a \u092e\u0947\u0902 \u0921\u093e\u0909\u0928\u0932\u094b\u0921 \u0915\u0930\u0947\u0902", -incorrect_try_again:"\u0917\u0932\u0924. \u092a\u0941\u0928: \u092a\u094d\u0930\u092f\u093e\u0938 \u0915\u0930\u0947\u0902.",image_alt_text:"reCAPTCHA \u091a\u0941\u0928\u094c\u0924\u0940 \u091a\u093f\u0924\u094d\u0930",privacy_and_terms:"\u0917\u094b\u092a\u0928\u0940\u092f\u0924\u093e \u0914\u0930 \u0936\u0930\u094d\u0924\u0947\u0902"},hr:{visual_challenge:"Dohvati vizualni upit",audio_challenge:"Dohvati zvu\u010dni upit",refresh_btn:"Dohvati novi upit",instructions_visual:"Unesite tekst:",instructions_audio:"Upi\u0161ite \u0161to \u010dujete:", -help_btn:"Pomo\u0107",play_again:"Ponovi zvuk",cant_hear_this:"Preuzmi zvuk u MP3 formatu",incorrect_try_again:"Nije to\u010dno. Poku\u0161ajte ponovno.",image_alt_text:"Slikovni izazov reCAPTCHA",privacy_and_terms:"Privatnost i odredbe"},hu:{visual_challenge:"Vizu\u00e1lis kih\u00edv\u00e1s k\u00e9r\u00e9se",audio_challenge:"Hangkih\u00edv\u00e1s k\u00e9r\u00e9se",refresh_btn:"\u00daj kih\u00edv\u00e1s k\u00e9r\u00e9se",instructions_visual:"\u00cdrja be a sz\u00f6veget:",instructions_audio:"\u00cdrja le, amit hall:", -help_btn:"S\u00fag\u00f3",play_again:"Hang ism\u00e9telt lej\u00e1tsz\u00e1sa",cant_hear_this:"Hang let\u00f6lt\u00e9se MP3 form\u00e1tumban",incorrect_try_again:"Hib\u00e1s. Pr\u00f3b\u00e1lkozzon \u00fajra.",image_alt_text:"reCAPTCHA ellen\u0151rz\u0151 k\u00e9p",privacy_and_terms:"Adatv\u00e9delem \u00e9s Szerz\u0151d\u00e9si Felt\u00e9telek"},hy:{visual_challenge:"\u054d\u057f\u0561\u0576\u0561\u056c \u057f\u0565\u057d\u0578\u0572\u0561\u056f\u0561\u0576 \u056d\u0576\u0564\u056b\u0580",audio_challenge:"\u054d\u057f\u0561\u0576\u0561\u056c \u0571\u0561\u0575\u0576\u0561\u0575\u056b\u0576 \u056d\u0576\u0564\u056b\u0580", -refresh_btn:"\u054d\u057f\u0561\u0576\u0561\u056c \u0576\u0578\u0580 \u056d\u0576\u0564\u056b\u0580",instructions_visual:"\u0544\u0578\u0582\u057f\u0584\u0561\u0563\u0580\u0565\u0584 \u057f\u0565\u0584\u057d\u057f\u0568\u055d",instructions_audio:"\u0544\u0578\u0582\u057f\u0584\u0561\u0563\u0580\u0565\u0584 \u0561\u0575\u0576, \u056b\u0576\u0579 \u056c\u057d\u0578\u0582\u0574 \u0565\u0584\u055d",help_btn:"\u0555\u0563\u0576\u0578\u0582\u0569\u0575\u0578\u0582\u0576",play_again:"\u0546\u057e\u0561\u0563\u0561\u0580\u056f\u0565\u056c \u0571\u0561\u0575\u0576\u0568 \u056f\u0580\u056f\u056b\u0576", -cant_hear_this:"\u0532\u0565\u057c\u0576\u0565\u056c \u0571\u0561\u0575\u0576\u0568 \u0578\u0580\u057a\u0565\u057d MP3",incorrect_try_again:"\u054d\u056d\u0561\u056c \u0567: \u0553\u0578\u0580\u0571\u0565\u0584 \u056f\u0580\u056f\u056b\u0576:",image_alt_text:"reCAPTCHA \u057a\u0561\u057f\u056f\u0565\u0580\u0578\u057e \u056d\u0576\u0564\u056b\u0580",privacy_and_terms:"\u0533\u0561\u0572\u057f\u0576\u056b\u0578\u0582\u0569\u0575\u0561\u0576 & \u057a\u0561\u0575\u0574\u0561\u0576\u0576\u0565\u0580"}, -id:na,is:{visual_challenge:"F\u00e1 a\u00f0gangspr\u00f3f sem mynd",audio_challenge:"F\u00e1 a\u00f0gangspr\u00f3f sem hlj\u00f3\u00f0skr\u00e1",refresh_btn:"F\u00e1 n\u00fdtt a\u00f0gangspr\u00f3f",instructions_visual:"",instructions_audio:"Sl\u00e1\u00f0u inn \u00fea\u00f0 sem \u00fe\u00fa heyrir:",help_btn:"Hj\u00e1lp",play_again:"Spila hlj\u00f3\u00f0 aftur",cant_hear_this:"S\u00e6kja hlj\u00f3\u00f0 sem MP3",incorrect_try_again:"Rangt. Reyndu aftur.",image_alt_text:"mynd reCAPTCHA a\u00f0gangspr\u00f3fs", -privacy_and_terms:"Pers\u00f3nuvernd og skilm\u00e1lar"},it:{visual_challenge:"Verifica visiva",audio_challenge:"Verifica audio",refresh_btn:"Nuova verifica",instructions_visual:"Digita il testo:",instructions_audio:"Digita ci\u00f2 che senti:",help_btn:"Guida",play_again:"Riproduci di nuovo audio",cant_hear_this:"Scarica audio in MP3",incorrect_try_again:"Sbagliato. Riprova.",image_alt_text:"Immagine di verifica reCAPTCHA",privacy_and_terms:"Privacy e Termini"},iw:oa,ja:{visual_challenge:"\u753b\u50cf\u3067\u78ba\u8a8d\u3057\u307e\u3059", -audio_challenge:"\u97f3\u58f0\u3067\u78ba\u8a8d\u3057\u307e\u3059",refresh_btn:"\u5225\u306e\u5358\u8a9e\u3067\u3084\u308a\u76f4\u3057\u307e\u3059",instructions_visual:"\u30c6\u30ad\u30b9\u30c8\u3092\u5165\u529b:",instructions_audio:"\u805e\u3053\u3048\u305f\u5358\u8a9e\u3092\u5165\u529b\u3057\u307e\u3059:",help_btn:"\u30d8\u30eb\u30d7",play_again:"\u3082\u3046\u4e00\u5ea6\u805e\u304f",cant_hear_this:"MP3 \u3067\u97f3\u58f0\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9",incorrect_try_again:"\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093\u3002\u3082\u3046\u4e00\u5ea6\u3084\u308a\u76f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002", -image_alt_text:"reCAPTCHA \u78ba\u8a8d\u7528\u753b\u50cf",privacy_and_terms:"\u30d7\u30e9\u30a4\u30d0\u30b7\u30fc\u3068\u5229\u7528\u898f\u7d04"},kn:{visual_challenge:"\u0ca6\u0cc3\u0cb6\u0ccd\u0caf \u0cb8\u0cb5\u0cbe\u0cb2\u0cca\u0c82\u0ca6\u0ca8\u0ccd\u0ca8\u0cc1 \u0cb8\u0ccd\u0cb5\u0cc0\u0c95\u0cb0\u0cbf\u0cb8\u0cbf",audio_challenge:"\u0c86\u0ca1\u0cbf\u0caf\u0ccb \u0cb8\u0cb5\u0cbe\u0cb2\u0cca\u0c82\u0ca6\u0ca8\u0ccd\u0ca8\u0cc1 \u0cb8\u0ccd\u0cb5\u0cc0\u0c95\u0cb0\u0cbf\u0cb8\u0cbf",refresh_btn:"\u0cb9\u0cca\u0cb8 \u0cb8\u0cb5\u0cbe\u0cb2\u0cca\u0c82\u0ca6\u0ca8\u0ccd\u0ca8\u0cc1 \u0caa\u0ca1\u0cc6\u0caf\u0cbf\u0cb0\u0cbf", -instructions_visual:"",instructions_audio:"\u0ca8\u0cbf\u0cae\u0c97\u0cc6 \u0c95\u0cc7\u0cb3\u0cbf\u0cb8\u0cc1\u0cb5\u0cc1\u0ca6\u0ca8\u0ccd\u0ca8\u0cc1 \u0c9f\u0cc8\u0caa\u0ccd\u200c \u0cae\u0cbe\u0ca1\u0cbf:",help_btn:"\u0cb8\u0cb9\u0cbe\u0caf",play_again:"\u0ca7\u0ccd\u0cb5\u0ca8\u0cbf\u0caf\u0ca8\u0ccd\u0ca8\u0cc1 \u0cae\u0ca4\u0ccd\u0ca4\u0cc6 \u0caa\u0ccd\u0cb2\u0cc7 \u0cae\u0cbe\u0ca1\u0cbf",cant_hear_this:"\u0ca7\u0ccd\u0cb5\u0ca8\u0cbf\u0caf\u0ca8\u0ccd\u0ca8\u0cc1 MP3 \u0cb0\u0cc2\u0caa\u0ca6\u0cb2\u0ccd\u0cb2\u0cbf \u0ca1\u0ccc\u0ca8\u0ccd\u200c\u0cb2\u0ccb\u0ca1\u0ccd \u0cae\u0cbe\u0ca1\u0cbf", -incorrect_try_again:"\u0ca4\u0caa\u0ccd\u0caa\u0cbe\u0c97\u0cbf\u0ca6\u0cc6. \u0cae\u0ca4\u0ccd\u0ca4\u0cca\u0cae\u0ccd\u0cae\u0cc6 \u0caa\u0ccd\u0cb0\u0caf\u0ca4\u0ccd\u0ca8\u0cbf\u0cb8\u0cbf.",image_alt_text:"reCAPTCHA \u0cb8\u0cb5\u0cbe\u0cb2\u0cc1 \u0c9a\u0cbf\u0ca4\u0ccd\u0cb0",privacy_and_terms:"\u0c97\u0ccc\u0caa\u0ccd\u0caf\u0ca4\u0cc6 \u0cae\u0ca4\u0ccd\u0ca4\u0cc1 \u0ca8\u0cbf\u0caf\u0cae\u0c97\u0cb3\u0cc1"},ko:{visual_challenge:"\uadf8\ub9bc\uc73c\ub85c \ubcf4\uc548\ubb38\uc790 \ubc1b\uae30", -audio_challenge:"\uc74c\uc131\uc73c\ub85c \ubcf4\uc548\ubb38\uc790 \ubc1b\uae30",refresh_btn:"\ubcf4\uc548\ubb38\uc790 \uc0c8\ub85c \ubc1b\uae30",instructions_visual:"\ud14d\uc2a4\ud2b8 \uc785\ub825:",instructions_audio:"\uc74c\uc131 \ubcf4\uc548\ubb38\uc790 \uc785\ub825:",help_btn:"\ub3c4\uc6c0\ub9d0",play_again:"\uc74c\uc131 \ub2e4\uc2dc \ub4e3\uae30",cant_hear_this:"\uc74c\uc131\uc744 MP3\ub85c \ub2e4\uc6b4\ub85c\ub4dc",incorrect_try_again:"\ud2c0\ub838\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574 \uc8fc\uc138\uc694.", -image_alt_text:"reCAPTCHA \ubcf4\uc548\ubb38\uc790 \uc774\ubbf8\uc9c0",privacy_and_terms:"\uac1c\uc778\uc815\ubcf4 \ubcf4\ud638 \ubc0f \uc57d\uad00"},ln:ma,lt:{visual_challenge:"Gauti vaizdin\u012f atpa\u017einimo test\u0105",audio_challenge:"Gauti garso atpa\u017einimo test\u0105",refresh_btn:"Gauti nauj\u0105 atpa\u017einimo test\u0105",instructions_visual:"\u012eveskite tekst\u0105:",instructions_audio:"\u012eveskite tai, k\u0105 girdite:",help_btn:"Pagalba",play_again:"Dar kart\u0105 paleisti gars\u0105", -cant_hear_this:"Atsisi\u0173sti gars\u0105 kaip MP3",incorrect_try_again:"Neteisingai. Bandykite dar kart\u0105.",image_alt_text:"Testo \u201ereCAPTCHA\u201c vaizdas",privacy_and_terms:"Privatumas ir s\u0105lygos"},lv:{visual_challenge:"Sa\u0146emt vizu\u0101lu izaicin\u0101jumu",audio_challenge:"Sa\u0146emt audio izaicin\u0101jumu",refresh_btn:"Sa\u0146emt jaunu izaicin\u0101jumu",instructions_visual:"Ievadiet tekstu:",instructions_audio:"Ierakstiet dzirdamo:",help_btn:"Pal\u012bdz\u012bba",play_again:"V\u0113lreiz atska\u0146ot ska\u0146u", -cant_hear_this:"Lejupiel\u0101d\u0113t ska\u0146u MP3\u00a0form\u0101t\u0101",incorrect_try_again:"Nepareizi. M\u0113\u0123iniet v\u0113lreiz.",image_alt_text:"reCAPTCHA izaicin\u0101juma att\u0113ls",privacy_and_terms:"Konfidencialit\u0101te un noteikumi"},ml:{visual_challenge:"\u0d12\u0d30\u0d41 \u0d26\u0d43\u0d36\u0d4d\u0d2f \u0d1a\u0d32\u0d1e\u0d4d\u0d1a\u0d4d \u0d28\u0d47\u0d1f\u0d41\u0d15",audio_challenge:"\u0d12\u0d30\u0d41 \u0d13\u0d21\u0d3f\u0d2f\u0d4b \u0d1a\u0d32\u0d1e\u0d4d\u0d1a\u0d4d \u0d28\u0d47\u0d1f\u0d41\u0d15", -refresh_btn:"\u0d12\u0d30\u0d41 \u0d2a\u0d41\u0d24\u0d3f\u0d2f \u0d1a\u0d32\u0d1e\u0d4d\u0d1a\u0d4d \u0d28\u0d47\u0d1f\u0d41\u0d15",instructions_visual:"",instructions_audio:"\u0d15\u0d47\u0d7e\u0d15\u0d4d\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d24\u0d4d \u0d1f\u0d48\u0d2a\u0d4d\u0d2a\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d42:",help_btn:"\u0d38\u0d39\u0d3e\u0d2f\u0d02",play_again:"\u0d36\u0d2c\u0d4d\u200c\u0d26\u0d02 \u0d35\u0d40\u0d23\u0d4d\u0d1f\u0d41\u0d02 \u0d2a\u0d4d\u0d32\u0d47 \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15", -cant_hear_this:"\u0d36\u0d2c\u0d4d\u200c\u0d26\u0d02 MP3 \u0d06\u0d2f\u0d3f \u0d21\u0d57\u0d7a\u0d32\u0d4b\u0d21\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15",incorrect_try_again:"\u0d24\u0d46\u0d31\u0d4d\u0d31\u0d3e\u0d23\u0d4d. \u0d35\u0d40\u0d23\u0d4d\u0d1f\u0d41\u0d02 \u0d36\u0d4d\u0d30\u0d2e\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15.",image_alt_text:"reCAPTCHA \u0d1a\u0d32\u0d1e\u0d4d\u0d1a\u0d4d \u0d07\u0d2e\u0d47\u0d1c\u0d4d",privacy_and_terms:"\u0d38\u0d4d\u0d35\u0d15\u0d3e\u0d30\u0d4d\u0d2f\u0d24\u0d2f\u0d41\u0d02 \u0d28\u0d3f\u0d2c\u0d28\u0d4d\u0d27\u0d28\u0d15\u0d33\u0d41\u0d02"}, -mr:{visual_challenge:"\u0926\u0943\u0936\u094d\u200d\u092f\u092e\u093e\u0928 \u0906\u0935\u094d\u0939\u093e\u0928 \u092a\u094d\u0930\u093e\u092a\u094d\u0924 \u0915\u0930\u093e",audio_challenge:"\u0911\u0921\u0940\u0913 \u0906\u0935\u094d\u0939\u093e\u0928 \u092a\u094d\u0930\u093e\u092a\u094d\u0924 \u0915\u0930\u093e",refresh_btn:"\u090f\u0915 \u0928\u0935\u0940\u0928 \u0906\u0935\u094d\u0939\u093e\u0928 \u092a\u094d\u0930\u093e\u092a\u094d\u0924 \u0915\u0930\u093e",instructions_visual:"",instructions_audio:"\u0906\u092a\u0932\u094d\u092f\u093e\u0932\u093e \u091c\u0947 \u0910\u0915\u0942 \u092f\u0947\u0908\u0932 \u0924\u0947 \u091f\u093e\u0907\u092a \u0915\u0930\u093e:", -help_btn:"\u092e\u0926\u0924",play_again:"\u0927\u094d\u200d\u0935\u0928\u0940 \u092a\u0941\u0928\u094d\u0939\u093e \u092a\u094d\u200d\u0932\u0947 \u0915\u0930\u093e",cant_hear_this:"MP3 \u0930\u0941\u092a\u093e\u0924 \u0927\u094d\u200d\u0935\u0928\u0940 \u0921\u093e\u0909\u0928\u0932\u094b\u0921 \u0915\u0930\u093e",incorrect_try_again:"\u0905\u092f\u094b\u0917\u094d\u200d\u092f. \u092a\u0941\u0928\u094d\u200d\u0939\u093e \u092a\u094d\u0930\u092f\u0924\u094d\u200d\u0928 \u0915\u0930\u093e.",image_alt_text:"reCAPTCHA \u0906\u0935\u094d\u200d\u0939\u093e\u0928 \u092a\u094d\u0930\u0924\u093f\u092e\u093e", -privacy_and_terms:"\u0917\u094b\u092a\u0928\u0940\u092f\u0924\u093e \u0906\u0923\u093f \u0905\u091f\u0940"},ms:{visual_challenge:"Dapatkan cabaran visual",audio_challenge:"Dapatkan cabaran audio",refresh_btn:"Dapatkan cabaran baru",instructions_visual:"Taipkan teksnya:",instructions_audio:"Taip apa yang didengari:",help_btn:"Bantuan",play_again:"Mainkan bunyi sekali lagi",cant_hear_this:"Muat turun bunyi sebagai MP3",incorrect_try_again:"Tidak betul. Cuba lagi.",image_alt_text:"Imej cabaran reCAPTCHA", -privacy_and_terms:"Privasi & Syarat"},nl:{visual_challenge:"Een visuele uitdaging proberen",audio_challenge:"Een audio-uitdaging proberen",refresh_btn:"Een nieuwe uitdaging proberen",instructions_visual:"Typ de tekst:",instructions_audio:"Typ wat u hoort:",help_btn:"Help",play_again:"Geluid opnieuw afspelen",cant_hear_this:"Geluid downloaden als MP3",incorrect_try_again:"Onjuist. Probeer het opnieuw.",image_alt_text:"reCAPTCHA-uitdagingsafbeelding",privacy_and_terms:"Privacy en voorwaarden"},no:{visual_challenge:"F\u00e5 en bildeutfordring", -audio_challenge:"F\u00e5 en lydutfordring",refresh_btn:"F\u00e5 en ny utfordring",instructions_visual:"Skriv inn teksten:",instructions_audio:"Skriv inn det du h\u00f8rer:",help_btn:"Hjelp",play_again:"Spill av lyd p\u00e5 nytt",cant_hear_this:"Last ned lyd som MP3",incorrect_try_again:"Feil. Pr\u00f8v p\u00e5 nytt.",image_alt_text:"reCAPTCHA-utfordringsbilde",privacy_and_terms:"Personvern og vilk\u00e5r"},pl:{visual_challenge:"Poka\u017c podpowied\u017a wizualn\u0105",audio_challenge:"Odtw\u00f3rz podpowied\u017a d\u017awi\u0119kow\u0105", -refresh_btn:"Nowa podpowied\u017a",instructions_visual:"Przepisz tekst:",instructions_audio:"Wpisz us\u0142yszane s\u0142owa:",help_btn:"Pomoc",play_again:"Odtw\u00f3rz d\u017awi\u0119k ponownie",cant_hear_this:"Pobierz d\u017awi\u0119k jako plik MP3",incorrect_try_again:"Nieprawid\u0142owo. Spr\u00f3buj ponownie.",image_alt_text:"Zadanie obrazkowe reCAPTCHA",privacy_and_terms:"Prywatno\u015b\u0107 i warunki"},pt:pa,"pt-BR":pa,"pt-PT":{visual_challenge:"Obter um desafio visual",audio_challenge:"Obter um desafio de \u00e1udio", -refresh_btn:"Obter um novo desafio",instructions_visual:"Introduza o texto:",instructions_audio:"Escreva o que ouvir:",help_btn:"Ajuda",play_again:"Reproduzir som novamente",cant_hear_this:"Transferir som como MP3",incorrect_try_again:"Incorreto. Tente novamente.",image_alt_text:"Imagem de teste reCAPTCHA",privacy_and_terms:"Privacidade e Termos de Utiliza\u00e7\u00e3o"},ro:qa,ru:{visual_challenge:"\u0412\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430", -audio_challenge:"\u0417\u0432\u0443\u043a\u043e\u0432\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430",refresh_btn:"\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c",instructions_visual:"\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043a\u0441\u0442:",instructions_audio:"\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u043e, \u0447\u0442\u043e \u0441\u043b\u044b\u0448\u0438\u0442\u0435:",help_btn:"\u0421\u043f\u0440\u0430\u0432\u043a\u0430",play_again:"\u041f\u0440\u043e\u0441\u043b\u0443\u0448\u0430\u0442\u044c \u0435\u0449\u0435 \u0440\u0430\u0437", -cant_hear_this:"\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c MP3-\u0444\u0430\u0439\u043b",incorrect_try_again:"\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e. \u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443.",image_alt_text:"\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u043e \u0441\u043b\u043e\u0432\u0443 reCAPTCHA",privacy_and_terms:"\u041f\u0440\u0430\u0432\u0438\u043b\u0430 \u0438 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u044b"}, -sk:{visual_challenge:"Zobrazi\u0165 vizu\u00e1lnu podobu",audio_challenge:"Prehra\u0165 zvukov\u00fa podobu",refresh_btn:"Zobrazi\u0165 nov\u00fd v\u00fdraz",instructions_visual:"Zadajte text:",instructions_audio:"Zadajte, \u010do po\u010dujete:",help_btn:"Pomocn\u00edk",play_again:"Znova prehra\u0165 zvuk",cant_hear_this:"Prevzia\u0165 zvuk v podobe s\u00faboru MP3",incorrect_try_again:"Nespr\u00e1vne. Sk\u00faste to znova.",image_alt_text:"Obr\u00e1zok zadania reCAPTCHA",privacy_and_terms:"Ochrana osobn\u00fdch \u00fadajov a Zmluvn\u00e9 podmienky"}, -sl:{visual_challenge:"Vizualni preskus",audio_challenge:"Zvo\u010dni preskus",refresh_btn:"Nov preskus",instructions_visual:"Vnesite besedilo:",instructions_audio:"Natipkajte, kaj sli\u0161ite:",help_btn:"Pomo\u010d",play_again:"Znova predvajaj zvok",cant_hear_this:"Prenesi zvok kot MP3",incorrect_try_again:"Napa\u010dno. Poskusite znova.",image_alt_text:"Slika izziva reCAPTCHA",privacy_and_terms:"Zasebnost in pogoji"},sr:{visual_challenge:"\u041f\u0440\u0438\u043c\u0438\u0442\u0435 \u0432\u0438\u0437\u0443\u0435\u043b\u043d\u0438 \u0443\u043f\u0438\u0442", -audio_challenge:"\u041f\u0440\u0438\u043c\u0438\u0442\u0435 \u0430\u0443\u0434\u0438\u043e \u0443\u043f\u0438\u0442",refresh_btn:"\u041f\u0440\u0438\u043c\u0438\u0442\u0435 \u043d\u043e\u0432\u0438 \u0443\u043f\u0438\u0442",instructions_visual:"\u0423\u043d\u0435\u0441\u0438\u0442\u0435 \u0442\u0435\u043a\u0441\u0442:",instructions_audio:"\u041e\u0442\u043a\u0443\u0446\u0430\u0458\u0442\u0435 \u043e\u043d\u043e \u0448\u0442\u043e \u0447\u0443\u0458\u0435\u0442\u0435:",help_btn:"\u041f\u043e\u043c\u043e\u045b", -play_again:"\u041f\u043e\u043d\u043e\u0432\u043e \u043f\u0443\u0441\u0442\u0438 \u0437\u0432\u0443\u043a",cant_hear_this:"\u041f\u0440\u0435\u0443\u0437\u043c\u0438 \u0437\u0432\u0443\u043a \u043a\u0430\u043e MP3 \u0441\u043d\u0438\u043c\u0430\u043a",incorrect_try_again:"\u041d\u0435\u0442\u0430\u0447\u043d\u043e. \u041f\u043e\u043a\u0443\u0448\u0430\u0458\u0442\u0435 \u043f\u043e\u043d\u043e\u0432\u043e.",image_alt_text:"\u0421\u043b\u0438\u043a\u0430 reCAPTCHA \u043f\u0440\u043e\u0432\u0435\u0440\u0435", -privacy_and_terms:"\u041f\u0440\u0438\u0432\u0430\u0442\u043d\u043e\u0441\u0442 \u0438 \u0443\u0441\u043b\u043e\u0432\u0438"},sv:{visual_challenge:"H\u00e4mta captcha i bildformat",audio_challenge:"H\u00e4mta captcha i ljudformat",refresh_btn:"H\u00e4mta ny captcha",instructions_visual:"Skriv texten:",instructions_audio:"Skriv det du h\u00f6r:",help_btn:"Hj\u00e4lp",play_again:"Spela upp ljudet igen",cant_hear_this:"H\u00e4mta ljud som MP3",incorrect_try_again:"Fel. F\u00f6rs\u00f6k igen.",image_alt_text:"reCAPTCHA-bild", -privacy_and_terms:"Sekretess och villkor"},sw:{visual_challenge:"Pata herufi za kusoma",audio_challenge:"Pata herufi za kusikiliza",refresh_btn:"Pata herufi mpya",instructions_visual:"",instructions_audio:"Charaza unachosikia:",help_btn:"Usaidizi",play_again:"Cheza sauti tena",cant_hear_this:"Pakua sauti kama MP3",incorrect_try_again:"Sio sahihi. Jaribu tena.",image_alt_text:"picha ya changamoto ya reCAPTCHA",privacy_and_terms:"Faragha & Masharti"},ta:{visual_challenge:"\u0baa\u0bbe\u0bb0\u0bcd\u0bb5\u0bc8 \u0b9a\u0bc7\u0bb2\u0b9e\u0bcd\u0b9a\u0bc8\u0baa\u0bcd \u0baa\u0bc6\u0bb1\u0bc1\u0b95", -audio_challenge:"\u0b86\u0b9f\u0bbf\u0baf\u0bcb \u0b9a\u0bc7\u0bb2\u0b9e\u0bcd\u0b9a\u0bc8\u0baa\u0bcd \u0baa\u0bc6\u0bb1\u0bc1\u0b95",refresh_btn:"\u0baa\u0bc1\u0ba4\u0bbf\u0baf \u0b9a\u0bc7\u0bb2\u0b9e\u0bcd\u0b9a\u0bc8\u0baa\u0bcd \u0baa\u0bc6\u0bb1\u0bc1\u0b95",instructions_visual:"",instructions_audio:"\u0b95\u0bc7\u0b9f\u0bcd\u0baa\u0ba4\u0bc8 \u0b9f\u0bc8\u0baa\u0bcd \u0b9a\u0bc6\u0baf\u0bcd\u0b95:",help_btn:"\u0b89\u0ba4\u0bb5\u0bbf",play_again:"\u0b92\u0bb2\u0bbf\u0baf\u0bc8 \u0bae\u0bc0\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bcd \u0b87\u0baf\u0b95\u0bcd\u0b95\u0bc1", -cant_hear_this:"\u0b92\u0bb2\u0bbf\u0baf\u0bc8 MP3 \u0b86\u0b95 \u0baa\u0ba4\u0bbf\u0bb5\u0bbf\u0bb1\u0b95\u0bcd\u0b95\u0bc1\u0b95",incorrect_try_again:"\u0ba4\u0bb5\u0bb1\u0bbe\u0ba9\u0ba4\u0bc1. \u0bae\u0bc0\u0ba3\u0bcd\u0b9f\u0bc1\u0bae\u0bcd \u0bae\u0bc1\u0baf\u0bb2\u0bb5\u0bc1\u0bae\u0bcd.",image_alt_text:"reCAPTCHA \u0b9a\u0bc7\u0bb2\u0b9e\u0bcd\u0b9a\u0bcd \u0baa\u0b9f\u0bae\u0bcd",privacy_and_terms:"\u0ba4\u0ba9\u0bbf\u0baf\u0bc1\u0bb0\u0bbf\u0bae\u0bc8 & \u0bb5\u0bbf\u0ba4\u0bbf\u0bae\u0bc1\u0bb1\u0bc8\u0b95\u0bb3\u0bcd"}, -te:{visual_challenge:"\u0c12\u0c15 \u0c26\u0c43\u0c36\u0c4d\u0c2f\u0c2e\u0c3e\u0c28 \u0c38\u0c35\u0c3e\u0c32\u0c41\u0c28\u0c41 \u0c38\u0c4d\u0c35\u0c40\u0c15\u0c30\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f",audio_challenge:"\u0c12\u0c15 \u0c06\u0c21\u0c3f\u0c2f\u0c4b \u0c38\u0c35\u0c3e\u0c32\u0c41\u0c28\u0c41 \u0c38\u0c4d\u0c35\u0c40\u0c15\u0c30\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f",refresh_btn:"\u0c15\u0c4d\u0c30\u0c4a\u0c24\u0c4d\u0c24 \u0c38\u0c35\u0c3e\u0c32\u0c41\u0c28\u0c41 \u0c38\u0c4d\u0c35\u0c40\u0c15\u0c30\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f", -instructions_visual:"",instructions_audio:"\u0c2e\u0c40\u0c30\u0c41 \u0c35\u0c3f\u0c28\u0c4d\u0c28\u0c26\u0c3f \u0c1f\u0c48\u0c2a\u0c4d \u0c1a\u0c47\u0c2f\u0c02\u0c21\u0c3f:",help_btn:"\u0c38\u0c39\u0c3e\u0c2f\u0c02",play_again:"\u0c27\u0c4d\u0c35\u0c28\u0c3f\u0c28\u0c3f \u0c2e\u0c33\u0c4d\u0c32\u0c40 \u0c2a\u0c4d\u0c32\u0c47 \u0c1a\u0c47\u0c2f\u0c3f",cant_hear_this:"\u0c27\u0c4d\u0c35\u0c28\u0c3f\u0c28\u0c3f MP3 \u0c35\u0c32\u0c46 \u0c21\u0c4c\u0c28\u0c4d\u200c\u0c32\u0c4b\u0c21\u0c4d \u0c1a\u0c47\u0c2f\u0c3f", -incorrect_try_again:"\u0c24\u0c2a\u0c4d\u0c2a\u0c41. \u0c2e\u0c33\u0c4d\u0c32\u0c40 \u0c2a\u0c4d\u0c30\u0c2f\u0c24\u0c4d\u0c28\u0c3f\u0c02\u0c1a\u0c02\u0c21\u0c3f.",image_alt_text:"reCAPTCHA \u0c38\u0c35\u0c3e\u0c32\u0c41 \u0c1a\u0c3f\u0c24\u0c4d\u0c30\u0c02",privacy_and_terms:"\u0c17\u0c4b\u0c2a\u0c4d\u0c2f\u0c24 & \u0c28\u0c3f\u0c2c\u0c02\u0c27\u0c28\u0c32\u0c41"},th:{visual_challenge:"\u0e23\u0e31\u0e1a\u0e04\u0e27\u0e32\u0e21\u0e17\u0e49\u0e32\u0e17\u0e32\u0e22\u0e14\u0e49\u0e32\u0e19\u0e20\u0e32\u0e1e", -audio_challenge:"\u0e23\u0e31\u0e1a\u0e04\u0e27\u0e32\u0e21\u0e17\u0e49\u0e32\u0e17\u0e32\u0e22\u0e14\u0e49\u0e32\u0e19\u0e40\u0e2a\u0e35\u0e22\u0e07",refresh_btn:"\u0e23\u0e31\u0e1a\u0e04\u0e27\u0e32\u0e21\u0e17\u0e49\u0e32\u0e17\u0e32\u0e22\u0e43\u0e2b\u0e21\u0e48",instructions_visual:"\u0e1e\u0e34\u0e21\u0e1e\u0e4c\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21\u0e19\u0e35\u0e49:",instructions_audio:"\u0e1e\u0e34\u0e21\u0e1e\u0e4c\u0e2a\u0e34\u0e48\u0e07\u0e17\u0e35\u0e48\u0e04\u0e38\u0e13\u0e44\u0e14\u0e49\u0e22\u0e34\u0e19:", -help_btn:"\u0e04\u0e27\u0e32\u0e21\u0e0a\u0e48\u0e27\u0e22\u0e40\u0e2b\u0e25\u0e37\u0e2d",play_again:"\u0e40\u0e25\u0e48\u0e19\u0e40\u0e2a\u0e35\u0e22\u0e07\u0e2d\u0e35\u0e01\u0e04\u0e23\u0e31\u0e49\u0e07",cant_hear_this:"\u0e14\u0e32\u0e27\u0e42\u0e2b\u0e25\u0e14\u0e40\u0e2a\u0e35\u0e22\u0e07\u0e40\u0e1b\u0e47\u0e19 MP3",incorrect_try_again:"\u0e44\u0e21\u0e48\u0e16\u0e39\u0e01\u0e15\u0e49\u0e2d\u0e07 \u0e25\u0e2d\u0e07\u0e2d\u0e35\u0e01\u0e04\u0e23\u0e31\u0e49\u0e07",image_alt_text:"\u0e23\u0e2b\u0e31\u0e2a\u0e20\u0e32\u0e1e reCAPTCHA", -privacy_and_terms:"\u0e19\u0e42\u0e22\u0e1a\u0e32\u0e22\u0e2a\u0e48\u0e27\u0e19\u0e1a\u0e38\u0e04\u0e04\u0e25\u0e41\u0e25\u0e30\u0e02\u0e49\u0e2d\u0e01\u0e33\u0e2b\u0e19\u0e14"},tr:{visual_challenge:"G\u00f6rsel sorgu al",audio_challenge:"Sesli sorgu al",refresh_btn:"Yeniden y\u00fckle",instructions_visual:"Metni yaz\u0131n:",instructions_audio:"Duydu\u011funuzu yaz\u0131n:",help_btn:"Yard\u0131m",play_again:"Sesi tekrar \u00e7al",cant_hear_this:"Sesi MP3 olarak indir",incorrect_try_again:"Yanl\u0131\u015f. Tekrar deneyin.", -image_alt_text:"reCAPTCHA sorusu resmi",privacy_and_terms:"Gizlilik ve \u015eartlar"},uk:{visual_challenge:"\u041e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0432\u0456\u0437\u0443\u0430\u043b\u044c\u043d\u0438\u0439 \u0442\u0435\u043a\u0441\u0442",audio_challenge:"\u041e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0430\u0443\u0434\u0456\u043e\u0437\u0430\u043f\u0438\u0441",refresh_btn:"\u041e\u043d\u043e\u0432\u0438\u0442\u0438 \u0442\u0435\u043a\u0441\u0442",instructions_visual:"\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0442\u0435\u043a\u0441\u0442:", -instructions_audio:"\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u043e\u0447\u0443\u0442\u0435:",help_btn:"\u0414\u043e\u0432\u0456\u0434\u043a\u0430",play_again:"\u0412\u0456\u0434\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u0437\u0430\u043f\u0438\u0441 \u0449\u0435 \u0440\u0430\u0437",cant_hear_this:"\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0437\u0430\u043f\u0438\u0441 \u044f\u043a MP3",incorrect_try_again:"\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e. \u0421\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437.", -image_alt_text:"\u0417\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f \u0437\u0430\u0432\u0434\u0430\u043d\u043d\u044f reCAPTCHA",privacy_and_terms:"\u041a\u043e\u043d\u0444\u0456\u0434\u0435\u043d\u0446\u0456\u0439\u043d\u0456\u0441\u0442\u044c \u0456 \u0443\u043c\u043e\u0432\u0438"},ur:{visual_challenge:"\u0627\u06cc\u06a9 \u0645\u0631\u0626\u06cc \u0686\u06cc\u0644\u0646\u062c \u062d\u0627\u0635\u0644 \u06a9\u0631\u06cc\u06ba",audio_challenge:"\u0627\u06cc\u06a9 \u0622\u0688\u06cc\u0648 \u0686\u06cc\u0644\u0646\u062c \u062d\u0627\u0635\u0644 \u06a9\u0631\u06cc\u06ba", -refresh_btn:"\u0627\u06cc\u06a9 \u0646\u06cc\u0627 \u0686\u06cc\u0644\u0646\u062c \u062d\u0627\u0635\u0644 \u06a9\u0631\u06cc\u06ba",instructions_visual:"",instructions_audio:"\u062c\u0648 \u0633\u0646\u0627\u0626\u06cc \u062f\u06cc\u062a\u0627 \u06c1\u06d2 \u0648\u06c1 \u0679\u0627\u0626\u067e \u06a9\u0631\u06cc\u06ba:",help_btn:"\u0645\u062f\u062f",play_again:"\u0622\u0648\u0627\u0632 \u062f\u0648\u0628\u0627\u0631\u06c1 \u0686\u0644\u0627\u0626\u06cc\u06ba",cant_hear_this:"\u0622\u0648\u0627\u0632 \u06a9\u0648 MP3 \u06a9\u06d2 \u0628\u0637\u0648\u0631 \u0688\u0627\u0624\u0646 \u0644\u0648\u0688 \u06a9\u0631\u06cc\u06ba", -incorrect_try_again:"\u063a\u0644\u0637\u06d4 \u062f\u0648\u0628\u0627\u0631\u06c1 \u06a9\u0648\u0634\u0634 \u06a9\u0631\u06cc\u06ba\u06d4",image_alt_text:"reCAPTCHA \u0686\u06cc\u0644\u0646\u062c \u0648\u0627\u0644\u06cc \u0634\u0628\u06cc\u06c1",privacy_and_terms:"\u0631\u0627\u0632\u062f\u0627\u0631\u06cc \u0648 \u0634\u0631\u0627\u0626\u0637"},vi:{visual_challenge:"Nh\u1eadn th\u1eed th\u00e1ch h\u00ecnh \u1ea3nh",audio_challenge:"Nh\u1eadn th\u1eed th\u00e1ch \u00e2m thanh",refresh_btn:"Nh\u1eadn th\u1eed th\u00e1ch m\u1edbi", -instructions_visual:"Nh\u1eadp v\u0103n b\u1ea3n:",instructions_audio:"Nh\u1eadp n\u1ed9i dung b\u1ea1n nghe th\u1ea5y:",help_btn:"Tr\u1ee3 gi\u00fap",play_again:"Ph\u00e1t l\u1ea1i \u00e2m thanh",cant_hear_this:"T\u1ea3i \u00e2m thanh xu\u1ed1ng d\u01b0\u1edbi d\u1ea1ng MP3",incorrect_try_again:"Kh\u00f4ng ch\u00ednh x\u00e1c. H\u00e3y th\u1eed l\u1ea1i.",image_alt_text:"H\u00ecnh x\u00e1c th\u1ef1c reCAPTCHA",privacy_and_terms:"B\u1ea3o m\u1eadt v\u00e0 \u0111i\u1ec1u kho\u1ea3n"},"zh-CN":ra,"zh-HK":{visual_challenge:"\u56de\u7b54\u5716\u50cf\u9a57\u8b49\u554f\u984c", -audio_challenge:"\u53d6\u5f97\u8a9e\u97f3\u9a57\u8b49\u554f\u984c",refresh_btn:"\u63db\u4e00\u500b\u9a57\u8b49\u554f\u984c",instructions_visual:"\u8f38\u5165\u6587\u5b57\uff1a",instructions_audio:"\u9375\u5165\u60a8\u6240\u807d\u5230\u7684\uff1a",help_btn:"\u8aaa\u660e",play_again:"\u518d\u6b21\u64ad\u653e\u8072\u97f3",cant_hear_this:"\u5c07\u8072\u97f3\u4e0b\u8f09\u70ba MP3",incorrect_try_again:"\u4e0d\u6b63\u78ba\uff0c\u518d\u8a66\u4e00\u6b21\u3002",image_alt_text:"reCAPTCHA \u9a57\u8b49\u6587\u5b57\u5716\u7247", -privacy_and_terms:"\u79c1\u96b1\u6b0a\u8207\u689d\u6b3e"},"zh-TW":{visual_challenge:"\u53d6\u5f97\u5716\u7247\u9a57\u8b49\u554f\u984c",audio_challenge:"\u53d6\u5f97\u8a9e\u97f3\u9a57\u8b49\u554f\u984c",refresh_btn:"\u53d6\u5f97\u65b0\u7684\u9a57\u8b49\u554f\u984c",instructions_visual:"\u8acb\u8f38\u5165\u5716\u7247\u4e2d\u7684\u6587\u5b57\uff1a",instructions_audio:"\u8acb\u8f38\u5165\u8a9e\u97f3\u5167\u5bb9\uff1a",help_btn:"\u8aaa\u660e",play_again:"\u518d\u6b21\u64ad\u653e",cant_hear_this:"\u4ee5 MP3 \u683c\u5f0f\u4e0b\u8f09\u8072\u97f3", -incorrect_try_again:"\u9a57\u8b49\u78bc\u6709\u8aa4\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002",image_alt_text:"reCAPTCHA \u9a57\u8b49\u6587\u5b57\u5716\u7247",privacy_and_terms:"\u96b1\u79c1\u6b0a\u8207\u689d\u6b3e"},zu:{visual_challenge:"Thola inselelo ebonakalayo",audio_challenge:"Thola inselelo yokulalelwayo",refresh_btn:"Thola inselelo entsha",instructions_visual:"",instructions_audio:"Bhala okuzwayo:",help_btn:"Usizo",play_again:"Phinda udlale okulalelwayo futhi",cant_hear_this:"Layisha umsindo njenge-MP3", -incorrect_try_again:"Akulungile. Zama futhi.",image_alt_text:"umfanekiso oyinselelo we-reCAPTCHA",privacy_and_terms:"Okwangasese kanye nemigomo"},tl:la,he:oa,"in":na,mo:qa,zh:ra};var x=function(a){if(Error.captureStackTrace)Error.captureStackTrace(this,x);else{var b=Error().stack;b&&(this.stack=b)}a&&(this.message=String(a))};u(x,Error);x.prototype.name="CustomError";var ta;var ua=function(a,b){for(var c=a.split("%s"),d="",e=Array.prototype.slice.call(arguments,1);e.length&&1<c.length;)d+=c.shift()+e.shift();return d+c.join("%s")},va=String.prototype.trim?function(a){return a.trim()}:function(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},Da=function(a){if(!wa.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(xa,"&"));-1!=a.indexOf("<")&&(a=a.replace(ya,"<"));-1!=a.indexOf(">")&&(a=a.replace(za,">"));-1!=a.indexOf('"')&&(a=a.replace(Aa,"""));-1!=a.indexOf("'")&& -(a=a.replace(Ba,"'"));-1!=a.indexOf("\x00")&&(a=a.replace(Ca,"�"));return a},xa=/&/g,ya=/</g,za=/>/g,Aa=/"/g,Ba=/'/g,Ca=/\x00/g,wa=/[\x00&<>"']/,Ea=function(a,b){return a<b?-1:a>b?1:0},Fa=function(a){return String(a).replace(/\-([a-z])/g,function(a,c){return c.toUpperCase()})},Ga=function(a){var b=q(void 0)?"undefined".replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08"):"\\s";return a.replace(new RegExp("(^"+(b?"|["+b+"]+":"")+")([a-z])","g"),function(a,b,e){return b+e.toUpperCase()})};var Ha=function(a,b){b.unshift(a);x.call(this,ua.apply(null,b));b.shift()};u(Ha,x);Ha.prototype.name="AssertionError"; -var Ia=function(a,b,c,d){var e="Assertion failed";if(c)var e=e+(": "+c),g=d;else a&&(e+=": "+a,g=b);throw new Ha(""+e,g||[]);},y=function(a,b,c){a||Ia("",null,b,Array.prototype.slice.call(arguments,2))},Ja=function(a,b){throw new Ha("Failure"+(a?": "+a:""),Array.prototype.slice.call(arguments,1));},Ka=function(a,b,c){q(a)||Ia("Expected string but got %s: %s.",[n(a),a],b,Array.prototype.slice.call(arguments,2));return a},La=function(a,b,c){r(a)||Ia("Expected function but got %s: %s.",[n(a),a],b,Array.prototype.slice.call(arguments, -2))};var z=Array.prototype,Ma=z.indexOf?function(a,b,c){y(null!=a.length);return z.indexOf.call(a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a.length+c):c;if(q(a))return q(b)&&1==b.length?a.indexOf(b,c):-1;for(;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},Na=z.forEach?function(a,b,c){y(null!=a.length);z.forEach.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=q(a)?a.split(""):a,g=0;g<d;g++)g in e&&b.call(c,e[g],g,a)},Oa=z.map?function(a,b,c){y(null!=a.length);return z.map.call(a,b,c)}: -function(a,b,c){for(var d=a.length,e=Array(d),g=q(a)?a.split(""):a,f=0;f<d;f++)f in g&&(e[f]=b.call(c,g[f],f,a));return e},Pa=z.some?function(a,b,c){y(null!=a.length);return z.some.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=q(a)?a.split(""):a,g=0;g<d;g++)if(g in e&&b.call(c,e[g],g,a))return!0;return!1},Qa=function(a,b){var c=Ma(a,b),d;if(d=0<=c)y(null!=a.length),z.splice.call(a,c,1);return d},Ra=function(a){var b=a.length;if(0<b){for(var c=Array(b),d=0;d<b;d++)c[d]=a[d];return c}return[]}, -Sa=function(a,b,c){y(null!=a.length);return 2>=arguments.length?z.slice.call(a,b):z.slice.call(a,b,c)};var Ta=function(a,b){for(var c in a)b.call(void 0,a[c],c,a)},Ua=function(a){var b=[],c=0,d;for(d in a)b[c++]=d;return b},Va=function(a){for(var b in a)return!1;return!0},Xa=function(){var a=Wa()?k.google_ad:null,b={},c;for(c in a)b[c]=a[c];return b},Ya="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),Za=function(a,b){for(var c,d,e=1;e<arguments.length;e++){d=arguments[e];for(c in d)a[c]=d[c];for(var g=0;g<Ya.length;g++)c=Ya[g],Object.prototype.hasOwnProperty.call(d, -c)&&(a[c]=d[c])}},$a=function(a){var b=arguments.length;if(1==b&&p(arguments[0]))return $a.apply(null,arguments[0]);for(var c={},d=0;d<b;d++)c[arguments[d]]=!0;return c};var A;t:{var ab=k.navigator;if(ab){var bb=ab.userAgent;if(bb){A=bb;break t}}A=""}var B=function(a){return-1!=A.indexOf(a)};var cb=B("Opera")||B("OPR"),C=B("Trident")||B("MSIE"),D=B("Gecko")&&-1==A.toLowerCase().indexOf("webkit")&&!(B("Trident")||B("MSIE")),E=-1!=A.toLowerCase().indexOf("webkit"),db=function(){var a=k.document;return a?a.documentMode:void 0},eb=function(){var a="",b;if(cb&&k.opera)return a=k.opera.version,r(a)?a():a;D?b=/rv\:([^\);]+)(\)|;)/:C?b=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:E&&(b=/WebKit\/(\S+)/);b&&(a=(a=b.exec(A))?a[1]:"");return C&&(b=db(),b>parseFloat(a))?String(b):a}(),fb={},F=function(a){var b; -if(!(b=fb[a])){b=0;for(var c=va(String(eb)).split("."),d=va(String(a)).split("."),e=Math.max(c.length,d.length),g=0;0==b&&g<e;g++){var f=c[g]||"",m=d[g]||"",$=RegExp("(\\d*)(\\D*)","g"),K=RegExp("(\\d*)(\\D*)","g");do{var G=$.exec(f)||["","",""],aa=K.exec(m)||["","",""];if(0==G[0].length&&0==aa[0].length)break;b=Ea(0==G[1].length?0:parseInt(G[1],10),0==aa[1].length?0:parseInt(aa[1],10))||Ea(0==G[2].length,0==aa[2].length)||Ea(G[2],aa[2])}while(0==b)}b=fb[a]=0<=b}return b},gb=k.document,hb=gb&&C?db()|| -("CSS1Compat"==gb.compatMode?parseInt(eb,10):5):void 0;var ib=function(a){if(8192>a.length)return String.fromCharCode.apply(null,a);for(var b="",c=0;c<a.length;c+=8192)var d=Sa(a,c,c+8192),b=b+String.fromCharCode.apply(null,d);return b},jb=function(a){return Oa(a,function(a){a=a.toString(16);return 1<a.length?a:"0"+a}).join("")};var kb=null,lb=null,mb=function(a){if(!kb){kb={};lb={};for(var b=0;65>b;b++)kb[b]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(b),lb[kb[b]]=b,62<=b&&(lb["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.".charAt(b)]=b)}for(var b=lb,c=[],d=0;d<a.length;){var e=b[a.charAt(d++)],g=d<a.length?b[a.charAt(d)]:0;++d;var f=d<a.length?b[a.charAt(d)]:64;++d;var m=d<a.length?b[a.charAt(d)]:64;++d;if(null==e||null==g||null==f||null==m)throw Error();c.push(e<<2|g>> -4);64!=f&&(c.push(g<<4&240|f>>2),64!=m&&c.push(f<<6&192|m))}return c};var H=function(){this.disposed_=this.disposed_;this.onDisposeCallbacks_=this.onDisposeCallbacks_};H.prototype.disposed_=!1;H.prototype.dispose=function(){this.disposed_||(this.disposed_=!0,this.disposeInternal())};var nb=function(a,b){a.onDisposeCallbacks_||(a.onDisposeCallbacks_=[]);a.onDisposeCallbacks_.push(l(void 0)?s(b,void 0):b)};H.prototype.disposeInternal=function(){if(this.onDisposeCallbacks_)for(;this.onDisposeCallbacks_.length;)this.onDisposeCallbacks_.shift()()}; -var ob=function(a){a&&"function"==typeof a.dispose&&a.dispose()};var pb=!C||C&&9<=hb;!D&&!C||C&&C&&9<=hb||D&&F("1.9.1");C&&F("9");var sb=function(a){return a?new qb(rb(a)):ta||(ta=new qb)},tb=function(a,b){return q(b)?a.getElementById(b):b},vb=function(a,b){Ta(b,function(b,d){"style"==d?a.style.cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:d in ub?a.setAttribute(ub[d],b):0==d.lastIndexOf("aria-",0)||0==d.lastIndexOf("data-",0)?a.setAttribute(d,b):a[d]=b})},ub={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan", -type:"type",usemap:"useMap",valign:"vAlign",width:"width"},xb=function(a,b,c){return wb(document,arguments)},wb=function(a,b){var c=b[0],d=b[1];if(!pb&&d&&(d.name||d.type)){c=["<",c];d.name&&c.push(' name="',Da(d.name),'"');if(d.type){c.push(' type="',Da(d.type),'"');var e={};Za(e,d);delete e.type;d=e}c.push(">");c=c.join("")}c=a.createElement(c);d&&(q(d)?c.className=d:p(d)?c.className=d.join(" "):vb(c,d));2<b.length&&yb(a,c,b);return c},yb=function(a,b,c){function d(c){c&&b.appendChild(q(c)?a.createTextNode(c): -c)}for(var e=2;e<c.length;e++){var g=c[e];!ca(g)||da(g)&&0<g.nodeType?d(g):Na(zb(g)?Ra(g):g,d)}},Ab=function(a){for(var b;b=a.firstChild;)a.removeChild(b)},Bb=function(a){a&&a.parentNode&&a.parentNode.removeChild(a)},rb=function(a){y(a,"Node cannot be null or undefined.");return 9==a.nodeType?a:a.ownerDocument||a.document},zb=function(a){if(a&&"number"==typeof a.length){if(da(a))return"function"==typeof a.item||"string"==typeof a.item;if(r(a))return"function"==typeof a.item}return!1},qb=function(a){this.document_= -a||k.document||document};h=qb.prototype;h.getDomHelper=sb;h.getElement=function(a){return tb(this.document_,a)};h.$=qb.prototype.getElement;h.createDom=function(a,b,c){return wb(this.document_,arguments)};h.createElement=function(a){return this.document_.createElement(a)};h.createTextNode=function(a){return this.document_.createTextNode(String(a))};h.appendChild=function(a,b){a.appendChild(b)};var Cb=function(a){k.setTimeout(function(){throw a;},0)},Db,Eb=function(){var a=k.MessageChannel;"undefined"===typeof a&&"undefined"!==typeof window&&window.postMessage&&window.addEventListener&&(a=function(){var a=document.createElement("iframe");a.style.display="none";a.src="";document.documentElement.appendChild(a);var b=a.contentWindow,a=b.document;a.open();a.write("");a.close();var c="callImmediate"+Math.random(),d="file:"==b.location.protocol?"*":b.location.protocol+"//"+b.location.host,a=s(function(a){if(("*"== -d||a.origin==d)&&a.data==c)this.port1.onmessage()},this);b.addEventListener("message",a,!1);this.port1={};this.port2={postMessage:function(){b.postMessage(c,d)}}});if("undefined"!==typeof a&&!B("Trident")&&!B("MSIE")){var b=new a,c={},d=c;b.port1.onmessage=function(){if(l(c.next)){c=c.next;var a=c.cb;c.cb=null;a()}};return function(a){d.next={cb:a};d=d.next;b.port2.postMessage(0)}}return"undefined"!==typeof document&&"onreadystatechange"in document.createElement("script")?function(a){var b=document.createElement("script"); -b.onreadystatechange=function(){b.onreadystatechange=null;b.parentNode.removeChild(b);b=null;a();a=null};document.documentElement.appendChild(b)}:function(a){k.setTimeout(a,0)}};var Kb=function(a,b){Fb||Gb();Hb||(Fb(),Hb=!0);Ib.push(new Jb(a,b))},Fb,Gb=function(){if(k.Promise&&k.Promise.resolve){var a=k.Promise.resolve();Fb=function(){a.then(Lb)}}else Fb=function(){var a=Lb;!r(k.setImmediate)||k.Window&&k.Window.prototype.setImmediate==k.setImmediate?(Db||(Db=Eb()),Db(a)):k.setImmediate(a)}},Hb=!1,Ib=[],Lb=function(){for(;Ib.length;){var a=Ib;Ib=[];for(var b=0;b<a.length;b++){var c=a[b];try{c.fn.call(c.scope)}catch(d){Cb(d)}}}Hb=!1},Jb=function(a,b){this.fn=a;this.scope= -b};var Mb=function(a){a.prototype.then=a.prototype.then;a.prototype.$goog_Thenable=!0},Nb=function(a){if(!a)return!1;try{return!!a.$goog_Thenable}catch(b){return!1}};var L=function(a,b){this.state_=0;this.result_=void 0;this.callbackEntries_=this.parent_=null;this.hadUnhandledRejection_=this.executing_=!1;try{var c=this;a.call(b,function(a){I(c,2,a)},function(a){if(!(a instanceof J))try{if(a instanceof Error)throw a;throw Error("Promise rejected.");}catch(b){}I(c,3,a)})}catch(d){I(this,3,d)}}; -L.prototype.then=function(a,b,c){null!=a&&La(a,"opt_onFulfilled should be a function.");null!=b&&La(b,"opt_onRejected should be a function. Did you pass opt_context as the second argument instead of the third?");return Ob(this,r(a)?a:null,r(b)?b:null,c)};Mb(L);L.prototype.cancel=function(a){0==this.state_&&Kb(function(){var b=new J(a);Pb(this,b)},this)}; -var Pb=function(a,b){if(0==a.state_)if(a.parent_){var c=a.parent_;if(c.callbackEntries_){for(var d=0,e=-1,g=0,f;f=c.callbackEntries_[g];g++)if(f=f.child)if(d++,f==a&&(e=g),0<=e&&1<d)break;0<=e&&(0==c.state_&&1==d?Pb(c,b):(d=c.callbackEntries_.splice(e,1)[0],d.child&&Qb(c),d.onRejected(b)))}}else I(a,3,b)},Sb=function(a,b){a.callbackEntries_&&a.callbackEntries_.length||2!=a.state_&&3!=a.state_||Rb(a);a.callbackEntries_||(a.callbackEntries_=[]);a.callbackEntries_.push(b)},Ob=function(a,b,c,d){var e= -{child:null,onFulfilled:null,onRejected:null};e.child=new L(function(a,f){e.onFulfilled=b?function(c){try{var e=b.call(d,c);a(e)}catch(K){f(K)}}:a;e.onRejected=c?function(b){try{var e=c.call(d,b);!l(e)&&b instanceof J?f(b):a(e)}catch(K){f(K)}}:f});e.child.parent_=a;Sb(a,e);return e.child};L.prototype.unblockAndFulfill_=function(a){y(1==this.state_);this.state_=0;I(this,2,a)};L.prototype.unblockAndReject_=function(a){y(1==this.state_);this.state_=0;I(this,3,a)}; -var I=function(a,b,c){if(0==a.state_){if(a==c)b=3,c=new TypeError("Promise cannot resolve to itself");else{if(Nb(c)){a.state_=1;c.then(a.unblockAndFulfill_,a.unblockAndReject_,a);return}if(da(c))try{var d=c.then;if(r(d)){Tb(a,c,d);return}}catch(e){b=3,c=e}}a.result_=c;a.state_=b;Rb(a);3!=b||c instanceof J||Ub(a,c)}},Tb=function(a,b,c){a.state_=1;var d=!1,e=function(b){d||(d=!0,a.unblockAndFulfill_(b))},g=function(b){d||(d=!0,a.unblockAndReject_(b))};try{c.call(b,e,g)}catch(f){g(f)}},Rb=function(a){a.executing_|| -(a.executing_=!0,Kb(a.executeCallbacks_,a))};L.prototype.executeCallbacks_=function(){for(;this.callbackEntries_&&this.callbackEntries_.length;){var a=this.callbackEntries_;this.callbackEntries_=[];for(var b=0;b<a.length;b++){var c=a[b],d=this.result_;if(2==this.state_)c.onFulfilled(d);else c.child&&Qb(this),c.onRejected(d)}}this.executing_=!1}; -var Qb=function(a){for(;a&&a.hadUnhandledRejection_;a=a.parent_)a.hadUnhandledRejection_=!1},Ub=function(a,b){a.hadUnhandledRejection_=!0;Kb(function(){a.hadUnhandledRejection_&&Vb.call(null,b)})},Vb=Cb,J=function(a){x.call(this,a)};u(J,x);J.prototype.name="cancel";/* - Portions of this code are from MochiKit, received by - The Closure Authors under the MIT license. All other code is Copyright - 2005-2009 The Closure Authors. All Rights Reserved. -*/ -var M=function(a,b){this.sequence_=[];this.onCancelFunction_=a;this.defaultScope_=b||null;this.hadError_=this.fired_=!1;this.result_=void 0;this.silentlyCanceled_=this.blocking_=this.blocked_=!1;this.unhandledErrorId_=0;this.parent_=null;this.branches_=0}; -M.prototype.cancel=function(a){if(this.fired_)this.result_ instanceof M&&this.result_.cancel();else{if(this.parent_){var b=this.parent_;delete this.parent_;a?b.cancel(a):(b.branches_--,0>=b.branches_&&b.cancel())}this.onCancelFunction_?this.onCancelFunction_.call(this.defaultScope_,this):this.silentlyCanceled_=!0;this.fired_||Wb(this,new Xb)}};M.prototype.continue_=function(a,b){this.blocked_=!1;Yb(this,a,b)}; -var Yb=function(a,b,c){a.fired_=!0;a.result_=c;a.hadError_=!b;Zb(a)},ac=function(a){if(a.fired_){if(!a.silentlyCanceled_)throw new $b;a.silentlyCanceled_=!1}};M.prototype.callback=function(a){ac(this);bc(a);Yb(this,!0,a)};var Wb=function(a,b){ac(a);bc(b);Yb(a,!1,b)},bc=function(a){y(!(a instanceof M),"An execution sequence may not be initiated with a blocking Deferred.")},cc=function(a,b,c,d){y(!a.blocking_,"Blocking Deferreds can not be re-used");a.sequence_.push([b,c,d]);a.fired_&&Zb(a)}; -M.prototype.then=function(a,b,c){var d,e,g=new L(function(a,b){d=a;e=b});cc(this,d,function(a){a instanceof Xb?g.cancel():e(a)});return g.then(a,b,c)};Mb(M); -var dc=function(a){return Pa(a.sequence_,function(a){return r(a[1])})},Zb=function(a){if(a.unhandledErrorId_&&a.fired_&&dc(a)){var b=a.unhandledErrorId_,c=ec[b];c&&(k.clearTimeout(c.id_),delete ec[b]);a.unhandledErrorId_=0}a.parent_&&(a.parent_.branches_--,delete a.parent_);for(var b=a.result_,d=c=!1;a.sequence_.length&&!a.blocked_;){var e=a.sequence_.shift(),g=e[0],f=e[1],e=e[2];if(g=a.hadError_?f:g)try{var m=g.call(e||a.defaultScope_,b);l(m)&&(a.hadError_=a.hadError_&&(m==b||m instanceof Error), -a.result_=b=m);Nb(b)&&(d=!0,a.blocked_=!0)}catch($){b=$,a.hadError_=!0,dc(a)||(c=!0)}}a.result_=b;d&&(m=s(a.continue_,a,!0),d=s(a.continue_,a,!1),b instanceof M?(cc(b,m,d),b.blocking_=!0):b.then(m,d));c&&(b=new fc(b),ec[b.id_]=b,a.unhandledErrorId_=b.id_)},$b=function(){x.call(this)};u($b,x);$b.prototype.message="Deferred has already fired";$b.prototype.name="AlreadyCalledError";var Xb=function(){x.call(this)};u(Xb,x);Xb.prototype.message="Deferred was canceled";Xb.prototype.name="CanceledError"; -var fc=function(a){this.id_=k.setTimeout(s(this.throwError,this),0);this.error_=a};fc.prototype.throwError=function(){y(ec[this.id_],"Cannot throw an error that is not scheduled.");delete ec[this.id_];throw this.error_;};var ec={};var kc=function(a){var b={},c=b.document||document,d=document.createElement("SCRIPT"),e={script_:d,timeout_:void 0},g=new M(gc,e),f=null,m=null!=b.timeout?b.timeout:5E3;0<m&&(f=window.setTimeout(function(){hc(d,!0);Wb(g,new ic(1,"Timeout reached for loading script "+a))},m),e.timeout_=f);d.onload=d.onreadystatechange=function(){d.readyState&&"loaded"!=d.readyState&&"complete"!=d.readyState||(hc(d,b.cleanupWhenDone||!1,f),g.callback(null))};d.onerror=function(){hc(d,!0,f);Wb(g,new ic(0,"Error while loading script "+ -a))};vb(d,{type:"text/javascript",charset:"UTF-8",src:a});jc(c).appendChild(d);return g},jc=function(a){var b=a.getElementsByTagName("HEAD");return b&&0!=b.length?b[0]:a.documentElement},gc=function(){if(this&&this.script_){var a=this.script_;a&&"SCRIPT"==a.tagName&&hc(a,!0,this.timeout_)}},hc=function(a,b,c){null!=c&&k.clearTimeout(c);a.onload=ba;a.onerror=ba;a.onreadystatechange=ba;b&&window.setTimeout(function(){Bb(a)},0)},ic=function(a,b){var c="Jsloader error (code #"+a+")";b&&(c+=": "+b);x.call(this, -c);this.code=a};u(ic,x);var lc=function(a){lc[" "](a);return a};lc[" "]=ba;var mc=!C||C&&9<=hb,nc=C&&!F("9");!E||F("528");D&&F("1.9b")||C&&F("8")||cb&&F("9.5")||E&&F("528");D&&!F("8")||C&&F("9");var N=function(a,b){this.type=a;this.currentTarget=this.target=b;this.defaultPrevented=this.propagationStopped_=!1;this.returnValue_=!0};N.prototype.disposeInternal=function(){};N.prototype.dispose=function(){};N.prototype.preventDefault=function(){this.defaultPrevented=!0;this.returnValue_=!1};var O=function(a,b){N.call(this,a?a.type:"");this.relatedTarget=this.currentTarget=this.target=null;this.charCode=this.keyCode=this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;this.metaKey=this.shiftKey=this.altKey=this.ctrlKey=!1;this.event_=this.state=null;if(a){var c=this.type=a.type;this.target=a.target||a.srcElement;this.currentTarget=b;var d=a.relatedTarget;if(d){if(D){var e;t:{try{lc(d.nodeName);e=!0;break t}catch(g){}e=!1}e||(d=null)}}else"mouseover"== -c?d=a.fromElement:"mouseout"==c&&(d=a.toElement);this.relatedTarget=d;this.offsetX=E||void 0!==a.offsetX?a.offsetX:a.layerX;this.offsetY=E||void 0!==a.offsetY?a.offsetY:a.layerY;this.clientX=void 0!==a.clientX?a.clientX:a.pageX;this.clientY=void 0!==a.clientY?a.clientY:a.pageY;this.screenX=a.screenX||0;this.screenY=a.screenY||0;this.button=a.button;this.keyCode=a.keyCode||0;this.charCode=a.charCode||("keypress"==c?a.keyCode:0);this.ctrlKey=a.ctrlKey;this.altKey=a.altKey;this.shiftKey=a.shiftKey;this.metaKey= -a.metaKey;this.state=a.state;this.event_=a;a.defaultPrevented&&this.preventDefault()}};u(O,N);O.prototype.preventDefault=function(){O.superClass_.preventDefault.call(this);var a=this.event_;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,nc)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};O.prototype.disposeInternal=function(){};var oc="closure_listenable_"+(1E6*Math.random()|0),pc=0;var qc=function(a,b,c,d,e){this.listener=a;this.proxy=null;this.src=b;this.type=c;this.capture=!!d;this.handler=e;this.key=++pc;this.removed=this.callOnce=!1},rc=function(a){a.removed=!0;a.listener=null;a.proxy=null;a.src=null;a.handler=null};var P=function(a){this.src=a;this.listeners={};this.typeCount_=0};P.prototype.add=function(a,b,c,d,e){var g=a.toString();a=this.listeners[g];a||(a=this.listeners[g]=[],this.typeCount_++);var f=sc(a,b,d,e);-1<f?(b=a[f],c||(b.callOnce=!1)):(b=new qc(b,this.src,g,!!d,e),b.callOnce=c,a.push(b));return b}; -P.prototype.remove=function(a,b,c,d){a=a.toString();if(!(a in this.listeners))return!1;var e=this.listeners[a];b=sc(e,b,c,d);return-1<b?(rc(e[b]),y(null!=e.length),z.splice.call(e,b,1),0==e.length&&(delete this.listeners[a],this.typeCount_--),!0):!1};var tc=function(a,b){var c=b.type;if(!(c in a.listeners))return!1;var d=Qa(a.listeners[c],b);d&&(rc(b),0==a.listeners[c].length&&(delete a.listeners[c],a.typeCount_--));return d}; -P.prototype.removeAll=function(a){a=a&&a.toString();var b=0,c;for(c in this.listeners)if(!a||c==a){for(var d=this.listeners[c],e=0;e<d.length;e++)++b,rc(d[e]);delete this.listeners[c];this.typeCount_--}return b};P.prototype.getListener=function(a,b,c,d){a=this.listeners[a.toString()];var e=-1;a&&(e=sc(a,b,c,d));return-1<e?a[e]:null};var sc=function(a,b,c,d){for(var e=0;e<a.length;++e){var g=a[e];if(!g.removed&&g.listener==b&&g.capture==!!c&&g.handler==d)return e}return-1};var uc="closure_lm_"+(1E6*Math.random()|0),vc={},wc=0,xc=function(a,b,c,d,e){if(p(b)){for(var g=0;g<b.length;g++)xc(a,b[g],c,d,e);return null}c=yc(c);if(a&&a[oc])a=a.listen(b,c,d,e);else{if(!b)throw Error("Invalid event type");var g=!!d,f=zc(a);f||(a[uc]=f=new P(a));c=f.add(b,c,!1,d,e);c.proxy||(d=Ac(),c.proxy=d,d.src=a,d.listener=c,a.addEventListener?a.addEventListener(b.toString(),d,g):a.attachEvent(Bc(b.toString()),d),wc++);a=c}return a},Ac=function(){var a=Cc,b=mc?function(c){return a.call(b.src, -b.listener,c)}:function(c){c=a.call(b.src,b.listener,c);if(!c)return c};return b},Dc=function(a,b,c,d,e){if(p(b))for(var g=0;g<b.length;g++)Dc(a,b[g],c,d,e);else c=yc(c),a&&a[oc]?a.unlisten(b,c,d,e):a&&(a=zc(a))&&(b=a.getListener(b,c,!!d,e))&&Ec(b)},Ec=function(a){if("number"==typeof a||!a||a.removed)return!1;var b=a.src;if(b&&b[oc])return tc(b.eventTargetListeners_,a);var c=a.type,d=a.proxy;b.removeEventListener?b.removeEventListener(c,d,a.capture):b.detachEvent&&b.detachEvent(Bc(c),d);wc--;(c=zc(b))? -(tc(c,a),0==c.typeCount_&&(c.src=null,b[uc]=null)):rc(a);return!0},Bc=function(a){return a in vc?vc[a]:vc[a]="on"+a},Gc=function(a,b,c,d){var e=1;if(a=zc(a))if(b=a.listeners[b.toString()])for(b=b.concat(),a=0;a<b.length;a++){var g=b[a];g&&g.capture==c&&!g.removed&&(e&=!1!==Fc(g,d))}return Boolean(e)},Fc=function(a,b){var c=a.listener,d=a.handler||a.src;a.callOnce&&Ec(a);return c.call(d,b)},Cc=function(a,b){if(a.removed)return!0;if(!mc){var c;if(!(c=b))t:{c=["window","event"];for(var d=k,e;e=c.shift();)if(null!= -d[e])d=d[e];else{c=null;break t}c=d}e=c;c=new O(e,this);d=!0;if(!(0>e.keyCode||void 0!=e.returnValue)){t:{var g=!1;if(0==e.keyCode)try{e.keyCode=-1;break t}catch(f){g=!0}if(g||void 0==e.returnValue)e.returnValue=!0}e=[];for(g=c.currentTarget;g;g=g.parentNode)e.push(g);for(var g=a.type,m=e.length-1;!c.propagationStopped_&&0<=m;m--)c.currentTarget=e[m],d&=Gc(e[m],g,!0,c);for(m=0;!c.propagationStopped_&&m<e.length;m++)c.currentTarget=e[m],d&=Gc(e[m],g,!1,c)}return d}return Fc(a,new O(b,this))},zc=function(a){a= -a[uc];return a instanceof P?a:null},Hc="__closure_events_fn_"+(1E9*Math.random()>>>0),yc=function(a){y(a,"Listener can not be null.");if(r(a))return a;y(a.handleEvent,"An object listener must have handleEvent method.");a[Hc]||(a[Hc]=function(b){return a.handleEvent(b)});return a[Hc]};var Q=function(a){H.call(this);this.handler_=a;this.keys_={}};u(Q,H);var Ic=[];h=Q.prototype;h.listen=function(a,b,c,d){p(b)||(b&&(Ic[0]=b.toString()),b=Ic);for(var e=0;e<b.length;e++){var g=xc(a,b[e],c||this.handleEvent,d||!1,this.handler_||this);if(!g)break;this.keys_[g.key]=g}return this}; -h.unlisten=function(a,b,c,d,e){if(p(b))for(var g=0;g<b.length;g++)this.unlisten(a,b[g],c,d,e);else c=c||this.handleEvent,e=e||this.handler_||this,c=yc(c),d=!!d,b=a&&a[oc]?a.getListener(b,c,d,e):a?(a=zc(a))?a.getListener(b,c,d,e):null:null,b&&(Ec(b),delete this.keys_[b.key]);return this};h.removeAll=function(){Ta(this.keys_,Ec);this.keys_={}};h.disposeInternal=function(){Q.superClass_.disposeInternal.call(this);this.removeAll()}; -h.handleEvent=function(){throw Error("EventHandler.handleEvent not implemented");};var R=function(){H.call(this);this.eventTargetListeners_=new P(this);this.actualEventTarget_=this;this.parentEventTarget_=null};u(R,H);R.prototype[oc]=!0;h=R.prototype;h.setParentEventTarget=function(a){this.parentEventTarget_=a};h.addEventListener=function(a,b,c,d){xc(this,a,b,c,d)};h.removeEventListener=function(a,b,c,d){Dc(this,a,b,c,d)}; -h.dispatchEvent=function(a){Jc(this);var b,c=this.parentEventTarget_;if(c){b=[];for(var d=1;c;c=c.parentEventTarget_)b.push(c),y(1E3>++d,"infinite loop")}c=this.actualEventTarget_;d=a.type||a;if(q(a))a=new N(a,c);else if(a instanceof N)a.target=a.target||c;else{var e=a;a=new N(d,c);Za(a,e)}var e=!0,g;if(b)for(var f=b.length-1;!a.propagationStopped_&&0<=f;f--)g=a.currentTarget=b[f],e=Kc(g,d,!0,a)&&e;a.propagationStopped_||(g=a.currentTarget=c,e=Kc(g,d,!0,a)&&e,a.propagationStopped_||(e=Kc(g,d,!1,a)&& -e));if(b)for(f=0;!a.propagationStopped_&&f<b.length;f++)g=a.currentTarget=b[f],e=Kc(g,d,!1,a)&&e;return e};h.disposeInternal=function(){R.superClass_.disposeInternal.call(this);this.eventTargetListeners_&&this.eventTargetListeners_.removeAll(void 0);this.parentEventTarget_=null};h.listen=function(a,b,c,d){Jc(this);return this.eventTargetListeners_.add(String(a),b,!1,c,d)};h.unlisten=function(a,b,c,d){return this.eventTargetListeners_.remove(String(a),b,c,d)}; -var Kc=function(a,b,c,d){b=a.eventTargetListeners_.listeners[String(b)];if(!b)return!0;b=b.concat();for(var e=!0,g=0;g<b.length;++g){var f=b[g];if(f&&!f.removed&&f.capture==c){var m=f.listener,$=f.handler||f.src;f.callOnce&&tc(a.eventTargetListeners_,f);e=!1!==m.call($,d)&&e}}return e&&0!=d.returnValue_};R.prototype.getListener=function(a,b,c,d){return this.eventTargetListeners_.getListener(String(a),b,c,d)};var Jc=function(a){y(a.eventTargetListeners_,"Event target is not initialized. Did you call the superclass (goog.events.EventTarget) constructor?")};var S=function(a){R.call(this);this.imageIdToRequestMap_={};this.imageIdToImageMap_={};this.handler_=new Q(this);this.parent_=a};u(S,R);var Lc=[C&&!F("11")?"readystatechange":"load","abort","error"],Mc=function(a,b,c){(c=q(c)?c:c.src)&&(a.imageIdToRequestMap_[b]={src:c,corsRequestType:l(void 0)?void 0:null})}; -S.prototype.start=function(){var a=this.imageIdToRequestMap_;Na(Ua(a),function(b){var c=a[b];if(c&&(delete a[b],!this.disposed_)){var d;d=this.parent_?sb(this.parent_).createDom("img"):new Image;c.corsRequestType&&(d.crossOrigin=c.corsRequestType);this.handler_.listen(d,Lc,this.onNetworkEvent_);this.imageIdToImageMap_[b]=d;d.id=b;d.src=c.src}},this)}; -S.prototype.onNetworkEvent_=function(a){var b=a.currentTarget;if(b){if("readystatechange"==a.type)if("complete"==b.readyState)a.type="load";else return;"undefined"==typeof b.naturalWidth&&("load"==a.type?(b.naturalWidth=b.width,b.naturalHeight=b.height):(b.naturalWidth=0,b.naturalHeight=0));this.dispatchEvent({type:a.type,target:b});!this.disposed_&&(a=b.id,delete this.imageIdToRequestMap_[a],b=this.imageIdToImageMap_[a])&&(delete this.imageIdToImageMap_[a],this.handler_.unlisten(b,Lc,this.onNetworkEvent_), -Va(this.imageIdToImageMap_)&&Va(this.imageIdToRequestMap_)&&this.dispatchEvent("complete"))}};S.prototype.disposeInternal=function(){delete this.imageIdToRequestMap_;delete this.imageIdToImageMap_;ob(this.handler_);S.superClass_.disposeInternal.call(this)};var T=function(){};T.getInstance=function(){return T.instance_?T.instance_:T.instance_=new T};T.prototype.nextId_=0;var U=function(a){R.call(this);this.dom_=a||sb();this.id_=null;this.inDocument_=!1;this.element_=null;this.googUiComponentHandler_=void 0;this.childIndex_=this.children_=this.parent_=null;this.wasDecorated_=!1};u(U,R);h=U.prototype;h.idGenerator_=T.getInstance();h.getElement=function(){return this.element_};h.setParentEventTarget=function(a){if(this.parent_&&this.parent_!=a)throw Error("Method not supported");U.superClass_.setParentEventTarget.call(this,a)};h.getDomHelper=function(){return this.dom_}; -h.createDom=function(){this.element_=this.dom_.createElement("div")}; -var Oc=function(a,b){if(a.inDocument_)throw Error("Component already rendered");a.element_||a.createDom();b?b.insertBefore(a.element_,null):a.dom_.document_.body.appendChild(a.element_);a.parent_&&!a.parent_.inDocument_||Nc(a)},Nc=function(a){a.inDocument_=!0;Pc(a,function(a){!a.inDocument_&&a.getElement()&&Nc(a)})},Qc=function(a){Pc(a,function(a){a.inDocument_&&Qc(a)});a.googUiComponentHandler_&&a.googUiComponentHandler_.removeAll();a.inDocument_=!1}; -U.prototype.disposeInternal=function(){this.inDocument_&&Qc(this);this.googUiComponentHandler_&&(this.googUiComponentHandler_.dispose(),delete this.googUiComponentHandler_);Pc(this,function(a){a.dispose()});!this.wasDecorated_&&this.element_&&Bb(this.element_);this.parent_=this.element_=this.childIndex_=this.children_=null;U.superClass_.disposeInternal.call(this)};var Pc=function(a,b){a.children_&&Na(a.children_,b,void 0)}; -U.prototype.removeChild=function(a,b){if(a){var c=q(a)?a:a.id_||(a.id_=":"+(a.idGenerator_.nextId_++).toString(36)),d;this.childIndex_&&c?(d=this.childIndex_,d=(c in d?d[c]:void 0)||null):d=null;a=d;if(c&&a){d=this.childIndex_;c in d&&delete d[c];Qa(this.children_,a);b&&(Qc(a),a.element_&&Bb(a.element_));c=a;if(null==c)throw Error("Unable to set parent component");c.parent_=null;U.superClass_.setParentEventTarget.call(c,null)}}if(!a)throw Error("Child is not in parent component");return a};var V=function(a,b,c){U.call(this,c);this.captchaImage_=a;this.adImage_=b&&300==b.naturalWidth&&57==b.naturalHeight?b:null};u(V,U);V.prototype.createDom=function(){V.superClass_.createDom.call(this);var a=this.getElement();this.captchaImage_.alt=W.image_alt_text;this.getDomHelper().appendChild(a,this.captchaImage_);this.adImage_&&(this.adImage_.alt=W.image_alt_text,this.getDomHelper().appendChild(a,this.adImage_),this.adImage_&&Rc(this.adImage_)&&(a.innerHTML+='<div id="recaptcha-ad-choices"><div class="recaptcha-ad-choices-collapsed"><img height="15" width="30" alt="AdChoices" border="0" src="//www.gstatic.com/recaptcha/api/img/adicon.png"/></div><div class="recaptcha-ad-choices-expanded"><a href="https://support.google.com/adsense/troubleshooter/1631343" target="_blank"><img height="15" width="75" alt="AdChoices" border="0" src="//www.gstatic.com/recaptcha/api/img/adchoices.png"/></a></div></div>'))}; -var Rc=function(a){var b=Sc(a,"visibility");a=Sc(a,"display");return"hidden"!=b&&"none"!=a},Sc=function(a,b){var c;t:{c=rb(a);if(c.defaultView&&c.defaultView.getComputedStyle&&(c=c.defaultView.getComputedStyle(a,null))){c=c[b]||c.getPropertyValue(b)||"";break t}c=""}if(!(c=c||(a.currentStyle?a.currentStyle[b]:null))&&(c=a.style[Fa(b)],"undefined"===typeof c)){c=a.style;var d;t:if(d=Fa(b),void 0===a.style[d]){var e=(E?"Webkit":D?"Moz":C?"ms":cb?"O":null)+Ga(d);if(void 0!==a.style[e]){d=e;break t}}c= -c[d]||""}return c};V.prototype.disposeInternal=function(){delete this.captchaImage_;delete this.adImage_;V.superClass_.disposeInternal.call(this)};var Tc=function(a,b,c){H.call(this);this.listener_=a;this.interval_=b||0;this.handler_=c;this.callback_=s(this.doAction_,this)};u(Tc,H);h=Tc.prototype;h.id_=0;h.disposeInternal=function(){Tc.superClass_.disposeInternal.call(this);this.stop();delete this.listener_;delete this.handler_}; -h.start=function(a){this.stop();var b=this.callback_;a=l(a)?a:this.interval_;if(!r(b))if(b&&"function"==typeof b.handleEvent)b=s(b.handleEvent,b);else throw Error("Invalid listener argument");this.id_=2147483647<a?-1:k.setTimeout(b,a||0)};h.stop=function(){this.isActive()&&k.clearTimeout(this.id_);this.id_=0};h.isActive=function(){return 0!=this.id_};h.doAction_=function(){this.id_=0;this.listener_&&this.listener_.call(this.handler_)};var Uc=function(a,b){H.call(this);this.listener_=a;this.handler_=b;this.delay_=new Tc(s(this.onTick_,this),0,this)};u(Uc,H);h=Uc.prototype;h.interval_=0;h.runUntil_=0;h.disposeInternal=function(){this.delay_.dispose();delete this.listener_;delete this.handler_;Uc.superClass_.disposeInternal.call(this)};h.start=function(a,b){this.stop();var c=b||0;this.interval_=Math.max(a||0,0);this.runUntil_=0>c?-1:ha()+c;this.delay_.start(0>c?this.interval_:Math.min(this.interval_,c))};h.stop=function(){this.delay_.stop()}; -h.isActive=function(){return this.delay_.isActive()};h.onSuccess=function(){};h.onFailure=function(){};h.onTick_=function(){if(this.listener_.call(this.handler_))this.onSuccess();else if(0>this.runUntil_)this.delay_.start(this.interval_);else{var a=this.runUntil_-ha();if(0>=a)this.onFailure();else this.delay_.start(Math.min(this.interval_,a))}};$a("area base br col command embed hr img input keygen link meta param source track wbr".split(" "));$a("action","cite","data","formaction","href","manifest","poster","src");$a("link","script","style");var Vc={sanitizedContentKindHtml:!0},Wc={sanitizedContentKindText:!0},Xc=function(){throw Error("Do not instantiate directly");};Xc.prototype.contentDir=null;Xc.prototype.toString=function(){return this.content};var bd=function(a){var b=Yc;y(b,"Soy template may not be null.");var c=sb().createElement("DIV");a=Zc(b(a||$c,void 0,void 0));b=a.match(ad);y(!b,"This template starts with a %s, which cannot be a child of a <div>, as required by soy internals. Consider using goog.soy.renderElement instead.\nTemplate output: %s",b&&b[0],a);c.innerHTML=a;return 1==c.childNodes.length&&(a=c.firstChild,1==a.nodeType)?a:c},Zc=function(a){if(!da(a))return String(a);if(a instanceof Xc){if(a.contentKind===Vc)return Ka(a.content); -if(a.contentKind===Wc)return Da(a.content)}Ja("Soy template output is unsafe for use as HTML: "+a);return"zSoyz"},ad=/^<(body|caption|col|colgroup|head|html|tr|td|tbody|thead|tfoot)>/i,$c={};C&&F(8);var cd=function(){Xc.call(this)};u(cd,Xc);cd.prototype.contentKind=Vc;var dd=function(a){function b(a){this.content=a}b.prototype=a.prototype;return function(a,d){var e=new b(String(a));void 0!==d&&(e.contentDir=d);return e}}(cd);(function(a){function b(a){this.content=a}b.prototype=a.prototype;return function(a,d){var e=String(a);if(!e)return"";e=new b(e);void 0!==d&&(e.contentDir=d);return e}})(cd); -var ed={"\x00":"\\x00","\b":"\\x08","\t":"\\t","\n":"\\n","\x0B":"\\x0b","\f":"\\f","\r":"\\r",'"':"\\x22",$:"\\x24","&":"\\x26","'":"\\x27","(":"\\x28",")":"\\x29","*":"\\x2a","+":"\\x2b",",":"\\x2c","-":"\\x2d",".":"\\x2e","/":"\\/",":":"\\x3a","<":"\\x3c","=":"\\x3d",">":"\\x3e","?":"\\x3f","[":"\\x5b","\\":"\\\\","]":"\\x5d","^":"\\x5e","{":"\\x7b","|":"\\x7c","}":"\\x7d","\u0085":"\\x85","\u2028":"\\u2028","\u2029":"\\u2029"},fd=function(a){return ed[a]},gd=/[\x00\x08-\x0d\x22\x26\x27\/\x3c-\x3e\\\x85\u2028\u2029]/g;var Yc=function(a){return dd('<script type="text/javascript">var challenge = \''+String(a.challenge).replace(gd,fd)+"'; var publisherId = '"+String(a.publisherId).replace(gd,fd)+"';"+("ca-mongoogle"==a.publisherId?'google_page_url = "3pcerttesting.com/dab/recaptcha.html";':"")+"\n google_ad_client = publisherId;\n google_ad_type = 'html';\n google_ad_output = 'js';\n google_image_size = '300x57';\n google_captcha_token = challenge;\n google_ad_request_done = function(ad) {\n window.parent.recaptcha.ads.adutils.googleAdRequestDone(ad);\n };\n \x3c/script><script type=\"text/javascript\" src=\"//pagead2.googlesyndication.com/pagead/show_ads.js\">\x3c/script>")}; -Yc.soyTemplateName="recaptcha.soy.ads.iframeAdsLoader.main";var Wa=function(){var a=k.google_ad;return!!(a&&a.token&&a.imageAdUrl&&a.hashedAnswer&&a.salt&&a.delayedImpressionUrl&&a.engagementUrl)},hd=function(){k.google_ad&&(k.google_ad=null)},id=function(a){a=a||document.body;var b=k.google_ad;b&&b.searchUpliftUrl&&(b=xb("iframe",{src:'data:text/html;charset=utf-8,<body><img src="https://'+b.searchUpliftUrl+'"></img></body>',style:"display:none"}),a.appendChild(b))},jd=0,kd=function(a){var b=new S;Mc(b,"recaptcha-url-"+jd++,a);b.start()},ld=function(a,b){var c= -RecaptchaState.publisher_id;hd();var d=xb("iframe",{id:"recaptcha-loader-"+jd++,style:"display: none"});document.body.appendChild(d);var e=d.contentWindow?d.contentWindow.document:d.contentDocument;e.open("text/html","replace");e.write(bd({challenge:a,publisherId:c}).innerHTML);e.close();c=new Uc(function(){return!!k.google_ad});c.onSuccess=function(){Bb(d);b()};c.onFailure=function(){Bb(d);b()};c.start(50,2E3)};t("recaptcha.ads.adutils.googleAdRequestDone",function(a){k.google_ad=a});var md=function(){this.blockSize=-1};var nd=function(){this.blockSize=-1;this.blockSize=64;this.chain_=Array(4);this.block_=Array(this.blockSize);this.totalLength_=this.blockLength_=0;this.reset()};u(nd,md);nd.prototype.reset=function(){this.chain_[0]=1732584193;this.chain_[1]=4023233417;this.chain_[2]=2562383102;this.chain_[3]=271733878;this.totalLength_=this.blockLength_=0}; -var od=function(a,b,c){c||(c=0);var d=Array(16);if(q(b))for(var e=0;16>e;++e)d[e]=b.charCodeAt(c++)|b.charCodeAt(c++)<<8|b.charCodeAt(c++)<<16|b.charCodeAt(c++)<<24;else for(e=0;16>e;++e)d[e]=b[c++]|b[c++]<<8|b[c++]<<16|b[c++]<<24;b=a.chain_[0];c=a.chain_[1];var e=a.chain_[2],g=a.chain_[3],f=0,f=b+(g^c&(e^g))+d[0]+3614090360&4294967295;b=c+(f<<7&4294967295|f>>>25);f=g+(e^b&(c^e))+d[1]+3905402710&4294967295;g=b+(f<<12&4294967295|f>>>20);f=e+(c^g&(b^c))+d[2]+606105819&4294967295;e=g+(f<<17&4294967295| -f>>>15);f=c+(b^e&(g^b))+d[3]+3250441966&4294967295;c=e+(f<<22&4294967295|f>>>10);f=b+(g^c&(e^g))+d[4]+4118548399&4294967295;b=c+(f<<7&4294967295|f>>>25);f=g+(e^b&(c^e))+d[5]+1200080426&4294967295;g=b+(f<<12&4294967295|f>>>20);f=e+(c^g&(b^c))+d[6]+2821735955&4294967295;e=g+(f<<17&4294967295|f>>>15);f=c+(b^e&(g^b))+d[7]+4249261313&4294967295;c=e+(f<<22&4294967295|f>>>10);f=b+(g^c&(e^g))+d[8]+1770035416&4294967295;b=c+(f<<7&4294967295|f>>>25);f=g+(e^b&(c^e))+d[9]+2336552879&4294967295;g=b+(f<<12&4294967295| -f>>>20);f=e+(c^g&(b^c))+d[10]+4294925233&4294967295;e=g+(f<<17&4294967295|f>>>15);f=c+(b^e&(g^b))+d[11]+2304563134&4294967295;c=e+(f<<22&4294967295|f>>>10);f=b+(g^c&(e^g))+d[12]+1804603682&4294967295;b=c+(f<<7&4294967295|f>>>25);f=g+(e^b&(c^e))+d[13]+4254626195&4294967295;g=b+(f<<12&4294967295|f>>>20);f=e+(c^g&(b^c))+d[14]+2792965006&4294967295;e=g+(f<<17&4294967295|f>>>15);f=c+(b^e&(g^b))+d[15]+1236535329&4294967295;c=e+(f<<22&4294967295|f>>>10);f=b+(e^g&(c^e))+d[1]+4129170786&4294967295;b=c+(f<< -5&4294967295|f>>>27);f=g+(c^e&(b^c))+d[6]+3225465664&4294967295;g=b+(f<<9&4294967295|f>>>23);f=e+(b^c&(g^b))+d[11]+643717713&4294967295;e=g+(f<<14&4294967295|f>>>18);f=c+(g^b&(e^g))+d[0]+3921069994&4294967295;c=e+(f<<20&4294967295|f>>>12);f=b+(e^g&(c^e))+d[5]+3593408605&4294967295;b=c+(f<<5&4294967295|f>>>27);f=g+(c^e&(b^c))+d[10]+38016083&4294967295;g=b+(f<<9&4294967295|f>>>23);f=e+(b^c&(g^b))+d[15]+3634488961&4294967295;e=g+(f<<14&4294967295|f>>>18);f=c+(g^b&(e^g))+d[4]+3889429448&4294967295;c= -e+(f<<20&4294967295|f>>>12);f=b+(e^g&(c^e))+d[9]+568446438&4294967295;b=c+(f<<5&4294967295|f>>>27);f=g+(c^e&(b^c))+d[14]+3275163606&4294967295;g=b+(f<<9&4294967295|f>>>23);f=e+(b^c&(g^b))+d[3]+4107603335&4294967295;e=g+(f<<14&4294967295|f>>>18);f=c+(g^b&(e^g))+d[8]+1163531501&4294967295;c=e+(f<<20&4294967295|f>>>12);f=b+(e^g&(c^e))+d[13]+2850285829&4294967295;b=c+(f<<5&4294967295|f>>>27);f=g+(c^e&(b^c))+d[2]+4243563512&4294967295;g=b+(f<<9&4294967295|f>>>23);f=e+(b^c&(g^b))+d[7]+1735328473&4294967295; -e=g+(f<<14&4294967295|f>>>18);f=c+(g^b&(e^g))+d[12]+2368359562&4294967295;c=e+(f<<20&4294967295|f>>>12);f=b+(c^e^g)+d[5]+4294588738&4294967295;b=c+(f<<4&4294967295|f>>>28);f=g+(b^c^e)+d[8]+2272392833&4294967295;g=b+(f<<11&4294967295|f>>>21);f=e+(g^b^c)+d[11]+1839030562&4294967295;e=g+(f<<16&4294967295|f>>>16);f=c+(e^g^b)+d[14]+4259657740&4294967295;c=e+(f<<23&4294967295|f>>>9);f=b+(c^e^g)+d[1]+2763975236&4294967295;b=c+(f<<4&4294967295|f>>>28);f=g+(b^c^e)+d[4]+1272893353&4294967295;g=b+(f<<11&4294967295| -f>>>21);f=e+(g^b^c)+d[7]+4139469664&4294967295;e=g+(f<<16&4294967295|f>>>16);f=c+(e^g^b)+d[10]+3200236656&4294967295;c=e+(f<<23&4294967295|f>>>9);f=b+(c^e^g)+d[13]+681279174&4294967295;b=c+(f<<4&4294967295|f>>>28);f=g+(b^c^e)+d[0]+3936430074&4294967295;g=b+(f<<11&4294967295|f>>>21);f=e+(g^b^c)+d[3]+3572445317&4294967295;e=g+(f<<16&4294967295|f>>>16);f=c+(e^g^b)+d[6]+76029189&4294967295;c=e+(f<<23&4294967295|f>>>9);f=b+(c^e^g)+d[9]+3654602809&4294967295;b=c+(f<<4&4294967295|f>>>28);f=g+(b^c^e)+d[12]+ -3873151461&4294967295;g=b+(f<<11&4294967295|f>>>21);f=e+(g^b^c)+d[15]+530742520&4294967295;e=g+(f<<16&4294967295|f>>>16);f=c+(e^g^b)+d[2]+3299628645&4294967295;c=e+(f<<23&4294967295|f>>>9);f=b+(e^(c|~g))+d[0]+4096336452&4294967295;b=c+(f<<6&4294967295|f>>>26);f=g+(c^(b|~e))+d[7]+1126891415&4294967295;g=b+(f<<10&4294967295|f>>>22);f=e+(b^(g|~c))+d[14]+2878612391&4294967295;e=g+(f<<15&4294967295|f>>>17);f=c+(g^(e|~b))+d[5]+4237533241&4294967295;c=e+(f<<21&4294967295|f>>>11);f=b+(e^(c|~g))+d[12]+1700485571& -4294967295;b=c+(f<<6&4294967295|f>>>26);f=g+(c^(b|~e))+d[3]+2399980690&4294967295;g=b+(f<<10&4294967295|f>>>22);f=e+(b^(g|~c))+d[10]+4293915773&4294967295;e=g+(f<<15&4294967295|f>>>17);f=c+(g^(e|~b))+d[1]+2240044497&4294967295;c=e+(f<<21&4294967295|f>>>11);f=b+(e^(c|~g))+d[8]+1873313359&4294967295;b=c+(f<<6&4294967295|f>>>26);f=g+(c^(b|~e))+d[15]+4264355552&4294967295;g=b+(f<<10&4294967295|f>>>22);f=e+(b^(g|~c))+d[6]+2734768916&4294967295;e=g+(f<<15&4294967295|f>>>17);f=c+(g^(e|~b))+d[13]+1309151649& -4294967295;c=e+(f<<21&4294967295|f>>>11);f=b+(e^(c|~g))+d[4]+4149444226&4294967295;b=c+(f<<6&4294967295|f>>>26);f=g+(c^(b|~e))+d[11]+3174756917&4294967295;g=b+(f<<10&4294967295|f>>>22);f=e+(b^(g|~c))+d[2]+718787259&4294967295;e=g+(f<<15&4294967295|f>>>17);f=c+(g^(e|~b))+d[9]+3951481745&4294967295;a.chain_[0]=a.chain_[0]+b&4294967295;a.chain_[1]=a.chain_[1]+(e+(f<<21&4294967295|f>>>11))&4294967295;a.chain_[2]=a.chain_[2]+e&4294967295;a.chain_[3]=a.chain_[3]+g&4294967295}; -nd.prototype.update=function(a,b){l(b)||(b=a.length);for(var c=b-this.blockSize,d=this.block_,e=this.blockLength_,g=0;g<b;){if(0==e)for(;g<=c;)od(this,a,g),g+=this.blockSize;if(q(a))for(;g<b;){if(d[e++]=a.charCodeAt(g++),e==this.blockSize){od(this,d);e=0;break}}else for(;g<b;)if(d[e++]=a[g++],e==this.blockSize){od(this,d);e=0;break}}this.blockLength_=e;this.totalLength_+=b};var X=function(){Q.call(this);this.callback_=this.element_=null;this.md5_=new nd};u(X,Q);var pd=function(a,b,c,d,e){a.unwatch();a.element_=b;a.callback_=e;a.listen(b,"keyup",s(a.onChanged_,a,c,d))};X.prototype.unwatch=function(){this.element_&&this.callback_&&(this.removeAll(),this.callback_=this.element_=null)}; -X.prototype.onChanged_=function(a,b){var c;c=(c=this.element_.value)?c.replace(/[\s\xa0]+/g,"").toLowerCase():"";this.md5_.reset();this.md5_.update(c+"."+b);c=this.md5_;var d=Array((56>c.blockLength_?c.blockSize:2*c.blockSize)-c.blockLength_);d[0]=128;for(var e=1;e<d.length-8;++e)d[e]=0;for(var g=8*c.totalLength_,e=d.length-8;e<d.length;++e)d[e]=g&255,g/=256;c.update(d);d=Array(16);for(e=g=0;4>e;++e)for(var f=0;32>f;f+=8)d[g++]=c.chain_[e]>>>f&255;jb(d).toLowerCase()==a.toLowerCase()&&this.callback_()}; -X.prototype.disposeInternal=function(){this.element_=null;X.superClass_.disposeInternal.call(this)};var rd=function(a,b,c){this.adObject_=a;this.captchaImageUrl_=b;this.opt_successCallback_=c||null;qd(this)};u(rd,H);var qd=function(a){var b=new S;nb(a,ga(ob,b));Mc(b,"recaptcha_challenge_image",a.captchaImageUrl_);Mc(b,"recaptcha_ad_image",a.adObject_.imageAdUrl);var c={};xc(b,"load",s(function(a,b){a[b.target.id]=b.target},a,c));xc(b,"complete",s(a.handleImagesLoaded_,a,c));b.start()}; -rd.prototype.handleImagesLoaded_=function(a){a=new V(a.recaptcha_challenge_image,a.recaptcha_ad_image);nb(this,ga(ob,a));var b=tb(document,"recaptcha_image");Ab(b);Oc(a,b);a.adImage_&&Rc(a.adImage_)&&(kd(this.adObject_.delayedImpressionUrl),a=new X,nb(this,ga(ob,a)),pd(a,tb(document,"recaptcha_response_field"),this.adObject_.hashedAnswer,this.adObject_.salt,s(function(a,b){a.unwatch();kd(b)},this,a,this.adObject_.engagementUrl)),this.opt_successCallback_&&this.opt_successCallback_("04"+this.adObject_.token))};var W=w;t("RecaptchaStr",W);var Y=k.RecaptchaOptions;t("RecaptchaOptions",Y);var sd={tabindex:0,theme:"red",callback:null,lang:null,custom_theme_widget:null,custom_translations:null};t("RecaptchaDefaultOptions",sd); -var Z={widget:null,timer_id:-1,style_set:!1,theme:null,type:"image",ajax_verify_cb:null,th1:null,th2:null,th3:null,element:"",ad_captcha_plugin:null,reload_timeout:-1,force_reload:!1,$:function(a){return"string"==typeof a?document.getElementById(a):a},attachEvent:function(a,b,c){a&&a.addEventListener?a.addEventListener(b,c,!1):a&&a.attachEvent&&a.attachEvent("on"+b,c)},create:function(a,b,c){Z.destroy();b&&(Z.widget=Z.$(b),Z.element=b);Z._init_options(c);Z._call_challenge(a)},destroy:function(){var a= -Z.$("recaptcha_challenge_field");a&&a.parentNode.removeChild(a);-1!=Z.timer_id&&clearInterval(Z.timer_id);Z.timer_id=-1;if(a=Z.$("recaptcha_image"))a.innerHTML="";Z.update_widget();Z.widget&&("custom"!=Z.theme?Z.widget.innerHTML="":Z.widget.style.display="none",Z.widget=null)},focus_response_field:function(){var a=Z.$("recaptcha_response_field");a&&a.focus()},get_challenge:function(){return"undefined"==typeof RecaptchaState?null:RecaptchaState.challenge},get_response:function(){var a=Z.$("recaptcha_response_field"); -return a?a.value:null},ajax_verify:function(a){Z.ajax_verify_cb=a;a=Z.get_challenge()||"";var b=Z.get_response()||"";a=Z._get_api_server()+"/ajaxverify?c="+encodeURIComponent(a)+"&response="+encodeURIComponent(b);Z._add_script(a)},_ajax_verify_callback:function(a){Z.ajax_verify_cb(a)},_get_overridable_url:function(a){var b=window.location.protocol;if("undefined"!=typeof _RecaptchaOverrideApiServer)a=_RecaptchaOverrideApiServer;else if("undefined"!=typeof RecaptchaState&&"string"==typeof RecaptchaState.server&& -0<RecaptchaState.server.length)return RecaptchaState.server.replace(/\/+$/,"");return b+"//"+a},_get_api_server:function(){return Z._get_overridable_url("www.google.com/recaptcha/api")},_get_static_url_root:function(){return Z._get_overridable_url("www.gstatic.com/recaptcha/api")},_call_challenge:function(a){a=Z._get_api_server()+"/challenge?k="+a+"&ajax=1&cachestop="+Math.random();Z.getLang_()&&(a+="&lang="+Z.getLang_());"undefined"!=typeof Y.extra_challenge_params&&(a+="&"+Y.extra_challenge_params); -Z._add_script(a)},_add_script:function(a){var b=document.createElement("script");b.type="text/javascript";b.src=a;Z._get_script_area().appendChild(b)},_get_script_area:function(){var a=document.getElementsByTagName("head");return a=!a||1>a.length?document.body:a[0]},_hash_merge:function(a){for(var b={},c=0;c<a.length;c++)for(var d in a[c])b[d]=a[c][d];return b},_init_options:function(a){Y=Z._hash_merge([sd,a||{}])},challenge_callback_internal:function(){Z.update_widget();Z._reset_timer();W=Z._hash_merge([w, -sa[Z.getLang_()]||{},Y.custom_translations||{}]);window.addEventListener&&window.addEventListener("unload",function(){Z.destroy()},!1);Z._is_ie()&&window.attachEvent&&window.attachEvent("onbeforeunload",function(){});if(0<navigator.userAgent.indexOf("KHTML")){var a=document.createElement("iframe");a.src="about:blank";a.style.height="0px";a.style.width="0px";a.style.visibility="hidden";a.style.border="none";a.appendChild(document.createTextNode("This frame prevents back/forward cache problems in Safari.")); -document.body.appendChild(a)}Z._finish_widget()},_add_css:function(a){if(-1!=navigator.appVersion.indexOf("MSIE 5"))document.write('<style type="text/css">'+a+"</style>");else{var b=document.createElement("style");b.type="text/css";b.styleSheet?b.styleSheet.cssText=a:b.appendChild(document.createTextNode(a));Z._get_script_area().appendChild(b)}},_set_style:function(a){Z.style_set||(Z.style_set=!0,Z._add_css(a+"\n\n.recaptcha_is_showing_audio .recaptcha_only_if_image,.recaptcha_isnot_showing_audio .recaptcha_only_if_audio,.recaptcha_had_incorrect_sol .recaptcha_only_if_no_incorrect_sol,.recaptcha_nothad_incorrect_sol .recaptcha_only_if_incorrect_sol{display:none !important}"))}, -_init_builtin_theme:function(){var a=Z.$,b=Z._get_static_url_root(),c=v.VertCss,d=v.VertHtml,e=b+"/img/"+Z.theme,g="gif",b=Z.theme;"clean"==b&&(c=v.CleanCss,d=v.CleanHtml,g="png");c=c.replace(/IMGROOT/g,e);Z._set_style(c);Z.update_widget();Z.widget.innerHTML='<div id="recaptcha_area">'+d+"</div>";c=Z.getLang_();a("recaptcha_privacy")&&null!=c&&"en"==c.substring(0,2).toLowerCase()&&null!=W.privacy_and_terms&&0<W.privacy_and_terms.length&&(c=document.createElement("a"),c.href="http://www.google.com/intl/en/policies/", -c.target="_blank",c.innerHTML=W.privacy_and_terms,a("recaptcha_privacy").appendChild(c));c=function(b,c,d,K){var G=a(b);G.src=e+"/"+c+"."+g;c=W[d];G.alt=c;b=a(b+"_btn");b.title=c;Z.attachEvent(b,"click",K)};c("recaptcha_reload","refresh","refresh_btn",function(){Z.reload_internal("r")});c("recaptcha_switch_audio","audio","audio_challenge",function(){Z.switch_type("audio")});c("recaptcha_switch_img","text","visual_challenge",function(){Z.switch_type("image")});c("recaptcha_whatsthis","help","help_btn", -Z.showhelp);"clean"==b&&(a("recaptcha_logo").src=e+"/logo."+g);a("recaptcha_table").className="recaptchatable recaptcha_theme_"+Z.theme;b=function(b,c){var d=a(b);d&&(RecaptchaState.rtl&&"span"==d.tagName.toLowerCase()&&(d.dir="rtl"),d.appendChild(document.createTextNode(W[c])))};b("recaptcha_instructions_image","instructions_visual");b("recaptcha_instructions_audio","instructions_audio");b("recaptcha_instructions_error","incorrect_try_again");a("recaptcha_instructions_image")||a("recaptcha_instructions_audio")|| -(b="audio"==Z.type?W.instructions_audio:W.instructions_visual,b=b.replace(/:$/,""),a("recaptcha_response_field").setAttribute("placeholder",b))},_finish_widget:function(){var a=Z.$,b=Y,c=b.theme;c in{blackglass:1,clean:1,custom:1,red:1,white:1}||(c="red");Z.theme||(Z.theme=c);"custom"!=Z.theme?Z._init_builtin_theme():Z._set_style("");c=document.createElement("span");c.id="recaptcha_challenge_field_holder";c.style.display="none";a("recaptcha_response_field").parentNode.insertBefore(c,a("recaptcha_response_field")); -a("recaptcha_response_field").setAttribute("autocomplete","off");a("recaptcha_image").style.width="300px";a("recaptcha_image").style.height="57px";a("recaptcha_challenge_field_holder").innerHTML='<input type="hidden" name="recaptcha_challenge_field" id="recaptcha_challenge_field" value=""/>';Z.th_init();Z.should_focus=!1;Z.th3||Z.force_reload?(Z._set_challenge(RecaptchaState.challenge,"image",!0),setTimeout(function(){Z.reload_internal("i")},100)):Z._set_challenge(RecaptchaState.challenge,"image", -!1);Z.updateTabIndexes_();Z.update_widget();Z.widget&&(Z.widget.style.display="");b.callback&&b.callback()},updateTabIndexes_:function(){var a=Z.$,b=Y;b.tabindex&&(b=b.tabindex,a("recaptcha_response_field").tabIndex=b++,"audio"==Z.type&&a("recaptcha_audio_play_again")&&(a("recaptcha_audio_play_again").tabIndex=b++,a("recaptcha_audio_download"),a("recaptcha_audio_download").tabIndex=b++),"custom"!=Z.theme&&(a("recaptcha_reload_btn").tabIndex=b++,a("recaptcha_switch_audio_btn").tabIndex=b++,a("recaptcha_switch_img_btn").tabIndex= -b++,a("recaptcha_whatsthis_btn").tabIndex=b,a("recaptcha_privacy").tabIndex=b++))},switch_type:function(a){if(!((new Date).getTime()<Z.reload_timeout)&&(Z.type=a,Z.reload_internal("audio"==Z.type?"a":"v"),"custom"!=Z.theme)){a=Z.$;var b="audio"==Z.type?W.instructions_audio:W.instructions_visual,b=b.replace(/:$/,"");a("recaptcha_response_field").setAttribute("placeholder",b)}},reload:function(){Z.reload_internal("r")},reload_internal:function(a){var b=Y,c=RecaptchaState,d=(new Date).getTime();d<Z.reload_timeout|| -(Z.reload_timeout=d+1E3,"undefined"==typeof a&&(a="r"),d=Z._get_api_server()+"/reload?c="+c.challenge+"&k="+c.site+"&reason="+a+"&type="+Z.type,Z.getLang_()&&(d+="&lang="+Z.getLang_()),"undefined"!=typeof b.extra_challenge_params&&(d+="&"+b.extra_challenge_params),Z.th_callback_invoke(),Z.th1&&(d+="&th="+Z.th1,Z.th1=""),"audio"==Z.type&&(d=b.audio_beta_12_08?d+"&audio_beta_12_08=1":d+"&new_audio_default=1"),Z.should_focus="t"!=a&&"i"!=a,Z._add_script(d),ob(Z.ad_captcha_plugin),c.publisher_id=null)}, -th_callback_invoke:function(){if(Z.th3)try{var a=Z.th3.exec();a&&1600>a.length&&(Z.th1=a)}catch(b){Z.th1=""}},finish_reload:function(a,b,c,d){RecaptchaState.payload_url=c;RecaptchaState.is_incorrect=!1;RecaptchaState.publisher_id=d;Z._set_challenge(a,b,!1);Z.updateTabIndexes_()},_set_challenge:function(a,b,c){"image"==b&&RecaptchaState.publisher_id?ld(a,function(){Z._set_challenge_internal(a,b,c)}):Z._set_challenge_internal(a,b,c)},_set_challenge_internal:function(a,b,c){var d=Z.$,e=RecaptchaState; -e.challenge=a;Z.type=b;d("recaptcha_challenge_field").value=e.challenge;c||("audio"==b?(d("recaptcha_image").innerHTML=Z.getAudioCaptchaHtml(),Z._loop_playback()):"image"==b&&(a=e.payload_url,a||(a=Z._get_api_server()+"/image?c="+e.challenge,Z.th_callback_invoke(),Z.th1&&(a+="&th="+Z.th1,Z.th1="")),id(d("recaptcha_widget_div")),Wa()?Z.ad_captcha_plugin=new rd(Xa(),a,function(a){RecaptchaState.challenge=a;d("recaptcha_challenge_field").value=a}):d("recaptcha_image").innerHTML='<img id="recaptcha_challenge_image" alt="'+ -W.image_alt_text+'" height="57" width="300" src="'+a+'" />',hd()));Z._css_toggle("recaptcha_had_incorrect_sol","recaptcha_nothad_incorrect_sol",e.is_incorrect);Z._css_toggle("recaptcha_is_showing_audio","recaptcha_isnot_showing_audio","audio"==b);Z._clear_input();Z.should_focus&&Z.focus_response_field();Z._reset_timer()},_reset_timer:function(){clearInterval(Z.timer_id);var a=Math.max(1E3*(RecaptchaState.timeout-60),6E4);Z.timer_id=setInterval(function(){Z.reload_internal("t")},a);return a},showhelp:function(){window.open(Z._get_help_link(), -"recaptcha_popup","width=460,height=580,location=no,menubar=no,status=no,toolbar=no,scrollbars=yes,resizable=yes")},_clear_input:function(){Z.$("recaptcha_response_field").value=""},_displayerror:function(a){var b=Z.$;b("recaptcha_image").innerHTML="";b("recaptcha_image").appendChild(document.createTextNode(a))},reloaderror:function(a){Z._displayerror(a)},_is_ie:function(){return 0<navigator.userAgent.indexOf("MSIE")&&!window.opera},_css_toggle:function(a,b,c){Z.update_widget();var d=Z.widget;d|| -(d=document.body);var e=d.className,e=e.replace(new RegExp("(^|\\s+)"+a+"(\\s+|$)")," "),e=e.replace(new RegExp("(^|\\s+)"+b+"(\\s+|$)")," ");d.className=e+(" "+(c?a:b))},_get_help_link:function(){var a="https://support.google.com/recaptcha/";Z.getLang_()&&(a+="?hl="+Z.getLang_());return a},playAgain:function(){Z.$("recaptcha_image").innerHTML=Z.getAudioCaptchaHtml();Z._loop_playback()},_loop_playback:function(){var a=Z.$("recaptcha_audio_play_again");a&&Z.attachEvent(a,"click",function(){Z.playAgain(); -return!1})},getAudioCaptchaHtml:function(){var a=RecaptchaState.payload_url;a||(a=Z._get_api_server()+"/audio.mp3?c="+RecaptchaState.challenge,Z.th_callback_invoke(),Z.th1&&(a+="&th="+Z.th1,Z.th1=""));var b=Z._get_api_server()+"/swf/audiocaptcha.swf?v2",b=Z._is_ie()?'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="audiocaptcha" width="0" height="0" codebase="https://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab"><param name="movie" value="'+b+'" /><param name="quality" value="high" /><param name="bgcolor" value="#869ca7" /><param name="allowScriptAccess" value="always" /></object><br/>': -'<embed src="'+b+'" quality="high" bgcolor="#869ca7" width="0" height="0" name="audiocaptcha" align="middle" play="true" loop="false" quality="high" allowScriptAccess="always" type="application/x-shockwave-flash" pluginspage="http://www.adobe.com/go/getflashplayer" /></embed>',c="";Z.checkFlashVer()&&(c="<br/>"+Z.getSpan_('<a id="recaptcha_audio_play_again" class="recaptcha_audio_cant_hear_link">'+W.play_again+"</a>"));c+="<br/>"+Z.getSpan_('<a id="recaptcha_audio_download" class="recaptcha_audio_cant_hear_link" target="_blank" href="'+ -a+'">'+W.cant_hear_this+"</a>");return b+c},getSpan_:function(a){return"<span"+(RecaptchaState&&RecaptchaState.rtl?' dir="rtl"':"")+">"+a+"</span>"},gethttpwavurl:function(){if("audio"!=Z.type)return"";var a=RecaptchaState.payload_url;a||(a=Z._get_api_server()+"/image?c="+RecaptchaState.challenge,Z.th_callback_invoke(),Z.th1&&(a+="&th="+Z.th1,Z.th1=""));return a},checkFlashVer:function(){var a=-1!=navigator.appVersion.indexOf("MSIE"),b=-1!=navigator.appVersion.toLowerCase().indexOf("win"),c=-1!=navigator.userAgent.indexOf("Opera"), -d=-1;if(null!=navigator.plugins&&0<navigator.plugins.length){if(navigator.plugins["Shockwave Flash 2.0"]||navigator.plugins["Shockwave Flash"])d=navigator.plugins["Shockwave Flash"+(navigator.plugins["Shockwave Flash 2.0"]?" 2.0":"")].description.split(" ")[2].split(".")[0]}else if(a&&b&&!c)try{d=(new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7")).GetVariable("$version").split(" ")[1].split(",")[0]}catch(e){}return 9<=d},getLang_:function(){return Y.lang?Y.lang:"undefined"!=typeof RecaptchaState&& -RecaptchaState.lang?RecaptchaState.lang:null},challenge_callback:function(){Z.force_reload=!!RecaptchaState.force_reload;if(RecaptchaState.t3){var a=RecaptchaState.t1?ib(mb(RecaptchaState.t1)):"",b=RecaptchaState.t2?ib(mb(RecaptchaState.t2)):"",c=RecaptchaState.t3?ib(mb(RecaptchaState.t3)):"";Z.th2=c;if(a)b=kc(a),cc(b,Z.challenge_callback_internal,null,void 0),cc(b,null,Z.challenge_callback_internal,void 0);else{if(k.execScript)k.execScript(b,"JavaScript");else if(k.eval)null==ia&&(k.eval("var _et_ = 1;"), -"undefined"!=typeof k._et_?(delete k._et_,ia=!0):ia=!1),ia?k.eval(b):(a=k.document,c=a.createElement("script"),c.type="text/javascript",c.defer=!1,c.appendChild(a.createTextNode(b)),a.body.appendChild(c),a.body.removeChild(c));else throw Error("goog.globalEval not available");Z.challenge_callback_internal()}}else Z.challenge_callback_internal()},th_init:function(){try{k.thintinel&&k.thintinel.th&&(Z.th3=new k.thintinel.th(Z.th2),Z.th2="")}catch(a){}},update_widget:function(){Z.element&&(Z.widget= -Z.$(Z.element))}};t("Recaptcha",Z);})() diff --git a/syweb/webclient/js/ui-bootstrap-tpls-0.11.2.js b/syweb/webclient/js/ui-bootstrap-tpls-0.11.2.js deleted file mode 100644 index 260c2769b8..0000000000 --- a/syweb/webclient/js/ui-bootstrap-tpls-0.11.2.js +++ /dev/null @@ -1,4167 +0,0 @@ -/* - * angular-ui-bootstrap - * http://angular-ui.github.io/bootstrap/ - - * Version: 0.11.2 - 2014-09-26 - * License: MIT - */ -angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); -angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]); -angular.module('ui.bootstrap.transition', []) - -/** - * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. - * @param {DOMElement} element The DOMElement that will be animated. - * @param {string|object|function} trigger The thing that will cause the transition to start: - * - As a string, it represents the css class to be added to the element. - * - As an object, it represents a hash of style attributes to be applied to the element. - * - As a function, it represents a function to be called that will cause the transition to occur. - * @return {Promise} A promise that is resolved when the transition finishes. - */ -.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { - - var $transition = function(element, trigger, options) { - options = options || {}; - var deferred = $q.defer(); - var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName']; - - var transitionEndHandler = function(event) { - $rootScope.$apply(function() { - element.unbind(endEventName, transitionEndHandler); - deferred.resolve(element); - }); - }; - - if (endEventName) { - element.bind(endEventName, transitionEndHandler); - } - - // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur - $timeout(function() { - if ( angular.isString(trigger) ) { - element.addClass(trigger); - } else if ( angular.isFunction(trigger) ) { - trigger(element); - } else if ( angular.isObject(trigger) ) { - element.css(trigger); - } - //If browser does not support transitions, instantly resolve - if ( !endEventName ) { - deferred.resolve(element); - } - }); - - // Add our custom cancel function to the promise that is returned - // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, - // i.e. it will therefore never raise a transitionEnd event for that transition - deferred.promise.cancel = function() { - if ( endEventName ) { - element.unbind(endEventName, transitionEndHandler); - } - deferred.reject('Transition cancelled'); - }; - - return deferred.promise; - }; - - // Work out the name of the transitionEnd event - var transElement = document.createElement('trans'); - var transitionEndEventNames = { - 'WebkitTransition': 'webkitTransitionEnd', - 'MozTransition': 'transitionend', - 'OTransition': 'oTransitionEnd', - 'transition': 'transitionend' - }; - var animationEndEventNames = { - 'WebkitTransition': 'webkitAnimationEnd', - 'MozTransition': 'animationend', - 'OTransition': 'oAnimationEnd', - 'transition': 'animationend' - }; - function findEndEventName(endEventNames) { - for (var name in endEventNames){ - if (transElement.style[name] !== undefined) { - return endEventNames[name]; - } - } - } - $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); - $transition.animationEndEventName = findEndEventName(animationEndEventNames); - return $transition; -}]); - -angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition']) - - .directive('collapse', ['$transition', function ($transition) { - - return { - link: function (scope, element, attrs) { - - var initialAnimSkip = true; - var currentTransition; - - function doTransition(change) { - var newTransition = $transition(element, change); - if (currentTransition) { - currentTransition.cancel(); - } - currentTransition = newTransition; - newTransition.then(newTransitionDone, newTransitionDone); - return newTransition; - - function newTransitionDone() { - // Make sure it's this transition, otherwise, leave it alone. - if (currentTransition === newTransition) { - currentTransition = undefined; - } - } - } - - function expand() { - if (initialAnimSkip) { - initialAnimSkip = false; - expandDone(); - } else { - element.removeClass('collapse').addClass('collapsing'); - doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone); - } - } - - function expandDone() { - element.removeClass('collapsing'); - element.addClass('collapse in'); - element.css({height: 'auto'}); - } - - function collapse() { - if (initialAnimSkip) { - initialAnimSkip = false; - collapseDone(); - element.css({height: 0}); - } else { - // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value - element.css({ height: element[0].scrollHeight + 'px' }); - //trigger reflow so a browser realizes that height was updated from auto to a specific value - var x = element[0].offsetWidth; - - element.removeClass('collapse in').addClass('collapsing'); - - doTransition({ height: 0 }).then(collapseDone); - } - } - - function collapseDone() { - element.removeClass('collapsing'); - element.addClass('collapse'); - } - - scope.$watch(attrs.collapse, function (shouldCollapse) { - if (shouldCollapse) { - collapse(); - } else { - expand(); - } - }); - } - }; - }]); - -angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) - -.constant('accordionConfig', { - closeOthers: true -}) - -.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) { - - // This array keeps track of the accordion groups - this.groups = []; - - // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to - this.closeOthers = function(openGroup) { - var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; - if ( closeOthers ) { - angular.forEach(this.groups, function (group) { - if ( group !== openGroup ) { - group.isOpen = false; - } - }); - } - }; - - // This is called from the accordion-group directive to add itself to the accordion - this.addGroup = function(groupScope) { - var that = this; - this.groups.push(groupScope); - - groupScope.$on('$destroy', function (event) { - that.removeGroup(groupScope); - }); - }; - - // This is called from the accordion-group directive when to remove itself - this.removeGroup = function(group) { - var index = this.groups.indexOf(group); - if ( index !== -1 ) { - this.groups.splice(index, 1); - } - }; - -}]) - -// The accordion directive simply sets up the directive controller -// and adds an accordion CSS class to itself element. -.directive('accordion', function () { - return { - restrict:'EA', - controller:'AccordionController', - transclude: true, - replace: false, - templateUrl: 'template/accordion/accordion.html' - }; -}) - -// The accordion-group directive indicates a block of html that will expand and collapse in an accordion -.directive('accordionGroup', function() { - return { - require:'^accordion', // We need this directive to be inside an accordion - restrict:'EA', - transclude:true, // It transcludes the contents of the directive into the template - replace: true, // The element containing the directive will be replaced with the template - templateUrl:'template/accordion/accordion-group.html', - scope: { - heading: '@', // Interpolate the heading attribute onto this scope - isOpen: '=?', - isDisabled: '=?' - }, - controller: function() { - this.setHeading = function(element) { - this.heading = element; - }; - }, - link: function(scope, element, attrs, accordionCtrl) { - accordionCtrl.addGroup(scope); - - scope.$watch('isOpen', function(value) { - if ( value ) { - accordionCtrl.closeOthers(scope); - } - }); - - scope.toggleOpen = function() { - if ( !scope.isDisabled ) { - scope.isOpen = !scope.isOpen; - } - }; - } - }; -}) - -// Use accordion-heading below an accordion-group to provide a heading containing HTML -// <accordion-group> -// <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading> -// </accordion-group> -.directive('accordionHeading', function() { - return { - restrict: 'EA', - transclude: true, // Grab the contents to be used as the heading - template: '', // In effect remove this element! - replace: true, - require: '^accordionGroup', - link: function(scope, element, attr, accordionGroupCtrl, transclude) { - // Pass the heading to the accordion-group controller - // so that it can be transcluded into the right place in the template - // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] - accordionGroupCtrl.setHeading(transclude(scope, function() {})); - } - }; -}) - -// Use in the accordion-group template to indicate where you want the heading to be transcluded -// You must provide the property on the accordion-group controller that will hold the transcluded element -// <div class="accordion-group"> -// <div class="accordion-heading" ><a ... accordion-transclude="heading">...</a></div> -// ... -// </div> -.directive('accordionTransclude', function() { - return { - require: '^accordionGroup', - link: function(scope, element, attr, controller) { - scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { - if ( heading ) { - element.html(''); - element.append(heading); - } - }); - } - }; -}); - -angular.module('ui.bootstrap.alert', []) - -.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) { - $scope.closeable = 'close' in $attrs; -}]) - -.directive('alert', function () { - return { - restrict:'EA', - controller:'AlertController', - templateUrl:'template/alert/alert.html', - transclude:true, - replace:true, - scope: { - type: '@', - close: '&' - } - }; -}); - -angular.module('ui.bootstrap.bindHtml', []) - - .directive('bindHtmlUnsafe', function () { - return function (scope, element, attr) { - element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); - scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { - element.html(value || ''); - }); - }; - }); -angular.module('ui.bootstrap.buttons', []) - -.constant('buttonConfig', { - activeClass: 'active', - toggleEvent: 'click' -}) - -.controller('ButtonsController', ['buttonConfig', function(buttonConfig) { - this.activeClass = buttonConfig.activeClass || 'active'; - this.toggleEvent = buttonConfig.toggleEvent || 'click'; -}]) - -.directive('btnRadio', function () { - return { - require: ['btnRadio', 'ngModel'], - controller: 'ButtonsController', - link: function (scope, element, attrs, ctrls) { - var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - - //model -> UI - ngModelCtrl.$render = function () { - element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); - }; - - //ui->model - element.bind(buttonsCtrl.toggleEvent, function () { - var isActive = element.hasClass(buttonsCtrl.activeClass); - - if (!isActive || angular.isDefined(attrs.uncheckable)) { - scope.$apply(function () { - ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio)); - ngModelCtrl.$render(); - }); - } - }); - } - }; -}) - -.directive('btnCheckbox', function () { - return { - require: ['btnCheckbox', 'ngModel'], - controller: 'ButtonsController', - link: function (scope, element, attrs, ctrls) { - var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - - function getTrueValue() { - return getCheckboxValue(attrs.btnCheckboxTrue, true); - } - - function getFalseValue() { - return getCheckboxValue(attrs.btnCheckboxFalse, false); - } - - function getCheckboxValue(attributeValue, defaultValue) { - var val = scope.$eval(attributeValue); - return angular.isDefined(val) ? val : defaultValue; - } - - //model -> UI - ngModelCtrl.$render = function () { - element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); - }; - - //ui->model - element.bind(buttonsCtrl.toggleEvent, function () { - scope.$apply(function () { - ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); - ngModelCtrl.$render(); - }); - }); - } - }; -}); - -/** -* @ngdoc overview -* @name ui.bootstrap.carousel -* -* @description -* AngularJS version of an image carousel. -* -*/ -angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) -.controller('CarouselController', ['$scope', '$timeout', '$transition', function ($scope, $timeout, $transition) { - var self = this, - slides = self.slides = $scope.slides = [], - currentIndex = -1, - currentTimeout, isPlaying; - self.currentSlide = null; - - var destroyed = false; - /* direction: "prev" or "next" */ - self.select = $scope.select = function(nextSlide, direction) { - var nextIndex = slides.indexOf(nextSlide); - //Decide direction if it's not given - if (direction === undefined) { - direction = nextIndex > currentIndex ? 'next' : 'prev'; - } - if (nextSlide && nextSlide !== self.currentSlide) { - if ($scope.$currentTransition) { - $scope.$currentTransition.cancel(); - //Timeout so ng-class in template has time to fix classes for finished slide - $timeout(goNext); - } else { - goNext(); - } - } - function goNext() { - // Scope has been destroyed, stop here. - if (destroyed) { return; } - //If we have a slide to transition from and we have a transition type and we're allowed, go - if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) { - //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime - nextSlide.$element.addClass(direction); - var reflow = nextSlide.$element[0].offsetWidth; //force reflow - - //Set all other slides to stop doing their stuff for the new transition - angular.forEach(slides, function(slide) { - angular.extend(slide, {direction: '', entering: false, leaving: false, active: false}); - }); - angular.extend(nextSlide, {direction: direction, active: true, entering: true}); - angular.extend(self.currentSlide||{}, {direction: direction, leaving: true}); - - $scope.$currentTransition = $transition(nextSlide.$element, {}); - //We have to create new pointers inside a closure since next & current will change - (function(next,current) { - $scope.$currentTransition.then( - function(){ transitionDone(next, current); }, - function(){ transitionDone(next, current); } - ); - }(nextSlide, self.currentSlide)); - } else { - transitionDone(nextSlide, self.currentSlide); - } - self.currentSlide = nextSlide; - currentIndex = nextIndex; - //every time you change slides, reset the timer - restartTimer(); - } - function transitionDone(next, current) { - angular.extend(next, {direction: '', active: true, leaving: false, entering: false}); - angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false}); - $scope.$currentTransition = null; - } - }; - $scope.$on('$destroy', function () { - destroyed = true; - }); - - /* Allow outside people to call indexOf on slides array */ - self.indexOfSlide = function(slide) { - return slides.indexOf(slide); - }; - - $scope.next = function() { - var newIndex = (currentIndex + 1) % slides.length; - - //Prevent this user-triggered transition from occurring if there is already one in progress - if (!$scope.$currentTransition) { - return self.select(slides[newIndex], 'next'); - } - }; - - $scope.prev = function() { - var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; - - //Prevent this user-triggered transition from occurring if there is already one in progress - if (!$scope.$currentTransition) { - return self.select(slides[newIndex], 'prev'); - } - }; - - $scope.isActive = function(slide) { - return self.currentSlide === slide; - }; - - $scope.$watch('interval', restartTimer); - $scope.$on('$destroy', resetTimer); - - function restartTimer() { - resetTimer(); - var interval = +$scope.interval; - if (!isNaN(interval) && interval>=0) { - currentTimeout = $timeout(timerFn, interval); - } - } - - function resetTimer() { - if (currentTimeout) { - $timeout.cancel(currentTimeout); - currentTimeout = null; - } - } - - function timerFn() { - if (isPlaying) { - $scope.next(); - restartTimer(); - } else { - $scope.pause(); - } - } - - $scope.play = function() { - if (!isPlaying) { - isPlaying = true; - restartTimer(); - } - }; - $scope.pause = function() { - if (!$scope.noPause) { - isPlaying = false; - resetTimer(); - } - }; - - self.addSlide = function(slide, element) { - slide.$element = element; - slides.push(slide); - //if this is the first slide or the slide is set to active, select it - if(slides.length === 1 || slide.active) { - self.select(slides[slides.length-1]); - if (slides.length == 1) { - $scope.play(); - } - } else { - slide.active = false; - } - }; - - self.removeSlide = function(slide) { - //get the index of the slide inside the carousel - var index = slides.indexOf(slide); - slides.splice(index, 1); - if (slides.length > 0 && slide.active) { - if (index >= slides.length) { - self.select(slides[index-1]); - } else { - self.select(slides[index]); - } - } else if (currentIndex > index) { - currentIndex--; - } - }; - -}]) - -/** - * @ngdoc directive - * @name ui.bootstrap.carousel.directive:carousel - * @restrict EA - * - * @description - * Carousel is the outer container for a set of image 'slides' to showcase. - * - * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide. - * @param {boolean=} noTransition Whether to disable transitions on the carousel. - * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover). - * - * @example -<example module="ui.bootstrap"> - <file name="index.html"> - <carousel> - <slide> - <img src="http://placekitten.com/150/150" style="margin:auto;"> - <div class="carousel-caption"> - <p>Beautiful!</p> - </div> - </slide> - <slide> - <img src="http://placekitten.com/100/150" style="margin:auto;"> - <div class="carousel-caption"> - <p>D'aww!</p> - </div> - </slide> - </carousel> - </file> - <file name="demo.css"> - .carousel-indicators { - top: auto; - bottom: 15px; - } - </file> -</example> - */ -.directive('carousel', [function() { - return { - restrict: 'EA', - transclude: true, - replace: true, - controller: 'CarouselController', - require: 'carousel', - templateUrl: 'template/carousel/carousel.html', - scope: { - interval: '=', - noTransition: '=', - noPause: '=' - } - }; -}]) - -/** - * @ngdoc directive - * @name ui.bootstrap.carousel.directive:slide - * @restrict EA - * - * @description - * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. - * - * @param {boolean=} active Model binding, whether or not this slide is currently active. - * - * @example -<example module="ui.bootstrap"> - <file name="index.html"> -<div ng-controller="CarouselDemoCtrl"> - <carousel> - <slide ng-repeat="slide in slides" active="slide.active"> - <img ng-src="{{slide.image}}" style="margin:auto;"> - <div class="carousel-caption"> - <h4>Slide {{$index}}</h4> - <p>{{slide.text}}</p> - </div> - </slide> - </carousel> - Interval, in milliseconds: <input type="number" ng-model="myInterval"> - <br />Enter a negative number to stop the interval. -</div> - </file> - <file name="script.js"> -function CarouselDemoCtrl($scope) { - $scope.myInterval = 5000; -} - </file> - <file name="demo.css"> - .carousel-indicators { - top: auto; - bottom: 15px; - } - </file> -</example> -*/ - -.directive('slide', function() { - return { - require: '^carousel', - restrict: 'EA', - transclude: true, - replace: true, - templateUrl: 'template/carousel/slide.html', - scope: { - active: '=?' - }, - link: function (scope, element, attrs, carouselCtrl) { - carouselCtrl.addSlide(scope, element); - //when the scope is destroyed then remove the slide from the current slides array - scope.$on('$destroy', function() { - carouselCtrl.removeSlide(scope); - }); - - scope.$watch('active', function(active) { - if (active) { - carouselCtrl.select(scope); - } - }); - } - }; -}); - -angular.module('ui.bootstrap.dateparser', []) - -.service('dateParser', ['$locale', 'orderByFilter', function($locale, orderByFilter) { - - this.parsers = {}; - - var formatCodeToRegex = { - 'yyyy': { - regex: '\\d{4}', - apply: function(value) { this.year = +value; } - }, - 'yy': { - regex: '\\d{2}', - apply: function(value) { this.year = +value + 2000; } - }, - 'y': { - regex: '\\d{1,4}', - apply: function(value) { this.year = +value; } - }, - 'MMMM': { - regex: $locale.DATETIME_FORMATS.MONTH.join('|'), - apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } - }, - 'MMM': { - regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), - apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } - }, - 'MM': { - regex: '0[1-9]|1[0-2]', - apply: function(value) { this.month = value - 1; } - }, - 'M': { - regex: '[1-9]|1[0-2]', - apply: function(value) { this.month = value - 1; } - }, - 'dd': { - regex: '[0-2][0-9]{1}|3[0-1]{1}', - apply: function(value) { this.date = +value; } - }, - 'd': { - regex: '[1-2]?[0-9]{1}|3[0-1]{1}', - apply: function(value) { this.date = +value; } - }, - 'EEEE': { - regex: $locale.DATETIME_FORMATS.DAY.join('|') - }, - 'EEE': { - regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') - } - }; - - function createParser(format) { - var map = [], regex = format.split(''); - - angular.forEach(formatCodeToRegex, function(data, code) { - var index = format.indexOf(code); - - if (index > -1) { - format = format.split(''); - - regex[index] = '(' + data.regex + ')'; - format[index] = '$'; // Custom symbol to define consumed part of format - for (var i = index + 1, n = index + code.length; i < n; i++) { - regex[i] = ''; - format[i] = '$'; - } - format = format.join(''); - - map.push({ index: index, apply: data.apply }); - } - }); - - return { - regex: new RegExp('^' + regex.join('') + '$'), - map: orderByFilter(map, 'index') - }; - } - - this.parse = function(input, format) { - if ( !angular.isString(input) || !format ) { - return input; - } - - format = $locale.DATETIME_FORMATS[format] || format; - - if ( !this.parsers[format] ) { - this.parsers[format] = createParser(format); - } - - var parser = this.parsers[format], - regex = parser.regex, - map = parser.map, - results = input.match(regex); - - if ( results && results.length ) { - var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt; - - for( var i = 1, n = results.length; i < n; i++ ) { - var mapper = map[i-1]; - if ( mapper.apply ) { - mapper.apply.call(fields, results[i]); - } - } - - if ( isValid(fields.year, fields.month, fields.date) ) { - dt = new Date( fields.year, fields.month, fields.date, fields.hours); - } - - return dt; - } - }; - - // Check if date is valid for specific month (and year for February). - // Month: 0 = Jan, 1 = Feb, etc - function isValid(year, month, date) { - if ( month === 1 && date > 28) { - return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); - } - - if ( month === 3 || month === 5 || month === 8 || month === 10) { - return date < 31; - } - - return true; - } -}]); - -angular.module('ui.bootstrap.position', []) - -/** - * A set of utility methods that can be use to retrieve position of DOM elements. - * It is meant to be used where we need to absolute-position DOM elements in - * relation to other, existing elements (this is the case for tooltips, popovers, - * typeahead suggestions etc.). - */ - .factory('$position', ['$document', '$window', function ($document, $window) { - - function getStyle(el, cssprop) { - if (el.currentStyle) { //IE - return el.currentStyle[cssprop]; - } else if ($window.getComputedStyle) { - return $window.getComputedStyle(el)[cssprop]; - } - // finally try and get inline style - return el.style[cssprop]; - } - - /** - * Checks if a given element is statically positioned - * @param element - raw DOM element - */ - function isStaticPositioned(element) { - return (getStyle(element, 'position') || 'static' ) === 'static'; - } - - /** - * returns the closest, non-statically positioned parentOffset of a given element - * @param element - */ - var parentOffsetEl = function (element) { - var docDomEl = $document[0]; - var offsetParent = element.offsetParent || docDomEl; - while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { - offsetParent = offsetParent.offsetParent; - } - return offsetParent || docDomEl; - }; - - return { - /** - * Provides read-only equivalent of jQuery's position function: - * http://api.jquery.com/position/ - */ - position: function (element) { - var elBCR = this.offset(element); - var offsetParentBCR = { top: 0, left: 0 }; - var offsetParentEl = parentOffsetEl(element[0]); - if (offsetParentEl != $document[0]) { - offsetParentBCR = this.offset(angular.element(offsetParentEl)); - offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; - offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; - } - - var boundingClientRect = element[0].getBoundingClientRect(); - return { - width: boundingClientRect.width || element.prop('offsetWidth'), - height: boundingClientRect.height || element.prop('offsetHeight'), - top: elBCR.top - offsetParentBCR.top, - left: elBCR.left - offsetParentBCR.left - }; - }, - - /** - * Provides read-only equivalent of jQuery's offset function: - * http://api.jquery.com/offset/ - */ - offset: function (element) { - var boundingClientRect = element[0].getBoundingClientRect(); - return { - width: boundingClientRect.width || element.prop('offsetWidth'), - height: boundingClientRect.height || element.prop('offsetHeight'), - top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop), - left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft) - }; - }, - - /** - * Provides coordinates for the targetEl in relation to hostEl - */ - positionElements: function (hostEl, targetEl, positionStr, appendToBody) { - - var positionStrParts = positionStr.split('-'); - var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center'; - - var hostElPos, - targetElWidth, - targetElHeight, - targetElPos; - - hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); - - targetElWidth = targetEl.prop('offsetWidth'); - targetElHeight = targetEl.prop('offsetHeight'); - - var shiftWidth = { - center: function () { - return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; - }, - left: function () { - return hostElPos.left; - }, - right: function () { - return hostElPos.left + hostElPos.width; - } - }; - - var shiftHeight = { - center: function () { - return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; - }, - top: function () { - return hostElPos.top; - }, - bottom: function () { - return hostElPos.top + hostElPos.height; - } - }; - - switch (pos0) { - case 'right': - targetElPos = { - top: shiftHeight[pos1](), - left: shiftWidth[pos0]() - }; - break; - case 'left': - targetElPos = { - top: shiftHeight[pos1](), - left: hostElPos.left - targetElWidth - }; - break; - case 'bottom': - targetElPos = { - top: shiftHeight[pos0](), - left: shiftWidth[pos1]() - }; - break; - default: - targetElPos = { - top: hostElPos.top - targetElHeight, - left: shiftWidth[pos1]() - }; - break; - } - - return targetElPos; - } - }; - }]); - -angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position']) - -.constant('datepickerConfig', { - formatDay: 'dd', - formatMonth: 'MMMM', - formatYear: 'yyyy', - formatDayHeader: 'EEE', - formatDayTitle: 'MMMM yyyy', - formatMonthTitle: 'yyyy', - datepickerMode: 'day', - minMode: 'day', - maxMode: 'year', - showWeeks: true, - startingDay: 0, - yearRange: 20, - minDate: null, - maxDate: null -}) - -.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) { - var self = this, - ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; - - // Modes chain - this.modes = ['day', 'month', 'year']; - - // Configuration attributes - angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', - 'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange'], function( key, index ) { - self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; - }); - - // Watchable date attributes - angular.forEach(['minDate', 'maxDate'], function( key ) { - if ( $attrs[key] ) { - $scope.$parent.$watch($parse($attrs[key]), function(value) { - self[key] = value ? new Date(value) : null; - self.refreshView(); - }); - } else { - self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null; - } - }); - - $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; - $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); - this.activeDate = angular.isDefined($attrs.initDate) ? $scope.$parent.$eval($attrs.initDate) : new Date(); - - $scope.isActive = function(dateObject) { - if (self.compare(dateObject.date, self.activeDate) === 0) { - $scope.activeDateId = dateObject.uid; - return true; - } - return false; - }; - - this.init = function( ngModelCtrl_ ) { - ngModelCtrl = ngModelCtrl_; - - ngModelCtrl.$render = function() { - self.render(); - }; - }; - - this.render = function() { - if ( ngModelCtrl.$modelValue ) { - var date = new Date( ngModelCtrl.$modelValue ), - isValid = !isNaN(date); - - if ( isValid ) { - this.activeDate = date; - } else { - $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); - } - ngModelCtrl.$setValidity('date', isValid); - } - this.refreshView(); - }; - - this.refreshView = function() { - if ( this.element ) { - this._refreshView(); - - var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; - ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date))); - } - }; - - this.createDateObject = function(date, format) { - var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; - return { - date: date, - label: dateFilter(date, format), - selected: model && this.compare(date, model) === 0, - disabled: this.isDisabled(date), - current: this.compare(date, new Date()) === 0 - }; - }; - - this.isDisabled = function( date ) { - return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}))); - }; - - // Split array into smaller arrays - this.split = function(arr, size) { - var arrays = []; - while (arr.length > 0) { - arrays.push(arr.splice(0, size)); - } - return arrays; - }; - - $scope.select = function( date ) { - if ( $scope.datepickerMode === self.minMode ) { - var dt = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0); - dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); - ngModelCtrl.$setViewValue( dt ); - ngModelCtrl.$render(); - } else { - self.activeDate = date; - $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) - 1 ]; - } - }; - - $scope.move = function( direction ) { - var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), - month = self.activeDate.getMonth() + direction * (self.step.months || 0); - self.activeDate.setFullYear(year, month, 1); - self.refreshView(); - }; - - $scope.toggleMode = function( direction ) { - direction = direction || 1; - - if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) { - return; - } - - $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) + direction ]; - }; - - // Key event mapper - $scope.keys = { 13:'enter', 32:'space', 33:'pageup', 34:'pagedown', 35:'end', 36:'home', 37:'left', 38:'up', 39:'right', 40:'down' }; - - var focusElement = function() { - $timeout(function() { - self.element[0].focus(); - }, 0 , false); - }; - - // Listen for focus requests from popup directive - $scope.$on('datepicker.focus', focusElement); - - $scope.keydown = function( evt ) { - var key = $scope.keys[evt.which]; - - if ( !key || evt.shiftKey || evt.altKey ) { - return; - } - - evt.preventDefault(); - evt.stopPropagation(); - - if (key === 'enter' || key === 'space') { - if ( self.isDisabled(self.activeDate)) { - return; // do nothing - } - $scope.select(self.activeDate); - focusElement(); - } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { - $scope.toggleMode(key === 'up' ? 1 : -1); - focusElement(); - } else { - self.handleKeyDown(key, evt); - self.refreshView(); - } - }; -}]) - -.directive( 'datepicker', function () { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/datepicker/datepicker.html', - scope: { - datepickerMode: '=?', - dateDisabled: '&' - }, - require: ['datepicker', '?^ngModel'], - controller: 'DatepickerController', - link: function(scope, element, attrs, ctrls) { - var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - - if ( ngModelCtrl ) { - datepickerCtrl.init( ngModelCtrl ); - } - } - }; -}) - -.directive('daypicker', ['dateFilter', function (dateFilter) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/datepicker/day.html', - require: '^datepicker', - link: function(scope, element, attrs, ctrl) { - scope.showWeeks = ctrl.showWeeks; - - ctrl.step = { months: 1 }; - ctrl.element = element; - - var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - function getDaysInMonth( year, month ) { - return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month]; - } - - function getDates(startDate, n) { - var dates = new Array(n), current = new Date(startDate), i = 0; - current.setHours(12); // Prevent repeated dates because of timezone bug - while ( i < n ) { - dates[i++] = new Date(current); - current.setDate( current.getDate() + 1 ); - } - return dates; - } - - ctrl._refreshView = function() { - var year = ctrl.activeDate.getFullYear(), - month = ctrl.activeDate.getMonth(), - firstDayOfMonth = new Date(year, month, 1), - difference = ctrl.startingDay - firstDayOfMonth.getDay(), - numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, - firstDate = new Date(firstDayOfMonth); - - if ( numDisplayedFromPreviousMonth > 0 ) { - firstDate.setDate( - numDisplayedFromPreviousMonth + 1 ); - } - - // 42 is the number of days on a six-month calendar - var days = getDates(firstDate, 42); - for (var i = 0; i < 42; i ++) { - days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), { - secondary: days[i].getMonth() !== month, - uid: scope.uniqueId + '-' + i - }); - } - - scope.labels = new Array(7); - for (var j = 0; j < 7; j++) { - scope.labels[j] = { - abbr: dateFilter(days[j].date, ctrl.formatDayHeader), - full: dateFilter(days[j].date, 'EEEE') - }; - } - - scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle); - scope.rows = ctrl.split(days, 7); - - if ( scope.showWeeks ) { - scope.weekNumbers = []; - var weekNumber = getISO8601WeekNumber( scope.rows[0][0].date ), - numWeeks = scope.rows.length; - while( scope.weekNumbers.push(weekNumber++) < numWeeks ) {} - } - }; - - ctrl.compare = function(date1, date2) { - return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) ); - }; - - function getISO8601WeekNumber(date) { - var checkDate = new Date(date); - checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday - var time = checkDate.getTime(); - checkDate.setMonth(0); // Compare with Jan 1 - checkDate.setDate(1); - return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; - } - - ctrl.handleKeyDown = function( key, evt ) { - var date = ctrl.activeDate.getDate(); - - if (key === 'left') { - date = date - 1; // up - } else if (key === 'up') { - date = date - 7; // down - } else if (key === 'right') { - date = date + 1; // down - } else if (key === 'down') { - date = date + 7; - } else if (key === 'pageup' || key === 'pagedown') { - var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); - ctrl.activeDate.setMonth(month, 1); - date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date); - } else if (key === 'home') { - date = 1; - } else if (key === 'end') { - date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()); - } - ctrl.activeDate.setDate(date); - }; - - ctrl.refreshView(); - } - }; -}]) - -.directive('monthpicker', ['dateFilter', function (dateFilter) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/datepicker/month.html', - require: '^datepicker', - link: function(scope, element, attrs, ctrl) { - ctrl.step = { years: 1 }; - ctrl.element = element; - - ctrl._refreshView = function() { - var months = new Array(12), - year = ctrl.activeDate.getFullYear(); - - for ( var i = 0; i < 12; i++ ) { - months[i] = angular.extend(ctrl.createDateObject(new Date(year, i, 1), ctrl.formatMonth), { - uid: scope.uniqueId + '-' + i - }); - } - - scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle); - scope.rows = ctrl.split(months, 3); - }; - - ctrl.compare = function(date1, date2) { - return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() ); - }; - - ctrl.handleKeyDown = function( key, evt ) { - var date = ctrl.activeDate.getMonth(); - - if (key === 'left') { - date = date - 1; // up - } else if (key === 'up') { - date = date - 3; // down - } else if (key === 'right') { - date = date + 1; // down - } else if (key === 'down') { - date = date + 3; - } else if (key === 'pageup' || key === 'pagedown') { - var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); - ctrl.activeDate.setFullYear(year); - } else if (key === 'home') { - date = 0; - } else if (key === 'end') { - date = 11; - } - ctrl.activeDate.setMonth(date); - }; - - ctrl.refreshView(); - } - }; -}]) - -.directive('yearpicker', ['dateFilter', function (dateFilter) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/datepicker/year.html', - require: '^datepicker', - link: function(scope, element, attrs, ctrl) { - var range = ctrl.yearRange; - - ctrl.step = { years: range }; - ctrl.element = element; - - function getStartingYear( year ) { - return parseInt((year - 1) / range, 10) * range + 1; - } - - ctrl._refreshView = function() { - var years = new Array(range); - - for ( var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++ ) { - years[i] = angular.extend(ctrl.createDateObject(new Date(start + i, 0, 1), ctrl.formatYear), { - uid: scope.uniqueId + '-' + i - }); - } - - scope.title = [years[0].label, years[range - 1].label].join(' - '); - scope.rows = ctrl.split(years, 5); - }; - - ctrl.compare = function(date1, date2) { - return date1.getFullYear() - date2.getFullYear(); - }; - - ctrl.handleKeyDown = function( key, evt ) { - var date = ctrl.activeDate.getFullYear(); - - if (key === 'left') { - date = date - 1; // up - } else if (key === 'up') { - date = date - 5; // down - } else if (key === 'right') { - date = date + 1; // down - } else if (key === 'down') { - date = date + 5; - } else if (key === 'pageup' || key === 'pagedown') { - date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years; - } else if (key === 'home') { - date = getStartingYear( ctrl.activeDate.getFullYear() ); - } else if (key === 'end') { - date = getStartingYear( ctrl.activeDate.getFullYear() ) + range - 1; - } - ctrl.activeDate.setFullYear(date); - }; - - ctrl.refreshView(); - } - }; -}]) - -.constant('datepickerPopupConfig', { - datepickerPopup: 'yyyy-MM-dd', - currentText: 'Today', - clearText: 'Clear', - closeText: 'Done', - closeOnDateSelection: true, - appendToBody: false, - showButtonBar: true -}) - -.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', -function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) { - return { - restrict: 'EA', - require: 'ngModel', - scope: { - isOpen: '=?', - currentText: '@', - clearText: '@', - closeText: '@', - dateDisabled: '&' - }, - link: function(scope, element, attrs, ngModel) { - var dateFormat, - closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, - appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; - - scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; - - scope.getText = function( key ) { - return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; - }; - - attrs.$observe('datepickerPopup', function(value) { - dateFormat = value || datepickerPopupConfig.datepickerPopup; - ngModel.$render(); - }); - - // popup element used to display calendar - var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>'); - popupEl.attr({ - 'ng-model': 'date', - 'ng-change': 'dateSelection()' - }); - - function cameltoDash( string ){ - return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); - } - - // datepicker element - var datepickerEl = angular.element(popupEl.children()[0]); - if ( attrs.datepickerOptions ) { - angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function( value, option ) { - datepickerEl.attr( cameltoDash(option), value ); - }); - } - - scope.watchData = {}; - angular.forEach(['minDate', 'maxDate', 'datepickerMode'], function( key ) { - if ( attrs[key] ) { - var getAttribute = $parse(attrs[key]); - scope.$parent.$watch(getAttribute, function(value){ - scope.watchData[key] = value; - }); - datepickerEl.attr(cameltoDash(key), 'watchData.' + key); - - // Propagate changes from datepicker to outside - if ( key === 'datepickerMode' ) { - var setAttribute = getAttribute.assign; - scope.$watch('watchData.' + key, function(value, oldvalue) { - if ( value !== oldvalue ) { - setAttribute(scope.$parent, value); - } - }); - } - } - }); - if (attrs.dateDisabled) { - datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); - } - - function parseDate(viewValue) { - if (!viewValue) { - ngModel.$setValidity('date', true); - return null; - } else if (angular.isDate(viewValue) && !isNaN(viewValue)) { - ngModel.$setValidity('date', true); - return viewValue; - } else if (angular.isString(viewValue)) { - var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue); - if (isNaN(date)) { - ngModel.$setValidity('date', false); - return undefined; - } else { - ngModel.$setValidity('date', true); - return date; - } - } else { - ngModel.$setValidity('date', false); - return undefined; - } - } - ngModel.$parsers.unshift(parseDate); - - // Inner change - scope.dateSelection = function(dt) { - if (angular.isDefined(dt)) { - scope.date = dt; - } - ngModel.$setViewValue(scope.date); - ngModel.$render(); - - if ( closeOnDateSelection ) { - scope.isOpen = false; - element[0].focus(); - } - }; - - element.bind('input change keyup', function() { - scope.$apply(function() { - scope.date = ngModel.$modelValue; - }); - }); - - // Outter change - ngModel.$render = function() { - var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; - element.val(date); - scope.date = parseDate( ngModel.$modelValue ); - }; - - var documentClickBind = function(event) { - if (scope.isOpen && event.target !== element[0]) { - scope.$apply(function() { - scope.isOpen = false; - }); - } - }; - - var keydown = function(evt, noApply) { - scope.keydown(evt); - }; - element.bind('keydown', keydown); - - scope.keydown = function(evt) { - if (evt.which === 27) { - evt.preventDefault(); - evt.stopPropagation(); - scope.close(); - } else if (evt.which === 40 && !scope.isOpen) { - scope.isOpen = true; - } - }; - - scope.$watch('isOpen', function(value) { - if (value) { - scope.$broadcast('datepicker.focus'); - scope.position = appendToBody ? $position.offset(element) : $position.position(element); - scope.position.top = scope.position.top + element.prop('offsetHeight'); - - $document.bind('click', documentClickBind); - } else { - $document.unbind('click', documentClickBind); - } - }); - - scope.select = function( date ) { - if (date === 'today') { - var today = new Date(); - if (angular.isDate(ngModel.$modelValue)) { - date = new Date(ngModel.$modelValue); - date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); - } else { - date = new Date(today.setHours(0, 0, 0, 0)); - } - } - scope.dateSelection( date ); - }; - - scope.close = function() { - scope.isOpen = false; - element[0].focus(); - }; - - var $popup = $compile(popupEl)(scope); - // Prevent jQuery cache memory leak (template is now redundant after linking) - popupEl.remove(); - - if ( appendToBody ) { - $document.find('body').append($popup); - } else { - element.after($popup); - } - - scope.$on('$destroy', function() { - $popup.remove(); - element.unbind('keydown', keydown); - $document.unbind('click', documentClickBind); - }); - } - }; -}]) - -.directive('datepickerPopupWrap', function() { - return { - restrict:'EA', - replace: true, - transclude: true, - templateUrl: 'template/datepicker/popup.html', - link:function (scope, element, attrs) { - element.bind('click', function(event) { - event.preventDefault(); - event.stopPropagation(); - }); - } - }; -}); - -angular.module('ui.bootstrap.dropdown', []) - -.constant('dropdownConfig', { - openClass: 'open' -}) - -.service('dropdownService', ['$document', function($document) { - var openScope = null; - - this.open = function( dropdownScope ) { - if ( !openScope ) { - $document.bind('click', closeDropdown); - $document.bind('keydown', escapeKeyBind); - } - - if ( openScope && openScope !== dropdownScope ) { - openScope.isOpen = false; - } - - openScope = dropdownScope; - }; - - this.close = function( dropdownScope ) { - if ( openScope === dropdownScope ) { - openScope = null; - $document.unbind('click', closeDropdown); - $document.unbind('keydown', escapeKeyBind); - } - }; - - var closeDropdown = function( evt ) { - var toggleElement = openScope.getToggleElement(); - if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) { - return; - } - - openScope.$apply(function() { - openScope.isOpen = false; - }); - }; - - var escapeKeyBind = function( evt ) { - if ( evt.which === 27 ) { - openScope.focusToggleElement(); - closeDropdown(); - } - }; -}]) - -.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) { - var self = this, - scope = $scope.$new(), // create a child scope so we are not polluting original one - openClass = dropdownConfig.openClass, - getIsOpen, - setIsOpen = angular.noop, - toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop; - - this.init = function( element ) { - self.$element = element; - - if ( $attrs.isOpen ) { - getIsOpen = $parse($attrs.isOpen); - setIsOpen = getIsOpen.assign; - - $scope.$watch(getIsOpen, function(value) { - scope.isOpen = !!value; - }); - } - }; - - this.toggle = function( open ) { - return scope.isOpen = arguments.length ? !!open : !scope.isOpen; - }; - - // Allow other directives to watch status - this.isOpen = function() { - return scope.isOpen; - }; - - scope.getToggleElement = function() { - return self.toggleElement; - }; - - scope.focusToggleElement = function() { - if ( self.toggleElement ) { - self.toggleElement[0].focus(); - } - }; - - scope.$watch('isOpen', function( isOpen, wasOpen ) { - $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass); - - if ( isOpen ) { - scope.focusToggleElement(); - dropdownService.open( scope ); - } else { - dropdownService.close( scope ); - } - - setIsOpen($scope, isOpen); - if (angular.isDefined(isOpen) && isOpen !== wasOpen) { - toggleInvoker($scope, { open: !!isOpen }); - } - }); - - $scope.$on('$locationChangeSuccess', function() { - scope.isOpen = false; - }); - - $scope.$on('$destroy', function() { - scope.$destroy(); - }); -}]) - -.directive('dropdown', function() { - return { - restrict: 'CA', - controller: 'DropdownController', - link: function(scope, element, attrs, dropdownCtrl) { - dropdownCtrl.init( element ); - } - }; -}) - -.directive('dropdownToggle', function() { - return { - restrict: 'CA', - require: '?^dropdown', - link: function(scope, element, attrs, dropdownCtrl) { - if ( !dropdownCtrl ) { - return; - } - - dropdownCtrl.toggleElement = element; - - var toggleDropdown = function(event) { - event.preventDefault(); - - if ( !element.hasClass('disabled') && !attrs.disabled ) { - scope.$apply(function() { - dropdownCtrl.toggle(); - }); - } - }; - - element.bind('click', toggleDropdown); - - // WAI-ARIA - element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); - scope.$watch(dropdownCtrl.isOpen, function( isOpen ) { - element.attr('aria-expanded', !!isOpen); - }); - - scope.$on('$destroy', function() { - element.unbind('click', toggleDropdown); - }); - } - }; -}); - -angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) - -/** - * A helper, internal data structure that acts as a map but also allows getting / removing - * elements in the LIFO order - */ - .factory('$$stackedMap', function () { - return { - createNew: function () { - var stack = []; - - return { - add: function (key, value) { - stack.push({ - key: key, - value: value - }); - }, - get: function (key) { - for (var i = 0; i < stack.length; i++) { - if (key == stack[i].key) { - return stack[i]; - } - } - }, - keys: function() { - var keys = []; - for (var i = 0; i < stack.length; i++) { - keys.push(stack[i].key); - } - return keys; - }, - top: function () { - return stack[stack.length - 1]; - }, - remove: function (key) { - var idx = -1; - for (var i = 0; i < stack.length; i++) { - if (key == stack[i].key) { - idx = i; - break; - } - } - return stack.splice(idx, 1)[0]; - }, - removeTop: function () { - return stack.splice(stack.length - 1, 1)[0]; - }, - length: function () { - return stack.length; - } - }; - } - }; - }) - -/** - * A helper directive for the $modal service. It creates a backdrop element. - */ - .directive('modalBackdrop', ['$timeout', function ($timeout) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/modal/backdrop.html', - link: function (scope, element, attrs) { - scope.backdropClass = attrs.backdropClass || ''; - - scope.animate = false; - - //trigger CSS transitions - $timeout(function () { - scope.animate = true; - }); - } - }; - }]) - - .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { - return { - restrict: 'EA', - scope: { - index: '@', - animate: '=' - }, - replace: true, - transclude: true, - templateUrl: function(tElement, tAttrs) { - return tAttrs.templateUrl || 'template/modal/window.html'; - }, - link: function (scope, element, attrs) { - element.addClass(attrs.windowClass || ''); - scope.size = attrs.size; - - $timeout(function () { - // trigger CSS transitions - scope.animate = true; - - /** - * Auto-focusing of a freshly-opened modal element causes any child elements - * with the autofocus attribute to loose focus. This is an issue on touch - * based devices which will show and then hide the onscreen keyboard. - * Attempts to refocus the autofocus element via JavaScript will not reopen - * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus - * the modal element if the modal does not contain an autofocus element. - */ - if (!element[0].querySelectorAll('[autofocus]').length) { - element[0].focus(); - } - }); - - scope.close = function (evt) { - var modal = $modalStack.getTop(); - if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { - evt.preventDefault(); - evt.stopPropagation(); - $modalStack.dismiss(modal.key, 'backdrop click'); - } - }; - } - }; - }]) - - .directive('modalTransclude', function () { - return { - link: function($scope, $element, $attrs, controller, $transclude) { - $transclude($scope.$parent, function(clone) { - $element.empty(); - $element.append(clone); - }); - } - }; - }) - - .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', - function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { - - var OPENED_MODAL_CLASS = 'modal-open'; - - var backdropDomEl, backdropScope; - var openedWindows = $$stackedMap.createNew(); - var $modalStack = {}; - - function backdropIndex() { - var topBackdropIndex = -1; - var opened = openedWindows.keys(); - for (var i = 0; i < opened.length; i++) { - if (openedWindows.get(opened[i]).value.backdrop) { - topBackdropIndex = i; - } - } - return topBackdropIndex; - } - - $rootScope.$watch(backdropIndex, function(newBackdropIndex){ - if (backdropScope) { - backdropScope.index = newBackdropIndex; - } - }); - - function removeModalWindow(modalInstance) { - - var body = $document.find('body').eq(0); - var modalWindow = openedWindows.get(modalInstance).value; - - //clean up the stack - openedWindows.remove(modalInstance); - - //remove window DOM element - removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function() { - modalWindow.modalScope.$destroy(); - body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); - checkRemoveBackdrop(); - }); - } - - function checkRemoveBackdrop() { - //remove backdrop if no longer needed - if (backdropDomEl && backdropIndex() == -1) { - var backdropScopeRef = backdropScope; - removeAfterAnimate(backdropDomEl, backdropScope, 150, function () { - backdropScopeRef.$destroy(); - backdropScopeRef = null; - }); - backdropDomEl = undefined; - backdropScope = undefined; - } - } - - function removeAfterAnimate(domEl, scope, emulateTime, done) { - // Closing animation - scope.animate = false; - - var transitionEndEventName = $transition.transitionEndEventName; - if (transitionEndEventName) { - // transition out - var timeout = $timeout(afterAnimating, emulateTime); - - domEl.bind(transitionEndEventName, function () { - $timeout.cancel(timeout); - afterAnimating(); - scope.$apply(); - }); - } else { - // Ensure this call is async - $timeout(afterAnimating); - } - - function afterAnimating() { - if (afterAnimating.done) { - return; - } - afterAnimating.done = true; - - domEl.remove(); - if (done) { - done(); - } - } - } - - $document.bind('keydown', function (evt) { - var modal; - - if (evt.which === 27) { - modal = openedWindows.top(); - if (modal && modal.value.keyboard) { - evt.preventDefault(); - $rootScope.$apply(function () { - $modalStack.dismiss(modal.key, 'escape key press'); - }); - } - } - }); - - $modalStack.open = function (modalInstance, modal) { - - openedWindows.add(modalInstance, { - deferred: modal.deferred, - modalScope: modal.scope, - backdrop: modal.backdrop, - keyboard: modal.keyboard - }); - - var body = $document.find('body').eq(0), - currBackdropIndex = backdropIndex(); - - if (currBackdropIndex >= 0 && !backdropDomEl) { - backdropScope = $rootScope.$new(true); - backdropScope.index = currBackdropIndex; - var angularBackgroundDomEl = angular.element('<div modal-backdrop></div>'); - angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass); - backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope); - body.append(backdropDomEl); - } - - var angularDomEl = angular.element('<div modal-window></div>'); - angularDomEl.attr({ - 'template-url': modal.windowTemplateUrl, - 'window-class': modal.windowClass, - 'size': modal.size, - 'index': openedWindows.length() - 1, - 'animate': 'animate' - }).html(modal.content); - - var modalDomEl = $compile(angularDomEl)(modal.scope); - openedWindows.top().value.modalDomEl = modalDomEl; - body.append(modalDomEl); - body.addClass(OPENED_MODAL_CLASS); - }; - - $modalStack.close = function (modalInstance, result) { - var modalWindow = openedWindows.get(modalInstance); - if (modalWindow) { - modalWindow.value.deferred.resolve(result); - removeModalWindow(modalInstance); - } - }; - - $modalStack.dismiss = function (modalInstance, reason) { - var modalWindow = openedWindows.get(modalInstance); - if (modalWindow) { - modalWindow.value.deferred.reject(reason); - removeModalWindow(modalInstance); - } - }; - - $modalStack.dismissAll = function (reason) { - var topModal = this.getTop(); - while (topModal) { - this.dismiss(topModal.key, reason); - topModal = this.getTop(); - } - }; - - $modalStack.getTop = function () { - return openedWindows.top(); - }; - - return $modalStack; - }]) - - .provider('$modal', function () { - - var $modalProvider = { - options: { - backdrop: true, //can be also false or 'static' - keyboard: true - }, - $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', - function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { - - var $modal = {}; - - function getTemplatePromise(options) { - return options.template ? $q.when(options.template) : - $http.get(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl, - {cache: $templateCache}).then(function (result) { - return result.data; - }); - } - - function getResolvePromises(resolves) { - var promisesArr = []; - angular.forEach(resolves, function (value) { - if (angular.isFunction(value) || angular.isArray(value)) { - promisesArr.push($q.when($injector.invoke(value))); - } - }); - return promisesArr; - } - - $modal.open = function (modalOptions) { - - var modalResultDeferred = $q.defer(); - var modalOpenedDeferred = $q.defer(); - - //prepare an instance of a modal to be injected into controllers and returned to a caller - var modalInstance = { - result: modalResultDeferred.promise, - opened: modalOpenedDeferred.promise, - close: function (result) { - $modalStack.close(modalInstance, result); - }, - dismiss: function (reason) { - $modalStack.dismiss(modalInstance, reason); - } - }; - - //merge and clean up options - modalOptions = angular.extend({}, $modalProvider.options, modalOptions); - modalOptions.resolve = modalOptions.resolve || {}; - - //verify options - if (!modalOptions.template && !modalOptions.templateUrl) { - throw new Error('One of template or templateUrl options is required.'); - } - - var templateAndResolvePromise = - $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); - - - templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { - - var modalScope = (modalOptions.scope || $rootScope).$new(); - modalScope.$close = modalInstance.close; - modalScope.$dismiss = modalInstance.dismiss; - - var ctrlInstance, ctrlLocals = {}; - var resolveIter = 1; - - //controllers - if (modalOptions.controller) { - ctrlLocals.$scope = modalScope; - ctrlLocals.$modalInstance = modalInstance; - angular.forEach(modalOptions.resolve, function (value, key) { - ctrlLocals[key] = tplAndVars[resolveIter++]; - }); - - ctrlInstance = $controller(modalOptions.controller, ctrlLocals); - if (modalOptions.controllerAs) { - modalScope[modalOptions.controllerAs] = ctrlInstance; - } - } - - $modalStack.open(modalInstance, { - scope: modalScope, - deferred: modalResultDeferred, - content: tplAndVars[0], - backdrop: modalOptions.backdrop, - keyboard: modalOptions.keyboard, - backdropClass: modalOptions.backdropClass, - windowClass: modalOptions.windowClass, - windowTemplateUrl: modalOptions.windowTemplateUrl, - size: modalOptions.size - }); - - }, function resolveError(reason) { - modalResultDeferred.reject(reason); - }); - - templateAndResolvePromise.then(function () { - modalOpenedDeferred.resolve(true); - }, function () { - modalOpenedDeferred.reject(false); - }); - - return modalInstance; - }; - - return $modal; - }] - }; - - return $modalProvider; - }); - -angular.module('ui.bootstrap.pagination', []) - -.controller('PaginationController', ['$scope', '$attrs', '$parse', function ($scope, $attrs, $parse) { - var self = this, - ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl - setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; - - this.init = function(ngModelCtrl_, config) { - ngModelCtrl = ngModelCtrl_; - this.config = config; - - ngModelCtrl.$render = function() { - self.render(); - }; - - if ($attrs.itemsPerPage) { - $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) { - self.itemsPerPage = parseInt(value, 10); - $scope.totalPages = self.calculateTotalPages(); - }); - } else { - this.itemsPerPage = config.itemsPerPage; - } - }; - - this.calculateTotalPages = function() { - var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); - return Math.max(totalPages || 0, 1); - }; - - this.render = function() { - $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1; - }; - - $scope.selectPage = function(page) { - if ( $scope.page !== page && page > 0 && page <= $scope.totalPages) { - ngModelCtrl.$setViewValue(page); - ngModelCtrl.$render(); - } - }; - - $scope.getText = function( key ) { - return $scope[key + 'Text'] || self.config[key + 'Text']; - }; - $scope.noPrevious = function() { - return $scope.page === 1; - }; - $scope.noNext = function() { - return $scope.page === $scope.totalPages; - }; - - $scope.$watch('totalItems', function() { - $scope.totalPages = self.calculateTotalPages(); - }); - - $scope.$watch('totalPages', function(value) { - setNumPages($scope.$parent, value); // Readonly variable - - if ( $scope.page > value ) { - $scope.selectPage(value); - } else { - ngModelCtrl.$render(); - } - }); -}]) - -.constant('paginationConfig', { - itemsPerPage: 10, - boundaryLinks: false, - directionLinks: true, - firstText: 'First', - previousText: 'Previous', - nextText: 'Next', - lastText: 'Last', - rotate: true -}) - -.directive('pagination', ['$parse', 'paginationConfig', function($parse, paginationConfig) { - return { - restrict: 'EA', - scope: { - totalItems: '=', - firstText: '@', - previousText: '@', - nextText: '@', - lastText: '@' - }, - require: ['pagination', '?ngModel'], - controller: 'PaginationController', - templateUrl: 'template/pagination/pagination.html', - replace: true, - link: function(scope, element, attrs, ctrls) { - var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - - if (!ngModelCtrl) { - return; // do nothing if no ng-model - } - - // Setup configuration parameters - var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize, - rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate; - scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks; - scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks; - - paginationCtrl.init(ngModelCtrl, paginationConfig); - - if (attrs.maxSize) { - scope.$parent.$watch($parse(attrs.maxSize), function(value) { - maxSize = parseInt(value, 10); - paginationCtrl.render(); - }); - } - - // Create page object used in template - function makePage(number, text, isActive) { - return { - number: number, - text: text, - active: isActive - }; - } - - function getPages(currentPage, totalPages) { - var pages = []; - - // Default page limits - var startPage = 1, endPage = totalPages; - var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages ); - - // recompute if maxSize - if ( isMaxSized ) { - if ( rotate ) { - // Current page is displayed in the middle of the visible ones - startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); - endPage = startPage + maxSize - 1; - - // Adjust if limit is exceeded - if (endPage > totalPages) { - endPage = totalPages; - startPage = endPage - maxSize + 1; - } - } else { - // Visible pages are paginated with maxSize - startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1; - - // Adjust last page if limit is exceeded - endPage = Math.min(startPage + maxSize - 1, totalPages); - } - } - - // Add page number links - for (var number = startPage; number <= endPage; number++) { - var page = makePage(number, number, number === currentPage); - pages.push(page); - } - - // Add links to move between page sets - if ( isMaxSized && ! rotate ) { - if ( startPage > 1 ) { - var previousPageSet = makePage(startPage - 1, '...', false); - pages.unshift(previousPageSet); - } - - if ( endPage < totalPages ) { - var nextPageSet = makePage(endPage + 1, '...', false); - pages.push(nextPageSet); - } - } - - return pages; - } - - var originalRender = paginationCtrl.render; - paginationCtrl.render = function() { - originalRender(); - if (scope.page > 0 && scope.page <= scope.totalPages) { - scope.pages = getPages(scope.page, scope.totalPages); - } - }; - } - }; -}]) - -.constant('pagerConfig', { - itemsPerPage: 10, - previousText: '« Previous', - nextText: 'Next »', - align: true -}) - -.directive('pager', ['pagerConfig', function(pagerConfig) { - return { - restrict: 'EA', - scope: { - totalItems: '=', - previousText: '@', - nextText: '@' - }, - require: ['pager', '?ngModel'], - controller: 'PaginationController', - templateUrl: 'template/pagination/pager.html', - replace: true, - link: function(scope, element, attrs, ctrls) { - var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - - if (!ngModelCtrl) { - return; // do nothing if no ng-model - } - - scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align; - paginationCtrl.init(ngModelCtrl, pagerConfig); - } - }; -}]); - -/** - * The following features are still outstanding: animation as a - * function, placement as a function, inside, support for more triggers than - * just mouse enter/leave, html tooltips, and selector delegation. - */ -angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) - -/** - * The $tooltip service creates tooltip- and popover-like directives as well as - * houses global options for them. - */ -.provider( '$tooltip', function () { - // The default options tooltip and popover. - var defaultOptions = { - placement: 'top', - animation: true, - popupDelay: 0 - }; - - // Default hide triggers for each show trigger - var triggerMap = { - 'mouseenter': 'mouseleave', - 'click': 'click', - 'focus': 'blur' - }; - - // The options specified to the provider globally. - var globalOptions = {}; - - /** - * `options({})` allows global configuration of all tooltips in the - * application. - * - * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { - * // place tooltips left instead of top by default - * $tooltipProvider.options( { placement: 'left' } ); - * }); - */ - this.options = function( value ) { - angular.extend( globalOptions, value ); - }; - - /** - * This allows you to extend the set of trigger mappings available. E.g.: - * - * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); - */ - this.setTriggers = function setTriggers ( triggers ) { - angular.extend( triggerMap, triggers ); - }; - - /** - * This is a helper function for translating camel-case to snake-case. - */ - function snake_case(name){ - var regexp = /[A-Z]/g; - var separator = '-'; - return name.replace(regexp, function(letter, pos) { - return (pos ? separator : '') + letter.toLowerCase(); - }); - } - - /** - * Returns the actual instance of the $tooltip service. - * TODO support multiple triggers - */ - this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) { - return function $tooltip ( type, prefix, defaultTriggerShow ) { - var options = angular.extend( {}, defaultOptions, globalOptions ); - - /** - * Returns an object of show and hide triggers. - * - * If a trigger is supplied, - * it is used to show the tooltip; otherwise, it will use the `trigger` - * option passed to the `$tooltipProvider.options` method; else it will - * default to the trigger supplied to this directive factory. - * - * The hide trigger is based on the show trigger. If the `trigger` option - * was passed to the `$tooltipProvider.options` method, it will use the - * mapped trigger from `triggerMap` or the passed trigger if the map is - * undefined; otherwise, it uses the `triggerMap` value of the show - * trigger; else it will just use the show trigger. - */ - function getTriggers ( trigger ) { - var show = trigger || options.trigger || defaultTriggerShow; - var hide = triggerMap[show] || show; - return { - show: show, - hide: hide - }; - } - - var directiveName = snake_case( type ); - - var startSym = $interpolate.startSymbol(); - var endSym = $interpolate.endSymbol(); - var template = - '<div '+ directiveName +'-popup '+ - 'title="'+startSym+'tt_title'+endSym+'" '+ - 'content="'+startSym+'tt_content'+endSym+'" '+ - 'placement="'+startSym+'tt_placement'+endSym+'" '+ - 'animation="tt_animation" '+ - 'is-open="tt_isOpen"'+ - '>'+ - '</div>'; - - return { - restrict: 'EA', - scope: true, - compile: function (tElem, tAttrs) { - var tooltipLinker = $compile( template ); - - return function link ( scope, element, attrs ) { - var tooltip; - var transitionTimeout; - var popupTimeout; - var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; - var triggers = getTriggers( undefined ); - var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); - - var positionTooltip = function () { - - var ttPosition = $position.positionElements(element, tooltip, scope.tt_placement, appendToBody); - ttPosition.top += 'px'; - ttPosition.left += 'px'; - - // Now set the calculated positioning. - tooltip.css( ttPosition ); - }; - - // By default, the tooltip is not open. - // TODO add ability to start tooltip opened - scope.tt_isOpen = false; - - function toggleTooltipBind () { - if ( ! scope.tt_isOpen ) { - showTooltipBind(); - } else { - hideTooltipBind(); - } - } - - // Show the tooltip with delay if specified, otherwise show it immediately - function showTooltipBind() { - if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { - return; - } - if ( scope.tt_popupDelay ) { - // Do nothing if the tooltip was already scheduled to pop-up. - // This happens if show is triggered multiple times before any hide is triggered. - if (!popupTimeout) { - popupTimeout = $timeout( show, scope.tt_popupDelay, false ); - popupTimeout.then(function(reposition){reposition();}); - } - } else { - show()(); - } - } - - function hideTooltipBind () { - scope.$apply(function () { - hide(); - }); - } - - // Show the tooltip popup element. - function show() { - - popupTimeout = null; - - // If there is a pending remove transition, we must cancel it, lest the - // tooltip be mysteriously removed. - if ( transitionTimeout ) { - $timeout.cancel( transitionTimeout ); - transitionTimeout = null; - } - - // Don't show empty tooltips. - if ( ! scope.tt_content ) { - return angular.noop; - } - - createTooltip(); - - // Set the initial positioning. - tooltip.css({ top: 0, left: 0, display: 'block' }); - - // Now we add it to the DOM because need some info about it. But it's not - // visible yet anyway. - if ( appendToBody ) { - $document.find( 'body' ).append( tooltip ); - } else { - element.after( tooltip ); - } - - positionTooltip(); - - // And show the tooltip. - scope.tt_isOpen = true; - scope.$digest(); // digest required as $apply is not called - - // Return positioning function as promise callback for correct - // positioning after draw. - return positionTooltip; - } - - // Hide the tooltip popup element. - function hide() { - // First things first: we don't show it anymore. - scope.tt_isOpen = false; - - //if tooltip is going to be shown after delay, we must cancel this - $timeout.cancel( popupTimeout ); - popupTimeout = null; - - // And now we remove it from the DOM. However, if we have animation, we - // need to wait for it to expire beforehand. - // FIXME: this is a placeholder for a port of the transitions library. - if ( scope.tt_animation ) { - if (!transitionTimeout) { - transitionTimeout = $timeout(removeTooltip, 500); - } - } else { - removeTooltip(); - } - } - - function createTooltip() { - // There can only be one tooltip element per directive shown at once. - if (tooltip) { - removeTooltip(); - } - tooltip = tooltipLinker(scope, function () {}); - - // Get contents rendered into the tooltip - scope.$digest(); - } - - function removeTooltip() { - transitionTimeout = null; - if (tooltip) { - tooltip.remove(); - tooltip = null; - } - } - - /** - * Observe the relevant attributes. - */ - attrs.$observe( type, function ( val ) { - scope.tt_content = val; - - if (!val && scope.tt_isOpen ) { - hide(); - } - }); - - attrs.$observe( prefix+'Title', function ( val ) { - scope.tt_title = val; - }); - - attrs.$observe( prefix+'Placement', function ( val ) { - scope.tt_placement = angular.isDefined( val ) ? val : options.placement; - }); - - attrs.$observe( prefix+'PopupDelay', function ( val ) { - var delay = parseInt( val, 10 ); - scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; - }); - - var unregisterTriggers = function () { - element.unbind(triggers.show, showTooltipBind); - element.unbind(triggers.hide, hideTooltipBind); - }; - - attrs.$observe( prefix+'Trigger', function ( val ) { - unregisterTriggers(); - - triggers = getTriggers( val ); - - if ( triggers.show === triggers.hide ) { - element.bind( triggers.show, toggleTooltipBind ); - } else { - element.bind( triggers.show, showTooltipBind ); - element.bind( triggers.hide, hideTooltipBind ); - } - }); - - var animation = scope.$eval(attrs[prefix + 'Animation']); - scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation; - - attrs.$observe( prefix+'AppendToBody', function ( val ) { - appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody; - }); - - // if a tooltip is attached to <body> we need to remove it on - // location change as its parent scope will probably not be destroyed - // by the change. - if ( appendToBody ) { - scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { - if ( scope.tt_isOpen ) { - hide(); - } - }); - } - - // Make sure tooltip is destroyed and removed. - scope.$on('$destroy', function onDestroyTooltip() { - $timeout.cancel( transitionTimeout ); - $timeout.cancel( popupTimeout ); - unregisterTriggers(); - removeTooltip(); - }); - }; - } - }; - }; - }]; -}) - -.directive( 'tooltipPopup', function () { - return { - restrict: 'EA', - replace: true, - scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/tooltip/tooltip-popup.html' - }; -}) - -.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); -}]) - -.directive( 'tooltipHtmlUnsafePopup', function () { - return { - restrict: 'EA', - replace: true, - scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' - }; -}) - -.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); -}]); - -/** - * The following features are still outstanding: popup delay, animation as a - * function, placement as a function, inside, support for more triggers than - * just mouse enter/leave, html popovers, and selector delegatation. - */ -angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) - -.directive( 'popoverPopup', function () { - return { - restrict: 'EA', - replace: true, - scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/popover/popover.html' - }; -}) - -.directive( 'popover', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'popover', 'popover', 'click' ); -}]); - -angular.module('ui.bootstrap.progressbar', []) - -.constant('progressConfig', { - animate: true, - max: 100 -}) - -.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) { - var self = this, - animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; - - this.bars = []; - $scope.max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max; - - this.addBar = function(bar, element) { - if ( !animate ) { - element.css({'transition': 'none'}); - } - - this.bars.push(bar); - - bar.$watch('value', function( value ) { - bar.percent = +(100 * value / $scope.max).toFixed(2); - }); - - bar.$on('$destroy', function() { - element = null; - self.removeBar(bar); - }); - }; - - this.removeBar = function(bar) { - this.bars.splice(this.bars.indexOf(bar), 1); - }; -}]) - -.directive('progress', function() { - return { - restrict: 'EA', - replace: true, - transclude: true, - controller: 'ProgressController', - require: 'progress', - scope: {}, - templateUrl: 'template/progressbar/progress.html' - }; -}) - -.directive('bar', function() { - return { - restrict: 'EA', - replace: true, - transclude: true, - require: '^progress', - scope: { - value: '=', - type: '@' - }, - templateUrl: 'template/progressbar/bar.html', - link: function(scope, element, attrs, progressCtrl) { - progressCtrl.addBar(scope, element); - } - }; -}) - -.directive('progressbar', function() { - return { - restrict: 'EA', - replace: true, - transclude: true, - controller: 'ProgressController', - scope: { - value: '=', - type: '@' - }, - templateUrl: 'template/progressbar/progressbar.html', - link: function(scope, element, attrs, progressCtrl) { - progressCtrl.addBar(scope, angular.element(element.children()[0])); - } - }; -}); -angular.module('ui.bootstrap.rating', []) - -.constant('ratingConfig', { - max: 5, - stateOn: null, - stateOff: null -}) - -.controller('RatingController', ['$scope', '$attrs', 'ratingConfig', function($scope, $attrs, ratingConfig) { - var ngModelCtrl = { $setViewValue: angular.noop }; - - this.init = function(ngModelCtrl_) { - ngModelCtrl = ngModelCtrl_; - ngModelCtrl.$render = this.render; - - this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; - this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; - - var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) : - new Array( angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max ); - $scope.range = this.buildTemplateObjects(ratingStates); - }; - - this.buildTemplateObjects = function(states) { - for (var i = 0, n = states.length; i < n; i++) { - states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff }, states[i]); - } - return states; - }; - - $scope.rate = function(value) { - if ( !$scope.readonly && value >= 0 && value <= $scope.range.length ) { - ngModelCtrl.$setViewValue(value); - ngModelCtrl.$render(); - } - }; - - $scope.enter = function(value) { - if ( !$scope.readonly ) { - $scope.value = value; - } - $scope.onHover({value: value}); - }; - - $scope.reset = function() { - $scope.value = ngModelCtrl.$viewValue; - $scope.onLeave(); - }; - - $scope.onKeydown = function(evt) { - if (/(37|38|39|40)/.test(evt.which)) { - evt.preventDefault(); - evt.stopPropagation(); - $scope.rate( $scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1) ); - } - }; - - this.render = function() { - $scope.value = ngModelCtrl.$viewValue; - }; -}]) - -.directive('rating', function() { - return { - restrict: 'EA', - require: ['rating', 'ngModel'], - scope: { - readonly: '=?', - onHover: '&', - onLeave: '&' - }, - controller: 'RatingController', - templateUrl: 'template/rating/rating.html', - replace: true, - link: function(scope, element, attrs, ctrls) { - var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - - if ( ngModelCtrl ) { - ratingCtrl.init( ngModelCtrl ); - } - } - }; -}); - -/** - * @ngdoc overview - * @name ui.bootstrap.tabs - * - * @description - * AngularJS version of the tabs directive. - */ - -angular.module('ui.bootstrap.tabs', []) - -.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { - var ctrl = this, - tabs = ctrl.tabs = $scope.tabs = []; - - ctrl.select = function(selectedTab) { - angular.forEach(tabs, function(tab) { - if (tab.active && tab !== selectedTab) { - tab.active = false; - tab.onDeselect(); - } - }); - selectedTab.active = true; - selectedTab.onSelect(); - }; - - ctrl.addTab = function addTab(tab) { - tabs.push(tab); - // we can't run the select function on the first tab - // since that would select it twice - if (tabs.length === 1) { - tab.active = true; - } else if (tab.active) { - ctrl.select(tab); - } - }; - - ctrl.removeTab = function removeTab(tab) { - var index = tabs.indexOf(tab); - //Select a new tab if the tab to be removed is selected - if (tab.active && tabs.length > 1) { - //If this is the last tab, select the previous tab. else, the next tab. - var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; - ctrl.select(tabs[newActiveIndex]); - } - tabs.splice(index, 1); - }; -}]) - -/** - * @ngdoc directive - * @name ui.bootstrap.tabs.directive:tabset - * @restrict EA - * - * @description - * Tabset is the outer container for the tabs directive - * - * @param {boolean=} vertical Whether or not to use vertical styling for the tabs. - * @param {boolean=} justified Whether or not to use justified styling for the tabs. - * - * @example -<example module="ui.bootstrap"> - <file name="index.html"> - <tabset> - <tab heading="Tab 1"><b>First</b> Content!</tab> - <tab heading="Tab 2"><i>Second</i> Content!</tab> - </tabset> - <hr /> - <tabset vertical="true"> - <tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab> - <tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab> - </tabset> - <tabset justified="true"> - <tab heading="Justified Tab 1"><b>First</b> Justified Content!</tab> - <tab heading="Justified Tab 2"><i>Second</i> Justified Content!</tab> - </tabset> - </file> -</example> - */ -.directive('tabset', function() { - return { - restrict: 'EA', - transclude: true, - replace: true, - scope: { - type: '@' - }, - controller: 'TabsetController', - templateUrl: 'template/tabs/tabset.html', - link: function(scope, element, attrs) { - scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; - scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; - } - }; -}) - -/** - * @ngdoc directive - * @name ui.bootstrap.tabs.directive:tab - * @restrict EA - * - * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. - * @param {string=} select An expression to evaluate when the tab is selected. - * @param {boolean=} active A binding, telling whether or not this tab is selected. - * @param {boolean=} disabled A binding, telling whether or not this tab is disabled. - * - * @description - * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. - * - * @example -<example module="ui.bootstrap"> - <file name="index.html"> - <div ng-controller="TabsDemoCtrl"> - <button class="btn btn-small" ng-click="items[0].active = true"> - Select item 1, using active binding - </button> - <button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled"> - Enable/disable item 2, using disabled binding - </button> - <br /> - <tabset> - <tab heading="Tab 1">First Tab</tab> - <tab select="alertMe()"> - <tab-heading><i class="icon-bell"></i> Alert me!</tab-heading> - Second Tab, with alert callback and html heading! - </tab> - <tab ng-repeat="item in items" - heading="{{item.title}}" - disabled="item.disabled" - active="item.active"> - {{item.content}} - </tab> - </tabset> - </div> - </file> - <file name="script.js"> - function TabsDemoCtrl($scope) { - $scope.items = [ - { title:"Dynamic Title 1", content:"Dynamic Item 0" }, - { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } - ]; - - $scope.alertMe = function() { - setTimeout(function() { - alert("You've selected the alert tab!"); - }); - }; - }; - </file> -</example> - */ - -/** - * @ngdoc directive - * @name ui.bootstrap.tabs.directive:tabHeading - * @restrict EA - * - * @description - * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. - * - * @example -<example module="ui.bootstrap"> - <file name="index.html"> - <tabset> - <tab> - <tab-heading><b>HTML</b> in my titles?!</tab-heading> - And some content, too! - </tab> - <tab> - <tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading> - That's right. - </tab> - </tabset> - </file> -</example> - */ -.directive('tab', ['$parse', function($parse) { - return { - require: '^tabset', - restrict: 'EA', - replace: true, - templateUrl: 'template/tabs/tab.html', - transclude: true, - scope: { - active: '=?', - heading: '@', - onSelect: '&select', //This callback is called in contentHeadingTransclude - //once it inserts the tab's content into the dom - onDeselect: '&deselect' - }, - controller: function() { - //Empty controller so other directives can require being 'under' a tab - }, - compile: function(elm, attrs, transclude) { - return function postLink(scope, elm, attrs, tabsetCtrl) { - scope.$watch('active', function(active) { - if (active) { - tabsetCtrl.select(scope); - } - }); - - scope.disabled = false; - if ( attrs.disabled ) { - scope.$parent.$watch($parse(attrs.disabled), function(value) { - scope.disabled = !! value; - }); - } - - scope.select = function() { - if ( !scope.disabled ) { - scope.active = true; - } - }; - - tabsetCtrl.addTab(scope); - scope.$on('$destroy', function() { - tabsetCtrl.removeTab(scope); - }); - - //We need to transclude later, once the content container is ready. - //when this link happens, we're inside a tab heading. - scope.$transcludeFn = transclude; - }; - } - }; -}]) - -.directive('tabHeadingTransclude', [function() { - return { - restrict: 'A', - require: '^tab', - link: function(scope, elm, attrs, tabCtrl) { - scope.$watch('headingElement', function updateHeadingElement(heading) { - if (heading) { - elm.html(''); - elm.append(heading); - } - }); - } - }; -}]) - -.directive('tabContentTransclude', function() { - return { - restrict: 'A', - require: '^tabset', - link: function(scope, elm, attrs) { - var tab = scope.$eval(attrs.tabContentTransclude); - - //Now our tab is ready to be transcluded: both the tab heading area - //and the tab content area are loaded. Transclude 'em both. - tab.$transcludeFn(tab.$parent, function(contents) { - angular.forEach(contents, function(node) { - if (isTabHeading(node)) { - //Let tabHeadingTransclude know. - tab.headingElement = node; - } else { - elm.append(node); - } - }); - }); - } - }; - function isTabHeading(node) { - return node.tagName && ( - node.hasAttribute('tab-heading') || - node.hasAttribute('data-tab-heading') || - node.tagName.toLowerCase() === 'tab-heading' || - node.tagName.toLowerCase() === 'data-tab-heading' - ); - } -}) - -; - -angular.module('ui.bootstrap.timepicker', []) - -.constant('timepickerConfig', { - hourStep: 1, - minuteStep: 1, - showMeridian: true, - meridians: null, - readonlyInput: false, - mousewheel: true -}) - -.controller('TimepickerController', ['$scope', '$attrs', '$parse', '$log', '$locale', 'timepickerConfig', function($scope, $attrs, $parse, $log, $locale, timepickerConfig) { - var selected = new Date(), - ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl - meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; - - this.init = function( ngModelCtrl_, inputs ) { - ngModelCtrl = ngModelCtrl_; - ngModelCtrl.$render = this.render; - - var hoursInputEl = inputs.eq(0), - minutesInputEl = inputs.eq(1); - - var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; - if ( mousewheel ) { - this.setupMousewheelEvents( hoursInputEl, minutesInputEl ); - } - - $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; - this.setupInputEvents( hoursInputEl, minutesInputEl ); - }; - - var hourStep = timepickerConfig.hourStep; - if ($attrs.hourStep) { - $scope.$parent.$watch($parse($attrs.hourStep), function(value) { - hourStep = parseInt(value, 10); - }); - } - - var minuteStep = timepickerConfig.minuteStep; - if ($attrs.minuteStep) { - $scope.$parent.$watch($parse($attrs.minuteStep), function(value) { - minuteStep = parseInt(value, 10); - }); - } - - // 12H / 24H mode - $scope.showMeridian = timepickerConfig.showMeridian; - if ($attrs.showMeridian) { - $scope.$parent.$watch($parse($attrs.showMeridian), function(value) { - $scope.showMeridian = !!value; - - if ( ngModelCtrl.$error.time ) { - // Evaluate from template - var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); - if (angular.isDefined( hours ) && angular.isDefined( minutes )) { - selected.setHours( hours ); - refresh(); - } - } else { - updateTemplate(); - } - }); - } - - // Get $scope.hours in 24H mode if valid - function getHoursFromTemplate ( ) { - var hours = parseInt( $scope.hours, 10 ); - var valid = ( $scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); - if ( !valid ) { - return undefined; - } - - if ( $scope.showMeridian ) { - if ( hours === 12 ) { - hours = 0; - } - if ( $scope.meridian === meridians[1] ) { - hours = hours + 12; - } - } - return hours; - } - - function getMinutesFromTemplate() { - var minutes = parseInt($scope.minutes, 10); - return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined; - } - - function pad( value ) { - return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value; - } - - // Respond on mousewheel spin - this.setupMousewheelEvents = function( hoursInputEl, minutesInputEl ) { - var isScrollingUp = function(e) { - if (e.originalEvent) { - e = e.originalEvent; - } - //pick correct delta variable depending on event - var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY; - return (e.detail || delta > 0); - }; - - hoursInputEl.bind('mousewheel wheel', function(e) { - $scope.$apply( (isScrollingUp(e)) ? $scope.incrementHours() : $scope.decrementHours() ); - e.preventDefault(); - }); - - minutesInputEl.bind('mousewheel wheel', function(e) { - $scope.$apply( (isScrollingUp(e)) ? $scope.incrementMinutes() : $scope.decrementMinutes() ); - e.preventDefault(); - }); - - }; - - this.setupInputEvents = function( hoursInputEl, minutesInputEl ) { - if ( $scope.readonlyInput ) { - $scope.updateHours = angular.noop; - $scope.updateMinutes = angular.noop; - return; - } - - var invalidate = function(invalidHours, invalidMinutes) { - ngModelCtrl.$setViewValue( null ); - ngModelCtrl.$setValidity('time', false); - if (angular.isDefined(invalidHours)) { - $scope.invalidHours = invalidHours; - } - if (angular.isDefined(invalidMinutes)) { - $scope.invalidMinutes = invalidMinutes; - } - }; - - $scope.updateHours = function() { - var hours = getHoursFromTemplate(); - - if ( angular.isDefined(hours) ) { - selected.setHours( hours ); - refresh( 'h' ); - } else { - invalidate(true); - } - }; - - hoursInputEl.bind('blur', function(e) { - if ( !$scope.invalidHours && $scope.hours < 10) { - $scope.$apply( function() { - $scope.hours = pad( $scope.hours ); - }); - } - }); - - $scope.updateMinutes = function() { - var minutes = getMinutesFromTemplate(); - - if ( angular.isDefined(minutes) ) { - selected.setMinutes( minutes ); - refresh( 'm' ); - } else { - invalidate(undefined, true); - } - }; - - minutesInputEl.bind('blur', function(e) { - if ( !$scope.invalidMinutes && $scope.minutes < 10 ) { - $scope.$apply( function() { - $scope.minutes = pad( $scope.minutes ); - }); - } - }); - - }; - - this.render = function() { - var date = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : null; - - if ( isNaN(date) ) { - ngModelCtrl.$setValidity('time', false); - $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); - } else { - if ( date ) { - selected = date; - } - makeValid(); - updateTemplate(); - } - }; - - // Call internally when we know that model is valid. - function refresh( keyboardChange ) { - makeValid(); - ngModelCtrl.$setViewValue( new Date(selected) ); - updateTemplate( keyboardChange ); - } - - function makeValid() { - ngModelCtrl.$setValidity('time', true); - $scope.invalidHours = false; - $scope.invalidMinutes = false; - } - - function updateTemplate( keyboardChange ) { - var hours = selected.getHours(), minutes = selected.getMinutes(); - - if ( $scope.showMeridian ) { - hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system - } - - $scope.hours = keyboardChange === 'h' ? hours : pad(hours); - $scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes); - $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; - } - - function addMinutes( minutes ) { - var dt = new Date( selected.getTime() + minutes * 60000 ); - selected.setHours( dt.getHours(), dt.getMinutes() ); - refresh(); - } - - $scope.incrementHours = function() { - addMinutes( hourStep * 60 ); - }; - $scope.decrementHours = function() { - addMinutes( - hourStep * 60 ); - }; - $scope.incrementMinutes = function() { - addMinutes( minuteStep ); - }; - $scope.decrementMinutes = function() { - addMinutes( - minuteStep ); - }; - $scope.toggleMeridian = function() { - addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) ); - }; -}]) - -.directive('timepicker', function () { - return { - restrict: 'EA', - require: ['timepicker', '?^ngModel'], - controller:'TimepickerController', - replace: true, - scope: {}, - templateUrl: 'template/timepicker/timepicker.html', - link: function(scope, element, attrs, ctrls) { - var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - - if ( ngModelCtrl ) { - timepickerCtrl.init( ngModelCtrl, element.find('input') ); - } - } - }; -}); - -angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml']) - -/** - * A helper service that can parse typeahead's syntax (string provided by users) - * Extracted to a separate service for ease of unit testing - */ - .factory('typeaheadParser', ['$parse', function ($parse) { - - // 00000111000000000000022200000000000000003333333333333330000000000044000 - var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/; - - return { - parse:function (input) { - - var match = input.match(TYPEAHEAD_REGEXP); - if (!match) { - throw new Error( - 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' + - ' but got "' + input + '".'); - } - - return { - itemName:match[3], - source:$parse(match[4]), - viewMapper:$parse(match[2] || match[1]), - modelMapper:$parse(match[1]) - }; - } - }; -}]) - - .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', - function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) { - - var HOT_KEYS = [9, 13, 27, 38, 40]; - - return { - require:'ngModel', - link:function (originalScope, element, attrs, modelCtrl) { - - //SUPPORTED ATTRIBUTES (OPTIONS) - - //minimal no of characters that needs to be entered before typeahead kicks-in - var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; - - //minimal wait time after last character typed before typehead kicks-in - var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; - - //should it restrict model values to the ones selected from the popup only? - var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; - - //binding to a variable that indicates if matches are being retrieved asynchronously - var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; - - //a callback executed when a match is selected - var onSelectCallback = $parse(attrs.typeaheadOnSelect); - - var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; - - var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; - - //INTERNAL VARIABLES - - //model setter executed upon match selection - var $setModelValue = $parse(attrs.ngModel).assign; - - //expressions used by typeahead - var parserResult = typeaheadParser.parse(attrs.typeahead); - - var hasFocus; - - //create a child scope for the typeahead directive so we are not polluting original scope - //with typeahead-specific data (matches, query etc.) - var scope = originalScope.$new(); - originalScope.$on('$destroy', function(){ - scope.$destroy(); - }); - - // WAI-ARIA - var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); - element.attr({ - 'aria-autocomplete': 'list', - 'aria-expanded': false, - 'aria-owns': popupId - }); - - //pop-up element used to display matches - var popUpEl = angular.element('<div typeahead-popup></div>'); - popUpEl.attr({ - id: popupId, - matches: 'matches', - active: 'activeIdx', - select: 'select(activeIdx)', - query: 'query', - position: 'position' - }); - //custom item template - if (angular.isDefined(attrs.typeaheadTemplateUrl)) { - popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); - } - - var resetMatches = function() { - scope.matches = []; - scope.activeIdx = -1; - element.attr('aria-expanded', false); - }; - - var getMatchId = function(index) { - return popupId + '-option-' + index; - }; - - // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. - // This attribute is added or removed automatically when the `activeIdx` changes. - scope.$watch('activeIdx', function(index) { - if (index < 0) { - element.removeAttr('aria-activedescendant'); - } else { - element.attr('aria-activedescendant', getMatchId(index)); - } - }); - - var getMatchesAsync = function(inputValue) { - - var locals = {$viewValue: inputValue}; - isLoadingSetter(originalScope, true); - $q.when(parserResult.source(originalScope, locals)).then(function(matches) { - - //it might happen that several async queries were in progress if a user were typing fast - //but we are interested only in responses that correspond to the current view value - var onCurrentRequest = (inputValue === modelCtrl.$viewValue); - if (onCurrentRequest && hasFocus) { - if (matches.length > 0) { - - scope.activeIdx = 0; - scope.matches.length = 0; - - //transform labels - for(var i=0; i<matches.length; i++) { - locals[parserResult.itemName] = matches[i]; - scope.matches.push({ - id: getMatchId(i), - label: parserResult.viewMapper(scope, locals), - model: matches[i] - }); - } - - scope.query = inputValue; - //position pop-up with matches - we need to re-calculate its position each time we are opening a window - //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page - //due to other elements being rendered - scope.position = appendToBody ? $position.offset(element) : $position.position(element); - scope.position.top = scope.position.top + element.prop('offsetHeight'); - - element.attr('aria-expanded', true); - } else { - resetMatches(); - } - } - if (onCurrentRequest) { - isLoadingSetter(originalScope, false); - } - }, function(){ - resetMatches(); - isLoadingSetter(originalScope, false); - }); - }; - - resetMatches(); - - //we need to propagate user's query so we can higlight matches - scope.query = undefined; - - //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later - var timeoutPromise; - - var scheduleSearchWithTimeout = function(inputValue) { - timeoutPromise = $timeout(function () { - getMatchesAsync(inputValue); - }, waitTime); - }; - - var cancelPreviousTimeout = function() { - if (timeoutPromise) { - $timeout.cancel(timeoutPromise); - } - }; - - //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM - //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue - modelCtrl.$parsers.unshift(function (inputValue) { - - hasFocus = true; - - if (inputValue && inputValue.length >= minSearch) { - if (waitTime > 0) { - cancelPreviousTimeout(); - scheduleSearchWithTimeout(inputValue); - } else { - getMatchesAsync(inputValue); - } - } else { - isLoadingSetter(originalScope, false); - cancelPreviousTimeout(); - resetMatches(); - } - - if (isEditable) { - return inputValue; - } else { - if (!inputValue) { - // Reset in case user had typed something previously. - modelCtrl.$setValidity('editable', true); - return inputValue; - } else { - modelCtrl.$setValidity('editable', false); - return undefined; - } - } - }); - - modelCtrl.$formatters.push(function (modelValue) { - - var candidateViewValue, emptyViewValue; - var locals = {}; - - if (inputFormatter) { - - locals['$model'] = modelValue; - return inputFormatter(originalScope, locals); - - } else { - - //it might happen that we don't have enough info to properly render input value - //we need to check for this situation and simply return model value if we can't apply custom formatting - locals[parserResult.itemName] = modelValue; - candidateViewValue = parserResult.viewMapper(originalScope, locals); - locals[parserResult.itemName] = undefined; - emptyViewValue = parserResult.viewMapper(originalScope, locals); - - return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; - } - }); - - scope.select = function (activeIdx) { - //called from within the $digest() cycle - var locals = {}; - var model, item; - - locals[parserResult.itemName] = item = scope.matches[activeIdx].model; - model = parserResult.modelMapper(originalScope, locals); - $setModelValue(originalScope, model); - modelCtrl.$setValidity('editable', true); - - onSelectCallback(originalScope, { - $item: item, - $model: model, - $label: parserResult.viewMapper(originalScope, locals) - }); - - resetMatches(); - - //return focus to the input element if a match was selected via a mouse click event - // use timeout to avoid $rootScope:inprog error - $timeout(function() { element[0].focus(); }, 0, false); - }; - - //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) - element.bind('keydown', function (evt) { - - //typeahead is open and an "interesting" key was pressed - if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { - return; - } - - evt.preventDefault(); - - if (evt.which === 40) { - scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; - scope.$digest(); - - } else if (evt.which === 38) { - scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1; - scope.$digest(); - - } else if (evt.which === 13 || evt.which === 9) { - scope.$apply(function () { - scope.select(scope.activeIdx); - }); - - } else if (evt.which === 27) { - evt.stopPropagation(); - - resetMatches(); - scope.$digest(); - } - }); - - element.bind('blur', function (evt) { - hasFocus = false; - }); - - // Keep reference to click handler to unbind it. - var dismissClickHandler = function (evt) { - if (element[0] !== evt.target) { - resetMatches(); - scope.$digest(); - } - }; - - $document.bind('click', dismissClickHandler); - - originalScope.$on('$destroy', function(){ - $document.unbind('click', dismissClickHandler); - }); - - var $popup = $compile(popUpEl)(scope); - if ( appendToBody ) { - $document.find('body').append($popup); - } else { - element.after($popup); - } - } - }; - -}]) - - .directive('typeaheadPopup', function () { - return { - restrict:'EA', - scope:{ - matches:'=', - query:'=', - active:'=', - position:'=', - select:'&' - }, - replace:true, - templateUrl:'template/typeahead/typeahead-popup.html', - link:function (scope, element, attrs) { - - scope.templateUrl = attrs.templateUrl; - - scope.isOpen = function () { - return scope.matches.length > 0; - }; - - scope.isActive = function (matchIdx) { - return scope.active == matchIdx; - }; - - scope.selectActive = function (matchIdx) { - scope.active = matchIdx; - }; - - scope.selectMatch = function (activeIdx) { - scope.select({activeIdx:activeIdx}); - }; - } - }; - }) - - .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) { - return { - restrict:'EA', - scope:{ - index:'=', - match:'=', - query:'=' - }, - link:function (scope, element, attrs) { - var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; - $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){ - element.replaceWith($compile(tplContent.trim())(scope)); - }); - } - }; - }]) - - .filter('typeaheadHighlight', function() { - - function escapeRegexp(queryToEscape) { - return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); - } - - return function(matchItem, query) { - return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem; - }; - }); - -angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/accordion/accordion-group.html", - "<div class=\"panel panel-default\">\n" + - " <div class=\"panel-heading\">\n" + - " <h4 class=\"panel-title\">\n" + - " <a class=\"accordion-toggle\" ng-click=\"toggleOpen()\" accordion-transclude=\"heading\"><span ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></a>\n" + - " </h4>\n" + - " </div>\n" + - " <div class=\"panel-collapse\" collapse=\"!isOpen\">\n" + - " <div class=\"panel-body\" ng-transclude></div>\n" + - " </div>\n" + - "</div>"); -}]); - -angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/accordion/accordion.html", - "<div class=\"panel-group\" ng-transclude></div>"); -}]); - -angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/alert/alert.html", - "<div class=\"alert\" ng-class=\"['alert-' + (type || 'warning'), closeable ? 'alert-dismissable' : null]\" role=\"alert\">\n" + - " <button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close()\">\n" + - " <span aria-hidden=\"true\">×</span>\n" + - " <span class=\"sr-only\">Close</span>\n" + - " </button>\n" + - " <div ng-transclude></div>\n" + - "</div>\n" + - ""); -}]); - -angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/carousel/carousel.html", - "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\" ng-swipe-right=\"prev()\" ng-swipe-left=\"next()\">\n" + - " <ol class=\"carousel-indicators\" ng-show=\"slides.length > 1\">\n" + - " <li ng-repeat=\"slide in slides track by $index\" ng-class=\"{active: isActive(slide)}\" ng-click=\"select(slide)\"></li>\n" + - " </ol>\n" + - " <div class=\"carousel-inner\" ng-transclude></div>\n" + - " <a class=\"left carousel-control\" ng-click=\"prev()\" ng-show=\"slides.length > 1\"><span class=\"glyphicon glyphicon-chevron-left\"></span></a>\n" + - " <a class=\"right carousel-control\" ng-click=\"next()\" ng-show=\"slides.length > 1\"><span class=\"glyphicon glyphicon-chevron-right\"></span></a>\n" + - "</div>\n" + - ""); -}]); - -angular.module("template/carousel/slide.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/carousel/slide.html", - "<div ng-class=\"{\n" + - " 'active': leaving || (active && !entering),\n" + - " 'prev': (next || active) && direction=='prev',\n" + - " 'next': (next || active) && direction=='next',\n" + - " 'right': direction=='prev',\n" + - " 'left': direction=='next'\n" + - " }\" class=\"item text-center\" ng-transclude></div>\n" + - ""); -}]); - -angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/datepicker/datepicker.html", - "<div ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" + - " <daypicker ng-switch-when=\"day\" tabindex=\"0\"></daypicker>\n" + - " <monthpicker ng-switch-when=\"month\" tabindex=\"0\"></monthpicker>\n" + - " <yearpicker ng-switch-when=\"year\" tabindex=\"0\"></yearpicker>\n" + - "</div>"); -}]); - -angular.module("template/datepicker/day.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/datepicker/day.html", - "<table role=\"grid\" aria-labelledby=\"{{uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" + - " <thead>\n" + - " <tr>\n" + - " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" + - " <th colspan=\"{{5 + showWeeks}}\"><button id=\"{{uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" + - " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" + - " </tr>\n" + - " <tr>\n" + - " <th ng-show=\"showWeeks\" class=\"text-center\"></th>\n" + - " <th ng-repeat=\"label in labels track by $index\" class=\"text-center\"><small aria-label=\"{{label.full}}\">{{label.abbr}}</small></th>\n" + - " </tr>\n" + - " </thead>\n" + - " <tbody>\n" + - " <tr ng-repeat=\"row in rows track by $index\">\n" + - " <td ng-show=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" + - " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{dt.uid}}\" aria-disabled=\"{{!!dt.disabled}}\">\n" + - " <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default btn-sm\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"{'text-muted': dt.secondary, 'text-info': dt.current}\">{{dt.label}}</span></button>\n" + - " </td>\n" + - " </tr>\n" + - " </tbody>\n" + - "</table>\n" + - ""); -}]); - -angular.module("template/datepicker/month.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/datepicker/month.html", - "<table role=\"grid\" aria-labelledby=\"{{uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" + - " <thead>\n" + - " <tr>\n" + - " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" + - " <th><button id=\"{{uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" + - " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" + - " </tr>\n" + - " </thead>\n" + - " <tbody>\n" + - " <tr ng-repeat=\"row in rows track by $index\">\n" + - " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{dt.uid}}\" aria-disabled=\"{{!!dt.disabled}}\">\n" + - " <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"{'text-info': dt.current}\">{{dt.label}}</span></button>\n" + - " </td>\n" + - " </tr>\n" + - " </tbody>\n" + - "</table>\n" + - ""); -}]); - -angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/datepicker/popup.html", - "<ul class=\"dropdown-menu\" ng-style=\"{display: (isOpen && 'block') || 'none', top: position.top+'px', left: position.left+'px'}\" ng-keydown=\"keydown($event)\">\n" + - " <li ng-transclude></li>\n" + - " <li ng-if=\"showButtonBar\" style=\"padding:10px 9px 2px\">\n" + - " <span class=\"btn-group\">\n" + - " <button type=\"button\" class=\"btn btn-sm btn-info\" ng-click=\"select('today')\">{{ getText('current') }}</button>\n" + - " <button type=\"button\" class=\"btn btn-sm btn-danger\" ng-click=\"select(null)\">{{ getText('clear') }}</button>\n" + - " </span>\n" + - " <button type=\"button\" class=\"btn btn-sm btn-success pull-right\" ng-click=\"close()\">{{ getText('close') }}</button>\n" + - " </li>\n" + - "</ul>\n" + - ""); -}]); - -angular.module("template/datepicker/year.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/datepicker/year.html", - "<table role=\"grid\" aria-labelledby=\"{{uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" + - " <thead>\n" + - " <tr>\n" + - " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" + - " <th colspan=\"3\"><button id=\"{{uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" + - " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" + - " </tr>\n" + - " </thead>\n" + - " <tbody>\n" + - " <tr ng-repeat=\"row in rows track by $index\">\n" + - " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{dt.uid}}\" aria-disabled=\"{{!!dt.disabled}}\">\n" + - " <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"{'text-info': dt.current}\">{{dt.label}}</span></button>\n" + - " </td>\n" + - " </tr>\n" + - " </tbody>\n" + - "</table>\n" + - ""); -}]); - -angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/modal/backdrop.html", - "<div class=\"modal-backdrop fade {{ backdropClass }}\"\n" + - " ng-class=\"{in: animate}\"\n" + - " ng-style=\"{'z-index': 1040 + (index && 1 || 0) + index*10}\"\n" + - "></div>\n" + - ""); -}]); - -angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/modal/window.html", - "<div tabindex=\"-1\" role=\"dialog\" class=\"modal fade\" ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\" ng-click=\"close($event)\">\n" + - " <div class=\"modal-dialog\" ng-class=\"{'modal-sm': size == 'sm', 'modal-lg': size == 'lg'}\"><div class=\"modal-content\" modal-transclude></div></div>\n" + - "</div>"); -}]); - -angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/pagination/pager.html", - "<ul class=\"pager\">\n" + - " <li ng-class=\"{disabled: noPrevious(), previous: align}\"><a href ng-click=\"selectPage(page - 1)\">{{getText('previous')}}</a></li>\n" + - " <li ng-class=\"{disabled: noNext(), next: align}\"><a href ng-click=\"selectPage(page + 1)\">{{getText('next')}}</a></li>\n" + - "</ul>"); -}]); - -angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/pagination/pagination.html", - "<ul class=\"pagination\">\n" + - " <li ng-if=\"boundaryLinks\" ng-class=\"{disabled: noPrevious()}\"><a href ng-click=\"selectPage(1)\">{{getText('first')}}</a></li>\n" + - " <li ng-if=\"directionLinks\" ng-class=\"{disabled: noPrevious()}\"><a href ng-click=\"selectPage(page - 1)\">{{getText('previous')}}</a></li>\n" + - " <li ng-repeat=\"page in pages track by $index\" ng-class=\"{active: page.active}\"><a href ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>\n" + - " <li ng-if=\"directionLinks\" ng-class=\"{disabled: noNext()}\"><a href ng-click=\"selectPage(page + 1)\">{{getText('next')}}</a></li>\n" + - " <li ng-if=\"boundaryLinks\" ng-class=\"{disabled: noNext()}\"><a href ng-click=\"selectPage(totalPages)\">{{getText('last')}}</a></li>\n" + - "</ul>"); -}]); - -angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html", - "<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" + - " <div class=\"tooltip-arrow\"></div>\n" + - " <div class=\"tooltip-inner\" bind-html-unsafe=\"content\"></div>\n" + - "</div>\n" + - ""); -}]); - -angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/tooltip/tooltip-popup.html", - "<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" + - " <div class=\"tooltip-arrow\"></div>\n" + - " <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" + - "</div>\n" + - ""); -}]); - -angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/popover/popover.html", - "<div class=\"popover {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" + - " <div class=\"arrow\"></div>\n" + - "\n" + - " <div class=\"popover-inner\">\n" + - " <h3 class=\"popover-title\" ng-bind=\"title\" ng-show=\"title\"></h3>\n" + - " <div class=\"popover-content\" ng-bind=\"content\"></div>\n" + - " </div>\n" + - "</div>\n" + - ""); -}]); - -angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/progressbar/bar.html", - "<div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: percent + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" ng-transclude></div>"); -}]); - -angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/progressbar/progress.html", - "<div class=\"progress\" ng-transclude></div>"); -}]); - -angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/progressbar/progressbar.html", - "<div class=\"progress\">\n" + - " <div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: percent + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" ng-transclude></div>\n" + - "</div>"); -}]); - -angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/rating/rating.html", - "<span ng-mouseleave=\"reset()\" ng-keydown=\"onKeydown($event)\" tabindex=\"0\" role=\"slider\" aria-valuemin=\"0\" aria-valuemax=\"{{range.length}}\" aria-valuenow=\"{{value}}\">\n" + - " <i ng-repeat=\"r in range track by $index\" ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" class=\"glyphicon\" ng-class=\"$index < value && (r.stateOn || 'glyphicon-star') || (r.stateOff || 'glyphicon-star-empty')\">\n" + - " <span class=\"sr-only\">({{ $index < value ? '*' : ' ' }})</span>\n" + - " </i>\n" + - "</span>"); -}]); - -angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/tabs/tab.html", - "<li ng-class=\"{active: active, disabled: disabled}\">\n" + - " <a ng-click=\"select()\" tab-heading-transclude>{{heading}}</a>\n" + - "</li>\n" + - ""); -}]); - -angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/tabs/tabset.html", - "<div>\n" + - " <ul class=\"nav nav-{{type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" + - " <div class=\"tab-content\">\n" + - " <div class=\"tab-pane\" \n" + - " ng-repeat=\"tab in tabs\" \n" + - " ng-class=\"{active: tab.active}\"\n" + - " tab-content-transclude=\"tab\">\n" + - " </div>\n" + - " </div>\n" + - "</div>\n" + - ""); -}]); - -angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/timepicker/timepicker.html", - "<table>\n" + - " <tbody>\n" + - " <tr class=\"text-center\">\n" + - " <td><a ng-click=\"incrementHours()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" + - " <td> </td>\n" + - " <td><a ng-click=\"incrementMinutes()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" + - " <td ng-show=\"showMeridian\"></td>\n" + - " </tr>\n" + - " <tr>\n" + - " <td style=\"width:50px;\" class=\"form-group\" ng-class=\"{'has-error': invalidHours}\">\n" + - " <input type=\"text\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"form-control text-center\" ng-mousewheel=\"incrementHours()\" ng-readonly=\"readonlyInput\" maxlength=\"2\">\n" + - " </td>\n" + - " <td>:</td>\n" + - " <td style=\"width:50px;\" class=\"form-group\" ng-class=\"{'has-error': invalidMinutes}\">\n" + - " <input type=\"text\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"form-control text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\">\n" + - " </td>\n" + - " <td ng-show=\"showMeridian\"><button type=\"button\" class=\"btn btn-default text-center\" ng-click=\"toggleMeridian()\">{{meridian}}</button></td>\n" + - " </tr>\n" + - " <tr class=\"text-center\">\n" + - " <td><a ng-click=\"decrementHours()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" + - " <td> </td>\n" + - " <td><a ng-click=\"decrementMinutes()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" + - " <td ng-show=\"showMeridian\"></td>\n" + - " </tr>\n" + - " </tbody>\n" + - "</table>\n" + - ""); -}]); - -angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/typeahead/typeahead-match.html", - "<a tabindex=\"-1\" bind-html-unsafe=\"match.label | typeaheadHighlight:query\"></a>"); -}]); - -angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/typeahead/typeahead-popup.html", - "<ul class=\"dropdown-menu\" ng-show=\"isOpen()\" ng-style=\"{top: position.top+'px', left: position.left+'px'}\" style=\"display: block;\" role=\"listbox\" aria-hidden=\"{{!isOpen()}}\">\n" + - " <li ng-repeat=\"match in matches track by $index\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index)\" role=\"option\" id=\"{{match.id}}\">\n" + - " <div typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" + - " </li>\n" + - "</ul>\n" + - ""); -}]); diff --git a/syweb/webclient/login/login-controller.js b/syweb/webclient/login/login-controller.js deleted file mode 100644 index 5ef39a7122..0000000000 --- a/syweb/webclient/login/login-controller.js +++ /dev/null @@ -1,115 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -angular.module('LoginController', ['matrixService']) -.controller('LoginController', ['$scope', '$rootScope', '$location', 'matrixService', 'eventStreamService', - function($scope, $rootScope, $location, matrixService, eventStreamService) { - 'use strict'; - - - // Assume that this is hosted on the home server, in which case the URL - // contains the home server. - var hs_url = $location.protocol() + "://" + $location.host(); - if ($location.port() && - !($location.protocol() === "http" && $location.port() === 80) && - !($location.protocol() === "https" && $location.port() === 443)) - { - hs_url += ":" + $location.port(); - } - - $scope.account = { - homeserver: hs_url, - desired_user_name: "", - user_id: "", - password: "", - identityServer: "http://matrix.org:8090", - pwd1: "", - pwd2: "", - }; - - $scope.login_types = [ "email", "mxid" ]; - $scope.login_type_label = { - "email": "Email address", - "mxid": "Matrix ID (e.g. @bob:matrix.org or bob)", - }; - $scope.login_type = 'mxid'; // TODO: remember the user's preferred login_type - - $scope.login = function() { - matrixService.setConfig({ - homeserver: $scope.account.homeserver, - identityServer: $scope.account.identityServer, - }); - switch ($scope.login_type) { - case 'mxid': - $scope.login_with_mxid($scope.account.user_id, $scope.account.password); - break; - case 'email': - matrixService.lookup3pid('email', $scope.account.user_id).then( - function(response) { - if (response.data['address'] == undefined) { - $scope.login_error_msg = "Invalid email address / password"; - } else { - console.log("Got address "+response.data['mxid']+" for email "+$scope.account.user_id); - $scope.login_with_mxid(response.data['mxid'], $scope.account.password); - } - }, - function() { - $scope.login_error_msg = "Couldn't look up email address. Is your identity server set correctly?"; - } - ); - } - }; - - $scope.login_with_mxid = function(mxid, password) { - matrixService.setConfig({ - homeserver: $scope.account.homeserver, - identityServer: $scope.account.identityServer, - user_id: $scope.account.user_id - }); - // try to login - matrixService.login(mxid, password).then( - function(response) { - if ("access_token" in response.data) { - $scope.feedback = "Login successful."; - matrixService.setConfig({ - homeserver: $scope.account.homeserver, - identityServer: $scope.account.identityServer, - user_id: response.data.user_id, - access_token: response.data.access_token - }); - matrixService.saveConfig(); - $rootScope.updateHeader(); - eventStreamService.resume(); - $location.url("home"); - } - else { - $scope.feedback = "Failed to login: " + JSON.stringify(response.data); - } - }, - function(error) { - if (error.data) { - if (error.data.errcode === "M_FORBIDDEN") { - $scope.login_error_msg = "Incorrect username or password."; - } - } - else if (error.status === 0) { - $scope.login_error_msg = "Unable to talk to the server."; - } - } - ); - }; -}]); - diff --git a/syweb/webclient/login/login.html b/syweb/webclient/login/login.html deleted file mode 100644 index 6b321f8fc5..0000000000 --- a/syweb/webclient/login/login.html +++ /dev/null @@ -1,50 +0,0 @@ -<div ng-controller="LoginController" class="login"> - <div id="wrapper" class="loginWrapper"> - - <a href ng-click="goToPage('/')"> - <img src="img/logo.png" width="240" height="102" alt="[matrix]" style="padding: 50px"/> - </a> - - <br/> - - <form id="loginForm" novalidate> - <!-- Login with an registered user --> - <div> - Log in using:<br/> - - <div ng-repeat="type in login_types"> - <input type="radio" ng-model="$parent.login_type" value="{{ type }}" id="radio_{{ type }}"/> - <label for="radio_{{ type }}">{{ login_type_label[type] }}</label> - </div> - - <div style="text-align: center"> - <br/> - <input id="user_id" size="32" type="text" ng-focus="true" ng-model="account.user_id" placeholder="{{ login_type_label[login_type] }}"/> - <br/> - <input id="password" size="32" type="password" ng-model="account.password" placeholder="Password"/> - <br/><br/> - <button id="login" ng-click="login()" ng-disabled="!account.user_id || !account.password || !account.homeserver">Login</button> - <br/><br/> - </div> - - <div class="feedback">{{ feedback }} {{ login_error_msg }}</div> - - <div id="serverConfig"> - <label for="homeserver">Home Server:</label> - <input id="homeserver" size="32" type="text" ng-model="account.homeserver" placeholder="URL (e.g. http://matrix.org:8080)"/> - <div class="smallPrint">Your home server stores all your conversation and account data.</div> - <label for="identityServer">Identity Server:</label> - <input id="identityServer" size="32" type="text" ng-model="account.identityServer" placeholder="URL (e.g. http://matrix.org:8090)"/> - <div class="smallPrint">Matrix provides identity servers to track which emails etc. belong to which Matrix IDs.<br/> - Only http://matrix.org:8090 currently exists.</div> - <br/> - <br/> - <a href="#/register" style="padding-right: 0em">Create account</a> - <a href="#/reset_password" style="display: none; ">Forgotten password?</a> - </div> - </div> - </form> - - </div> - </div> -</div> diff --git a/syweb/webclient/login/register-controller.js b/syweb/webclient/login/register-controller.js deleted file mode 100644 index ec48def487..0000000000 --- a/syweb/webclient/login/register-controller.js +++ /dev/null @@ -1,190 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -angular.module('RegisterController', ['matrixService']) -.controller('RegisterController', ['$scope', '$rootScope', '$location', 'matrixService', 'eventStreamService', - function($scope, $rootScope, $location, matrixService, eventStreamService) { - 'use strict'; - - var config = window.webClientConfig; - var useCaptcha = false; // default to no captcha to make it easier to get a homeserver up and running... - if (config !== undefined) { - useCaptcha = config.useCaptcha; - } - - // FIXME: factor out duplication with login-controller.js - - // Assume that this is hosted on the home server, in which case the URL - // contains the home server. - var hs_url = $location.protocol() + "://" + $location.host(); - if ($location.port() && - !($location.protocol() === "http" && $location.port() === 80) && - !($location.protocol() === "https" && $location.port() === 443)) - { - hs_url += ":" + $location.port(); - } - - var generateClientSecret = function() { - var ret = ""; - var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - - for (var i = 0; i < 32; i++) { - ret += chars.charAt(Math.floor(Math.random() * chars.length)); - } - - return ret; - }; - - $scope.account = { - homeserver: hs_url, - desired_user_id: "", - desired_user_name: "", - password: "", - identityServer: "http://matrix.org:8090", - pwd1: "", - pwd2: "", - displayName : "" - }; - - $scope.register = function() { - // Set the urls - matrixService.setConfig({ - homeserver: $scope.account.homeserver, - identityServer: $scope.account.identityServer - }); - - if ($scope.account.pwd1 !== $scope.account.pwd2) { - $scope.feedback = "Passwords don't match."; - return; - } - else if ($scope.account.pwd1.length < 6) { - $scope.feedback = "Password must be at least 6 characters."; - return; - } - - if ($scope.account.email) { - $scope.clientSecret = generateClientSecret(); - matrixService.linkEmail($scope.account.email, $scope.clientSecret, 1).then( - function(response) { - $scope.wait_3pid_code = true; - $scope.sid = response.data.sid; - $scope.feedback = ""; - }, - function(response) { - $scope.feedback = "Couldn't request verification email!"; - } - ); - } else { - $scope.registerWithMxidAndPassword($scope.account.desired_user_id, $scope.account.pwd1); - } - }; - - $scope.registerWithMxidAndPassword = function(mxid, password, threepidCreds) { - matrixService.register(mxid, password, threepidCreds, useCaptcha).then( - function(response) { - $scope.feedback = "Success"; - if (useCaptcha) { - Recaptcha.destroy(); - } - // Update the current config - var config = matrixService.config(); - angular.extend(config, { - access_token: response.data.access_token, - user_id: response.data.user_id - }); - matrixService.setConfig(config); - - // And permanently save it - matrixService.saveConfig(); - - // Update the global scoped used_id var (used in the app header) - $rootScope.updateHeader(); - - eventStreamService.resume(); - - if ($scope.account.displayName) { - // FIXME: handle errors setting displayName - matrixService.setDisplayName($scope.account.displayName); - } - - // Go to the user's rooms list page - $location.url("home"); - }, - function(error) { - console.error("Registration error: "+JSON.stringify(error)); - if (useCaptcha) { - Recaptcha.reload(); - } - if (error.data) { - if (error.data.errcode === "M_USER_IN_USE") { - $scope.feedback = "Username already taken."; - $scope.reenter_username = true; - } - else if (error.data.errcode == "M_CAPTCHA_INVALID") { - $scope.feedback = "Failed captcha."; - } - else if (error.data.errcode == "M_CAPTCHA_NEEDED") { - $scope.feedback = "Captcha is required on this home " + - "server."; - } - else if (error.data.error) { - $scope.feedback = error.data.error; - } - } - else if (error.status === 0) { - $scope.feedback = "Unable to talk to the server."; - } - }); - } - - $scope.verifyToken = function() { - matrixService.authEmail($scope.clientSecret, $scope.sid, $scope.account.threepidtoken).then( - function(response) { - if (!response.data.success) { - $scope.feedback = "Unable to verify code."; - } else { - $scope.registerWithMxidAndPassword($scope.account.desired_user_id, $scope.account.pwd1, [{'sid':$scope.sid, 'clientSecret':$scope.clientSecret, 'idServer': $scope.account.identityServer.split('//')[1]}]); - } - }, - function(error) { - $scope.feedback = "Unable to verify code."; - } - ); - }; - - var setupCaptcha = function() { - console.log("Setting up ReCaptcha") - var public_key = window.webClientConfig.recaptcha_public_key; - if (public_key === undefined) { - console.error("No public key defined for captcha!") - return; - } - Recaptcha.create(public_key, - "regcaptcha", - { - theme: "red", - callback: Recaptcha.focus_response_field - }); - }; - - $scope.init = function() { - if (useCaptcha) { - setupCaptcha(); - } - }; - -}]); - diff --git a/syweb/webclient/login/register.html b/syweb/webclient/login/register.html deleted file mode 100644 index a27f9ad4e8..0000000000 --- a/syweb/webclient/login/register.html +++ /dev/null @@ -1,58 +0,0 @@ -<div ng-controller="RegisterController" class="register"> - <div id="wrapper" class="loginWrapper"> - - <a href ng-click="goToPage('/')"> - <img src="img/logo.png" width="240" height="102" alt="[matrix]" style="padding: 50px"/> - </a> - <br/> - - <form id="loginForm" novalidate> - <div> - Create account:<br/> - - <div style="text-align: center"> - <br/> - <input ng-show="!wait_3pid_code" id="email" size="32" type="text" ng-focus="true" ng-model="account.email" placeholder="Email address (optional)"/> - <div ng-show="!wait_3pid_code" class="smallPrint">Specifying an email address lets other users find you on Matrix more easily,<br/> - and will give you a way to reset your password in the future</div> - <span ng-show="reenter_username">Choose another username:</span> - <input ng-show="!wait_3pid_code || reenter_username" id="desired_user_id" size="32" type="text" ng-model="account.desired_user_id" placeholder="Matrix ID (e.g. bob)"/> - <br ng-show="!wait_3pid_code" /> - <input ng-show="!wait_3pid_code" id="pwd1" size="32" type="password" ng-model="account.pwd1" placeholder="Type a password"/> - <br ng-show="!wait_3pid_code" /> - <input ng-show="!wait_3pid_code" id="pwd2" size="32" type="password" ng-model="account.pwd2" placeholder="Confirm your password"/> - <br ng-show="!wait_3pid_code" /> - <input ng-show="!wait_3pid_code" id="displayName" size="32" type="text" ng-model="account.displayName" placeholder="Display name (e.g. Bob Obson)"/> - <br ng-show="!wait_3pid_code" /> - <br ng-show="!wait_3pid_code" /> - - - <div id="regcaptcha" ng-init="init()" /> - - <button ng-show="!wait_3pid_code" ng-click="register()" ng-disabled="!account.desired_user_id || !account.homeserver || !account.pwd1 || !account.pwd2 || account.pwd1 !== account.pwd2">Sign up</button> - - <div ng-show="wait_3pid_code"> - <span>Please enter the verification code sent to {{ account.email }}</span><br /> - <input id="threepidtoken" size="32" type="text" ng-focus="true" ng-model="account.threepidtoken" placeholder="Verification Code"/><br /> - <button ng-click="verifyToken()" ng-disabled="!account.threepidtoken">Validate</button> - </div> - <br/><br/> - </div> - - <div class="feedback">{{ feedback }} {{ login_error_msg }}</div> - - <div id="serverConfig" ng-show="!wait_3pid_code"> - <label for="homeserver">Home Server:</label> - <input id="homeserver" size="32" type="text" ng-model="account.homeserver" placeholder="URL (e.g. http://matrix.org:8080)"/> - <div class="smallPrint">Your home server stores all your conversation and account data.</div> - <label for="identityServer">Identity Server:</label> - <input id="identityServer" size="32" type="text" ng-model="account.identityServer" placeholder="URL (e.g. http://matrix.org:8090)"/> - <div class="smallPrint">Matrix provides identity servers to track which emails etc. belong to which Matrix IDs.<br/> - Only http://matrix.org:8090 currently exists.</div> - </div> - </div> - </form> - - </div> - </div> -</div> diff --git a/syweb/webclient/media/busy.mp3 b/syweb/webclient/media/busy.mp3 deleted file mode 100644 index fec27ba4c5..0000000000 --- a/syweb/webclient/media/busy.mp3 +++ /dev/null Binary files differdiff --git a/syweb/webclient/media/busy.ogg b/syweb/webclient/media/busy.ogg deleted file mode 100644 index 5d64a7d0d9..0000000000 --- a/syweb/webclient/media/busy.ogg +++ /dev/null Binary files differdiff --git a/syweb/webclient/media/callend.mp3 b/syweb/webclient/media/callend.mp3 deleted file mode 100644 index 50c34e5640..0000000000 --- a/syweb/webclient/media/callend.mp3 +++ /dev/null Binary files differdiff --git a/syweb/webclient/media/callend.ogg b/syweb/webclient/media/callend.ogg deleted file mode 100644 index 927ce1f634..0000000000 --- a/syweb/webclient/media/callend.ogg +++ /dev/null Binary files differdiff --git a/syweb/webclient/media/ring.mp3 b/syweb/webclient/media/ring.mp3 deleted file mode 100644 index 3c3cdde3f9..0000000000 --- a/syweb/webclient/media/ring.mp3 +++ /dev/null Binary files differdiff --git a/syweb/webclient/media/ring.ogg b/syweb/webclient/media/ring.ogg deleted file mode 100644 index de49b8ae6f..0000000000 --- a/syweb/webclient/media/ring.ogg +++ /dev/null Binary files differdiff --git a/syweb/webclient/media/ringback.mp3 b/syweb/webclient/media/ringback.mp3 deleted file mode 100644 index 6ee34bf395..0000000000 --- a/syweb/webclient/media/ringback.mp3 +++ /dev/null Binary files differdiff --git a/syweb/webclient/media/ringback.ogg b/syweb/webclient/media/ringback.ogg deleted file mode 100644 index 7dbfdcd017..0000000000 --- a/syweb/webclient/media/ringback.ogg +++ /dev/null Binary files differdiff --git a/syweb/webclient/mobile.css b/syweb/webclient/mobile.css deleted file mode 100644 index 32b01c503d..0000000000 --- a/syweb/webclient/mobile.css +++ /dev/null @@ -1,113 +0,0 @@ -/*** Mobile voodoo ***/ - -/** iPads **/ -@media all and (max-device-width: 768px) { - #roomRecentsTableWrapper { - display: none; - } -} - -/** iPhones **/ -@media all and (max-device-width: 640px) { - - #messageTableWrapper { - margin-right: 0px ! important; - } - - .leftBlock { - width: 8em ! important; - font-size: 8px ! important; - } - - .rightBlock { - width: 0px ! important; - display: none ! important; - } - - .avatar { - width: 36px ! important; - } - - #header { - background-color: transparent; - } - - #headerContent { - padding-right: 5px; - } - - #headerContent button { - font-size: 8px; - } - - #messageTable, - #wrapper, - #controls { - max-width: 640px ! important; - } - - #controls { - padding: 0px; - } - - #headerUserId, - #roomHeader img, - #userIdCell, - #roomRecentsTableWrapper, - #usersTableWrapper, - #controlButtons, - .extraControls { - display: none; - } - - #buttonsCell { - width: 60px ! important; - padding-left: 20px ! important; - } - - #roomLogo { - display: none; - } - - .bubble { - font-size: 12px ! important; - min-height: 20px ! important; - } - - #roomHeader { - padding-top: 10px; - } - - .roomHeaderInfo { - margin-right: 0px; - } - - #roomName { - font-size: 12px ! important; - margin-top: 0px ! important; - } - - .roomTopicSection { - display: none; - } - - #roomPage { - top: 40px ! important; - left: 5px ! important; - right: 5px ! important; - bottom: 70px ! important; - } - - #controlPanel { - height: 70px; - } - - /* stop zoom on select */ - select:focus, - textarea, - input - { - font-size: 16px ! important; - } - -} diff --git a/syweb/webclient/recents/recents-controller.js b/syweb/webclient/recents/recents-controller.js deleted file mode 100644 index 6b2915d03b..0000000000 --- a/syweb/webclient/recents/recents-controller.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -'use strict'; - -angular.module('RecentsController', ['matrixService', 'matrixFilter']) -.controller('RecentsController', ['$rootScope', '$scope', 'modelService', 'recentsService', - function($rootScope, $scope, modelService, recentsService) { - - // Expose the service to the view - $scope.modelService = modelService; - - // retrieve all rooms and expose them - $scope.rooms = modelService.getRooms(); - - // track the selected room ID: the html will use this - $scope.recentsSelectedRoomID = recentsService.getSelectedRoomId(); - $scope.$on(recentsService.BROADCAST_SELECTED_ROOM_ID, function(ngEvent, room_id) { - $scope.recentsSelectedRoomID = room_id; - }); - - // track the list of unread messages: the html will use this - $scope.unreadMessages = recentsService.getUnreadMessages(); - $scope.$on(recentsService.BROADCAST_UNREAD_MESSAGES, function(ngEvent, room_id, unreadCount) { - $scope.unreadMessages = recentsService.getUnreadMessages(); - }); - - // track the list of unread BING messages: the html will use this - $scope.unreadBings = recentsService.getUnreadBingMessages(); - $scope.$on(recentsService.BROADCAST_UNREAD_BING_MESSAGES, function(ngEvent, room_id, event) { - $scope.unreadBings = recentsService.getUnreadBingMessages(); - }); - - $scope.selectRoom = function(room) { - recentsService.markAsRead(room.room_id); - $rootScope.goToPage('room/' + (room.room_alias ? room.room_alias : room.room_id) ); - }; - -}]); - diff --git a/syweb/webclient/recents/recents-filter.js b/syweb/webclient/recents/recents-filter.js deleted file mode 100644 index 31859b1c7f..0000000000 --- a/syweb/webclient/recents/recents-filter.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -'use strict'; - -angular.module('RecentsController') -.filter('orderRecents', ["matrixService", "modelService", function(matrixService, modelService) { - return function(rooms) { - var user_id = matrixService.config().user_id; - - // Transform the dict into an array - // The key, room_id, is already in value objects - var filtered = []; - angular.forEach(rooms, function(room, room_id) { - room.recent = {}; - var meEvent = room.current_room_state.state("m.room.member", user_id); - // Show the room only if the user has joined it or has been invited - // (ie, do not show it if he has been banned) - var member = modelService.getMember(room_id, user_id); - if (member) { - member = member.event; - } - room.recent.me = member; - if (member && ("invite" === member.content.membership || "join" === member.content.membership)) { - if ("invite" === member.content.membership) { - room.recent.inviter = member.user_id; - } - // Count users here - // TODO: Compute it directly in modelService - room.recent.numUsersInRoom = modelService.getUserCountInRoom(room_id); - - filtered.push(room); - } - else if (meEvent && "invite" === meEvent.content.membership) { - // The only information we have about the room is that the user has been invited - filtered.push(room); - } - }); - - // And time sort them - // The room with the latest message at first - filtered.sort(function (roomA, roomB) { - - var lastMsgRoomA = modelService.getLastMessage(roomA.room_id, true); - var lastMsgRoomB = modelService.getLastMessage(roomB.room_id, true); - - // Invite message does not have a body message nor ts - // Puth them at the top of the list - if (undefined === lastMsgRoomA) { - return -1; - } - else if (undefined === lastMsgRoomB) { - return 1; - } - else { - return lastMsgRoomB.origin_server_ts - lastMsgRoomA.origin_server_ts; - } - }); - return filtered; - }; -}]); diff --git a/syweb/webclient/recents/recents.html b/syweb/webclient/recents/recents.html deleted file mode 100644 index 2596b094f7..0000000000 --- a/syweb/webclient/recents/recents.html +++ /dev/null @@ -1,110 +0,0 @@ -<div ng-controller="RecentsController"> - <table class="recentsTable"> - <tbody ng-repeat="(index, room) in rooms | orderRecents" - ng-click="selectRoom(room)" - class="recentsRoom" - ng-class="{'recentsRoomSelected': (room.room_id === recentsSelectedRoomID), 'recentsRoomBing': (unreadBings[room.room_id]), 'recentsRoomUnread': (unreadMessages[room.room_id])}"> - <tr> - <td ng-class="room.current_room_state.state('m.room.join_rules').content.join_rule == 'public' ? 'recentsRoomName recentsPublicRoom' : 'recentsRoomName'"> - {{ room.room_id | mRoomName }} - </td> - <td class="recentsRoomSummaryUsersCount"> - <span ng-show="undefined !== room.recent.numUsersInRoom"> - {{ room.recent.numUsersInRoom || '1' }} {{ room.recent.numUsersInRoom == 1 ? 'user' : 'users' }} - </span> - </td> - <td class="recentsRoomSummaryTS"> - <!-- Use a temp var as alias to the last room message. - Declaring it in this way ensures the data-binding --> - {{ lastMsg = modelService.getLastMessage(room.room_id, true);"" }} - - {{ (lastMsg.origin_server_ts) | date:'MMM d HH:mm' }} - - <img ng-click="leave(room.room_id); $event.stopPropagation();" src="img/close.png" width="10" height="10" style="margin-bottom: -1px; margin-left: 2px;" alt="close"/> - </td> - </tr> - - <tr> - <td colspan="3" class="recentsRoomSummary"> - - <div ng-show="room.recent.me.content.membership === 'invite'"> - {{ room.recent.inviter | mUserDisplayName: room.room_id }} invited you - </div> - - <div ng-hide="room.recent.me.membership === 'invite'" ng-switch="lastMsg.type"> - <div ng-switch-when="m.room.member"> - <span ng-switch="lastMsg.changedKey"> - <span ng-switch-when="membership"> - <span ng-if="'join' === lastMsg.content.membership"> - {{ lastMsg.state_key | mUserDisplayName: room.room_id }} joined - </span> - <span ng-if="'leave' === lastMsg.content.membership"> - <span ng-if="lastMsg.user_id === lastMsg.state_key"> - {{lastMsg.state_key | mUserDisplayName: room.room_id }} left - </span> - <span ng-if="lastMsg.user_id !== lastMsg.state_key && lastMsg.prev_content"> - {{ lastMsg.user_id | mUserDisplayName: room.room_id }} - {{ {"invite": "kicked", "join": "kicked", "ban": "unbanned"}[lastMsg.prev_content.membership] }} - {{ lastMsg.state_key | mUserDisplayName: room.room_id }} - </span> - <span ng-if="lastMsg.prev_content && 'join' === lastMsg.prev_content.membership && lastMsg.content.reason"> - : {{ lastMsg.content.reason }} - </span> - </span> - <span ng-if="'invite' === lastMsg.content.membership || 'ban' === lastMsg.content.membership"> - {{ lastMsg.user_id | mUserDisplayName: room.room_id }} - {{ {"invite": "invited", "ban": "banned"}[lastMsg.content.membership] }} - {{ lastMsg.state_key | mUserDisplayName: room.room_id }} - <span ng-if="lastMsg.prev_content && 'ban' === lastMsg.prev_content.membership && lastMsg.content.reason"> - : {{ lastMsg.content.reason }} - </span> - </span> - </span> - <span ng-switch-when="displayname"> - {{ lastMsg.user_id }} changed their display name from {{ lastMsg.prev_content.displayname }} to {{ lastMsg.content.displayname }} - </span> - </span> - </div> - - <div ng-switch-when="m.room.message"> - <div ng-switch="lastMsg.content.msgtype"> - <div ng-switch-when="m.text"> - {{ lastMsg.user_id | mUserDisplayName: room.room_id }} : - <span ng-bind-html="(lastMsg.content.body) | linky:'_blank'"> - </span> - </div> - - <div ng-switch-when="m.image"> - {{ lastMsg.user_id | mUserDisplayName: room.room_id }} sent an image - </div> - - <div ng-switch-when="m.emote"> - <span ng-bind-html="'* ' + (lastMsg.user_id | mUserDisplayName: room.room_id) + ' ' + lastMsg.content.body | linky:'_blank'"> - </span> - </div> - - <div ng-switch-default> - {{ lastMsg.content.body | linky:'_blank' }} - </div> - </div> - </div> - - <div ng-switch-when="m.room.topic"> - {{ lastMsg.user_id | mUserDisplayName: room.room_id }} changed the topic to: {{ lastMsg.content.topic }} - </div> - - <div ng-switch-when="m.room.name"> - {{ lastMsg.user_id | mUserDisplayName: room.room_id }} changed the room name to: {{ lastMsg.content.name }} - </div> - - <div ng-switch-default> - <div ng-if="lastMsg.type.indexOf('m.call.') === 0"> - Call - </div> - </div> - </div> - </td> - </tr> - </tbody> - </table> -</div> diff --git a/syweb/webclient/room/room-controller.js b/syweb/webclient/room/room-controller.js deleted file mode 100644 index df05a8e573..0000000000 --- a/syweb/webclient/room/room-controller.js +++ /dev/null @@ -1,581 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput', 'angular-peity']) -.controller('RoomController', ['$modal', '$filter', '$scope', '$timeout', '$routeParams', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventHandlerService', 'mFileUpload', 'matrixPhoneService', 'MatrixCall', 'modelService', 'recentsService', 'commandsService', 'mUserDisplayNameFilter', - function($modal, $filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, mPresence, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall, modelService, recentsService, commandsService, mUserDisplayNameFilter) { - 'use strict'; - var MESSAGES_PER_PAGINATION = 30; - var THUMBNAIL_SIZE = 320; - - // .html needs this - $scope.containsBingWord = eventHandlerService.eventContainsBingWord; - - // Room ids. Computed and resolved in onInit - $scope.room_id = undefined; - - $scope.state = { - user_id: matrixService.config().user_id, - permission_denied: undefined, // If defined, this string contains the reason why the user cannot join the room - first_pagination: true, // this is toggled off when the first pagination is done - can_paginate: false, // this is toggled off when we are not ready yet to paginate or when we run out of items - paginating: false, // used to avoid concurrent pagination requests pulling in dup contents - stream_failure: undefined, // the response when the stream fails - waiting_for_joined_event: false, // true when the join request is pending. Back to false once the corresponding m.room.member event is received - messages_visibility: "hidden", // In order to avoid flickering when scrolling down the message table at the page opening, delay the message table display - }; - - $scope.imageURLToSend = ""; - - - // vars and functions for updating the name - $scope.name = { - isEditing: false, - newNameText: "", - editName: function() { - if ($scope.name.isEditing) { - console.log("Warning: Already editing name."); - return; - }; - - var nameEvent = $scope.room.current_room_state.state_events['m.room.name']; - if (nameEvent) { - $scope.name.newNameText = nameEvent.content.name; - } - else { - $scope.name.newNameText = ""; - } - - // Force focus to the input - $timeout(function() { - angular.element('.roomNameInput').focus(); - }, 0); - - $scope.name.isEditing = true; - }, - updateName: function() { - console.log("Updating name to "+$scope.name.newNameText); - matrixService.setName($scope.room_id, $scope.name.newNameText).then( - function() { - }, - function(error) { - $scope.feedback = "Request failed: " + error.data.error; - } - ); - - $scope.name.isEditing = false; - }, - cancelEdit: function() { - $scope.name.isEditing = false; - } - }; - - // vars and functions for updating the topic - $scope.topic = { - isEditing: false, - newTopicText: "", - editTopic: function() { - if ($scope.topic.isEditing) { - console.log("Warning: Already editing topic."); - return; - } - var topicEvent = $scope.room.current_room_state.state_events['m.room.topic']; - if (topicEvent) { - $scope.topic.newTopicText = topicEvent.content.topic; - } - else { - $scope.topic.newTopicText = ""; - } - - // Force focus to the input - $timeout(function() { - angular.element('.roomTopicInput').focus(); - }, 0); - - $scope.topic.isEditing = true; - }, - updateTopic: function() { - console.log("Updating topic to "+$scope.topic.newTopicText); - matrixService.setTopic($scope.room_id, $scope.topic.newTopicText).then( - function() { - }, - function(error) { - $scope.feedback = "Request failed: " + error.data.error; - } - ); - - $scope.topic.isEditing = false; - }, - cancelEdit: function() { - $scope.topic.isEditing = false; - } - }; - - var scrollToBottom = function(force) { - console.log("Scrolling to bottom"); - - // Do not autoscroll to the bottom to display the new event if the user is not at the bottom. - // Exception: in case where the event is from the user, we want to force scroll to the bottom - var objDiv = document.getElementById("messageTableWrapper"); - // add a 10px buffer to this check so if the message list is not *quite* - // at the bottom it still scrolls since it basically is at the bottom. - if ((10 + objDiv.offsetHeight + objDiv.scrollTop >= objDiv.scrollHeight) || force) { - - $timeout(function() { - objDiv.scrollTop = objDiv.scrollHeight; - - // Show the message table once the first scrolldown is done - if ("visible" !== $scope.state.messages_visibility) { - $timeout(function() { - $scope.state.messages_visibility = "visible"; - }, 0); - } - }, 0); - } - }; - - $scope.$on(eventHandlerService.MSG_EVENT, function(ngEvent, event, isLive) { - if (isLive && event.room_id === $scope.room_id) { - scrollToBottom(); - } - }); - - $scope.$on(eventHandlerService.MEMBER_EVENT, function(ngEvent, event, isLive) { - // if there is a live event affecting us - if (isLive && event.room_id === $scope.room_id && event.state_key === $scope.state.user_id) { - // if someone else changed our state.. - if (event.user_id !== $scope.state.user_id && "invite" !== event.content.membership && "join" !== event.content.membership) { - if ("ban" === event.content.membership) { - $scope.state.permission_denied = "You have been banned by " + mUserDisplayNameFilter(event.user_id); - } - else { - $scope.state.permission_denied = "You have been kicked by " + mUserDisplayNameFilter(event.user_id); - } - } - else { - scrollToBottom(); - } - } - }); - - $scope.memberCount = function() { - return Object.keys($scope.room.now.members).length; - }; - - $scope.paginateMore = function() { - if ($scope.state.can_paginate) { - // console.log("Paginating more."); - paginate(MESSAGES_PER_PAGINATION); - } - }; - - var paginate = function(numItems) { - //console.log("paginate " + numItems + " and first_pagination is " + $scope.state.first_pagination); - if ($scope.state.paginating || !$scope.room_id) { - return; - } - else { - $scope.state.paginating = true; - } - - console.log("paginateBackMessages from " + $scope.room.old_room_state.pagination_token + " for " + numItems); - var originalTopRow = $("#messageTable>tbody>tr:first")[0]; - - // Paginate events from the point in cache - matrixService.paginateBackMessages($scope.room_id, $scope.room.old_room_state.pagination_token, numItems).then( - function(response) { - - eventHandlerService.handleRoomMessages($scope.room_id, response.data, false, 'b'); - if (response.data.chunk.length < MESSAGES_PER_PAGINATION) { - // no more messages to paginate. this currently never gets turned true again, as we never - // expire paginated contents in the current implementation. - $scope.state.can_paginate = false; - } - - $scope.state.paginating = false; - - var wrapper = $("#messageTableWrapper")[0]; - var table = $("#messageTable")[0]; - // console.log("wrapper height=" + wrapper.clientHeight + ", table scrollHeight=" + table.scrollHeight); - - if ($scope.state.can_paginate) { - // check we don't have to pull in more messages - // n.b. we dispatch through a timeout() to allow the digest to run otherwise the .height methods are stale - $timeout(function() { - if (table.scrollHeight < wrapper.clientHeight) { - paginate(MESSAGES_PER_PAGINATION); - scrollToBottom(); - } - }, 0); - } - - if ($scope.state.first_pagination) { - scrollToBottom(true); - $scope.state.first_pagination = false; - } - else { - // lock the scroll position - $timeout(function() { - // FIXME: this risks a flicker before the scrollTop is actually updated, but we have to - // dispatch it into a function in order to first update the layout. The right solution - // might be to implement it as a directive, more like - // http://stackoverflow.com/questions/23736647/how-to-retain-scroll-position-of-ng-repeat-in-angularjs - // however, this specific solution breaks because it measures the rows height before - // the contents are interpolated. - wrapper.scrollTop = originalTopRow ? (originalTopRow.offsetTop + wrapper.scrollTop) : 0; - }, 0); - } - }, - function(error) { - console.log("Failed to paginateBackMessages: " + JSON.stringify(error)); - $scope.state.paginating = false; - } - ); - }; - - var updatePresenceTimes = function() { - $scope.now = new Date().getTime(); - // TODO: don't bother polling every 5s if we know none of our counters are younger than 1 minute - $timeout(updatePresenceTimes, 5 * 1000); - }; - - $scope.send = function() { - var input = $('#mainInput').val(); - - if (undefined === input || input === "") { - return; - } - - scrollToBottom(true); - - // Store the command in the history - $rootScope.$broadcast("commandHistory:BROADCAST_NEW_HISTORY_ITEM(item)", - input); - - var isEmote = input.indexOf("/me ") === 0; - var promise; - if (!isEmote) { - promise = commandsService.processInput($scope.room_id, input); - } - var echo = false; - - - if (!promise) { // not a non-echoable command - echo = true; - if (isEmote) { - promise = matrixService.sendEmoteMessage($scope.room_id, input.substring(4)); - } - else { - promise = matrixService.sendTextMessage($scope.room_id, input); - } - } - - if (echo) { - // Echo the message to the room - // To do so, create a minimalist fake text message event and add it to the in-memory list of room messages - var echoMessage = { - content: { - body: (isEmote ? input.substring(4) : input), - msgtype: (isEmote ? "m.emote" : "m.text"), - }, - origin_server_ts: new Date().getTime(), // fake a timestamp - room_id: $scope.room_id, - type: "m.room.message", - user_id: $scope.state.user_id, - echo_msg_state: "messagePending" // Add custom field to indicate the state of this fake message to HTML - }; - - $('#mainInput').val(''); - $scope.room.addMessageEvent(echoMessage); - scrollToBottom(); - } - - if (promise) { - // Reset previous feedback - $scope.feedback = ""; - - promise.then( - function(response) { - console.log("Request successfully sent"); - - if (echo) { - // Mark this fake message event with its allocated event_id - // When the true message event will come from the events stream (in handleMessage), - // we will be able to replace the fake one by the true one - echoMessage.event_id = response.data.event_id; - } - else { - $('#mainInput').val(''); - } - }, - function(error) { - $scope.feedback = error.data.error; - - if (echoMessage) { - // Mark the message as unsent for the rest of the page life - echoMessage.origin_server_ts = "Unsent"; - echoMessage.echo_msg_state = "messageUnSent"; - } - }); - } - }; - - // Tries to find a suitable room ID for this room. - $scope.onInit = function() { - console.log("onInit"); - - // Extract the room identifier being loaded - var room_id_or_alias; - if ($routeParams.room_id_or_alias) { // provided in the url - room_id_or_alias = decodeURIComponent($routeParams.room_id_or_alias); - } - - eventHandlerService.joinRoom(room_id_or_alias).then(function(roomId) { - $scope.room_id = roomId; - $scope.room = modelService.getRoom($scope.room_id); - - var messages = $scope.room.events; - - if (0 === messages.length - || (1 === messages.length && "m.room.member" === messages[0].type && "invite" === messages[0].content.membership && $scope.state.user_id === messages[0].state_key)) { - // If we just joined a room, we won't have this history from initial sync, so we should try to paginate it anyway - $scope.state.first_pagination = true; - } - else { - // There is no need to do a 1st pagination (initialSync provided enough to fill a page) - $scope.state.first_pagination = false; - } - - // Make recents highlight the current room - recentsService.setSelectedRoomId($scope.room_id); - - updatePresenceTimes(); - - // Allow pagination - $scope.state.can_paginate = true; - - // Do a first pagination only if it is required (e.g. we've JUST joined a room and have no messages to display.) - // FIXME: Should be no more require when initialSync/{room_id} will be available - if ($scope.state.first_pagination) { - paginate(MESSAGES_PER_PAGINATION); - } - - // Scroll down as soon as possible so that we point to the last message - // if it already exists in memory - scrollToBottom(true); - }, - function(err) { - if (err.data.errcode === "M_FORBIDDEN") { - $scope.state.permission_denied = "You do not have permission to join this room"; - } - else { - console.log("Error: cannot join room: "+JSON.stringify(err)); - $location.url("/"); - } - }); - }; - - $scope.leaveRoom = function() { - - matrixService.leave($scope.room_id).then( - function(response) { - console.log("Left room " + $scope.room_id); - $location.url("home"); - }, - function(error) { - $scope.feedback = "Failed to leave room: " + error.data.error; - }); - }; - - // used to send an image based on just a URL, rather than uploading one - $scope.sendImage = function(url, body) { - scrollToBottom(true); - - matrixService.sendImageMessage($scope.room_id, url, body).then( - function() { - console.log("Image sent"); - }, - function(error) { - $scope.feedback = "Failed to send image: " + error.data.error; - }); - }; - - $scope.fileToSend; - $scope.$watch("fileToSend", function(newValue, oldValue) { - if ($scope.fileToSend) { - // Upload this file - mFileUpload.uploadFileAndThumbnail($scope.fileToSend, THUMBNAIL_SIZE).then( - function(fileMessage) { - // fileMessage is complete message structure, send it as is - matrixService.sendMessage($scope.room_id, undefined, fileMessage).then( - function() { - console.log("File message sent"); - }, - function(error) { - $scope.feedback = "Failed to send file message: " + error.data.error; - }); - }, - function(error) { - $scope.feedback = "Can't upload file"; - } - ); - } - }); - - $scope.loadMoreHistory = function() { - paginate(MESSAGES_PER_PAGINATION); - }; - - $scope.checkWebRTC = function() { - if (!$rootScope.isWebRTCSupported()) { - alert("Your browser does not support WebRTC"); - return false; - } - if ($scope.memberCount() != 2) { - alert("WebRTC calls are currently only supported on rooms with two members"); - return false; - } - return true; - }; - - $scope.startVoiceCall = function() { - if (!$scope.checkWebRTC()) return; - var call = new MatrixCall($scope.room_id); - call.onError = $rootScope.onCallError; - call.onHangup = $rootScope.onCallHangup; - // remote video element is used for playing audio in voice calls - call.remoteVideoSelector = angular.element('#remoteVideo')[0]; - call.placeVoiceCall(); - $rootScope.currentCall = call; - }; - - $scope.startVideoCall = function() { - if (!$scope.checkWebRTC()) return; - - var call = new MatrixCall($scope.room_id); - call.onError = $rootScope.onCallError; - call.onHangup = $rootScope.onCallHangup; - call.localVideoSelector = '#localVideo'; - call.remoteVideoSelector = '#remoteVideo'; - call.placeVideoCall(); - $rootScope.currentCall = call; - }; - - $scope.openJson = function(content) { - $scope.event_selected = angular.copy(content); - - // FIXME: Pre-calculated event data should be stripped in a nicer way. - $scope.event_selected.__room_member = undefined; - $scope.event_selected.__target_room_member = undefined; - - // scope this so the template can check power levels and enable/disable - // buttons - $scope.pow = modelService.getUserPowerLevel; - - var modalInstance = $modal.open({ - templateUrl: 'eventInfoTemplate.html', - controller: 'EventInfoController', - scope: $scope - }); - - modalInstance.result.then(function(action) { - if (action === "redact") { - var eventId = $scope.event_selected.event_id; - console.log("Redacting event ID " + eventId); - matrixService.redactEvent( - $scope.event_selected.room_id, - eventId - ).then(function(response) { - console.log("Redaction = " + JSON.stringify(response)); - }, function(error) { - console.error("Failed to redact event: "+JSON.stringify(error)); - if (error.data.error) { - $scope.feedback = error.data.error; - } - }); - } - }, function() { - // any dismiss code - }); - }; - - $scope.openRoomInfo = function() { - $scope.roomInfo = {}; - $scope.roomInfo.newEvent = { - content: {}, - type: "", - state_key: "" - }; - - var stateEvents = $scope.room.current_room_state.state_events; - // The modal dialog will 2-way bind this field, so we MUST make a deep - // copy of the state events else we will be *actually adjusing our view - // of the world* when fiddling with the JSON!! Apparently parse/stringify - // is faster than jQuery's extend when doing deep copies. - $scope.roomInfo.stateEvents = JSON.parse(JSON.stringify(stateEvents)); - var modalInstance = $modal.open({ - templateUrl: 'roomInfoTemplate.html', - controller: 'RoomInfoController', - size: 'lg', - scope: $scope - }); - }; - -}]) -.controller('EventInfoController', function($scope, $modalInstance) { - console.log("Displaying modal dialog for >>>> " + JSON.stringify($scope.event_selected)); - $scope.redact = function() { - console.log("User level = "+$scope.pow($scope.room_id, $scope.state.user_id)+ - " Redact level = "+$scope.room.current_room_state.state_events["m.room.ops_levels"].content.redact_level); - console.log("Redact event >> " + JSON.stringify($scope.event_selected)); - $modalInstance.close("redact"); - }; - $scope.dismiss = $modalInstance.dismiss; -}) -.controller('RoomInfoController', function($scope, $modalInstance, $filter, matrixService) { - console.log("Displaying room info."); - - $scope.userIDToInvite = ""; - - $scope.inviteUser = function() { - - matrixService.invite($scope.room_id, $scope.userIDToInvite).then( - function() { - console.log("Invited."); - $scope.feedback = "Invite successfully sent to " + $scope.userIDToInvite; - $scope.userIDToInvite = ""; - }, - function(reason) { - $scope.feedback = "Failure: " + reason.data.error; - }); - }; - - $scope.submit = function(event) { - if (event.content) { - console.log("submit >>> " + JSON.stringify(event.content)); - matrixService.sendStateEvent($scope.room_id, event.type, - event.content, event.state_key).then(function(response) { - $modalInstance.dismiss(); - }, function(err) { - $scope.feedback = err.data.error; - } - ); - } - }; - - $scope.dismiss = $modalInstance.dismiss; - -}); diff --git a/syweb/webclient/room/room-directive.js b/syweb/webclient/room/room-directive.js deleted file mode 100644 index 4707d12c8c..0000000000 --- a/syweb/webclient/room/room-directive.js +++ /dev/null @@ -1,276 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -'use strict'; - -angular.module('RoomController') -// XXX FIXME : This has tight coupling with $scope.room.now.members -.directive('tabComplete', ['$timeout', function ($timeout) { - return function (scope, element, attrs) { - element.bind("keydown keypress", function (event) { - // console.log("event: " + event.which); - var TAB = 9; - var SHIFT = 16; - var keypressCode = event.which; - if (keypressCode === TAB) { - if (!scope.tabCompleting) { // cache our starting text - scope.tabCompleteOriginal = element[0].value; - scope.tabCompleting = true; - scope.tabCompleteIndex = 0; - } - - // loop in the right direction - if (event.shiftKey) { - scope.tabCompleteIndex--; - if (scope.tabCompleteIndex < 0) { - // wrap to the last search match, and fix up to a real - // index value after we've matched - scope.tabCompleteIndex = Number.MAX_VALUE; - } - } - else { - scope.tabCompleteIndex++; - } - - - var searchIndex = 0; - var targetIndex = scope.tabCompleteIndex; - var text = scope.tabCompleteOriginal; - - // console.log("targetIndex: " + targetIndex + ", - // text=" + text); - - // FIXME: use the correct regexp to recognise userIDs --M - // - // XXX: I don't really know what the point of this is. You - // WANT to match freeform text given you want to match display - // names AND user IDs. Surely you just want to get the last - // word out of the input text and that's that? - // Am I missing something here? -- Kegan - // - // You're not missing anything - my point was that we should - // explicitly define the syntax for user IDs /somewhere/. - // Meanwhile as long as the delimeters are well defined, we - // could just pick "the last word". But to know what the - // correct delimeters are, we probably do need a formal - // syntax for user IDs to refer to... --Matthew - - var search = /@?([a-zA-Z0-9_\-:\.]+)$/.exec(text); - - if (targetIndex === 0) { // 0 is always the original text - element[0].value = text; - // Force angular to wake up and update the input ng-model - // by firing up input event - angular.element(element[0]).triggerHandler('input'); - } - else if (search && search[1]) { - // console.log("search found: " + search+" from "+text); - var expansion; - - // FIXME: could do better than linear search here - angular.forEach(scope.room.now.members, function(item, name) { - if (item.event.content.displayname && searchIndex < targetIndex) { - if (item.event.content.displayname.toLowerCase().indexOf(search[1].toLowerCase()) === 0) { - expansion = item.event.content.displayname; - searchIndex++; - } - } - }); - if (searchIndex < targetIndex) { // then search raw mxids - angular.forEach(scope.room.now.members, function(item, name) { - if (searchIndex < targetIndex) { - // === 1 because mxids are @username - if (name.toLowerCase().indexOf(search[1].toLowerCase()) === 1) { - expansion = name; - searchIndex++; - } - } - }); - } - - if (searchIndex === targetIndex || - targetIndex === Number.MAX_VALUE) { - // xchat-style tab complete, add a colon if tab - // completing at the start of the text - if (search[0].length === text.length) - expansion += ": "; - else - expansion += " "; - element[0].value = text.replace(/@?([a-zA-Z0-9_\-:\.]+)$/, expansion); - // cancel blink - element[0].className = ""; - if (targetIndex === Number.MAX_VALUE) { - // wrap the index around to the last index found - scope.tabCompleteIndex = searchIndex; - targetIndex = searchIndex; - } - } - else { - // console.log("wrapped!"); - element[0].className = "blink"; // XXX: slightly naughty to bypass angular - $timeout(function() { - element[0].className = ""; - }, 150); - element[0].value = text; - scope.tabCompleteIndex = 0; - } - - // Force angular to wak up and update the input ng-model by - // firing up input event - angular.element(element[0]).triggerHandler('input'); - } - else { - scope.tabCompleteIndex = 0; - } - // prevent the default TAB operation (typically focus shifting) - event.preventDefault(); - } - else if (keypressCode !== SHIFT && scope.tabCompleting) { - scope.tabCompleting = false; - scope.tabCompleteIndex = 0; - } - }); - }; -}]) -// A directive which stores text sent into it and restores it via up/down arrows -.directive('commandHistory', [ function() { - var BROADCAST_NEW_HISTORY_ITEM = "commandHistory:BROADCAST_NEW_HISTORY_ITEM(item)"; - - // Manage history of typed messages - // History is saved in sessionStorage so that it survives when the user - // navigates through the rooms and when it refreshes the page - var history = { - // The list of typed messages. Index 0 is the more recents - data: [], - - // The position in the history currently displayed - position: -1, - - element: undefined, - roomId: undefined, - - // The message the user has started to type before going into the history - typingMessage: undefined, - - // Init/load data for the current room - init: function(element, roomId) { - this.roomId = roomId; - this.element = element; - var data = sessionStorage.getItem("history_" + this.roomId); - if (data) { - this.data = JSON.parse(data); - } - }, - - // Store a message in the history - push: function(message) { - this.data.unshift(message); - - // Update the session storage - sessionStorage.setItem("history_" + this.roomId, JSON.stringify(this.data)); - - // Reset history position - this.position = -1; - this.typingMessage = undefined; - }, - - // Move in the history - go: function(offset) { - - if (-1 === this.position) { - // User starts to go to into the history, save the current line - this.typingMessage = this.element.val(); - } - else { - // If the user modified this line in history, keep the change - this.data[this.position] = this.element.val(); - } - - // Bounds the new position to valid data - var newPosition = this.position + offset; - newPosition = Math.max(-1, newPosition); - newPosition = Math.min(newPosition, this.data.length - 1); - this.position = newPosition; - - if (-1 !== this.position) { - // Show the message from the history - this.element.val(this.data[this.position]); - } - else if (undefined !== this.typingMessage) { - // Go back to the message the user started to type - this.element.val(this.typingMessage); - } - } - }; - - return { - restrict: "AE", - scope: { - roomId: "=commandHistory" - }, - link: function (scope, element, attrs) { - element.bind("keydown", function (event) { - var keycodePressed = event.which; - var UP_ARROW = 38; - var DOWN_ARROW = 40; - if (scope.roomId) { - if (keycodePressed === UP_ARROW) { - history.go(1); - event.preventDefault(); - } - else if (keycodePressed === DOWN_ARROW) { - history.go(-1); - event.preventDefault(); - } - } - }); - - scope.$on(BROADCAST_NEW_HISTORY_ITEM, function(ngEvent, item) { - history.push(item); - }); - - history.init(element, scope.roomId); - }, - - } -}]) - -// A directive to anchor the scroller position at the bottom when the browser is resizing. -// When the screen resizes, the bottom of the element remains the same, not the top. -.directive('keepScroll', ['$window', function($window) { - return { - link: function(scope, elem, attrs) { - - scope.windowHeight = $window.innerHeight; - - // Listen to window size change - angular.element($window).bind('resize', function() { - - // If the scroller is scrolled to the bottom, there is nothing to do. - // The browser will move it as expected - if (elem.scrollTop() + elem.height() !== elem[0].scrollHeight) { - // Else, move the scroller position according to the window height change delta - var windowHeightDelta = $window.innerHeight - scope.windowHeight; - elem.scrollTop(elem.scrollTop() - windowHeightDelta); - } - - // Store the new window height for the next screen size change - scope.windowHeight = $window.innerHeight; - }); - } - }; -}]); - diff --git a/syweb/webclient/room/room.html b/syweb/webclient/room/room.html deleted file mode 100644 index 8baf9eda71..0000000000 --- a/syweb/webclient/room/room.html +++ /dev/null @@ -1,282 +0,0 @@ -<div ng-controller="RoomController" data-ng-init="onInit()" class="room" style="height: 100%;"> - - <script type="text/ng-template" id="eventInfoTemplate.html"> - <div class="modal-body"> - <pre> {{event_selected | json}} </pre> - </div> - <div class="modal-footer"> - <button ng-click="redact()" type="button" class="btn btn-danger redact-button" - ng-disabled="!room.current_room_state.state('m.room.ops_levels').content.redact_level || !pow(room_id, state.user_id) || pow(room_id, state.user_id) < room.current_room_state.state('m.room.ops_levels').content.redact_level" - title="Delete this event on all home servers. This cannot be undone."> - Redact - </button> - - <button ng-click="dismiss()" type="button" class="btn"> - Close - </button> - </div> - </script> - - <script type="text/ng-template" id="roomInfoTemplate.html"> - <div class="modal-body"> - <span> - Invite a user: - <input ng-model="userIDToInvite" size="32" type="text" ng-enter="inviteUser()" ng-disabled="state.permission_denied" placeholder="User ID (ex:@user:homeserver)"/> - <button ng-click="inviteUser()" ng-disabled="state.permission_denied">Invite</button> - </span> - <br/> - <br/> - <button ng-click="leaveRoom()" ng-disabled="state.permission_denied">Leave Room</button> - </br/> - <table class="room-info"> - <tr ng-repeat="(key, event) in roomInfo.stateEvents" class="room-info-event"> - <td class="room-info-event-meta" width="30%"> - <span class="monospace">{{ event.type }}</span> - <span ng-show="event.state_key" class="monospace"> ({{event.state_key}})</span> - <br/> - {{ (event.origin_server_ts) | date:'MMM d HH:mm' }} - <br/> - Set by: <span class="monospace">{{ event.user_id }}</span> - <br/> - <span ng-show="event.required_power_level >= 0">Required power level: {{event.required_power_level}}<br/></span> - <button ng-click="submit(event)" type="button" class="btn btn-success" ng-disabled="!event.content"> - Submit - </button> - </td> - <td class="room-info-event-content" width="70%"> - <textarea class="room-info-textarea-content" msd-elastic ng-model="event.content" asjson></textarea> - </td> - </tr> - <tr> - <td class="room-info-event-meta" width="30%"> - <input ng-model="roomInfo.newEvent.type" placeholder="your.event.type" /> - <br/> - <button ng-click="submit(roomInfo.newEvent)" type="button" class="btn btn-success" ng-disabled="!roomInfo.newEvent.content || !roomInfo.newEvent.type"> - Submit - </button> - </td> - <td class="room-info-event-content" width="70%"> - <textarea class="room-info-textarea-content" msd-elastic ng-model="roomInfo.newEvent.content" asjson></textarea> - </td> - </tr> - </table> - </div> - <div class="modal-footer"> - <button ng-click="dismiss()" type="button" class="btn"> - Close - </button> - </div> - </script> - - <div id="roomHeader"> - <a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a> - - <div id="controlButtons"> - <button ng-click="startVoiceCall()" class="controlButton" - style="background: url('img/voice.png')" - ng-show="(currentCall == undefined || currentCall.state == 'ended')" - ng-disabled="state.permission_denied" - > - </button> - <button ng-click="startVideoCall()" class="controlButton" - style="background: url('img/video.png')" - ng-show="(currentCall == undefined || currentCall.state == 'ended')" - ng-disabled="state.permission_denied" - > - </button> - <button ng-click="openRoomInfo()" class="controlButton" - style="background: url('img/settings.png')" - > - </button> - </div> - - <div class="roomHeaderInfo"> - - <div class="roomNameSection"> - <div ng-hide="name.isEditing" ng-dblclick="name.editName()" id="roomName"> - {{ room_id | mRoomName }} - </div> - <form ng-submit="name.updateName()" ng-show="name.isEditing" class="roomNameForm"> - <input ng-model="name.newNameText" ng-blur="name.cancelEdit()" class="roomNameInput" placeholder="Room name"/> - </form> - </div> - - <div class="roomTopicSection"> - <button ng-hide="room.current_room_state.state_events['m.room.topic'].content.topic || topic.isEditing" - ng-click="topic.editTopic()" class="roomTopicSetNew"> - Set Topic - </button> - <div ng-show="room.current_room_state.state_events['m.room.topic'].content.topic || topic.isEditing"> - <div ng-hide="topic.isEditing" ng-dblclick="topic.editTopic()" id="roomTopic" - ng-bind-html="room.current_room_state.state_events['m.room.topic'].content.topic | limitTo: 200 | linky:'_blank'"> - </div> - <form ng-submit="topic.updateTopic()" ng-show="topic.isEditing" class="roomTopicForm"> - <input ng-model="topic.newTopicText" ng-blur="topic.cancelEdit()" class="roomTopicInput" placeholder="Topic"/> - </form> - </div> - </div> - </div> - </div> - - <div id="roomPage"> - <div id="roomWrapper"> - - <div id="roomRecentsTableWrapper"> - <div ng-include="'recents/recents.html'"></div> - </div> - - <div id="usersTableWrapper" ng-hide="state.permission_denied"> - <div ng-repeat="member in room.now.members | orderMembersList" class="userAvatar"> - <div class="userAvatarFrame" ng-class="(member.user.event.content.presence === 'online' ? 'online' : (member.user.event.content.presence === 'unavailable' ? 'unavailable' : '')) + ' ' + (member.event.content.membership == 'invite' ? 'invited' : '')"> - <img class="userAvatarImage mouse-pointer" - ng-click="$parent.goToUserPage(member.id)" - ng-src="{{member.user.event.content.avatar_url || 'img/default-profile.png'}}" - alt="{{ member.user.event.content.displayname || member.id.substr(0, member.id.indexOf(':')) }}" - title="{{ member.id }} - power: {{ member.power_level }}" - width="80" height="80"/> - <!-- <div class="userPowerLevel" ng-style="{'width': member.powerLevelNorm +'%'}"></div> --> - </div> - <div class="userName"> - <pie-chart ng-show="member.power_level_norm" data="[ (member.power_level_norm + 0), (100 - member.power_level_norm) ]"></pie-chart> - {{ member.id | mUserDisplayName:room_id:true }} - <span ng-show="member.user.event.content.last_active_ago" style="color: #aaa">({{ member.user.event.content.last_active_ago + (now - member.user.last_updated) | duration }})</span> - </div> - </div> - </div> - - <div id="messageTableWrapper" - ng-hide="state.permission_denied" - ng-style="{ 'visibility': state.messages_visibility }" - keep-scroll> - <table id="messageTable" infinite-scroll="paginateMore()"> - <tr ng-repeat="msg in room.events" - ng-class="(room.events[$index - 1].user_id !== msg.user_id ? 'differentUser' : '') + (msg.user_id === state.user_id ? ' mine' : '')" scroll-item> - <td class="leftBlock" ng-mouseover="state.showTs = 1" ng-mouseout="state.showTs = 0"> - <div class="timestamp" - ng-style="{ 'opacity': state.showTs ? 1.0 : 0.0 }" - ng-class="msg.echo_msg_state"> - {{ (msg.origin_server_ts) | date:'MMM d HH:mm' }} - </div> - <div class="sender" ng-hide="room.events[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"> {{ msg.__room_member.cnt.displayname || msg.user_id | mUserDisplayName:room_id:true }}</div> - </td> - <td class="avatar"> - <!-- room.now.members[msg.user_id].user.event.content.avatar_url is just backwards compat, and can be removed in the future. Synapse didn't used to - send m.room.member updates when avatar urls changed, so the image which should be visible here just going off room state isn't visible. We fix - this by reading off the m.presence url --> - <img class="avatarImage" ng-src="{{ msg.__room_member.cnt.avatar_url || msg.__room_member.content.avatar_url || room.now.members[msg.user_id].user.event.content.avatar_url || 'img/default-profile.png' }}" width="32" height="32" title="{{msg.user_id}}" - ng-hide="room.events[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"/> - </td> - <td class="msg" ng-class="(!msg.content.membership && ('m.room.topic' !== msg.type && 'm.room.name' !== msg.type))? (msg.content.msgtype === 'm.emote' ? 'emote text' : 'text') : 'membership text'"> - <div class="bubble" ng-dblclick="openJson(msg)"> - <span ng-if="'join' === msg.content.membership && msg.changedKey === 'membership'"> - {{ msg.content.displayname || room.now.members[msg.state_key].user.event.content.displayname || msg.state_key }} joined - </span> - <span ng-if="'leave' === msg.content.membership && msg.changedKey === 'membership'"> - <span ng-if="msg.user_id === msg.state_key"> - <!-- FIXME: This seems like a synapse bug that the 'leave' content doesn't give the displayname... --> - {{ msg.__room_member.cnt.displayname || room.now.members[msg.state_key].user.event.content.displayname || msg.state_key }} left - </span> - <span ng-if="msg.user_id !== msg.state_key && msg.prev_content"> - {{ msg.content.displayname || room.now.members[msg.user_id].user.event.content.displayname || msg.user_id }} - {{ {"invite": "kicked", "join": "kicked", "ban": "unbanned"}[msg.prev_content.membership] }} - {{ msg.__target_room_member.content.displayname || msg.state_key }} - <span ng-if="'join' === msg.prev_content.membership && msg.content.reason"> - : {{ msg.content.reason }} - </span> - </span> - </span> - <span ng-if="'invite' === msg.content.membership && msg.changedKey === 'membership' || - 'ban' === msg.content.membership && msg.changedKey === 'membership'"> - {{ msg.__room_member.cnt.displayname || msg.user_id }} - {{ {"invite": "invited", "ban": "banned"}[msg.content.membership] }} - {{ msg.__target_room_member.cnt.displayname || msg.state_key }} - <span ng-if="msg.prev_content && 'ban' === msg.prev_content.membership && msg.content.reason"> - : {{ msg.content.reason }} - </span> - </span> - <span ng-if="msg.changedKey === 'displayname'"> - {{ msg.user_id }} changed their display name from {{ msg.prev_content.displayname }} to {{ msg.content.displayname }} - </span> - - <span ng-show='msg.type === "m.room.message"' ng-switch='msg.content.msgtype'> - <span ng-switch-when="m.emote" - ng-class="containsBingWord(msg) && msg.user_id != state.user_id ? msg.echo_msg_state + ' messageBing' : msg.echo_msg_state" - ng-bind-html="'* ' + (msg.__room_member.cnt.displayname || msg.user_id) + ' ' + msg.content.body | linky:'_blank'" - /> - - <span ng-switch-when="m.text" - class="message" - ng-class="containsBingWord(msg) && msg.user_id != state.user_id ? msg.echo_msg_state + ' messageBing' : msg.echo_msg_state" - ng-bind-html="(msg.content.format === 'org.matrix.custom.html') ? (msg.content.formatted_body | unsanitizedLinky) : (msg.content.body | linky:'_blank') "/> - - <div ng-switch-when="m.image"> - <div ng-hide='msg.content.thumbnail_url' ng-style="msg.content.body.h && { 'height' : (msg.content.body.h < 320) ? msg.content.body.h : 320}"> - <img class="image" ng-src="{{ msg.content.url }}"/> - </div> - <div ng-show='msg.content.thumbnail_url' ng-style="{ 'height' : msg.content.thumbnail_info.h }"> - <img class="image mouse-pointer" ng-src="{{ msg.content.thumbnail_url }}" - ng-click="$parent.$parent.fullScreenImageURL = msg.content.url; $event.stopPropagation();"/> - </div> - </div> - - <span ng-switch-when="m.file" ng-class="msg.echo_msg_state"> - <a href="{{ msg.content.url}}" target="_blank">{{ msg.content.body }}</a> - <div ng-show='msg.content.thumbnail_url' ng-style="{ 'height' : msg.content.thumbnail_info.h }"> - <a href="{{ msg.content.url}}" target="_blank"> - <img class="image mouse-pointer" ng-src="{{ msg.content.thumbnail_url }}"/> - </a> - </div> - </span> - - <span ng-switch-default - ng-class="containsBingWord(msg) && msg.user_id != state.user_id ? msg.echo_msg_state + ' messageBing' : msg.echo_msg_state" - ng-bind-html="msg.content.body | linky:'_blank'" - /> - </span> - - <span ng-show='msg.type === "m.call.invite" && msg.user_id == state.user_id'>Outgoing Call{{ isWebRTCSupported() ? '' : ' (But your browser does not support VoIP)' }}</span> - <span ng-show='msg.type === "m.call.invite" && msg.user_id != state.user_id'>Incoming Call{{ isWebRTCSupported() ? '' : ' (But your browser does not support VoIP)' }}</span> - - <span ng-if="'m.room.topic' === msg.type"> - {{ msg.__room_member.cnt.displayname || msg.user_id }} changed the topic to: {{ msg.content.topic }} - </span> - - <span ng-if="'m.room.name' === msg.type"> - {{ msg.__room_member.cnt.displayname || msg.user_id }} changed the room name to: {{ msg.content.name }} - </span> - - </div> - </td> - <td class="rightBlock"> - <img class="avatarImage" ng-src="{{ room.now.members[msg.user_id].user.event.content.avatar_url || 'img/default-profile.png' }}" width="32" height="32" - ng-hide="room.events[$index - 1].user_id === msg.user_id || msg.user_id !== state.user_id"/> - </td> - </tr> - </table> - </div> - - <div ng-show="state.permission_denied"> - {{ state.permission_denied }} - </div> - - </div> - </div> - - <div id="controlPanel"> - <div id="controls"> - <button id="attachButton" m-file-input="fileToSend" class="extraControls" ng-disabled="state.permission_denied"></button> - <textarea id="mainInput" rows="1" ng-enter="send()" - ng-disabled="state.permission_denied" - ng-focus="true" autocomplete="off" tab-complete command-history="room_id"/> - {{ feedback }} - <div ng-show="state.stream_failure"> - {{ state.stream_failure.data.error || "Connection failure" }} - </div> - </div> - </div> - - <div id="room-fullscreen-image" ng-show="fullScreenImageURL" ng-click="fullScreenImageURL = undefined;"> - <img ng-src="{{ fullScreenImageURL }}"/> - </div> - - </div> diff --git a/syweb/webclient/settings/settings-controller.js b/syweb/webclient/settings/settings-controller.js deleted file mode 100644 index 9cdace704a..0000000000 --- a/syweb/webclient/settings/settings-controller.js +++ /dev/null @@ -1,221 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -angular.module('SettingsController', ['matrixService', 'mFileUpload', 'mFileInput']) -.controller('SettingsController', ['$scope', 'matrixService', 'mFileUpload', - function($scope, matrixService, mFileUpload) { - // XXX: duplicated from register - var generateClientSecret = function() { - var ret = ""; - var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - - for (var i = 0; i < 32; i++) { - ret += chars.charAt(Math.floor(Math.random() * chars.length)); - } - - return ret; - }; - $scope.config = matrixService.config(); - - $scope.profile = { - displayName: "", - avatarUrl: "" - }; - - // The profile as stored on the server - $scope.profileOnServer = { - displayName: "", - avatarUrl: "" - }; - - $scope.onInit = function() { - // Load profile data - // Display name - matrixService.getDisplayName($scope.config.user_id).then( - function(response) { - $scope.profile.displayName = response.data.displayname; - $scope.profileOnServer.displayName = response.data.displayname; - }, - function(error) { - $scope.feedback = "Can't load display name"; - } - ); - // Avatar - matrixService.getProfilePictureUrl($scope.config.user_id).then( - function(response) { - $scope.profile.avatarUrl = response.data.avatar_url; - $scope.profileOnServer.avatarUrl = response.data.avatar_url; - }, - function(error) { - $scope.feedback = "Can't load avatar URL"; - } - ); - }; - - $scope.$watch("profile.avatarFile", function(newValue, oldValue) { - if ($scope.profile.avatarFile) { - console.log("Uploading new avatar file..."); - mFileUpload.uploadFile($scope.profile.avatarFile).then( - function(url) { - $scope.profile.avatarUrl = url; - }, - function(error) { - $scope.feedback = "Can't upload image"; - } - ); - } - }); - - $scope.saveProfile = function() { - if ($scope.profile.displayName !== $scope.profileOnServer.displayName) { - setDisplayName($scope.profile.displayName); - } - if ($scope.profile.avatarUrl !== $scope.profileOnServer.avatarUrl) { - setAvatar($scope.profile.avatarUrl); - } - }; - - var setDisplayName = function(displayName) { - matrixService.setDisplayName(displayName).then( - function(response) { - $scope.feedback = "Updated display name."; - }, - function(error) { - $scope.feedback = "Can't update display name: " + error.data; - } - ); - }; - - var setAvatar = function(avatarURL) { - console.log("Updating avatar to " + avatarURL); - matrixService.setProfilePictureUrl(avatarURL).then( - function(response) { - console.log("Updated avatar"); - $scope.feedback = "Updated avatar."; - }, - function(error) { - $scope.feedback = "Can't update avatar: " + error.data; - } - ); - }; - - $scope.linkedEmails = { - linkNewEmail: "", // the email entry box - emailBeingAuthed: undefined, // to populate verification text - authSid: undefined, // the token id from the IS - emailCode: "", // the code entry box - linkedEmailList: matrixService.config().emailList // linked email list - }; - - $scope.linkEmail = function(email) { - if (email != $scope.linkedEmails.emailBeingAuthed) { - $scope.linkedEmails.emailBeingAuthed = email; - $scope.clientSecret = generateClientSecret(); - $scope.sendAttempt = 0; - } - $scope.sendAttempt++; - matrixService.linkEmail(email, $scope.clientSecret, $scope.sendAttempt).then( - function(response) { - if (response.data.success === true) { - $scope.linkedEmails.authSid = response.data.sid; - $scope.emailFeedback = "You have been sent an email."; - $scope.linkedEmails.emailBeingAuthed = email; - } - else { - $scope.emailFeedback = "Failed to send email."; - } - }, - function(error) { - $scope.emailFeedback = "Can't send email: " + error.data; - } - ); - }; - - $scope.submitEmailCode = function() { - var tokenId = $scope.linkedEmails.authSid; - if (tokenId === undefined) { - $scope.emailFeedback = "You have not requested a code with this email."; - return; - } - matrixService.authEmail($scope.clientSecret, $scope.linkedEmails.authSid, $scope.linkedEmails.emailCode).then( - function(response) { - if ("errcode" in response.data) { - $scope.emailFeedback = "Failed to authenticate email."; - return; - } - matrixService.bindEmail(matrixService.config().user_id, tokenId, $scope.clientSecret).then( - function(response) { - if ('errcode' in response.data) { - $scope.emailFeedback = "Failed to link email."; - return; - } - var config = matrixService.config(); - var emailList = {}; - if ("emailList" in config) { - emailList = config.emailList; - } - emailList[$scope.linkedEmails.emailBeingAuthed] = response; - // save the new email list - config.emailList = emailList; - matrixService.setConfig(config); - matrixService.saveConfig(); - // invalidate the email being authed and update UI. - $scope.linkedEmails.emailBeingAuthed = undefined; - $scope.emailFeedback = ""; - $scope.linkedEmails.linkedEmailList = emailList; - $scope.linkedEmails.linkNewEmail = ""; - $scope.linkedEmails.emailCode = ""; - }, function(reason) { - $scope.emailFeedback = "Failed to link email: " + reason; - } - ); - }, - function(reason) { - $scope.emailFeedback = "Failed to auth email: " + reason; - } - ); - }; - - - /*** Desktop notifications section ***/ - $scope.settings = { - notifications: undefined, - bingWords: matrixService.config().bingWords - }; - - $scope.saveBingWords = function() { - console.log("Saving words: "+JSON.stringify($scope.settings.bingWords)); - var config = matrixService.config(); - config.bingWords = $scope.settings.bingWords; - matrixService.setConfig(config); - matrixService.saveConfig(); - }; - - // If the browser supports it, check the desktop notification state - if ("Notification" in window) { - $scope.settings.notifications = window.Notification.permission; - } - - $scope.requestNotifications = function() { - console.log("requestNotifications"); - window.Notification.requestPermission(function (permission) { - console.log(" -> User decision: " + permission); - $scope.settings.notifications = permission; - }); - }; -}]); diff --git a/syweb/webclient/settings/settings.html b/syweb/webclient/settings/settings.html deleted file mode 100644 index 4c20634a76..0000000000 --- a/syweb/webclient/settings/settings.html +++ /dev/null @@ -1,106 +0,0 @@ -<div ng-controller="SettingsController" class="user" data-ng-init="onInit()"> - - <div id="wrapper"> - - <div id="genericHeading"> - <a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a> - </div> - - <h1>Settings</h1> - <div class="section"> - <form> - <div class="profile-avatar"> - <img ng-src="{{ (null !== profile.avatarUrl) ? profile.avatarUrl : 'img/default-profile.png' }}" m-file-input="profile.avatarFile"/> - </div> - <div> - <input id="user-displayname-input" size="40" ng-model="profile.displayName" placeholder="Your display name"/> - <br/> - <button id="user-save-button" - ng-disabled="(profile.displayName === profileOnServer.displayName) && (profile.avatarUrl === profileOnServer.avatarUrl)" - ng-click="saveProfile()">Save changes</button> - </div> - </form> - </div> - <br/> - - <h3>Linked emails</h3> - <div class="section"> - <form> - <input size="40" ng-model="linkedEmails.linkNewEmail" ng-enter="linkEmail(linkedEmails.linkNewEmail)" /> - <button ng-disabled="!linkedEmails.linkNewEmail" ng-click="linkEmail(linkedEmails.linkNewEmail)"> - Link Email - </button> - {{ emailFeedback }} - </form> - <form ng-hide="!linkedEmails.emailBeingAuthed"> - Enter validation token for {{ linkedEmails.emailBeingAuthed }}: - <br /> - <input size="20" ng-model="linkedEmails.emailCode" ng-enter="submitEmailCode(linkedEmails.emailCode)" /> - <button ng-disabled="!linkedEmails.emailCode || !linkedEmails.linkNewEmail" ng-click="submitEmailCode(linkedEmails.emailCode)"> - Submit Code - </button> - </form> - <table> - <tr ng-repeat="(address, info) in linkedEmails.linkedEmailList"> - <td>{{address}}</td> - </tr> - </table> - </div> - <br/> - - <h3>Desktop notifications</h3> - <div class="section" ng-switch="settings.notifications"> - <div ng-switch-when="granted"> - Notifications are enabled. - <div class="section"> - <h4>Specific words to alert on:</h4> - <p>If blank, all messages will trigger an alert. Your username & display name always alerts.</p> - <input size=40 name="bingWords" ng-model="settings.bingWords" ng-list placeholder="Enter words separated with , (supports regex)" - ng-blur="saveBingWords()"/> - <ul> - <li ng-repeat="word in settings.bingWords">{{word}}</li> - </ul> - </div> - </div> - <div ng-switch-when="denied"> - You have denied permission for notifications.<br/> - To enable it, reset the notification setting for this web site into your browser settings. - </div> - <div ng-switch-when="default"> - <button ng-click="requestNotifications()" style="font-size: 14pt">Enable desktop notifications</button> - </div> - <div ng-switch-default=""> - Sorry, your browser does not support notifications. - </div> - </div> - <br/> - - <h3>Configuration</h3> - <div class="section"> - <div>Home server: {{ config.homeserver }} </div> - <div>Identity server: {{ config.identityServer }} </div> - <div>User ID: {{ config.user_id }} </div> - <div>Access token: {{ config.access_token }} </div> - </div> - <br/> - - <h3>Commands</h3> - <div class="section"> - The following commands are available in the room chat: - <ul> - <li>/nick <display_name>: change your display name</li> - <li>/me <action>: send the action you are doing. /me will be replaced by your display name</li> - <li>/join <room_alias>: join a room</li> - <li>/kick <user_id> [<reason>]: kick the user</li> - <li>/ban <user_id> [<reason>]: ban the user</li> - <li>/unban <user_id>: unban the user</li> - <li>/op <user_id> <power_level>: set user power level</li> - <li>/deop <user_id>: reset user power level to the room default value</li> - </ul> - </div> - <br/> - - {{ feedback }} - - </div> -</div> diff --git a/syweb/webclient/test/README b/syweb/webclient/test/README deleted file mode 100644 index e7ed4eaa87..0000000000 --- a/syweb/webclient/test/README +++ /dev/null @@ -1,51 +0,0 @@ -Testing is done using Karma. - - -UNIT TESTING -============ - -Requires the following: - - npm/nodejs - - phantomjs - -Requires the following node packages: - - npm install jasmine - - npm install karma - - npm install karma-jasmine - - npm install karma-phantomjs-launcher - - npm install karma-junit-reporter - -Make sure you're in this directory so it can find the config file and run: - karma start - -You should see all the tests pass. - - -E2E TESTING -=========== - -npm install protractor - - -Setting up e2e tests (only if you don't have a selenium server to run the tests -on. If you do, edit the config to point to that url): - - webdriver-manager update - webdriver-manager start - - Create a file "environment-protractor.js" in this directory and type: - module.exports = { - seleniumAddress: 'http://localhost:4444/wd/hub', - baseUrl: "http://localhost:8008", - username: "YOUR_TEST_USERNAME", - password: "YOUR_TEST_PASSWORD" - } - -Running e2e tests: - protractor protractor.conf.js - -NOTE: This will create a public room on the target home server. - - - - diff --git a/syweb/webclient/test/e2e/home.spec.js b/syweb/webclient/test/e2e/home.spec.js deleted file mode 100644 index 470237d557..0000000000 --- a/syweb/webclient/test/e2e/home.spec.js +++ /dev/null @@ -1,16 +0,0 @@ -var env = require("../environment-protractor.js"); - -describe("home page", function() { - - beforeEach(function() { - ptor = protractor.getInstance(); - // FIXME we use longpoll on the event stream, and I can't get $interval - // playing nicely with it. Patches welcome to fix this. - ptor.ignoreSynchronization = true; - }); - - it("should have a title", function() { - browser.get(env.baseUrl); - expect(browser.getTitle()).toEqual("[matrix]"); - }); -}); diff --git a/syweb/webclient/test/karma.conf.js b/syweb/webclient/test/karma.conf.js deleted file mode 100644 index 37a9eaf1c1..0000000000 --- a/syweb/webclient/test/karma.conf.js +++ /dev/null @@ -1,107 +0,0 @@ -// Karma configuration -// Generated on Thu Sep 18 2014 14:25:57 GMT+0100 (BST) - -module.exports = function(config) { - config.set({ - - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: '', - - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['jasmine'], - - - // list of files / patterns to load in the browser - // XXX: Order is important, and doing /js/angular* makes the tests not run :/ - files: [ - '../js/jquery*', - '../js/angular.js', - '../js/angular-mocks.js', - '../js/angular-route.js', - '../js/angular-animate.js', - '../js/angular-sanitize.js', - '../js/jquery.peity.min.js', - '../js/angular-peity.js', - '../js/ng-infinite-scroll-matrix.js', - '../js/ui-bootstrap*', - '../js/elastic.js', - '../login/**/*.js', - '../room/**/*.js', - '../components/**/*.js', - '../user/**/*.js', - '../home/**/*.js', - '../recents/**/*.js', - '../settings/**/*.js', - '../app.js', - '../app*', - './unit/**/*.js' - ], - - plugins: [ - 'karma-*', - ], - - - // list of files to exclude - exclude: [ - ], - - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - '../login/**/*.js': 'coverage', - '../room/**/*.js': 'coverage', - '../components/**/*.js': 'coverage', - '../user/**/*.js': 'coverage', - '../home/**/*.js': 'coverage', - '../recents/**/*.js': 'coverage', - '../settings/**/*.js': 'coverage', - '../app.js': 'coverage' - }, - - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress', 'junit', 'coverage'], - junitReporter: { - outputFile: 'test-results.xml', - suite: '' - }, - - coverageReporter: { - type: 'cobertura', - dir: 'coverage/', - file: 'coverage.xml' - }, - - // web server port - port: 9876, - - - // enable / disable colors in the output (reporters and logs) - colors: true, - - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_DEBUG, - - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: true, - - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['PhantomJS'], - - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true - }); -}; diff --git a/syweb/webclient/test/protractor.conf.js b/syweb/webclient/test/protractor.conf.js deleted file mode 100644 index 76ae7b712b..0000000000 --- a/syweb/webclient/test/protractor.conf.js +++ /dev/null @@ -1,18 +0,0 @@ -var env = require("./environment-protractor.js"); -exports.config = { - seleniumAddress: env.seleniumAddress, - specs: ['e2e/*.spec.js'], - onPrepare: function() { - browser.driver.get(env.baseUrl); - browser.driver.findElement(by.id("user_id")).sendKeys(env.username); - browser.driver.findElement(by.id("password")).sendKeys(env.password); - browser.driver.findElement(by.id("login")).click(); - - // wait till the login is done, detect via url change - browser.driver.wait(function() { - return browser.driver.getCurrentUrl().then(function(url) { - return !(/login/.test(url)) - }); - }); - } -} diff --git a/syweb/webclient/test/unit/commands-service.spec.js b/syweb/webclient/test/unit/commands-service.spec.js deleted file mode 100644 index 142044f153..0000000000 --- a/syweb/webclient/test/unit/commands-service.spec.js +++ /dev/null @@ -1,143 +0,0 @@ -describe('CommandsService', function() { - var scope; - var roomId = "!dlwifhweu:localhost"; - - var testPowerLevelsEvent, testMatrixServicePromise; - - var matrixService = { // these will be spyed on by jasmine, hence stub methods - setDisplayName: function(args){}, - kick: function(args){}, - ban: function(args){}, - unban: function(args){}, - setUserPowerLevel: function(args){} - }; - - var modelService = { - getRoom: function(roomId) { - return { - room_id: roomId, - current_room_state: { - events: { - "m.room.power_levels": testPowerLevelsEvent - }, - state: function(type, key) { - return key ? this.events[type+key] : this.events[type]; - } - } - }; - } - }; - - - // helper function for asserting promise outcomes - NOTHING = "[Promise]"; - RESOLVED = "[Resolved promise]"; - REJECTED = "[Rejected promise]"; - var expectPromise = function(promise, expects) { - var value = NOTHING; - promise.then(function(result) { - value = RESOLVED; - }, function(fail) { - value = REJECTED; - }); - scope.$apply(); - expect(value).toEqual(expects); - }; - - // setup the service and mocked dependencies - beforeEach(function() { - - // set default mock values - testPowerLevelsEvent = { - content: { - default: 50 - }, - user_id: "@foo:bar", - room_id: roomId - } - - // mocked dependencies - module(function ($provide) { - $provide.value('matrixService', matrixService); - $provide.value('modelService', modelService); - }); - - // tested service - module('commandsService'); - }); - - beforeEach(inject(function($rootScope, $q) { - scope = $rootScope; - testMatrixServicePromise = $q.defer(); - })); - - it('should reject a no-arg "/nick".', inject( - function(commandsService) { - var promise = commandsService.processInput(roomId, "/nick"); - expectPromise(promise, REJECTED); - })); - - it('should be able to set a /nick with multiple words.', inject( - function(commandsService) { - spyOn(matrixService, 'setDisplayName').and.returnValue(testMatrixServicePromise); - var promise = commandsService.processInput(roomId, "/nick Bob Smith"); - expect(matrixService.setDisplayName).toHaveBeenCalledWith("Bob Smith"); - expect(promise).toBe(testMatrixServicePromise); - })); - - it('should be able to /kick a user without a reason.', inject( - function(commandsService) { - spyOn(matrixService, 'kick').and.returnValue(testMatrixServicePromise); - var promise = commandsService.processInput(roomId, "/kick @bob:matrix.org"); - expect(matrixService.kick).toHaveBeenCalledWith(roomId, "@bob:matrix.org", undefined); - expect(promise).toBe(testMatrixServicePromise); - })); - - it('should be able to /kick a user with a reason.', inject( - function(commandsService) { - spyOn(matrixService, 'kick').and.returnValue(testMatrixServicePromise); - var promise = commandsService.processInput(roomId, "/kick @bob:matrix.org he smells"); - expect(matrixService.kick).toHaveBeenCalledWith(roomId, "@bob:matrix.org", "he smells"); - expect(promise).toBe(testMatrixServicePromise); - })); - - it('should be able to /ban a user without a reason.', inject( - function(commandsService) { - spyOn(matrixService, 'ban').and.returnValue(testMatrixServicePromise); - var promise = commandsService.processInput(roomId, "/ban @bob:matrix.org"); - expect(matrixService.ban).toHaveBeenCalledWith(roomId, "@bob:matrix.org", undefined); - expect(promise).toBe(testMatrixServicePromise); - })); - - it('should be able to /ban a user with a reason.', inject( - function(commandsService) { - spyOn(matrixService, 'ban').and.returnValue(testMatrixServicePromise); - var promise = commandsService.processInput(roomId, "/ban @bob:matrix.org he smells"); - expect(matrixService.ban).toHaveBeenCalledWith(roomId, "@bob:matrix.org", "he smells"); - expect(promise).toBe(testMatrixServicePromise); - })); - - it('should be able to /unban a user.', inject( - function(commandsService) { - spyOn(matrixService, 'unban').and.returnValue(testMatrixServicePromise); - var promise = commandsService.processInput(roomId, "/unban @bob:matrix.org"); - expect(matrixService.unban).toHaveBeenCalledWith(roomId, "@bob:matrix.org"); - expect(promise).toBe(testMatrixServicePromise); - })); - - it('should be able to /op a user.', inject( - function(commandsService) { - spyOn(matrixService, 'setUserPowerLevel').and.returnValue(testMatrixServicePromise); - var promise = commandsService.processInput(roomId, "/op @bob:matrix.org 50"); - expect(matrixService.setUserPowerLevel).toHaveBeenCalledWith(roomId, "@bob:matrix.org", 50, testPowerLevelsEvent); - expect(promise).toBe(testMatrixServicePromise); - })); - - it('should be able to /deop a user.', inject( - function(commandsService) { - spyOn(matrixService, 'setUserPowerLevel').and.returnValue(testMatrixServicePromise); - var promise = commandsService.processInput(roomId, "/deop @bob:matrix.org"); - expect(matrixService.setUserPowerLevel).toHaveBeenCalledWith(roomId, "@bob:matrix.org", undefined, testPowerLevelsEvent); - expect(promise).toBe(testMatrixServicePromise); - })); -}); diff --git a/syweb/webclient/test/unit/event-handler-service.spec.js b/syweb/webclient/test/unit/event-handler-service.spec.js deleted file mode 100644 index 5eb6fe10ab..0000000000 --- a/syweb/webclient/test/unit/event-handler-service.spec.js +++ /dev/null @@ -1,30 +0,0 @@ -describe('EventHandlerService', function() { - var scope; - - var modelService = {}; - - // setup the service and mocked dependencies - beforeEach(function() { - // dependencies - module('matrixService'); - module('notificationService'); - module('mPresence'); - - // cleanup mocked methods - modelService = {}; - - // mocked dependencies - module(function ($provide) { - $provide.value('modelService', modelService); - }); - - // tested service - module('eventHandlerService'); - }); - - beforeEach(inject(function($rootScope) { - scope = $rootScope; - })); - - -}); diff --git a/syweb/webclient/test/unit/event-stream-service.spec.js b/syweb/webclient/test/unit/event-stream-service.spec.js deleted file mode 100644 index 6fd7693a70..0000000000 --- a/syweb/webclient/test/unit/event-stream-service.spec.js +++ /dev/null @@ -1,80 +0,0 @@ -describe('EventStreamService', function() { - var q, scope; - - var testInitialSync, testEventStream; - - var matrixService = { - initialSync: function(limit, feedback) { - var defer = q.defer(); - defer.resolve(testInitialSync); - return defer.promise; - }, - getEventStream: function(from, svrTimeout, cliTimeout) { - var defer = q.defer(); - defer.resolve(testEventStream); - return defer.promise; - } - }; - - var eventHandlerService = { - handleInitialSyncDone: function(response) { - - }, - - handleEvents: function(chunk, isLive) { - - } - }; - - // setup the dependencies - beforeEach(function() { - - // reset test data - testInitialSync = { - data: { - end: "foo", - presence: [], - rooms: [] - } - }; - testEventStream = { - data: { - start: "foostart", - end: "fooend", - chunk: [] - } - }; - - // dependencies - module(function ($provide) { - $provide.value('matrixService', matrixService); - $provide.value('eventHandlerService', eventHandlerService); - }); - - // tested service - module('eventStreamService'); - }); - - beforeEach(inject(function($q, $rootScope) { - q = $q; - scope = $rootScope; - })); - - it('should start with /initialSync then go onto /events', inject( - function(eventStreamService) { - spyOn(eventHandlerService, "handleInitialSyncDone"); - spyOn(eventHandlerService, "handleEvents"); - eventStreamService.resume(); - scope.$apply(); // initialSync request - expect(eventHandlerService.handleInitialSyncDone).toHaveBeenCalledWith(testInitialSync); - expect(eventHandlerService.handleEvents).toHaveBeenCalledWith(testEventStream.data.chunk, true); - })); - - it('should use the end token in /initialSync for the next /events request', inject( - function(eventStreamService) { - spyOn(matrixService, "getEventStream").and.callThrough(); - eventStreamService.resume(); - scope.$apply(); // initialSync request - expect(matrixService.getEventStream).toHaveBeenCalledWith("foo", eventStreamService.SERVER_TIMEOUT, eventStreamService.CLIENT_TIMEOUT); - })); -}); diff --git a/syweb/webclient/test/unit/filters.spec.js b/syweb/webclient/test/unit/filters.spec.js deleted file mode 100644 index fe31da709b..0000000000 --- a/syweb/webclient/test/unit/filters.spec.js +++ /dev/null @@ -1,822 +0,0 @@ -describe('mRoomName filter', function() { - var filter, mRoomName, mUserDisplayName; - - var roomId = "!weufhewifu:matrix.org"; - - // test state values (f.e. test) - var testUserId, testAlias, testDisplayName, testOtherDisplayName, testRoomState; - - // mocked services which return the test values above. - var matrixService = { - config: function() { - return { - user_id: testUserId - }; - } - }; - - var modelService = { - getRoom: function(room_id) { - return { - current_room_state: testRoomState - }; - }, - - getRoomIdToAliasMapping: function(room_id) { - return testAlias; - }, - }; - - beforeEach(function() { - // inject mocked dependencies - module(function ($provide) { - $provide.value('matrixService', matrixService); - $provide.value('modelService', modelService); - }); - - module('matrixFilter'); - - // angular resolves dependencies with the same name via a 'last wins' - // rule, hence we need to have this mock filter impl AFTER module('matrixFilter') - // so it clobbers the actual mUserDisplayName implementation. - module(function ($filterProvider) { - // provide a fake filter - $filterProvider.register('mUserDisplayName', function() { - return function(user_id, room_id) { - if (user_id === testUserId) { - return testDisplayName; - } - return testOtherDisplayName; - }; - }); - }); - }); - - - beforeEach(inject(function($filter) { - filter = $filter; - mRoomName = filter("mRoomName"); - - // purge the previous test values - testUserId = undefined; - testAlias = undefined; - testDisplayName = undefined; - testOtherDisplayName = undefined; - - // mock up a stub room state - testRoomState = { - s:{}, // internal; stores the state events - state: function(type, key) { - // accessor used by filter - return key ? this.s[type+key] : this.s[type]; - }, - members: {}, // struct used by filter - - // test helper methods - setJoinRule: function(rule) { - this.s["m.room.join_rules"] = { - content: { - join_rule: rule - } - }; - }, - setRoomName: function(name) { - this.s["m.room.name"] = { - content: { - name: name - } - }; - }, - setMember: function(user_id, membership, inviter_user_id) { - if (!inviter_user_id) { - inviter_user_id = user_id; - } - this.s["m.room.member" + user_id] = { - event: { - content: { - membership: membership - }, - state_key: user_id, - user_id: inviter_user_id - } - }; - this.members[user_id] = this.s["m.room.member" + user_id]; - } - }; - })); - - /**** ROOM NAME ****/ - - it("should show the room name if one exists for private (invite join_rules) rooms.", function() { - var roomName = "The Room Name"; - testUserId = "@me:matrix.org"; - testRoomState.setJoinRule("invite"); - testRoomState.setRoomName(roomName); - testRoomState.setMember(testUserId, "join"); - var output = mRoomName(roomId); - expect(output).toEqual(roomName); - }); - - it("should show the room name if one exists for public (public join_rules) rooms.", function() { - var roomName = "The Room Name"; - testUserId = "@me:matrix.org"; - testRoomState.setJoinRule("public"); - testRoomState.setRoomName(roomName); - testRoomState.setMember(testUserId, "join"); - var output = mRoomName(roomId); - expect(output).toEqual(roomName); - }); - - /**** ROOM ALIAS ****/ - - it("should show the room alias if one exists for private (invite join_rules) rooms if a room name doesn't exist.", function() { - testAlias = "#thealias:matrix.org"; - testUserId = "@me:matrix.org"; - testRoomState.setJoinRule("invite"); - testRoomState.setMember(testUserId, "join"); - var output = mRoomName(roomId); - expect(output).toEqual(testAlias); - }); - - it("should show the room alias if one exists for public (public join_rules) rooms if a room name doesn't exist.", function() { - testAlias = "#thealias:matrix.org"; - testUserId = "@me:matrix.org"; - testRoomState.setJoinRule("public"); - testRoomState.setMember(testUserId, "join"); - var output = mRoomName(roomId); - expect(output).toEqual(testAlias); - }); - - /**** ROOM ID ****/ - - it("should show the room ID for public (public join_rules) rooms if a room name and alias don't exist.", function() { - testUserId = "@me:matrix.org"; - testRoomState.setJoinRule("public"); - testRoomState.setMember(testUserId, "join"); - var output = mRoomName(roomId); - expect(output).toEqual(roomId); - }); - - it("should show the room ID for private (invite join_rules) rooms if a room name and alias don't exist and there are >2 members.", function() { - testUserId = "@me:matrix.org"; - testRoomState.setJoinRule("public"); - testRoomState.setMember(testUserId, "join"); - testRoomState.setMember("@alice:matrix.org", "join"); - testRoomState.setMember("@bob:matrix.org", "join"); - var output = mRoomName(roomId); - expect(output).toEqual(roomId); - }); - - /**** SELF-CHAT ****/ - - it("should show your display name for private (invite join_rules) rooms if a room name and alias don't exist and it is a self-chat.", function() { - testUserId = "@me:matrix.org"; - testDisplayName = "Me"; - testRoomState.setJoinRule("private"); - testRoomState.setMember(testUserId, "join"); - var output = mRoomName(roomId); - expect(output).toEqual(testDisplayName); - }); - - it("should show your user ID for private (invite join_rules) rooms if a room name and alias don't exist and it is a self-chat and they don't have a display name set.", function() { - testUserId = "@me:matrix.org"; - testRoomState.setJoinRule("private"); - testRoomState.setMember(testUserId, "join"); - var output = mRoomName(roomId); - expect(output).toEqual(testUserId); - }); - - /**** ONE-TO-ONE CHAT ****/ - - it("should show the other user's display name for private (invite join_rules) rooms if a room name and alias don't exist and it is a 1:1-chat.", function() { - testUserId = "@me:matrix.org"; - otherUserId = "@alice:matrix.org"; - testOtherDisplayName = "Alice"; - testRoomState.setJoinRule("private"); - testRoomState.setMember(testUserId, "join"); - testRoomState.setMember("@alice:matrix.org", "join"); - var output = mRoomName(roomId); - expect(output).toEqual(testOtherDisplayName); - }); - - it("should show the other user's ID for private (invite join_rules) rooms if a room name and alias don't exist and it is a 1:1-chat and they don't have a display name set.", function() { - testUserId = "@me:matrix.org"; - otherUserId = "@alice:matrix.org"; - testRoomState.setJoinRule("private"); - testRoomState.setMember(testUserId, "join"); - testRoomState.setMember("@alice:matrix.org", "join"); - var output = mRoomName(roomId); - expect(output).toEqual(otherUserId); - }); - - /**** INVITED TO ROOM ****/ - - it("should show the other user's display name for private (invite join_rules) rooms if you are invited to it.", function() { - testUserId = "@me:matrix.org"; - testDisplayName = "Me"; - otherUserId = "@alice:matrix.org"; - testOtherDisplayName = "Alice"; - testRoomState.setJoinRule("private"); - testRoomState.setMember(testUserId, "join"); - testRoomState.setMember(otherUserId, "join"); - testRoomState.setMember(testUserId, "invite"); - var output = mRoomName(roomId); - expect(output).toEqual(testOtherDisplayName); - }); - - it("should show the other user's ID for private (invite join_rules) rooms if you are invited to it and the inviter doesn't have a display name.", function() { - testUserId = "@me:matrix.org"; - testDisplayName = "Me"; - otherUserId = "@alice:matrix.org"; - testRoomState.setJoinRule("private"); - testRoomState.setMember(testUserId, "join"); - testRoomState.setMember(otherUserId, "join"); - testRoomState.setMember(testUserId, "invite"); - var output = mRoomName(roomId); - expect(output).toEqual(otherUserId); - }); -}); - -describe('duration filter', function() { - var filter, durationFilter; - - beforeEach(module('matrixWebClient')); - beforeEach(inject(function($filter) { - filter = $filter; - durationFilter = filter("duration"); - })); - - it("should represent 15000 ms as '15s'", function() { - var output = durationFilter(15000); - expect(output).toEqual("15s"); - }); - - it("should represent 60000 ms as '1m'", function() { - var output = durationFilter(60000); - expect(output).toEqual("1m"); - }); - - it("should represent 65000 ms as '1m'", function() { - var output = durationFilter(65000); - expect(output).toEqual("1m"); - }); - - it("should represent 10 ms as '0s'", function() { - var output = durationFilter(10); - expect(output).toEqual("0s"); - }); - - it("should represent 4m as '4m'", function() { - var output = durationFilter(1000*60*4); - expect(output).toEqual("4m"); - }); - - it("should represent 4m30s as '4m'", function() { - var output = durationFilter(1000*60*4 + 1000*30); - expect(output).toEqual("4m"); - }); - - it("should represent 2h as '2h'", function() { - var output = durationFilter(1000*60*60*2); - expect(output).toEqual("2h"); - }); - - it("should represent 2h35m as '2h'", function() { - var output = durationFilter(1000*60*60*2 + 1000*60*35); - expect(output).toEqual("2h"); - }); - - it("should represent -ve numbers as '0s'", function() { - var output = durationFilter(-2000); - expect(output).toEqual("0s"); - }); -}); - -describe('orderMembersList filter', function() { - var filter, orderMembersList; - - beforeEach(module('matrixWebClient')); - beforeEach(inject(function($filter) { - filter = $filter; - orderMembersList = filter("orderMembersList"); - })); - - it("should sort a single entry", function() { - var output = orderMembersList({ - "@a:example.com": { - user: { - event: { - content: { - last_active_ago: 50 - } - }, - last_updated: 1415266943964 - } - } - }); - expect(output).toEqual([{ - id: "@a:example.com", - user: { - event: { - content: { - last_active_ago: 50 - } - }, - last_updated: 1415266943964 - } - }]); - }); - - it("should sort by taking last_active_ago into account", function() { - var output = orderMembersList({ - "@a:example.com": { - user: { - event: { - content: { - last_active_ago: 1000 - } - }, - last_updated: 1415266943964 - } - }, - "@b:example.com": { - user: { - event: { - content: { - last_active_ago: 50 - } - }, - last_updated: 1415266943964 - } - }, - "@c:example.com": { - user: { - event: { - content: { - last_active_ago: 99999 - } - }, - last_updated: 1415266943964 - } - } - }); - expect(output).toEqual([ - { - id: "@b:example.com", - user: { - event: { - content: { - last_active_ago: 50 - } - }, - last_updated: 1415266943964 - } - }, - { - id: "@a:example.com", - user: { - event: { - content: { - last_active_ago: 1000 - } - }, - last_updated: 1415266943964 - } - }, - { - id: "@c:example.com", - user: { - event: { - content: { - last_active_ago: 99999 - } - }, - last_updated: 1415266943964 - } - }, - ]); - }); - - it("should sort by taking last_updated into account", function() { - var output = orderMembersList({ - "@a:example.com": { - user: { - event: { - content: { - last_active_ago: 1000 - } - }, - last_updated: 1415266943964 - } - }, - "@b:example.com": { - user: { - event: { - content: { - last_active_ago: 1000 - } - }, - last_updated: 1415266900000 - } - }, - "@c:example.com": { - user: { - event: { - content: { - last_active_ago: 1000 - } - }, - last_updated: 1415266943000 - } - } - }); - expect(output).toEqual([ - { - id: "@a:example.com", - user: { - event: { - content: { - last_active_ago: 1000 - } - }, - last_updated: 1415266943964 - } - }, - { - id: "@c:example.com", - user: { - event: { - content: { - last_active_ago: 1000 - } - }, - last_updated: 1415266943000 - } - }, - { - id: "@b:example.com", - user: { - event: { - content: { - last_active_ago: 1000 - } - }, - last_updated: 1415266900000 - } - }, - ]); - }); - - it("should sort by taking last_updated and last_active_ago into account", - function() { - var output = orderMembersList({ - "@a:example.com": { - user: { - event: { - content: { - last_active_ago: 1000 - } - }, - last_updated: 1415266943000 - } - }, - "@b:example.com": { - user: { - event: { - content: { - last_active_ago: 100000 - } - }, - last_updated: 1415266943900 - } - }, - "@c:example.com": { - user: { - event: { - content: { - last_active_ago: 1000 - } - }, - last_updated: 1415266943964 - } - } - }); - expect(output).toEqual([ - { - id: "@c:example.com", - user: { - event: { - content: { - last_active_ago: 1000 - } - }, - last_updated: 1415266943964 - } - }, - { - id: "@a:example.com", - user: { - event: { - content: { - last_active_ago: 1000 - } - }, - last_updated: 1415266943000 - } - }, - { - id: "@b:example.com", - user: { - event: { - content: { - last_active_ago: 100000 - } - }, - last_updated: 1415266943900 - } - }, - ]); - }); - - // SYWEB-26 comment - it("should sort members who do not have last_active_ago value at the end of the list", - function() { - // single undefined entry - var output = orderMembersList({ - "@a:example.com": { - user: { - event: { - content: { - last_active_ago: 1000 - } - }, - last_updated: 1415266943964 - } - }, - "@b:example.com": { - user: { - event: { - content: { - last_active_ago: 100000 - } - }, - last_updated: 1415266943964 - } - }, - "@c:example.com": { - user: { - last_updated: 1415266943964 - } - } - }); - expect(output).toEqual([ - { - id: "@a:example.com", - user: { - event: { - content: { - last_active_ago: 1000 - } - }, - last_updated: 1415266943964 - } - }, - { - id: "@b:example.com", - user: { - event: { - content: { - last_active_ago: 100000 - } - }, - last_updated: 1415266943964 - } - }, - { - id: "@c:example.com", - user: { - last_updated: 1415266943964 - } - }, - ]); - }); - - it("should sort multiple members who do not have last_active_ago according to presence", - function() { - // single undefined entry - var output = orderMembersList({ - "@a:example.com": { - user: { - event: { - content: { - presence: "unavailable", - last_active_ago: undefined - } - }, - last_updated: 1415266943964 - } - }, - "@b:example.com": { - user: { - event: { - content: { - presence: "online", - last_active_ago: undefined - } - }, - last_updated: 1415266943964, - } - }, - "@c:example.com": { - user: { - event: { - content: { - presence: "offline", - last_active_ago: undefined - } - }, - last_updated: 1415266943964 - } - } - }); - expect(output).toEqual([ - { - id: "@b:example.com", - user: { - event: { - content: { - presence: "online", - last_active_ago: undefined - } - }, - last_updated: 1415266943964 - } - }, - { - id: "@a:example.com", - user: { - event: { - content: { - presence: "unavailable", - last_active_ago: undefined - } - }, - last_updated: 1415266943964 - } - }, - { - id: "@c:example.com", - user: { - event: { - content: { - presence: "offline", - last_active_ago: undefined - } - }, - last_updated: 1415266943964 - } - }, - ]); - }); -}); -describe('mUserDisplayName filter', function() { - var filter, mUserDisplayName; - - var roomId = "!weufhewifu:matrix.org"; - - // test state values (f.e. test) - var testUser_displayname, testUser_user_id; - var testSelf_displayname, testSelf_user_id; - var testRoomState; - - // mocked services which return the test values above. - var matrixService = { - config: function() { - return { - user_id: testSelf_user_id - }; - } - }; - - var modelService = { - getRoom: function(room_id) { - return { - current_room_state: testRoomState - }; - }, - - getUser: function(user_id) { - return { - event: { - content: { - displayname: testUser_displayname - }, - event_id: "wfiuhwf@matrix.org", - user_id: testUser_user_id - } - }; - }, - - getMember: function(room_id, user_id) { - return testRoomState.members[user_id]; - } - }; - - beforeEach(function() { - // inject mocked dependencies - module(function ($provide) { - $provide.value('matrixService', matrixService); - $provide.value('modelService', modelService); - }); - - module('matrixFilter'); - }); - - - beforeEach(inject(function($filter) { - filter = $filter; - mUserDisplayName = filter("mUserDisplayName"); - - // purge the previous test values - testSelf_displayname = "Me"; - testSelf_user_id = "@me:matrix.org"; - testUser_displayname = undefined; - testUser_user_id = undefined; - - // mock up a stub room state - testRoomState = { - s:{}, // internal; stores the state events - state: function(type, key) { - // accessor used by filter - return key ? this.s[type+key] : this.s[type]; - }, - members: {}, // struct used by filter - - // test helper methods - setMember: function(user_id, displayname, membership, inviter_user_id) { - if (!inviter_user_id) { - inviter_user_id = user_id; - } - if (!membership) { - membership = "join"; - } - this.s["m.room.member" + user_id] = { - event: { - content: { - displayname: displayname, - membership: membership - }, - state_key: user_id, - user_id: inviter_user_id - } - }; - this.members[user_id] = this.s["m.room.member" + user_id]; - } - }; - })); - - it("should show the display name of a user in a room if they have set one.", function() { - testUser_displayname = "Tom Scott"; - testUser_user_id = "@tymnhk:matrix.org"; - testRoomState.setMember(testUser_user_id, testUser_displayname); - testRoomState.setMember(testSelf_user_id, testSelf_displayname); - var output = mUserDisplayName(testUser_user_id, roomId); - expect(output).toEqual(testUser_displayname); - }); - - it("should show the user_id of a user in a room if they have no display name.", function() { - testUser_user_id = "@mike:matrix.org"; - testRoomState.setMember(testUser_user_id, testUser_displayname); - testRoomState.setMember(testSelf_user_id, testSelf_displayname); - var output = mUserDisplayName(testUser_user_id, roomId); - expect(output).toEqual(testUser_user_id); - }); - - it("should still show the displayname of a user in a room if they are not a member of the room but there exists a User entry for them.", function() { - testUser_user_id = "@alice:matrix.org"; - testUser_displayname = "Alice M"; - testRoomState.setMember(testSelf_user_id, testSelf_displayname); - var output = mUserDisplayName(testUser_user_id, roomId); - expect(output).toEqual(testUser_displayname); - }); - - it("should disambiguate users with the same displayname with their user id.", function() { - testUser_displayname = "Reimu"; - testSelf_displayname = "Reimu"; - testUser_user_id = "@reimu:matrix.org"; - testSelf_user_id = "@xreimux:matrix.org"; - testRoomState.setMember(testUser_user_id, testUser_displayname); - testRoomState.setMember(testSelf_user_id, testSelf_displayname); - var output = mUserDisplayName(testUser_user_id, roomId); - expect(output).toEqual(testUser_displayname + " (" + testUser_user_id + ")"); - }); - - it("should wrap user IDs after the : if the wrap flag is set.", function() { - testUser_user_id = "@mike:matrix.org"; - testRoomState.setMember(testUser_user_id, testUser_displayname); - testRoomState.setMember(testSelf_user_id, testSelf_displayname); - var output = mUserDisplayName(testUser_user_id, roomId, true); - expect(output).toEqual("@mike :matrix.org"); - }); -}); - diff --git a/syweb/webclient/test/unit/matrix-service.spec.js b/syweb/webclient/test/unit/matrix-service.spec.js deleted file mode 100644 index 4959f2395d..0000000000 --- a/syweb/webclient/test/unit/matrix-service.spec.js +++ /dev/null @@ -1,504 +0,0 @@ -describe('MatrixService', function() { - var scope, httpBackend; - var BASE = "http://example.com"; - var PREFIX = "/_matrix/client/api/v1"; - var URL = BASE + PREFIX; - var roomId = "!wejigf387t34:matrix.org"; - - var CONFIG = { - access_token: "foobar", - homeserver: BASE - }; - - beforeEach(module('matrixService')); - - beforeEach(inject(function($rootScope, $httpBackend) { - httpBackend = $httpBackend; - scope = $rootScope; - })); - - afterEach(function() { - httpBackend.verifyNoOutstandingExpectation(); - httpBackend.verifyNoOutstandingRequest(); - }); - - it('should be able to POST /createRoom with an alias', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var alias = "flibble"; - matrixService.create(alias).then(function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectPOST(URL + "/createRoom?access_token=foobar", - { - room_alias_name: alias - }) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to GET /initialSync', inject(function(matrixService) { - matrixService.setConfig(CONFIG); - var limit = 15; - matrixService.initialSync(limit).then(function(response) { - expect(response.data).toEqual([]); - }); - - httpBackend.expectGET( - URL + "/initialSync?access_token=foobar&limit=15") - .respond([]); - httpBackend.flush(); - })); - - it('should be able to GET /rooms/$roomid/state', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - matrixService.roomState(roomId).then(function(response) { - expect(response.data).toEqual([]); - }); - - httpBackend.expectGET( - URL + "/rooms/" + encodeURIComponent(roomId) + - "/state?access_token=foobar") - .respond([]); - httpBackend.flush(); - })); - - it('should be able to POST /join', inject(function(matrixService) { - matrixService.setConfig(CONFIG); - matrixService.joinAlias(roomId).then(function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectPOST( - URL + "/join/" + encodeURIComponent(roomId) + - "?access_token=foobar", - {}) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to POST /rooms/$roomid/join', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - matrixService.join(roomId).then(function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectPOST( - URL + "/rooms/" + encodeURIComponent(roomId) + - "/join?access_token=foobar", - {}) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to POST /rooms/$roomid/invite', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var inviteUserId = "@user:example.com"; - matrixService.invite(roomId, inviteUserId).then(function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectPOST( - URL + "/rooms/" + encodeURIComponent(roomId) + - "/invite?access_token=foobar", - { - user_id: inviteUserId - }) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to POST /rooms/$roomid/leave', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - matrixService.leave(roomId).then(function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectPOST( - URL + "/rooms/" + encodeURIComponent(roomId) + - "/leave?access_token=foobar", - {}) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to POST /rooms/$roomid/ban', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var userId = "@example:example.com"; - var reason = "Because."; - matrixService.ban(roomId, userId, reason).then(function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectPOST( - URL + "/rooms/" + encodeURIComponent(roomId) + - "/ban?access_token=foobar", - { - user_id: userId, - reason: reason - }) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to GET /directory/room/$alias', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var alias = "#test:example.com"; - var roomId = "!wefuhewfuiw:example.com"; - matrixService.resolveRoomAlias(alias).then(function(response) { - expect(response.data).toEqual({ - room_id: roomId - }); - }); - - httpBackend.expectGET( - URL + "/directory/room/" + encodeURIComponent(alias) + - "?access_token=foobar") - .respond({ - room_id: roomId - }); - httpBackend.flush(); - })); - - it('should be able to send m.room.name', inject(function(matrixService) { - matrixService.setConfig(CONFIG); - var roomId = "!fh38hfwfwef:example.com"; - var name = "Room Name"; - matrixService.setName(roomId, name).then(function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectPUT( - URL + "/rooms/" + encodeURIComponent(roomId) + - "/state/m.room.name?access_token=foobar", - { - name: name - }) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to send m.room.topic', inject(function(matrixService) { - matrixService.setConfig(CONFIG); - var roomId = "!fh38hfwfwef:example.com"; - var topic = "A room topic can go here."; - matrixService.setTopic(roomId, topic).then(function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectPUT( - URL + "/rooms/" + encodeURIComponent(roomId) + - "/state/m.room.topic?access_token=foobar", - { - topic: topic - }) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to send generic state events without a state key', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var roomId = "!fh38hfwfwef:example.com"; - var eventType = "com.example.events.test"; - var content = { - testing: "1 2 3" - }; - matrixService.sendStateEvent(roomId, eventType, content).then( - function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectPUT( - URL + "/rooms/" + encodeURIComponent(roomId) + "/state/" + - encodeURIComponent(eventType) + "?access_token=foobar", - content) - .respond({}); - httpBackend.flush(); - })); - - // TODO: Skipped since the webclient is purposefully broken so as not to - // 500 matrix.org - xit('should be able to send generic state events with a state key', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var roomId = "!fh38hfwfwef:example.com"; - var eventType = "com.example.events.test:special@characters"; - var content = { - testing: "1 2 3" - }; - var stateKey = "version:1"; - matrixService.sendStateEvent(roomId, eventType, content, stateKey).then( - function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectPUT( - URL + "/rooms/" + encodeURIComponent(roomId) + "/state/" + - encodeURIComponent(eventType) + "/" + encodeURIComponent(stateKey)+ - "?access_token=foobar", - content) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to PUT generic events ', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var roomId = "!fh38hfwfwef:example.com"; - var eventType = "com.example.events.test"; - var txnId = "42"; - var content = { - testing: "1 2 3" - }; - matrixService.sendEvent(roomId, eventType, txnId, content).then( - function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectPUT( - URL + "/rooms/" + encodeURIComponent(roomId) + "/send/" + - encodeURIComponent(eventType) + "/" + encodeURIComponent(txnId)+ - "?access_token=foobar", - content) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to PUT text messages ', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var roomId = "!fh38hfwfwef:example.com"; - var body = "ABC 123"; - matrixService.sendTextMessage(roomId, body).then( - function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectPUT( - new RegExp(URL + "/rooms/" + encodeURIComponent(roomId) + - "/send/m.room.message/(.*)" + - "?access_token=foobar"), - { - body: body, - msgtype: "m.text" - }) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to PUT emote messages ', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var roomId = "!fh38hfwfwef:example.com"; - var body = "ABC 123"; - matrixService.sendEmoteMessage(roomId, body).then( - function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectPUT( - new RegExp(URL + "/rooms/" + encodeURIComponent(roomId) + - "/send/m.room.message/(.*)" + - "?access_token=foobar"), - { - body: body, - msgtype: "m.emote" - }) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to POST redactions', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var roomId = "!fh38hfwfwef:example.com"; - var eventId = "fwefwexample.com"; - matrixService.redactEvent(roomId, eventId).then( - function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectPOST(URL + "/rooms/" + encodeURIComponent(roomId) + - "/redact/" + encodeURIComponent(eventId) + - "?access_token=foobar") - .respond({}); - httpBackend.flush(); - })); - - it('should be able to GET /directory/room/$alias', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var alias = "#test:example.com"; - var roomId = "!wefuhewfuiw:example.com"; - matrixService.resolveRoomAlias(alias).then(function(response) { - expect(response.data).toEqual({ - room_id: roomId - }); - }); - - httpBackend.expectGET( - URL + "/directory/room/" + encodeURIComponent(alias) + - "?access_token=foobar") - .respond({ - room_id: roomId - }); - httpBackend.flush(); - })); - - it('should be able to GET /rooms/$roomid/members', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var roomId = "!wefuhewfuiw:example.com"; - matrixService.getMemberList(roomId).then(function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectGET( - URL + "/rooms/" + encodeURIComponent(roomId) + - "/members?access_token=foobar") - .respond({}); - httpBackend.flush(); - })); - - it('should be able to paginate a room', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var roomId = "!wefuhewfuiw:example.com"; - var from = "3t_44e_54z"; - var limit = 20; - matrixService.paginateBackMessages(roomId, from, limit).then(function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectGET( - URL + "/rooms/" + encodeURIComponent(roomId) + - "/messages?access_token=foobar&dir=b&from="+ - encodeURIComponent(from)+"&limit="+limit) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to GET /publicRooms', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - matrixService.publicRooms().then(function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectGET( - new RegExp(URL + "/publicRooms(.*)")) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to GET /profile/$userid/displayname', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var userId = "@foo:example.com"; - matrixService.getDisplayName(userId).then(function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectGET(URL + "/profile/" + encodeURIComponent(userId) + - "/displayname?access_token=foobar") - .respond({}); - httpBackend.flush(); - })); - - it('should be able to GET /profile/$userid/avatar_url', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var userId = "@foo:example.com"; - matrixService.getProfilePictureUrl(userId).then(function(response) { - expect(response.data).toEqual({}); - }); - - httpBackend.expectGET(URL + "/profile/" + encodeURIComponent(userId) + - "/avatar_url?access_token=foobar") - .respond({}); - httpBackend.flush(); - })); - - it('should be able to PUT /profile/$me/avatar_url', inject( - function(matrixService) { - var testConfig = angular.copy(CONFIG); - testConfig.user_id = "@bob:example.com"; - matrixService.setConfig(testConfig); - var url = "http://example.com/mypic.jpg"; - matrixService.setProfilePictureUrl(url).then(function(response) { - expect(response.data).toEqual({}); - }); - httpBackend.expectPUT(URL + "/profile/" + - encodeURIComponent(testConfig.user_id) + - "/avatar_url?access_token=foobar", - { - avatar_url: url - }) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to PUT /profile/$me/displayname', inject( - function(matrixService) { - var testConfig = angular.copy(CONFIG); - testConfig.user_id = "@bob:example.com"; - matrixService.setConfig(testConfig); - var displayname = "Bob Smith"; - matrixService.setDisplayName(displayname).then(function(response) { - expect(response.data).toEqual({}); - }); - httpBackend.expectPUT(URL + "/profile/" + - encodeURIComponent(testConfig.user_id) + - "/displayname?access_token=foobar", - { - displayname: displayname - }) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to login with password', inject( - function(matrixService) { - matrixService.setConfig(CONFIG); - var userId = "@bob:example.com"; - var password = "monkey"; - matrixService.login(userId, password).then(function(response) { - expect(response.data).toEqual({}); - }); - httpBackend.expectPOST(new RegExp(URL+"/login(.*)"), - { - user: userId, - password: password, - type: "m.login.password" - }) - .respond({}); - httpBackend.flush(); - })); - - it('should be able to PUT presence status', inject( - function(matrixService) { - var testConfig = angular.copy(CONFIG); - testConfig.user_id = "@bob:example.com"; - matrixService.setConfig(testConfig); - var status = "unavailable"; - matrixService.setUserPresence(status).then(function(response) { - expect(response.data).toEqual({}); - }); - httpBackend.expectPUT(URL+"/presence/"+ - encodeURIComponent(testConfig.user_id)+ - "/status?access_token=foobar", - { - presence: status - }) - .respond({}); - httpBackend.flush(); - })); -}); diff --git a/syweb/webclient/test/unit/model-service.spec.js b/syweb/webclient/test/unit/model-service.spec.js deleted file mode 100644 index d92930e2e0..0000000000 --- a/syweb/webclient/test/unit/model-service.spec.js +++ /dev/null @@ -1,230 +0,0 @@ -describe('ModelService', function() { - - // setup the dependencies - beforeEach(function() { - // dependencies - module('matrixService'); - - // tested service - module('modelService'); - }); - - it('should be able to get a member in a room', inject( - function(modelService) { - var roomId = "!wefiohwefuiow:matrix.org"; - var userId = "@bob:matrix.org"; - - modelService.getRoom(roomId).current_room_state.storeStateEvent({ - type: "m.room.member", - id: "fwefw:matrix.org", - user_id: userId, - state_key: userId, - content: { - membership: "join" - } - }); - - var user = modelService.getMember(roomId, userId); - expect(user.event.state_key).toEqual(userId); - })); - - it('should be able to get a users power level', inject( - function(modelService) { - var roomId = "!foo:matrix.org"; - - var room = modelService.getRoom(roomId); - room.current_room_state.storeStateEvent({ - content: { membership: "join" }, - user_id: "@adam:matrix.org", - state_key: "@adam:matrix.org", - type: "m.room.member" - }); - room.current_room_state.storeStateEvent({ - content: { membership: "join" }, - user_id: "@beth:matrix.org", - state_key: "@beth:matrix.org", - type: "m.room.member" - }); - room.current_room_state.storeStateEvent({ - content: { - "@adam:matrix.org": 90, - "default": 50 - }, - user_id: "@adam:matrix.org", - type: "m.room.power_levels" - }); - - var num = modelService.getUserPowerLevel(roomId, "@beth:matrix.org"); - expect(num).toEqual(50); - - num = modelService.getUserPowerLevel(roomId, "@adam:matrix.org"); - expect(num).toEqual(90); - - num = modelService.getUserPowerLevel(roomId, "@unknown:matrix.org"); - expect(num).toEqual(50); - })); - - it('should be able to get a user', inject( - function(modelService) { - var roomId = "!wefiohwefuiow:matrix.org"; - var userId = "@bob:matrix.org"; - - var presenceEvent = { - content: { - user_id: userId, - displayname: "Bob", - last_active_ago: 1415981891580 - }, - type: "m.presence", - event_id: "weofhwe@matrix.org" - }; - - modelService.setUser(presenceEvent); - var user = modelService.getUser(userId); - expect(user.event).toEqual(presenceEvent); - })); - - it('should be able to create and get alias mappings.', inject( - function(modelService) { - var roomId = "!wefiohwefuiow:matrix.org"; - var alias = "#foobar:matrix.org"; - - modelService.createRoomIdToAliasMapping(roomId, alias); - - expect(modelService.getRoomIdToAliasMapping(roomId)).toEqual(alias); - expect(modelService.getAliasToRoomIdMapping(alias)).toEqual(roomId); - - })); - - it('should clobber alias mappings.', inject( - function(modelService) { - var roomId = "!wefiohwefuiow:matrix.org"; - var alias = "#foobar:matrix.org"; - var newAlias = "#foobarNEW:matrix.org"; - - modelService.createRoomIdToAliasMapping(roomId, alias); - - expect(modelService.getRoomIdToAliasMapping(roomId)).toEqual(alias); - expect(modelService.getAliasToRoomIdMapping(alias)).toEqual(roomId); - - modelService.createRoomIdToAliasMapping(roomId, newAlias); - - expect(modelService.getRoomIdToAliasMapping(roomId)).toEqual(newAlias); - expect(modelService.getAliasToRoomIdMapping(newAlias)).toEqual(roomId); - - })); - - it('should update RoomMember when User is updated to point to the latest info.', inject( - function(modelService) { - var roomId = "!wefiohwefuiow:matrix.org"; - var userId = "@bob:matrix.org"; - - var presenceEvent = { - content: { - user_id: userId, - displayname: "Bob", - last_active_ago: 1415 - }, - type: "m.presence", - event_id: "weofhwe@matrix.org" - }; - - var newPresenceEvent = { - content: { - user_id: userId, - displayname: "The only and only Bob", - last_active_ago: 1900 - }, - type: "m.presence", - event_id: "weofhtweterte@matrix.org" - }; - - modelService.setUser(presenceEvent); - - modelService.getRoom(roomId).current_room_state.storeStateEvent({ - type: "m.room.member", - id: "fwefw:matrix.org", - user_id: userId, - state_key: userId, - content: { - membership: "join" - } - }); - - var roomMember = modelService.getMember(roomId, userId); - expect(roomMember.user.event).toEqual(presenceEvent); - expect(roomMember.user.event.content.displayname).toEqual("Bob"); - - modelService.setUser(newPresenceEvent); - - expect(roomMember.user.event.content.displayname).toEqual("The only and only Bob"); - - })); - - it('should normalise power levels between 0-100.', inject( - function(modelService) { - var roomId = "!foo:matrix.org"; - - var room = modelService.getRoom(roomId); - room.current_room_state.storeStateEvent({ - content: { membership: "join" }, - user_id: "@adam:matrix.org", - state_key: "@adam:matrix.org", - type: "m.room.member" - }); - room.current_room_state.storeStateEvent({ - content: { membership: "join" }, - user_id: "@beth:matrix.org", - state_key: "@beth:matrix.org", - type: "m.room.member" - }); - room.current_room_state.storeStateEvent({ - content: { - "@adam:matrix.org": 1000, - "default": 500 - }, - user_id: "@adam:matrix.org", - type: "m.room.power_levels" - }); - - var roomMember = modelService.getMember(roomId, "@beth:matrix.org"); - expect(roomMember.power_level).toEqual(500); - expect(roomMember.power_level_norm).toEqual(50); - - - })); - - it('should be able to get the number of joined users in a room', inject( - function(modelService) { - var roomId = "!foo:matrix.org"; - // set mocked data - var room = modelService.getRoom(roomId); - room.current_room_state.storeStateEvent({ - content: { membership: "join" }, - user_id: "@adam:matrix.org", - state_key: "@adam:matrix.org", - type: "m.room.member" - }); - room.current_room_state.storeStateEvent({ - content: { membership: "invite" }, - user_id: "@adam:matrix.org", - state_key: "@beth:matrix.org", - type: "m.room.member" - }); - room.current_room_state.storeStateEvent({ - content: { membership: "join" }, - user_id: "@charlie:matrix.org", - state_key: "@charlie:matrix.org", - type: "m.room.member" - }); - room.current_room_state.storeStateEvent({ - content: { membership: "leave" }, - user_id: "@danice:matrix.org", - state_key: "@danice:matrix.org", - type: "m.room.member" - }); - - var num = modelService.getUserCountInRoom(roomId); - expect(num).toEqual(2); - })); -}); diff --git a/syweb/webclient/test/unit/notification-service.spec.js b/syweb/webclient/test/unit/notification-service.spec.js deleted file mode 100644 index 4205ca0969..0000000000 --- a/syweb/webclient/test/unit/notification-service.spec.js +++ /dev/null @@ -1,78 +0,0 @@ -describe('NotificationService', function() { - - var userId = "@ali:matrix.org"; - var displayName = "Alice M"; - var bingWords = ["coffee","foo(.*)bar"]; // literal and wildcard - - beforeEach(function() { - module('notificationService'); - }); - - // User IDs - - it('should bing on a user ID.', inject( - function(notificationService) { - expect(notificationService.containsBingWord(userId, displayName, - bingWords, "Hello @ali:matrix.org, how are you?")).toEqual(true); - })); - - it('should bing on a partial user ID.', inject( - function(notificationService) { - expect(notificationService.containsBingWord(userId, displayName, - bingWords, "Hello @ali, how are you?")).toEqual(true); - })); - - it('should bing on a case-insensitive user ID.', inject( - function(notificationService) { - expect(notificationService.containsBingWord(userId, displayName, - bingWords, "Hello @AlI:matrix.org, how are you?")).toEqual(true); - })); - - // Display names - - it('should bing on a display name.', inject( - function(notificationService) { - expect(notificationService.containsBingWord(userId, displayName, - bingWords, "Hello Alice M, how are you?")).toEqual(true); - })); - - it('should bing on a case-insensitive display name.', inject( - function(notificationService) { - expect(notificationService.containsBingWord(userId, displayName, - bingWords, "Hello ALICE M, how are you?")).toEqual(true); - })); - - // Bing words - - it('should bing on a bing word.', inject( - function(notificationService) { - expect(notificationService.containsBingWord(userId, displayName, - bingWords, "I really like coffee")).toEqual(true); - })); - - it('should bing on case-insensitive bing words.', inject( - function(notificationService) { - expect(notificationService.containsBingWord(userId, displayName, - bingWords, "Coffee is great")).toEqual(true); - })); - - it('should bing on wildcard (.*) bing words.', inject( - function(notificationService) { - expect(notificationService.containsBingWord(userId, displayName, - bingWords, "It was foomahbar I think.")).toEqual(true); - })); - - // invalid - - it('should gracefully handle bad input.', inject( - function(notificationService) { - expect(notificationService.containsBingWord(userId, displayName, - bingWords, { "foo": "bar" })).toEqual(false); - })); - - it('should gracefully handle just a user ID.', inject( - function(notificationService) { - expect(notificationService.containsBingWord(userId, undefined, - undefined, "Hello @ali:matrix.org, how are you?")).toEqual(true); - })); -}); diff --git a/syweb/webclient/test/unit/recents-service.spec.js b/syweb/webclient/test/unit/recents-service.spec.js deleted file mode 100644 index a2f9ecbaf8..0000000000 --- a/syweb/webclient/test/unit/recents-service.spec.js +++ /dev/null @@ -1,153 +0,0 @@ -describe('RecentsService', function() { - var scope; - var MSG_EVENT = "__test__"; - - var testEventContainsBingWord, testIsLive, testEvent; - - var eventHandlerService = { - MSG_EVENT: MSG_EVENT, - eventContainsBingWord: function(event) { - return testEventContainsBingWord; - } - }; - - // setup the service and mocked dependencies - beforeEach(function() { - - // set default mock values - testEventContainsBingWord = false; - testIsLive = true; - testEvent = { - content: { - body: "Hello world", - msgtype: "m.text" - }, - user_id: "@alfred:localhost", - room_id: "!fl1bb13:localhost", - event_id: "fwuegfw@localhost" - } - - // mocked dependencies - module(function ($provide) { - $provide.value('eventHandlerService', eventHandlerService); - }); - - // tested service - module('recentsService'); - }); - - beforeEach(inject(function($rootScope) { - scope = $rootScope; - })); - - it('should start with no unread messages.', inject( - function(recentsService) { - expect(recentsService.getUnreadMessages()).toEqual({}); - expect(recentsService.getUnreadBingMessages()).toEqual({}); - })); - - it('should NOT add an unread message to the room currently selected.', inject( - function(recentsService) { - recentsService.setSelectedRoomId(testEvent.room_id); - scope.$broadcast(MSG_EVENT, testEvent, testIsLive); - expect(recentsService.getUnreadMessages()).toEqual({}); - expect(recentsService.getUnreadBingMessages()).toEqual({}); - })); - - it('should add an unread message to the room NOT currently selected.', inject( - function(recentsService) { - recentsService.setSelectedRoomId("!someotherroomid:localhost"); - scope.$broadcast(MSG_EVENT, testEvent, testIsLive); - - var unread = {}; - unread[testEvent.room_id] = 1; - expect(recentsService.getUnreadMessages()).toEqual(unread); - })); - - it('should add an unread message and an unread bing message if a message contains a bing word.', inject( - function(recentsService) { - recentsService.setSelectedRoomId("!someotherroomid:localhost"); - testEventContainsBingWord = true; - scope.$broadcast(MSG_EVENT, testEvent, testIsLive); - - var unread = {}; - unread[testEvent.room_id] = 1; - expect(recentsService.getUnreadMessages()).toEqual(unread); - - var bing = {}; - bing[testEvent.room_id] = testEvent; - expect(recentsService.getUnreadBingMessages()).toEqual(bing); - })); - - it('should clear both unread and unread bing messages when markAsRead is called.', inject( - function(recentsService) { - recentsService.setSelectedRoomId("!someotherroomid:localhost"); - testEventContainsBingWord = true; - scope.$broadcast(MSG_EVENT, testEvent, testIsLive); - - var unread = {}; - unread[testEvent.room_id] = 1; - expect(recentsService.getUnreadMessages()).toEqual(unread); - - var bing = {}; - bing[testEvent.room_id] = testEvent; - expect(recentsService.getUnreadBingMessages()).toEqual(bing); - - recentsService.markAsRead(testEvent.room_id); - - unread[testEvent.room_id] = 0; - bing[testEvent.room_id] = undefined; - expect(recentsService.getUnreadMessages()).toEqual(unread); - expect(recentsService.getUnreadBingMessages()).toEqual(bing); - })); - - it('should not add messages as unread if they are not live.', inject( - function(recentsService) { - testIsLive = false; - - recentsService.setSelectedRoomId("!someotherroomid:localhost"); - testEventContainsBingWord = true; - scope.$broadcast(MSG_EVENT, testEvent, testIsLive); - - expect(recentsService.getUnreadMessages()).toEqual({}); - expect(recentsService.getUnreadBingMessages()).toEqual({}); - })); - - it('should increment the unread message count.', inject( - function(recentsService) { - recentsService.setSelectedRoomId("!someotherroomid:localhost"); - scope.$broadcast(MSG_EVENT, testEvent, testIsLive); - - var unread = {}; - unread[testEvent.room_id] = 1; - expect(recentsService.getUnreadMessages()).toEqual(unread); - - scope.$broadcast(MSG_EVENT, testEvent, testIsLive); - - unread[testEvent.room_id] = 2; - expect(recentsService.getUnreadMessages()).toEqual(unread); - })); - - it('should set the bing event to the latest message to contain a bing word.', inject( - function(recentsService) { - recentsService.setSelectedRoomId("!someotherroomid:localhost"); - testEventContainsBingWord = true; - scope.$broadcast(MSG_EVENT, testEvent, testIsLive); - - var nextEvent = angular.copy(testEvent); - nextEvent.content.body = "Goodbye cruel world."; - nextEvent.event_id = "erfuerhfeaaaa@localhost"; - scope.$broadcast(MSG_EVENT, nextEvent, testIsLive); - - var bing = {}; - bing[testEvent.room_id] = nextEvent; - expect(recentsService.getUnreadBingMessages()).toEqual(bing); - })); - - it('should do nothing when marking an unknown room ID as read.', inject( - function(recentsService) { - recentsService.markAsRead("!someotherroomid:localhost"); - expect(recentsService.getUnreadMessages()).toEqual({}); - expect(recentsService.getUnreadBingMessages()).toEqual({}); - })); -}); diff --git a/syweb/webclient/test/unit/register-controller.spec.js b/syweb/webclient/test/unit/register-controller.spec.js deleted file mode 100644 index 62d7a4aa32..0000000000 --- a/syweb/webclient/test/unit/register-controller.spec.js +++ /dev/null @@ -1,84 +0,0 @@ -describe("RegisterController ", function() { - var rootScope, scope, ctrl, $q, $timeout; - var userId = "@foo:bar"; - var displayName = "Foo"; - var avatarUrl = "avatar.url"; - - window.webClientConfig = { - useCaptcha: false - }; - - // test vars - var testRegisterData, testFailRegisterData; - - - // mock services - var matrixService = { - config: function() { - return { - user_id: userId - } - }, - setConfig: function(){}, - register: function(mxid, password, threepidCreds, useCaptcha) { - var d = $q.defer(); - if (testFailRegisterData) { - d.reject({ - data: testFailRegisterData - }); - } - else { - d.resolve({ - data: testRegisterData - }); - } - return d.promise; - } - }; - - var eventStreamService = {}; - - beforeEach(function() { - module('matrixWebClient'); - - // reset test vars - testRegisterData = undefined; - testFailRegisterData = undefined; - }); - - beforeEach(inject(function($rootScope, $injector, $location, $controller, _$q_, _$timeout_) { - $q = _$q_; - $timeout = _$timeout_; - scope = $rootScope.$new(); - rootScope = $rootScope; - routeParams = { - user_matrix_id: userId - }; - ctrl = $controller('RegisterController', { - '$scope': scope, - '$rootScope': $rootScope, - '$location': $location, - 'matrixService': matrixService, - 'eventStreamService': eventStreamService - }); - }) - ); - - // SYWEB-109 - it('should display an error if the HS rejects the username on registration', function() { - var prevFeedback = angular.copy(scope.feedback); - - testFailRegisterData = { - errcode: "M_UNKNOWN", - error: "I am rejecting you." - }; - - scope.account.pwd1 = "password"; - scope.account.pwd2 = "password"; - scope.account.desired_user_id = "bob"; - scope.register(); // this depends on the result of a deferred - rootScope.$digest(); // which is delivered after the digest - - expect(scope.feedback).not.toEqual(prevFeedback); - }); -}); diff --git a/syweb/webclient/test/unit/user-controller.spec.js b/syweb/webclient/test/unit/user-controller.spec.js deleted file mode 100644 index 798cc4de48..0000000000 --- a/syweb/webclient/test/unit/user-controller.spec.js +++ /dev/null @@ -1,57 +0,0 @@ -describe("UserCtrl", function() { - var scope, ctrl, matrixService, routeParams, $q, $timeout; - var userId = "@foo:bar"; - var displayName = "Foo"; - var avatarUrl = "avatar.url"; - - beforeEach(module('matrixWebClient')); - - beforeEach(function() { - - inject(function($rootScope, $injector, $controller, _$q_, _$timeout_) { - $q = _$q_; - $timeout = _$timeout_; - - matrixService = { - config: function() { - return { - user_id: userId - }; - }, - - getDisplayName: function(uid) { - var d = $q.defer(); - d.resolve({ - data: { - displayname: displayName - } - }); - return d.promise; - }, - - getProfilePictureUrl: function(uid) { - var d = $q.defer(); - d.resolve({ - data: { - avatar_url: avatarUrl - } - }); - return d.promise; - } - }; - scope = $rootScope.$new(); - routeParams = { - user_matrix_id: userId - }; - ctrl = $controller('UserController', { - '$scope': scope, - '$routeParams': routeParams, - 'matrixService': matrixService - }); - }); - }); - - it('should display your user id', function() { - expect(scope.user_id).toEqual(userId); - }); -}); diff --git a/syweb/webclient/user/user-controller.js b/syweb/webclient/user/user-controller.js deleted file mode 100644 index 0dbfa325d0..0000000000 --- a/syweb/webclient/user/user-controller.js +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -angular.module('UserController', ['matrixService']) -.controller('UserController', ['$scope', '$routeParams', 'matrixService', - function($scope, $routeParams, matrixService) { - $scope.user = { - id: $routeParams.user_matrix_id, - displayname: "", - avatar_url: undefined - }; - - $scope.user_id = matrixService.config().user_id; - - matrixService.getDisplayName($scope.user.id).then( - function(response) { - $scope.user.displayname = response.data.displayname; - } - ); - - matrixService.getProfilePictureUrl($scope.user.id).then( - function(response) { - $scope.user.avatar_url = response.data.avatar_url; - } - ); - - // FIXME: factor this out between user-controller and home-controller etc. - $scope.messageUser = function() { - - // FIXME: create a new room every time, for now - - matrixService.create(null, 'private').then( - function(response) { - // This room has been created. Refresh the rooms list - var room_id = response.data.room_id; - console.log("Created room with id: "+ room_id); - - matrixService.invite(room_id, $scope.user.id).then( - function() { - $scope.feedback = "Invite sent successfully"; - $scope.$parent.goToPage("/room/" + room_id); - }, - function(reason) { - $scope.feedback = "Failure: " + JSON.stringify(reason); - }); - }, - function(error) { - $scope.feedback = "Failure: " + JSON.stringify(error.data); - }); - }; - -}]); \ No newline at end of file diff --git a/syweb/webclient/user/user.html b/syweb/webclient/user/user.html deleted file mode 100644 index 2aa981437b..0000000000 --- a/syweb/webclient/user/user.html +++ /dev/null @@ -1,25 +0,0 @@ -<div ng-controller="UserController" class="user"> - - <div id="wrapper"> - - <div id="genericHeading"> - <a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a> - </div> - - <h1>{{ user.displayname || user.id }}</h1> - - <div> - <div class="profile-avatar"> - <img ng-src="{{ user.avatar_url || 'img/default-profile.png' }}"/> - </div> - <div id="user-ids"> - <div>{{ user.id }}</div> - </div> - </div> - - <button ng-hide="user.id == user_id" ng-click="messageUser()" style="font-size: 14pt; margin-top: 40px; margin-bottom: 40px">Start chat</button> - <br/> - {{ feedback }} - - </div> -</div> |