From c60de71cc590ad6a668689763ff447f0bb5ace89 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 31 May 2018 21:55:54 -0400 Subject: [PATCH] Update 2018-06-01T01:55:51.052Z --- index.html | 14 +- js/app.min.js | 1282 +-- js/fallback-styles.min.js | 2 +- js/lib.min.js | 20998 ++++++++++++++++++------------------ u2f-connector.html | 2 +- version.json | 2 +- 6 files changed, 11157 insertions(+), 11143 deletions(-) diff --git a/index.html b/index.html index c85289fc..f10452ab 100644 --- a/index.html +++ b/index.html @@ -15,9 +15,9 @@ - + - + @@ -35,11 +35,11 @@ integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"> - - + + - - - + + + diff --git a/js/app.min.js b/js/app.min.js index 1dbed523..093ba3c4 100644 --- a/js/app.min.js +++ b/js/app.min.js @@ -25,20 +25,23 @@ angular ]); angular.module("bit") -.constant("appSettings", {"apiUri":"/api","identityUri":"/identity","iconsUri":"https://icons.bitwarden.com","stripeKey":"pk_live_bpN0P37nMxrMQkcaHXtAybJk","braintreeKey":"production_qfbsv8kc_njj2zjtyngtjmbjd","selfHosted":false,"version":"1.26.0","environment":"Production"}); +.constant("appSettings", {"apiUri":"/api","identityUri":"/identity","iconsUri":"https://icons.bitwarden.com","stripeKey":"pk_live_bpN0P37nMxrMQkcaHXtAybJk","braintreeKey":"production_qfbsv8kc_njj2zjtyngtjmbjd","selfHosted":false,"version":"1.27.0","environment":"Production"}); angular .module('bit.accounts', ['ui.bootstrap', 'ngCookies']); -angular - .module('bit.directives', []); - angular .module('bit.filters', []); +angular + .module('bit.directives', []); + angular .module('bit.global', []); +angular + .module('bit.reports', ['toastr', 'ngSanitize']); + angular .module('bit.organization', ['ui.bootstrap']); @@ -46,7 +49,7 @@ angular .module('bit.services', ['ngResource', 'ngStorage', 'angular-jwt']); angular - .module('bit.reports', ['toastr', 'ngSanitize']); + .module('bit.tools', ['ui.bootstrap', 'toastr']); angular .module('bit.settings', ['ui.bootstrap', 'toastr']); @@ -54,9 +57,6 @@ angular angular .module('bit.vault', ['ui.bootstrap', 'ngclipboard']); -angular - .module('bit.tools', ['ui.bootstrap', 'toastr']); - angular .module('bit') @@ -1090,6 +1090,13 @@ angular }; $scope.readOnlyEmail = stateParams.email !== null; + var registerOrgUserId = null; + var registerToken = null; + if(stateParams.returnState && stateParams.returnState.params && + stateParams.returnState.name === 'frontend.organizationAccept') { + registerOrgUserId = stateParams.returnState.params.organizationUserId || null; + registerToken = stateParams.returnState.params.token || null; + } $timeout(function () { if ($scope.model.email) { @@ -1134,7 +1141,9 @@ angular keys: { publicKey: result.publicKey, encryptedPrivateKey: result.privateKeyEnc - } + }, + token: registerToken, + organizationUserId: registerOrgUserId }; return apiService.accounts.register(request).$promise; @@ -1262,6 +1271,86 @@ angular }; }]); +angular + .module('bit.filters') + + .filter('enumLabelClass', function () { + return function (input, name) { + if (typeof input !== 'number') { + return input.toString(); + } + + var output; + switch (name) { + case 'OrgUserStatus': + switch (input) { + case 0: + output = 'label-default'; + break; + case 1: + output = 'label-warning'; + break; + case 2: + /* falls through */ + default: + output = 'label-success'; + } + break; + default: + output = 'label-default'; + } + + return output; + }; + }); + +angular + .module('bit.filters') + + .filter('enumName', function () { + return function (input, name) { + if (typeof input !== 'number') { + return input.toString(); + } + + var output; + switch (name) { + case 'OrgUserStatus': + switch (input) { + case 0: + output = 'Invited'; + break; + case 1: + output = 'Accepted'; + break; + case 2: + /* falls through */ + default: + output = 'Confirmed'; + } + break; + case 'OrgUserType': + switch (input) { + case 0: + output = 'Owner'; + break; + case 1: + output = 'Admin'; + break; + case 2: + /* falls through */ + default: + output = 'User'; + } + break; + default: + output = input.toString(); + } + + return output; + }; + }); + angular .module('bit.directives') @@ -1880,86 +1969,6 @@ angular }; }]); -angular - .module('bit.filters') - - .filter('enumLabelClass', function () { - return function (input, name) { - if (typeof input !== 'number') { - return input.toString(); - } - - var output; - switch (name) { - case 'OrgUserStatus': - switch (input) { - case 0: - output = 'label-default'; - break; - case 1: - output = 'label-warning'; - break; - case 2: - /* falls through */ - default: - output = 'label-success'; - } - break; - default: - output = 'label-default'; - } - - return output; - }; - }); - -angular - .module('bit.filters') - - .filter('enumName', function () { - return function (input, name) { - if (typeof input !== 'number') { - return input.toString(); - } - - var output; - switch (name) { - case 'OrgUserStatus': - switch (input) { - case 0: - output = 'Invited'; - break; - case 1: - output = 'Accepted'; - break; - case 2: - /* falls through */ - default: - output = 'Confirmed'; - } - break; - case 'OrgUserType': - switch (input) { - case 0: - output = 'Owner'; - break; - case 1: - output = 'Admin'; - break; - case 2: - /* falls through */ - default: - output = 'User'; - } - break; - default: - output = input.toString(); - } - - return output; - }; - }); - angular .module('bit.global') @@ -2266,6 +2275,45 @@ angular }; }]); +angular + .module('bit.tools') + + .controller('reportsBreachController', ["$scope", "apiService", "toastr", "authService", function ($scope, apiService, toastr, authService) { + $scope.loading = true; + $scope.error = false; + $scope.breachAccounts = []; + $scope.email = null; + + $scope.$on('$viewContentLoaded', function () { + authService.getUserProfile().then(function (userProfile) { + $scope.email = userProfile.email; + return apiService.hibp.get({ email: $scope.email }).$promise; + }).then(function (response) { + var breachAccounts = []; + for (var i = 0; i < response.length; i++) { + var breach = { + id: response[i].Name, + title: response[i].Title, + domain: response[i].Domain, + date: new Date(response[i].BreachDate), + reportedDate: new Date(response[i].AddedDate), + modifiedDate: new Date(response[i].ModifiedDate), + count: response[i].PwnCount, + description: response[i].Description, + classes: response[i].DataClasses, + image: 'https://haveibeenpwned.com/Content/Images/PwnedLogos/' + response[i].Name + '.' + response[i].LogoType + }; + breachAccounts.push(breach); + } + $scope.breachAccounts = breachAccounts; + $scope.loading = false; + }, function (response) { + $scope.error = response.status !== 404; + $scope.loading = false; + }); + }); + }]); + angular .module('bit.organization') @@ -11337,40 +11385,513 @@ angular angular .module('bit.tools') - .controller('reportsBreachController', ["$scope", "apiService", "toastr", "authService", function ($scope, apiService, toastr, authService) { - $scope.loading = true; - $scope.error = false; - $scope.breachAccounts = []; - $scope.email = null; - - $scope.$on('$viewContentLoaded', function () { - authService.getUserProfile().then(function (userProfile) { - $scope.email = userProfile.email; - return apiService.hibp.get({ email: $scope.email }).$promise; - }).then(function (response) { - var breachAccounts = []; - for (var i = 0; i < response.length; i++) { - var breach = { - id: response[i].Name, - title: response[i].Title, - domain: response[i].Domain, - date: new Date(response[i].BreachDate), - reportedDate: new Date(response[i].AddedDate), - modifiedDate: new Date(response[i].ModifiedDate), - count: response[i].PwnCount, - description: response[i].Description, - classes: response[i].DataClasses, - image: 'https://haveibeenpwned.com/Content/Images/PwnedLogos/' + response[i].Name + '.' + response[i].LogoType - }; - breachAccounts.push(breach); - } - $scope.breachAccounts = breachAccounts; - $scope.loading = false; - }, function (response) { - $scope.error = response.status !== 404; - $scope.loading = false; + .controller('toolsController', ["$scope", "$uibModal", "apiService", "toastr", "authService", function ($scope, $uibModal, apiService, toastr, authService) { + $scope.import = function () { + $uibModal.open({ + animation: true, + templateUrl: 'app/tools/views/toolsImport.html', + controller: 'toolsImportController' }); - }); + }; + + $scope.export = function () { + $uibModal.open({ + animation: true, + templateUrl: 'app/tools/views/toolsExport.html', + controller: 'toolsExportController' + }); + }; + }]); + +angular + .module('bit.tools') + + .controller('toolsExportController', ["$scope", "apiService", "$uibModalInstance", "cipherService", "$q", "toastr", "$analytics", "constants", function ($scope, apiService, $uibModalInstance, cipherService, $q, + toastr, $analytics, constants) { + $analytics.eventTrack('toolsExportController', { category: 'Modal' }); + $scope.export = function (model) { + $scope.startedExport = true; + var decCiphers = [], + decFolders = []; + + var folderPromise = apiService.folders.list({}, function (folders) { + decFolders = cipherService.decryptFolders(folders.Data); + }).$promise; + + var ciphersPromise = apiService.ciphers.list({}, function (ciphers) { + decCiphers = cipherService.decryptCiphers(ciphers.Data); + }).$promise; + + $q.all([folderPromise, ciphersPromise]).then(function () { + if (!decCiphers.length) { + toastr.error('Nothing to export.', 'Error!'); + $scope.close(); + return; + } + + var foldersDict = {}; + for (var i = 0; i < decFolders.length; i++) { + foldersDict[decFolders[i].id] = decFolders[i]; + } + + try { + var exportCiphers = []; + for (i = 0; i < decCiphers.length; i++) { + // only export logins and secure notes + if (decCiphers[i].type !== constants.cipherType.login && + decCiphers[i].type !== constants.cipherType.secureNote) { + continue; + } + + var cipher = { + folder: decCiphers[i].folderId && (decCiphers[i].folderId in foldersDict) ? + foldersDict[decCiphers[i].folderId].name : null, + favorite: decCiphers[i].favorite ? 1 : null, + type: null, + name: decCiphers[i].name, + notes: decCiphers[i].notes, + fields: null, + // Login props + login_uri: null, + login_username: null, + login_password: null, + login_totp: null + }; + + var j; + if (decCiphers[i].fields) { + for (j = 0; j < decCiphers[i].fields.length; j++) { + if (!cipher.fields) { + cipher.fields = ''; + } + else { + cipher.fields += '\n'; + } + + cipher.fields += ((decCiphers[i].fields[j].name || '') + ': ' + decCiphers[i].fields[j].value); + } + } + + switch (decCiphers[i].type) { + case constants.cipherType.login: + cipher.type = 'login'; + cipher.login_username = decCiphers[i].login.username; + cipher.login_password = decCiphers[i].login.password; + cipher.login_totp = decCiphers[i].login.totp; + + if (decCiphers[i].login.uris && decCiphers[i].login.uris.length) { + cipher.login_uri = []; + for (j = 0; j < decCiphers[i].login.uris.length; j++) { + cipher.login_uri.push(decCiphers[i].login.uris[j].uri); + } + } + break; + case constants.cipherType.secureNote: + cipher.type = 'note'; + break; + default: + continue; + } + + exportCiphers.push(cipher); + } + + var csvString = Papa.unparse(exportCiphers); + var csvBlob = new Blob([csvString]); + + // IE hack. ref http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx + if (window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveBlob(csvBlob, makeFileName()); + } + else { + var a = window.document.createElement('a'); + a.href = window.URL.createObjectURL(csvBlob, { type: 'text/plain' }); + a.download = makeFileName(); + document.body.appendChild(a); + // IE: "Access is denied". + // ref: https://connect.microsoft.com/IE/feedback/details/797361/ie-10-treats-blob-url-as-cross-origin-and-denies-access + a.click(); + document.body.removeChild(a); + } + + $analytics.eventTrack('Exported Data'); + toastr.success('Your data has been exported. Check your browser\'s downloads folder.', 'Success!'); + $scope.close(); + } + catch (err) { + toastr.error('Something went wrong. Please try again.', 'Error!'); + $scope.close(); + } + }, function () { + toastr.error('Something went wrong. Please try again.', 'Error!'); + $scope.close(); + }); + }; + + $scope.close = function () { + $uibModalInstance.dismiss('cancel'); + }; + + function makeFileName() { + var now = new Date(); + var dateString = + now.getFullYear() + '' + padNumber(now.getMonth() + 1, 2) + '' + padNumber(now.getDate(), 2) + + padNumber(now.getHours(), 2) + '' + padNumber(now.getMinutes(), 2) + + padNumber(now.getSeconds(), 2); + + return 'bitwarden_export_' + dateString + '.csv'; + } + + function padNumber(number, width, paddingCharacter) { + paddingCharacter = paddingCharacter || '0'; + number = number + ''; + return number.length >= width ? number : new Array(width - number.length + 1).join(paddingCharacter) + number; + } + }]); + +angular + .module('bit.tools') + + .controller('toolsImportController', ["$scope", "$state", "apiService", "$uibModalInstance", "cryptoService", "cipherService", "toastr", "importService", "$analytics", "$sce", "validationService", function ($scope, $state, apiService, $uibModalInstance, cryptoService, cipherService, + toastr, importService, $analytics, $sce, validationService) { + $analytics.eventTrack('toolsImportController', { category: 'Modal' }); + $scope.model = { source: '' }; + $scope.source = {}; + $scope.splitFeatured = true; + + $scope.options = [ + { + id: 'bitwardencsv', + name: 'Bitwarden (csv)', + featured: true, + sort: 1, + instructions: $sce.trustAsHtml('Export using the web vault (vault.bitwarden.com). ' + + 'Log into the web vault and navigate to "Tools" > "Export".') + }, + { + id: 'lastpass', + name: 'LastPass (csv)', + featured: true, + sort: 2, + instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + + '' + + 'https://help.bitwarden.com/article/import-from-lastpass/') + }, + { + id: 'chromecsv', + name: 'Chrome (csv)', + featured: true, + sort: 3, + instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + + '' + + 'https://help.bitwarden.com/article/import-from-chrome/') + }, + { + id: 'firefoxpasswordexportercsv', + name: 'Firefox Password Exporter (csv)', + featured: true, + sort: 4, + instructions: $sce.trustAsHtml('Use the ' + + '' + + 'FF Password Exporter application to export your passwords to a CSV file.') + }, + { + id: 'keepass2xml', + name: 'KeePass 2 (xml)', + featured: true, + sort: 5, + instructions: $sce.trustAsHtml('Using the KeePass 2 desktop application, navigate to "File" > "Export" and ' + + 'select the KeePass XML (2.x) option.') + }, + { + id: 'keepassxcsv', + name: 'KeePassX (csv)', + instructions: $sce.trustAsHtml('Using the KeePassX desktop application, navigate to "Database" > ' + + '"Export to CSV file" and save the CSV file.') + }, + { + id: 'dashlanecsv', + name: 'Dashlane (csv)', + featured: true, + sort: 7, + instructions: $sce.trustAsHtml('Using the Dashlane desktop application, navigate to "File" > "Export" > ' + + '"Unsecured archive (readable) in CSV format" and save the CSV file.') + }, + { + id: '1password1pif', + name: '1Password (1pif)', + featured: true, + sort: 6, + instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + + '' + + 'https://help.bitwarden.com/article/import-from-1password/') + }, + { + id: '1password6wincsv', + name: '1Password 6 Windows (csv)', + instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + + '' + + 'https://help.bitwarden.com/article/import-from-1password/') + }, + { + id: 'roboformhtml', + name: 'RoboForm (html)', + instructions: $sce.trustAsHtml('Using the RoboForm Editor desktop application, navigate to "RoboForm" ' + + '(top left) > "Print List" > "Logins". When the following print dialog pops up click on the "Save" button ' + + 'and save the HTML file.') + }, + { + id: 'keepercsv', + name: 'Keeper (csv)', + instructions: $sce.trustAsHtml('Log into the Keeper web vault (keepersecurity.com/vault). Navigate to "Backup" ' + + '(top right) and find the "Export to Text File" option. Click "Export Now" to save the TXT/CSV file.') + }, + { + id: 'enpasscsv', + name: 'Enpass (csv)', + instructions: $sce.trustAsHtml('Using the Enpass desktop application, navigate to "File" > "Export" > ' + + '"As CSV". Select "Yes" to the warning alert and save the CSV file. Note that the importer only fully ' + + 'supports files exported while Enpass is set to the English language, so adjust your settings accordingly.') + }, + { + id: 'safeincloudxml', + name: 'SafeInCloud (xml)', + instructions: $sce.trustAsHtml('Using the SaveInCloud desktop application, navigate to "File" > "Export" > ' + + '"As XML" and save the XML file.') + }, + { + id: 'pwsafexml', + name: 'Password Safe (xml)', + instructions: $sce.trustAsHtml('Using the Password Safe desktop application, navigate to "File" > ' + + '"Export To" > "XML format..." and save the XML file.') + }, + { + id: 'stickypasswordxml', + name: 'Sticky Password (xml)', + instructions: $sce.trustAsHtml('Using the Sticky Password desktop application, navigate to "Menu" ' + + '(top right) > "Export" > "Export all". Select the unencrypted format XML option and then the ' + + '"Save to file" button. Save the XML file.') + }, + { + id: 'msecurecsv', + name: 'mSecure (csv)', + instructions: $sce.trustAsHtml('Using the mSecure desktop application, navigate to "File" > ' + + '"Export" > "CSV File..." and save the CSV file.') + }, + { + id: 'truekeycsv', + name: 'True Key (csv)', + instructions: $sce.trustAsHtml('Using the True Key desktop application, click the gear icon (top right) and ' + + 'then navigate to "App Settings". Click the "Export" button, enter your password and save the CSV file.') + }, + { + id: 'passwordbossjson', + name: 'Password Boss (json)', + instructions: $sce.trustAsHtml('Using the Password Boss desktop application, navigate to "File" > ' + + '"Export data" > "Password Boss JSON - not encrypted" and save the JSON file.') + }, + { + id: 'zohovaultcsv', + name: 'Zoho Vault (csv)', + instructions: $sce.trustAsHtml('Log into the Zoho web vault (vault.zoho.com). Navigate to "Tools" > ' + + '"Export Secrets". Select "All Secrets" and click the "Zoho Vault Format CSV" button. Highlight ' + + 'and copy the data from the textarea. Open a text editor like Notepad and paste the data. Save the ' + + 'data from the text editor as zoho_export.csv.') + }, + { + id: 'splashidcsv', + name: 'SplashID (csv)', + instructions: $sce.trustAsHtml('Using the SplashID Safe desktop application, click on the SplashID ' + + 'blue lock logo in the top right corner. Navigate to "Export" > "Export as CSV" and save the CSV file.') + }, + { + id: 'passworddragonxml', + name: 'Password Dragon (xml)', + instructions: $sce.trustAsHtml('Using the Password Dragon desktop application, navigate to "File" > ' + + '"Export" > "To XML". In the dialog that pops up select "All Rows" and check all fields. Click ' + + 'the "Export" button and save the XML file.') + }, + { + id: 'padlockcsv', + name: 'Padlock (csv)', + instructions: $sce.trustAsHtml('Using the Padlock desktop application, click the hamburger icon ' + + 'in the top left corner and navigate to "Settings". Click the "Export Data" option. Ensure that ' + + 'the "CSV" option is selected from the dropdown. Highlight and copy the data from the textarea. ' + + 'Open a text editor like Notepad and paste the data. Save the data from the text editor as ' + + 'padlock_export.csv.') + }, + { + id: 'clipperzhtml', + name: 'Clipperz (html)', + instructions: $sce.trustAsHtml('Log into the Clipperz web application (clipperz.is/app). Click the ' + + 'hamburger menu icon in the top right to expand the navigation bar. Navigate to "Data" > ' + + '"Export". Click the "download HTML+JSON" button to save the HTML file.') + }, + { + id: 'avirajson', + name: 'Avira (json)', + instructions: $sce.trustAsHtml('Using the Avira browser extension, click your username in the top ' + + 'right corner and navigate to "Settings". Locate the "Export Data" section and click "Export". ' + + 'In the dialog that pops up, click the "Export Password Manager Data" button to save the ' + + 'TXT/JSON file.') + }, + { + id: 'saferpasscsv', + name: 'SaferPass (csv)', + instructions: $sce.trustAsHtml('Using the SaferPass browser extension, click the hamburger icon ' + + 'in the top left corner and navigate to "Settings". Click the "Export accounts" button to ' + + 'save the CSV file.') + }, + { + id: 'upmcsv', + name: 'Universal Password Manager (csv)', + instructions: $sce.trustAsHtml('Using the Universal Password Manager desktop application, navigate ' + + 'to "Database" > "Export" and save the CSV file.') + }, + { + id: 'ascendocsv', + name: 'Ascendo DataVault (csv)', + instructions: $sce.trustAsHtml('Using the Ascendo DataVault desktop application, navigate ' + + 'to "Tools" > "Export". In the dialog that pops up, select the "All Items (DVX, CSV)" ' + + 'option. Click the "Ok" button to save the CSV file.') + }, + { + id: 'meldiumcsv', + name: 'Meldium (csv)', + instructions: $sce.trustAsHtml('Using the Meldium web vault, navigate to "Settings". ' + + 'Locate the "Export data" function and click "Show me my data" to save the CSV file.') + }, + { + id: 'passkeepcsv', + name: 'PassKeep (csv)', + instructions: $sce.trustAsHtml('Using the PassKeep mobile app, navigate to "Backup/Restore". ' + + 'Locate the "CSV Backup/Restore" section and click "Backup to CSV" to save the CSV file.') + }, + { + id: 'operacsv', + name: 'Opera (csv)', + instructions: $sce.trustAsHtml('The process for importing from Opera is exactly the same as ' + + 'importing from Google Chrome. See detailed instructions on our help site at ' + + '' + + 'https://help.bitwarden.com/article/import-from-chrome/') + }, + { + id: 'vivaldicsv', + name: 'Vivaldi (csv)', + instructions: $sce.trustAsHtml('The process for importing from Vivaldi is exactly the same as ' + + 'importing from Google Chrome. See detailed instructions on our help site at ' + + '' + + 'https://help.bitwarden.com/article/import-from-chrome/') + }, + { + id: 'gnomejson', + name: 'GNOME Passwords and Keys/Seahorse (json)', + instructions: $sce.trustAsHtml('Make sure you have python-keyring and python-gnomekeyring installed. ' + + 'Save the GNOME Keyring Import/Export ' + + 'python script by Luke Plant to your desktop as pw_helper.py. Open terminal and run ' + + 'chmod +rx Desktop/pw_helper.py and then ' + + 'python Desktop/pw_helper.py export Desktop/my_passwords.json. Then upload ' + + 'the resulting my_passwords.json file here to Bitwarden.') + } + ]; + + $scope.setSource = function () { + for (var i = 0; i < $scope.options.length; i++) { + if ($scope.options[i].id === $scope.model.source) { + $scope.source = $scope.options[i]; + break; + } + } + }; + $scope.setSource(); + + $scope.import = function (model, form) { + if (!model.source || model.source === '') { + validationService.addError(form, 'source', 'Select the format of the import file.', true); + return; + } + + var file = document.getElementById('file').files[0]; + if (!file && (!model.fileContents || model.fileContents === '')) { + validationService.addError(form, 'file', 'Select the import file or copy/paste the import file contents.', true); + return; + } + + $scope.processing = true; + importService.import(model.source, file || model.fileContents, importSuccess, importError); + }; + + function importSuccess(folders, ciphers, folderRelationships) { + if (!folders.length && !ciphers.length) { + importError('Nothing was imported.'); + return; + } + else if (ciphers.length) { + var halfway = Math.floor(ciphers.length / 2); + var last = ciphers.length - 1; + if (cipherIsBadData(ciphers[0]) && cipherIsBadData(ciphers[halfway]) && cipherIsBadData(ciphers[last])) { + importError('Data is not formatted correctly. Please check your import file and try again.'); + return; + } + } + + apiService.ciphers.import({ + folders: cipherService.encryptFolders(folders), + ciphers: cipherService.encryptCiphers(ciphers), + folderRelationships: folderRelationships + }, function () { + $uibModalInstance.dismiss('cancel'); + $state.go('backend.user.vault', { refreshFromServer: true }).then(function () { + $analytics.eventTrack('Imported Data', { label: $scope.model.source }); + toastr.success('Data has been successfully imported into your vault.', 'Import Success'); + }); + }, importError); + } + + function cipherIsBadData(cipher) { + return (cipher.name === null || cipher.name === '--') && + (cipher.login && (cipher.login.password === null || cipher.login.password === '')); + } + + function importError(error) { + $analytics.eventTrack('Import Data Failed', { label: $scope.model.source }); + $uibModalInstance.dismiss('cancel'); + + if (error) { + var data = error.data; + if (data && data.ValidationErrors) { + var message = ''; + for (var key in data.ValidationErrors) { + if (!data.ValidationErrors.hasOwnProperty(key)) { + continue; + } + + for (var i = 0; i < data.ValidationErrors[key].length; i++) { + message += (key + ': ' + data.ValidationErrors[key][i] + ' '); + } + } + + if (message !== '') { + toastr.error(message); + return; + } + } + else if (data && data.Message) { + toastr.error(data.Message); + return; + } + else { + toastr.error(error); + return; + } + } + + toastr.error('Something went wrong. Try again.', 'Oh No!'); + } + + $scope.close = function () { + $uibModalInstance.dismiss('cancel'); + }; }]); angular @@ -14058,6 +14579,11 @@ angular var index = $rootScope.vaultFolders.indexOf(folder); if (index > -1) { $rootScope.vaultFolders.splice(index, 1); + for(var i = 0; i < $rootScope.vaultCiphers.length; i++) { + if($rootScope.vaultCiphers[i].folderId === folder.id) { + $rootScope.vaultCiphers[i].folderId = null; + } + } $scope.filterAll(); } }); @@ -14713,515 +15239,3 @@ angular }); }; }]); - -angular - .module('bit.tools') - - .controller('toolsController', ["$scope", "$uibModal", "apiService", "toastr", "authService", function ($scope, $uibModal, apiService, toastr, authService) { - $scope.import = function () { - $uibModal.open({ - animation: true, - templateUrl: 'app/tools/views/toolsImport.html', - controller: 'toolsImportController' - }); - }; - - $scope.export = function () { - $uibModal.open({ - animation: true, - templateUrl: 'app/tools/views/toolsExport.html', - controller: 'toolsExportController' - }); - }; - }]); - -angular - .module('bit.tools') - - .controller('toolsExportController', ["$scope", "apiService", "$uibModalInstance", "cipherService", "$q", "toastr", "$analytics", "constants", function ($scope, apiService, $uibModalInstance, cipherService, $q, - toastr, $analytics, constants) { - $analytics.eventTrack('toolsExportController', { category: 'Modal' }); - $scope.export = function (model) { - $scope.startedExport = true; - var decCiphers = [], - decFolders = []; - - var folderPromise = apiService.folders.list({}, function (folders) { - decFolders = cipherService.decryptFolders(folders.Data); - }).$promise; - - var ciphersPromise = apiService.ciphers.list({}, function (ciphers) { - decCiphers = cipherService.decryptCiphers(ciphers.Data); - }).$promise; - - $q.all([folderPromise, ciphersPromise]).then(function () { - if (!decCiphers.length) { - toastr.error('Nothing to export.', 'Error!'); - $scope.close(); - return; - } - - var foldersDict = {}; - for (var i = 0; i < decFolders.length; i++) { - foldersDict[decFolders[i].id] = decFolders[i]; - } - - try { - var exportCiphers = []; - for (i = 0; i < decCiphers.length; i++) { - // only export logins and secure notes - if (decCiphers[i].type !== constants.cipherType.login && - decCiphers[i].type !== constants.cipherType.secureNote) { - continue; - } - - var cipher = { - folder: decCiphers[i].folderId && (decCiphers[i].folderId in foldersDict) ? - foldersDict[decCiphers[i].folderId].name : null, - favorite: decCiphers[i].favorite ? 1 : null, - type: null, - name: decCiphers[i].name, - notes: decCiphers[i].notes, - fields: null, - // Login props - login_uri: null, - login_username: null, - login_password: null, - login_totp: null - }; - - var j; - if (decCiphers[i].fields) { - for (j = 0; j < decCiphers[i].fields.length; j++) { - if (!cipher.fields) { - cipher.fields = ''; - } - else { - cipher.fields += '\n'; - } - - cipher.fields += ((decCiphers[i].fields[j].name || '') + ': ' + decCiphers[i].fields[j].value); - } - } - - switch (decCiphers[i].type) { - case constants.cipherType.login: - cipher.type = 'login'; - cipher.login_username = decCiphers[i].login.username; - cipher.login_password = decCiphers[i].login.password; - cipher.login_totp = decCiphers[i].login.totp; - - if (decCiphers[i].login.uris && decCiphers[i].login.uris.length) { - cipher.login_uri = []; - for (j = 0; j < decCiphers[i].login.uris.length; j++) { - cipher.login_uri.push(decCiphers[i].login.uris[j].uri); - } - } - break; - case constants.cipherType.secureNote: - cipher.type = 'note'; - break; - default: - continue; - } - - exportCiphers.push(cipher); - } - - var csvString = Papa.unparse(exportCiphers); - var csvBlob = new Blob([csvString]); - - // IE hack. ref http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx - if (window.navigator.msSaveOrOpenBlob) { - window.navigator.msSaveBlob(csvBlob, makeFileName()); - } - else { - var a = window.document.createElement('a'); - a.href = window.URL.createObjectURL(csvBlob, { type: 'text/plain' }); - a.download = makeFileName(); - document.body.appendChild(a); - // IE: "Access is denied". - // ref: https://connect.microsoft.com/IE/feedback/details/797361/ie-10-treats-blob-url-as-cross-origin-and-denies-access - a.click(); - document.body.removeChild(a); - } - - $analytics.eventTrack('Exported Data'); - toastr.success('Your data has been exported. Check your browser\'s downloads folder.', 'Success!'); - $scope.close(); - } - catch (err) { - toastr.error('Something went wrong. Please try again.', 'Error!'); - $scope.close(); - } - }, function () { - toastr.error('Something went wrong. Please try again.', 'Error!'); - $scope.close(); - }); - }; - - $scope.close = function () { - $uibModalInstance.dismiss('cancel'); - }; - - function makeFileName() { - var now = new Date(); - var dateString = - now.getFullYear() + '' + padNumber(now.getMonth() + 1, 2) + '' + padNumber(now.getDate(), 2) + - padNumber(now.getHours(), 2) + '' + padNumber(now.getMinutes(), 2) + - padNumber(now.getSeconds(), 2); - - return 'bitwarden_export_' + dateString + '.csv'; - } - - function padNumber(number, width, paddingCharacter) { - paddingCharacter = paddingCharacter || '0'; - number = number + ''; - return number.length >= width ? number : new Array(width - number.length + 1).join(paddingCharacter) + number; - } - }]); - -angular - .module('bit.tools') - - .controller('toolsImportController', ["$scope", "$state", "apiService", "$uibModalInstance", "cryptoService", "cipherService", "toastr", "importService", "$analytics", "$sce", "validationService", function ($scope, $state, apiService, $uibModalInstance, cryptoService, cipherService, - toastr, importService, $analytics, $sce, validationService) { - $analytics.eventTrack('toolsImportController', { category: 'Modal' }); - $scope.model = { source: '' }; - $scope.source = {}; - $scope.splitFeatured = true; - - $scope.options = [ - { - id: 'bitwardencsv', - name: 'Bitwarden (csv)', - featured: true, - sort: 1, - instructions: $sce.trustAsHtml('Export using the web vault (vault.bitwarden.com). ' + - 'Log into the web vault and navigate to "Tools" > "Export".') - }, - { - id: 'lastpass', - name: 'LastPass (csv)', - featured: true, - sort: 2, - instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + - '' + - 'https://help.bitwarden.com/article/import-from-lastpass/') - }, - { - id: 'chromecsv', - name: 'Chrome (csv)', - featured: true, - sort: 3, - instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + - '' + - 'https://help.bitwarden.com/article/import-from-chrome/') - }, - { - id: 'firefoxpasswordexportercsv', - name: 'Firefox Password Exporter (csv)', - featured: true, - sort: 4, - instructions: $sce.trustAsHtml('Use the ' + - '' + - 'FF Password Exporter application to export your passwords to a CSV file.') - }, - { - id: 'keepass2xml', - name: 'KeePass 2 (xml)', - featured: true, - sort: 5, - instructions: $sce.trustAsHtml('Using the KeePass 2 desktop application, navigate to "File" > "Export" and ' + - 'select the KeePass XML (2.x) option.') - }, - { - id: 'keepassxcsv', - name: 'KeePassX (csv)', - instructions: $sce.trustAsHtml('Using the KeePassX desktop application, navigate to "Database" > ' + - '"Export to CSV file" and save the CSV file.') - }, - { - id: 'dashlanecsv', - name: 'Dashlane (csv)', - featured: true, - sort: 7, - instructions: $sce.trustAsHtml('Using the Dashlane desktop application, navigate to "File" > "Export" > ' + - '"Unsecured archive (readable) in CSV format" and save the CSV file.') - }, - { - id: '1password1pif', - name: '1Password (1pif)', - featured: true, - sort: 6, - instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + - '' + - 'https://help.bitwarden.com/article/import-from-1password/') - }, - { - id: '1password6wincsv', - name: '1Password 6 Windows (csv)', - instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' + - '' + - 'https://help.bitwarden.com/article/import-from-1password/') - }, - { - id: 'roboformhtml', - name: 'RoboForm (html)', - instructions: $sce.trustAsHtml('Using the RoboForm Editor desktop application, navigate to "RoboForm" ' + - '(top left) > "Print List" > "Logins". When the following print dialog pops up click on the "Save" button ' + - 'and save the HTML file.') - }, - { - id: 'keepercsv', - name: 'Keeper (csv)', - instructions: $sce.trustAsHtml('Log into the Keeper web vault (keepersecurity.com/vault). Navigate to "Backup" ' + - '(top right) and find the "Export to Text File" option. Click "Export Now" to save the TXT/CSV file.') - }, - { - id: 'enpasscsv', - name: 'Enpass (csv)', - instructions: $sce.trustAsHtml('Using the Enpass desktop application, navigate to "File" > "Export" > ' + - '"As CSV". Select "Yes" to the warning alert and save the CSV file. Note that the importer only fully ' + - 'supports files exported while Enpass is set to the English language, so adjust your settings accordingly.') - }, - { - id: 'safeincloudxml', - name: 'SafeInCloud (xml)', - instructions: $sce.trustAsHtml('Using the SaveInCloud desktop application, navigate to "File" > "Export" > ' + - '"As XML" and save the XML file.') - }, - { - id: 'pwsafexml', - name: 'Password Safe (xml)', - instructions: $sce.trustAsHtml('Using the Password Safe desktop application, navigate to "File" > ' + - '"Export To" > "XML format..." and save the XML file.') - }, - { - id: 'stickypasswordxml', - name: 'Sticky Password (xml)', - instructions: $sce.trustAsHtml('Using the Sticky Password desktop application, navigate to "Menu" ' + - '(top right) > "Export" > "Export all". Select the unencrypted format XML option and then the ' + - '"Save to file" button. Save the XML file.') - }, - { - id: 'msecurecsv', - name: 'mSecure (csv)', - instructions: $sce.trustAsHtml('Using the mSecure desktop application, navigate to "File" > ' + - '"Export" > "CSV File..." and save the CSV file.') - }, - { - id: 'truekeycsv', - name: 'True Key (csv)', - instructions: $sce.trustAsHtml('Using the True Key desktop application, click the gear icon (top right) and ' + - 'then navigate to "App Settings". Click the "Export" button, enter your password and save the CSV file.') - }, - { - id: 'passwordbossjson', - name: 'Password Boss (json)', - instructions: $sce.trustAsHtml('Using the Password Boss desktop application, navigate to "File" > ' + - '"Export data" > "Password Boss JSON - not encrypted" and save the JSON file.') - }, - { - id: 'zohovaultcsv', - name: 'Zoho Vault (csv)', - instructions: $sce.trustAsHtml('Log into the Zoho web vault (vault.zoho.com). Navigate to "Tools" > ' + - '"Export Secrets". Select "All Secrets" and click the "Zoho Vault Format CSV" button. Highlight ' + - 'and copy the data from the textarea. Open a text editor like Notepad and paste the data. Save the ' + - 'data from the text editor as zoho_export.csv.') - }, - { - id: 'splashidcsv', - name: 'SplashID (csv)', - instructions: $sce.trustAsHtml('Using the SplashID Safe desktop application, click on the SplashID ' + - 'blue lock logo in the top right corner. Navigate to "Export" > "Export as CSV" and save the CSV file.') - }, - { - id: 'passworddragonxml', - name: 'Password Dragon (xml)', - instructions: $sce.trustAsHtml('Using the Password Dragon desktop application, navigate to "File" > ' + - '"Export" > "To XML". In the dialog that pops up select "All Rows" and check all fields. Click ' + - 'the "Export" button and save the XML file.') - }, - { - id: 'padlockcsv', - name: 'Padlock (csv)', - instructions: $sce.trustAsHtml('Using the Padlock desktop application, click the hamburger icon ' + - 'in the top left corner and navigate to "Settings". Click the "Export Data" option. Ensure that ' + - 'the "CSV" option is selected from the dropdown. Highlight and copy the data from the textarea. ' + - 'Open a text editor like Notepad and paste the data. Save the data from the text editor as ' + - 'padlock_export.csv.') - }, - { - id: 'clipperzhtml', - name: 'Clipperz (html)', - instructions: $sce.trustAsHtml('Log into the Clipperz web application (clipperz.is/app). Click the ' + - 'hamburger menu icon in the top right to expand the navigation bar. Navigate to "Data" > ' + - '"Export". Click the "download HTML+JSON" button to save the HTML file.') - }, - { - id: 'avirajson', - name: 'Avira (json)', - instructions: $sce.trustAsHtml('Using the Avira browser extension, click your username in the top ' + - 'right corner and navigate to "Settings". Locate the "Export Data" section and click "Export". ' + - 'In the dialog that pops up, click the "Export Password Manager Data" button to save the ' + - 'TXT/JSON file.') - }, - { - id: 'saferpasscsv', - name: 'SaferPass (csv)', - instructions: $sce.trustAsHtml('Using the SaferPass browser extension, click the hamburger icon ' + - 'in the top left corner and navigate to "Settings". Click the "Export accounts" button to ' + - 'save the CSV file.') - }, - { - id: 'upmcsv', - name: 'Universal Password Manager (csv)', - instructions: $sce.trustAsHtml('Using the Universal Password Manager desktop application, navigate ' + - 'to "Database" > "Export" and save the CSV file.') - }, - { - id: 'ascendocsv', - name: 'Ascendo DataVault (csv)', - instructions: $sce.trustAsHtml('Using the Ascendo DataVault desktop application, navigate ' + - 'to "Tools" > "Export". In the dialog that pops up, select the "All Items (DVX, CSV)" ' + - 'option. Click the "Ok" button to save the CSV file.') - }, - { - id: 'meldiumcsv', - name: 'Meldium (csv)', - instructions: $sce.trustAsHtml('Using the Meldium web vault, navigate to "Settings". ' + - 'Locate the "Export data" function and click "Show me my data" to save the CSV file.') - }, - { - id: 'passkeepcsv', - name: 'PassKeep (csv)', - instructions: $sce.trustAsHtml('Using the PassKeep mobile app, navigate to "Backup/Restore". ' + - 'Locate the "CSV Backup/Restore" section and click "Backup to CSV" to save the CSV file.') - }, - { - id: 'operacsv', - name: 'Opera (csv)', - instructions: $sce.trustAsHtml('The process for importing from Opera is exactly the same as ' + - 'importing from Google Chrome. See detailed instructions on our help site at ' + - '' + - 'https://help.bitwarden.com/article/import-from-chrome/') - }, - { - id: 'vivaldicsv', - name: 'Vivaldi (csv)', - instructions: $sce.trustAsHtml('The process for importing from Vivaldi is exactly the same as ' + - 'importing from Google Chrome. See detailed instructions on our help site at ' + - '' + - 'https://help.bitwarden.com/article/import-from-chrome/') - }, - { - id: 'gnomejson', - name: 'GNOME Passwords and Keys/Seahorse (json)', - instructions: $sce.trustAsHtml('Make sure you have python-keyring and python-gnomekeyring installed. ' + - 'Save the GNOME Keyring Import/Export ' + - 'python script by Luke Plant to your desktop as pw_helper.py. Open terminal and run ' + - 'chmod +rx Desktop/pw_helper.py and then ' + - 'python Desktop/pw_helper.py export Desktop/my_passwords.json. Then upload ' + - 'the resulting my_passwords.json file here to Bitwarden.') - } - ]; - - $scope.setSource = function () { - for (var i = 0; i < $scope.options.length; i++) { - if ($scope.options[i].id === $scope.model.source) { - $scope.source = $scope.options[i]; - break; - } - } - }; - $scope.setSource(); - - $scope.import = function (model, form) { - if (!model.source || model.source === '') { - validationService.addError(form, 'source', 'Select the format of the import file.', true); - return; - } - - var file = document.getElementById('file').files[0]; - if (!file && (!model.fileContents || model.fileContents === '')) { - validationService.addError(form, 'file', 'Select the import file or copy/paste the import file contents.', true); - return; - } - - $scope.processing = true; - importService.import(model.source, file || model.fileContents, importSuccess, importError); - }; - - function importSuccess(folders, ciphers, folderRelationships) { - if (!folders.length && !ciphers.length) { - importError('Nothing was imported.'); - return; - } - else if (ciphers.length) { - var halfway = Math.floor(ciphers.length / 2); - var last = ciphers.length - 1; - if (cipherIsBadData(ciphers[0]) && cipherIsBadData(ciphers[halfway]) && cipherIsBadData(ciphers[last])) { - importError('Data is not formatted correctly. Please check your import file and try again.'); - return; - } - } - - apiService.ciphers.import({ - folders: cipherService.encryptFolders(folders), - ciphers: cipherService.encryptCiphers(ciphers), - folderRelationships: folderRelationships - }, function () { - $uibModalInstance.dismiss('cancel'); - $state.go('backend.user.vault', { refreshFromServer: true }).then(function () { - $analytics.eventTrack('Imported Data', { label: $scope.model.source }); - toastr.success('Data has been successfully imported into your vault.', 'Import Success'); - }); - }, importError); - } - - function cipherIsBadData(cipher) { - return (cipher.name === null || cipher.name === '--') && - (cipher.login && (cipher.login.password === null || cipher.login.password === '')); - } - - function importError(error) { - $analytics.eventTrack('Import Data Failed', { label: $scope.model.source }); - $uibModalInstance.dismiss('cancel'); - - if (error) { - var data = error.data; - if (data && data.ValidationErrors) { - var message = ''; - for (var key in data.ValidationErrors) { - if (!data.ValidationErrors.hasOwnProperty(key)) { - continue; - } - - for (var i = 0; i < data.ValidationErrors[key].length; i++) { - message += (key + ': ' + data.ValidationErrors[key][i] + ' '); - } - } - - if (message !== '') { - toastr.error(message); - return; - } - } - else if (data && data.Message) { - toastr.error(data.Message); - return; - } - else { - toastr.error(error); - return; - } - } - - toastr.error('Something went wrong. Try again.', 'Oh No!'); - } - - $scope.close = function () { - $uibModalInstance.dismiss('cancel'); - }; - }]); diff --git a/js/fallback-styles.min.js b/js/fallback-styles.min.js index 3a5f1136..53ee5a7a 100644 --- a/js/fallback-styles.min.js +++ b/js/fallback-styles.min.js @@ -1,4 +1,4 @@ -var cacheTag = 'dlcwii' || ''; +var cacheTag = '3qobma' || ''; function loadStylesheetIfMissing(property, value, paths) { var scripts = document.getElementsByTagName('SCRIPT'), diff --git a/js/lib.min.js b/js/lib.min.js index 7806ac52..546c8703 100644 --- a/js/lib.min.js +++ b/js/lib.min.js @@ -462,106 +462,6 @@ function propertyName(name) { } })(angular); -(function() { - var showErrorsModule; - - showErrorsModule = angular.module('ui.bootstrap.showErrors', []); - - showErrorsModule.directive('showErrors', [ - '$timeout', 'showErrorsConfig', '$interpolate', function($timeout, showErrorsConfig, $interpolate) { - var getShowSuccess, getTrigger, linkFn; - getTrigger = function(options) { - var trigger; - trigger = showErrorsConfig.trigger; - if (options && (options.trigger != null)) { - trigger = options.trigger; - } - return trigger; - }; - getShowSuccess = function(options) { - var showSuccess; - showSuccess = showErrorsConfig.showSuccess; - if (options && (options.showSuccess != null)) { - showSuccess = options.showSuccess; - } - return showSuccess; - }; - linkFn = function(scope, el, attrs, formCtrl) { - var blurred, inputEl, inputName, inputNgEl, options, showSuccess, toggleClasses, trigger; - blurred = false; - options = scope.$eval(attrs.showErrors); - showSuccess = getShowSuccess(options); - trigger = getTrigger(options); - inputEl = el[0].querySelector('.form-control[name]'); - inputNgEl = angular.element(inputEl); - inputName = $interpolate(inputNgEl.attr('name') || '')(scope); - if (!inputName) { - throw "show-errors element has no child input elements with a 'name' attribute and a 'form-control' class"; - } - inputNgEl.bind(trigger, function() { - blurred = true; - return toggleClasses(formCtrl[inputName].$invalid); - }); - scope.$watch(function() { - return formCtrl[inputName] && formCtrl[inputName].$invalid; - }, function(invalid) { - if (!blurred) { - return; - } - return toggleClasses(invalid); - }); - scope.$on('show-errors-check-validity', function() { - return toggleClasses(formCtrl[inputName].$invalid); - }); - scope.$on('show-errors-reset', function() { - return $timeout(function() { - el.removeClass('has-error'); - el.removeClass('has-success'); - return blurred = false; - }, 0, false); - }); - return toggleClasses = function(invalid) { - el.toggleClass('has-error', invalid); - if (showSuccess) { - return el.toggleClass('has-success', !invalid); - } - }; - }; - return { - restrict: 'A', - require: '^form', - compile: function(elem, attrs) { - if (attrs['showErrors'].indexOf('skipFormGroupCheck') === -1) { - if (!(elem.hasClass('form-group') || elem.hasClass('input-group'))) { - throw "show-errors element does not have the 'form-group' or 'input-group' class"; - } - } - return linkFn; - } - }; - } - ]); - - showErrorsModule.provider('showErrorsConfig', function() { - var _showSuccess, _trigger; - _showSuccess = false; - _trigger = 'blur'; - this.showSuccess = function(showSuccess) { - return _showSuccess = showSuccess; - }; - this.trigger = function(trigger) { - return _trigger = trigger; - }; - this.$get = function() { - return { - showSuccess: _showSuccess, - trigger: _trigger - }; - }; - }); - -}).call(this); - /** * @license AngularJS v1.6.7 * (c) 2010-2017 Google, Inc. http://angularjs.org @@ -889,906 +789,106 @@ angular.module('ngCookies').provider('$$cookieWriter', /** @this */ function $$C })(window, window.angular); -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.angularCreditCards = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= new Date(year, month) -} - -function parseMonth (month) { - return parseIntStrict(month) -} - -function formatExpYear (year, strip) { - year = year.toString() - return strip ? year.substr(2, 4) : year -} - -function isExpYearValid (year) { - if (typeof year !== 'number') return false - year = parseIntStrict(year) - return year > 0 -} - -function isExpYearPast (year) { - return new Date().getFullYear() > year -} - -},{"is-valid-month":21,"parse-int":24,"parse-year":25}],13:[function(_dereq_,module,exports){ -'use strict' - -module.exports = { - card: _dereq_('./card'), - cvc: _dereq_('./cvc'), - expiration: _dereq_('./expiration') -} - -},{"./card":10,"./cvc":11,"./expiration":12}],14:[function(_dereq_,module,exports){ -'use strict' - -var ccTypes = _dereq_('creditcards-types') -var camel = _dereq_('to-camel-case') -var extend = _dereq_('xtend') - -module.exports = extend(ccTypes, { - get: function getTypeByName (name) { - return ccTypes.types[camel(name)] - } -}) - -},{"creditcards-types":7,"to-camel-case":26,"xtend":29}],15:[function(_dereq_,module,exports){ -'use strict' - -var zeroFill = _dereq_('zero-fill') -var parseIntStrict = _dereq_('parse-int') - -var pad = zeroFill(2) - -module.exports = function expandYear (year, now) { - now = now || new Date() - var base = now.getFullYear().toString().substr(0, 2) - year = parseIntStrict(year) - return parseIntStrict(base + pad(year)) -} - -},{"parse-int":24,"zero-fill":31}],16:[function(_dereq_,module,exports){ -'use strict' - -module.exports = (function (array) { - return function luhn (number) { - if (typeof number !== 'string') throw new TypeError('Expected string input') - if (!number) return false - var length = number.length - var bit = 1 - var sum = 0 - var value - - while (length) { - value = parseInt(number.charAt(--length), 10) - sum += (bit ^= 1) ? array[value] : value - } - - return !!sum && sum % 10 === 0 - } -}([0, 2, 4, 6, 8, 1, 3, 5, 7, 9])) - -},{}],17:[function(_dereq_,module,exports){ -'use strict'; - -/* eslint no-invalid-this: 1 */ - -var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible '; -var slice = Array.prototype.slice; -var toStr = Object.prototype.toString; -var funcType = '[object Function]'; - -module.exports = function bind(that) { - var target = this; - if (typeof target !== 'function' || toStr.call(target) !== funcType) { - throw new TypeError(ERROR_MESSAGE + target); - } - var args = slice.call(arguments, 1); - - var bound; - var binder = function () { - if (this instanceof bound) { - var result = target.apply( - this, - args.concat(slice.call(arguments)) - ); - if (Object(result) === result) { - return result; - } - return this; - } else { - return target.apply( - that, - args.concat(slice.call(arguments)) - ); - } - }; - - var boundLength = Math.max(0, target.length - args.length); - var boundArgs = []; - for (var i = 0; i < boundLength; i++) { - boundArgs.push('$' + i); - } - - bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder); - - if (target.prototype) { - var Empty = function Empty() {}; - Empty.prototype = target.prototype; - bound.prototype = new Empty(); - Empty.prototype = null; - } - - return bound; -}; - -},{}],18:[function(_dereq_,module,exports){ -'use strict'; - -var implementation = _dereq_('./implementation'); - -module.exports = Function.prototype.bind || implementation; - -},{"./implementation":17}],19:[function(_dereq_,module,exports){ -'use strict'; -var numberIsNan = _dereq_('number-is-nan'); - -module.exports = Number.isFinite || function (val) { - return !(typeof val !== 'number' || numberIsNan(val) || val === Infinity || val === -Infinity); -}; - -},{"number-is-nan":23}],20:[function(_dereq_,module,exports){ -// https://github.com/paulmillr/es6-shim -// http://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.isinteger -var isFinite = _dereq_("is-finite"); -module.exports = Number.isInteger || function(val) { - return typeof val === "number" && - isFinite(val) && - Math.floor(val) === val; -}; - -},{"is-finite":19}],21:[function(_dereq_,module,exports){ -'use strict' - -var isInteger = _dereq_('is-integer') - -module.exports = function isValidMonth (month) { - if (typeof month !== 'number' || !isInteger(month)) return false - return month >= 1 && month <= 12 -} - -},{"is-integer":20}],22:[function(_dereq_,module,exports){ -module.exports = Array.isArray || function (arr) { - return Object.prototype.toString.call(arr) == '[object Array]'; -}; - -},{}],23:[function(_dereq_,module,exports){ -'use strict'; -module.exports = Number.isNaN || function (x) { - return x !== x; -}; - -},{}],24:[function(_dereq_,module,exports){ -'use strict' - -var isInteger = _dereq_('is-integer') - -module.exports = function parseIntStrict (integer) { - if (typeof integer === 'number') { - return isInteger(integer) ? integer : undefined - } - if (typeof integer === 'string') { - return /^-?\d+$/.test(integer) ? parseInt(integer, 10) : undefined - } -} - -},{"is-integer":20}],25:[function(_dereq_,module,exports){ -'use strict' - -var parseIntStrict = _dereq_('parse-int') -var expandYear = _dereq_('expand-year') - -module.exports = function parseYear (year, expand, now) { - year = parseIntStrict(year) - if (year == null) return - if (!expand) return year - return expandYear(year, now) -} - -},{"expand-year":15,"parse-int":24}],26:[function(_dereq_,module,exports){ - -var space = _dereq_('to-space-case') - -/** - * Export. - */ - -module.exports = toCamelCase - -/** - * Convert a `string` to camel case. - * - * @param {String} string - * @return {String} - */ - -function toCamelCase(string) { - return space(string).replace(/\s(\w)/g, function (matches, letter) { - return letter.toUpperCase() - }) -} - -},{"to-space-case":28}],27:[function(_dereq_,module,exports){ - -/** - * Export. - */ - -module.exports = toNoCase - -/** - * Test whether a string is camel-case. - */ - -var hasSpace = /\s/ -var hasSeparator = /(_|-|\.|:)/ -var hasCamel = /([a-z][A-Z]|[A-Z][a-z])/ - -/** - * Remove any starting case from a `string`, like camel or snake, but keep - * spaces and punctuation that may be important otherwise. - * - * @param {String} string - * @return {String} - */ - -function toNoCase(string) { - if (hasSpace.test(string)) return string.toLowerCase() - if (hasSeparator.test(string)) return (unseparate(string) || string).toLowerCase() - if (hasCamel.test(string)) return uncamelize(string).toLowerCase() - return string.toLowerCase() -} - -/** - * Separator splitter. - */ - -var separatorSplitter = /[\W_]+(.|$)/g - -/** - * Un-separate a `string`. - * - * @param {String} string - * @return {String} - */ - -function unseparate(string) { - return string.replace(separatorSplitter, function (m, next) { - return next ? ' ' + next : '' - }) -} - -/** - * Camelcase splitter. - */ - -var camelSplitter = /(.)([A-Z]+)/g - -/** - * Un-camelcase a `string`. - * - * @param {String} string - * @return {String} - */ - -function uncamelize(string) { - return string.replace(camelSplitter, function (m, previous, uppers) { - return previous + ' ' + uppers.toLowerCase().split('').join(' ') - }) -} - -},{}],28:[function(_dereq_,module,exports){ - -var clean = _dereq_('to-no-case') - -/** - * Export. - */ - -module.exports = toSpaceCase - -/** - * Convert a `string` to space case. - * - * @param {String} string - * @return {String} - */ - -function toSpaceCase(string) { - return clean(string).replace(/[\W_]+(.|$)/g, function (matches, match) { - return match ? ' ' + match : '' - }).trim() -} - -},{"to-no-case":27}],29:[function(_dereq_,module,exports){ -module.exports = extend - -var hasOwnProperty = Object.prototype.hasOwnProperty; - -function extend() { - var target = {} - - for (var i = 0; i < arguments.length; i++) { - var source = arguments[i] - - for (var key in source) { - if (hasOwnProperty.call(source, key)) { - target[key] = source[key] - } - } - } - - return target -} - -},{}],30:[function(_dereq_,module,exports){ -module.exports = extend - -var hasOwnProperty = Object.prototype.hasOwnProperty; - -function extend(target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i] - - for (var key in source) { - if (hasOwnProperty.call(source, key)) { - target[key] = source[key] - } - } - } - - return target -} - -},{}],31:[function(_dereq_,module,exports){ -/** - * Given a number, return a zero-filled string. - * From http://stackoverflow.com/questions/1267283/ - * @param {number} width - * @param {number} number - * @return {string} - */ -module.exports = function zeroFill (width, number, pad) { - if (number === undefined) { - return function (number, pad) { - return zeroFill(width, number, pad) - } - } - if (pad === undefined) pad = '0' - width -= number.toString().length - if (width > 0) return new Array(width + (/\./.test(number) ? 2 : 1)).join(pad) + number - return number + '' -} - -},{}]},{},[3])(3) -}); (function() { @@ -2925,46 +2025,6 @@ function ngMessageDirectiveFactory() { })(window, window.angular); -'use strict'; - -var app = angular - .module('angular-promise-polyfill', []) - .run(['$q', '$window', function($q, $window) { - $window.Promise = function(executor) { - return $q(executor); - }; - - $window.Promise.all = $q.all.bind($q); - $window.Promise.reject = $q.reject.bind($q); - $window.Promise.resolve = $q.when.bind($q); - - $window.Promise.race = function(promises) { - var promiseMgr = $q.defer(); - - for(var i = 0; i < promises.length; i++) { - promises[i].then(function(result) { - if(promiseMgr) { - promiseMgr.resolve(result); - promiseMgr = null; - } - }); - - promises[i].catch(function(result) { - if(promiseMgr) { - promiseMgr.reject(result); - promiseMgr = null; - } - }); - } - - return promiseMgr.promise; - }; - }]); - -if (typeof module === 'object') { - module.exports = app.name; -} - /** * @license AngularJS v1.6.7 * (c) 2010-2017 Google, Inc. http://angularjs.org @@ -3823,6 +2883,46 @@ angular.module('ngResource', ['ng']). })(window, window.angular); +'use strict'; + +var app = angular + .module('angular-promise-polyfill', []) + .run(['$q', '$window', function($q, $window) { + $window.Promise = function(executor) { + return $q(executor); + }; + + $window.Promise.all = $q.all.bind($q); + $window.Promise.reject = $q.reject.bind($q); + $window.Promise.resolve = $q.when.bind($q); + + $window.Promise.race = function(promises) { + var promiseMgr = $q.defer(); + + for(var i = 0; i < promises.length; i++) { + promises[i].then(function(result) { + if(promiseMgr) { + promiseMgr.resolve(result); + promiseMgr = null; + } + }); + + promises[i].catch(function(result) { + if(promiseMgr) { + promiseMgr.reject(result); + promiseMgr = null; + } + }); + } + + return promiseMgr.promise; + }; + }]); + +if (typeof module === 'object') { + module.exports = app.name; +} + /** * @license AngularJS v1.6.7 * (c) 2010-2017 Google, Inc. http://angularjs.org @@ -12428,2612 +11528,845 @@ angular.module('ui.bootstrap.datepickerPopup').run(function() {!angular.$$csp(). angular.module('ui.bootstrap.tooltip').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTooltipCss && angular.element(document).find('head').prepend(''); angular.$$uibTooltipCss = true; }); angular.module('ui.bootstrap.timepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTimepickerCss && angular.element(document).find('head').prepend(''); angular.$$uibTimepickerCss = true; }); angular.module('ui.bootstrap.typeahead').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTypeaheadCss && angular.element(document).find('head').prepend(''); angular.$$uibTypeaheadCss = true; }); -(function() { - 'use strict'; - - angular.module('toastr', []) - .factory('toastr', toastr); - - toastr.$inject = ['$animate', '$injector', '$document', '$rootScope', '$sce', 'toastrConfig', '$q']; - - function toastr($animate, $injector, $document, $rootScope, $sce, toastrConfig, $q) { - var container; - var index = 0; - var toasts = []; - - var previousToastMessage = ''; - var openToasts = {}; - - var containerDefer = $q.defer(); - - var toast = { - active: active, - clear: clear, - error: error, - info: info, - remove: remove, - success: success, - warning: warning, - refreshTimer: refreshTimer - }; - - return toast; - - /* Public API */ - function active() { - return toasts.length; - } - - function clear(toast) { - // Bit of a hack, I will remove this soon with a BC - if (arguments.length === 1 && !toast) { return; } - - if (toast) { - remove(toast.toastId); - } else { - for (var i = 0; i < toasts.length; i++) { - remove(toasts[i].toastId); - } - } - } - - function error(message, title, optionsOverride) { - var type = _getOptions().iconClasses.error; - return _buildNotification(type, message, title, optionsOverride); - } - - function info(message, title, optionsOverride) { - var type = _getOptions().iconClasses.info; - return _buildNotification(type, message, title, optionsOverride); - } - - function success(message, title, optionsOverride) { - var type = _getOptions().iconClasses.success; - return _buildNotification(type, message, title, optionsOverride); - } - - function warning(message, title, optionsOverride) { - var type = _getOptions().iconClasses.warning; - return _buildNotification(type, message, title, optionsOverride); - } - - function refreshTimer(toast, newTime) { - if (toast && toast.isOpened && toasts.indexOf(toast) >= 0) { - toast.scope.refreshTimer(newTime); - } - } - - function remove(toastId, wasClicked) { - var toast = findToast(toastId); - - if (toast && ! toast.deleting) { // Avoid clicking when fading out - toast.deleting = true; - toast.isOpened = false; - $animate.leave(toast.el).then(function() { - if (toast.scope.options.onHidden) { - toast.scope.options.onHidden(!!wasClicked, toast); - } - toast.scope.$destroy(); - var index = toasts.indexOf(toast); - delete openToasts[toast.scope.message]; - toasts.splice(index, 1); - var maxOpened = toastrConfig.maxOpened; - if (maxOpened && toasts.length >= maxOpened) { - toasts[maxOpened - 1].open.resolve(); - } - if (lastToast()) { - container.remove(); - container = null; - containerDefer = $q.defer(); - } - }); - } - - function findToast(toastId) { - for (var i = 0; i < toasts.length; i++) { - if (toasts[i].toastId === toastId) { - return toasts[i]; - } - } - } - - function lastToast() { - return !toasts.length; - } - } - - /* Internal functions */ - function _buildNotification(type, message, title, optionsOverride) { - if (angular.isObject(title)) { - optionsOverride = title; - title = null; - } - - return _notify({ - iconClass: type, - message: message, - optionsOverride: optionsOverride, - title: title - }); - } - - function _getOptions() { - return angular.extend({}, toastrConfig); - } - - function _createOrGetContainer(options) { - if(container) { return containerDefer.promise; } - - container = angular.element('
'); - container.attr('id', options.containerId); - container.addClass(options.positionClass); - container.css({'pointer-events': 'auto'}); - - var target = angular.element(document.querySelector(options.target)); - - if ( ! target || ! target.length) { - throw 'Target for toasts doesn\'t exist'; - } - - $animate.enter(container, target).then(function() { - containerDefer.resolve(); - }); - - return containerDefer.promise; - } - - function _notify(map) { - var options = _getOptions(); - - if (shouldExit()) { return; } - - var newToast = createToast(); - - toasts.push(newToast); - - if (ifMaxOpenedAndAutoDismiss()) { - var oldToasts = toasts.slice(0, (toasts.length - options.maxOpened)); - for (var i = 0, len = oldToasts.length; i < len; i++) { - remove(oldToasts[i].toastId); - } - } - - if (maxOpenedNotReached()) { - newToast.open.resolve(); - } - - newToast.open.promise.then(function() { - _createOrGetContainer(options).then(function() { - newToast.isOpened = true; - if (options.newestOnTop) { - $animate.enter(newToast.el, container).then(function() { - newToast.scope.init(); - }); - } else { - var sibling = container[0].lastChild ? angular.element(container[0].lastChild) : null; - $animate.enter(newToast.el, container, sibling).then(function() { - newToast.scope.init(); - }); - } - }); - }); - - return newToast; - - function ifMaxOpenedAndAutoDismiss() { - return options.autoDismiss && options.maxOpened && toasts.length > options.maxOpened; - } - - function createScope(toast, map, options) { - if (options.allowHtml) { - toast.scope.allowHtml = true; - toast.scope.title = $sce.trustAsHtml(map.title); - toast.scope.message = $sce.trustAsHtml(map.message); - } else { - toast.scope.title = map.title; - toast.scope.message = map.message; - } - - toast.scope.toastType = toast.iconClass; - toast.scope.toastId = toast.toastId; - toast.scope.extraData = options.extraData; - - toast.scope.options = { - extendedTimeOut: options.extendedTimeOut, - messageClass: options.messageClass, - onHidden: options.onHidden, - onShown: generateEvent('onShown'), - onTap: generateEvent('onTap'), - progressBar: options.progressBar, - tapToDismiss: options.tapToDismiss, - timeOut: options.timeOut, - titleClass: options.titleClass, - toastClass: options.toastClass - }; - - if (options.closeButton) { - toast.scope.options.closeHtml = options.closeHtml; - } - - function generateEvent(event) { - if (options[event]) { - return function() { - options[event](toast); - }; - } - } - } - - function createToast() { - var newToast = { - toastId: index++, - isOpened: false, - scope: $rootScope.$new(), - open: $q.defer() - }; - newToast.iconClass = map.iconClass; - if (map.optionsOverride) { - angular.extend(options, cleanOptionsOverride(map.optionsOverride)); - newToast.iconClass = map.optionsOverride.iconClass || newToast.iconClass; - } - - createScope(newToast, map, options); - - newToast.el = createToastEl(newToast.scope); - - return newToast; - - function cleanOptionsOverride(options) { - var badOptions = ['containerId', 'iconClasses', 'maxOpened', 'newestOnTop', - 'positionClass', 'preventDuplicates', 'preventOpenDuplicates', 'templates']; - for (var i = 0, l = badOptions.length; i < l; i++) { - delete options[badOptions[i]]; - } - - return options; - } - } - - function createToastEl(scope) { - var angularDomEl = angular.element('
'), - $compile = $injector.get('$compile'); - return $compile(angularDomEl)(scope); - } - - function maxOpenedNotReached() { - return options.maxOpened && toasts.length <= options.maxOpened || !options.maxOpened; - } - - function shouldExit() { - var isDuplicateOfLast = options.preventDuplicates && map.message === previousToastMessage; - var isDuplicateOpen = options.preventOpenDuplicates && openToasts[map.message]; - - if (isDuplicateOfLast || isDuplicateOpen) { - return true; - } - - previousToastMessage = map.message; - openToasts[map.message] = true; - - return false; - } - } - } -}()); - -(function() { - 'use strict'; - - angular.module('toastr') - .constant('toastrConfig', { - allowHtml: false, - autoDismiss: false, - closeButton: false, - closeHtml: '', - containerId: 'toast-container', - extendedTimeOut: 1000, - iconClasses: { - error: 'toast-error', - info: 'toast-info', - success: 'toast-success', - warning: 'toast-warning' - }, - maxOpened: 0, - messageClass: 'toast-message', - newestOnTop: true, - onHidden: null, - onShown: null, - onTap: null, - positionClass: 'toast-top-right', - preventDuplicates: false, - preventOpenDuplicates: false, - progressBar: false, - tapToDismiss: true, - target: 'body', - templates: { - toast: 'directives/toast/toast.html', - progressbar: 'directives/progressbar/progressbar.html' - }, - timeOut: 5000, - titleClass: 'toast-title', - toastClass: 'toast' - }); -}()); - -(function() { - 'use strict'; - - angular.module('toastr') - .directive('progressBar', progressBar); - - progressBar.$inject = ['toastrConfig']; - - function progressBar(toastrConfig) { - return { - require: '^toast', - templateUrl: function() { - return toastrConfig.templates.progressbar; - }, - link: linkFunction - }; - - function linkFunction(scope, element, attrs, toastCtrl) { - var intervalId, currentTimeOut, hideTime; - - toastCtrl.progressBar = scope; - - scope.start = function(duration) { - if (intervalId) { - clearInterval(intervalId); - } - - currentTimeOut = parseFloat(duration); - hideTime = new Date().getTime() + currentTimeOut; - intervalId = setInterval(updateProgress, 10); - }; - - scope.stop = function() { - if (intervalId) { - clearInterval(intervalId); - } - }; - - function updateProgress() { - var percentage = ((hideTime - (new Date().getTime())) / currentTimeOut) * 100; - element.css('width', percentage + '%'); - } - - scope.$on('$destroy', function() { - // Failsafe stop - clearInterval(intervalId); - }); - } - } -}()); - -(function() { - 'use strict'; - - angular.module('toastr') - .controller('ToastController', ToastController); - - function ToastController() { - this.progressBar = null; - - this.startProgressBar = function(duration) { - if (this.progressBar) { - this.progressBar.start(duration); - } - }; - - this.stopProgressBar = function() { - if (this.progressBar) { - this.progressBar.stop(); - } - }; - } -}()); - -(function() { - 'use strict'; - - angular.module('toastr') - .directive('toast', toast); - - toast.$inject = ['$injector', '$interval', 'toastrConfig', 'toastr']; - - function toast($injector, $interval, toastrConfig, toastr) { - return { - templateUrl: function() { - return toastrConfig.templates.toast; - }, - controller: 'ToastController', - link: toastLinkFunction - }; - - function toastLinkFunction(scope, element, attrs, toastCtrl) { - var timeout; - - scope.toastClass = scope.options.toastClass; - scope.titleClass = scope.options.titleClass; - scope.messageClass = scope.options.messageClass; - scope.progressBar = scope.options.progressBar; - - if (wantsCloseButton()) { - var button = angular.element(scope.options.closeHtml), - $compile = $injector.get('$compile'); - button.addClass('toast-close-button'); - button.attr('ng-click', 'close(true, $event)'); - $compile(button)(scope); - element.children().prepend(button); - } - - scope.init = function() { - if (scope.options.timeOut) { - timeout = createTimeout(scope.options.timeOut); - } - if (scope.options.onShown) { - scope.options.onShown(); - } - }; - - element.on('mouseenter', function() { - hideAndStopProgressBar(); - if (timeout) { - $interval.cancel(timeout); - } - }); - - scope.tapToast = function () { - if (angular.isFunction(scope.options.onTap)) { - scope.options.onTap(); - } - if (scope.options.tapToDismiss) { - scope.close(true); - } - }; - - scope.close = function (wasClicked, $event) { - if ($event && angular.isFunction($event.stopPropagation)) { - $event.stopPropagation(); - } - toastr.remove(scope.toastId, wasClicked); - }; - - scope.refreshTimer = function(newTime) { - if (timeout) { - $interval.cancel(timeout); - timeout = createTimeout(newTime || scope.options.timeOut); - } - }; - - element.on('mouseleave', function() { - if (scope.options.timeOut === 0 && scope.options.extendedTimeOut === 0) { return; } - scope.$apply(function() { - scope.progressBar = scope.options.progressBar; - }); - timeout = createTimeout(scope.options.extendedTimeOut); - }); - - function createTimeout(time) { - toastCtrl.startProgressBar(time); - return $interval(function() { - toastCtrl.stopProgressBar(); - toastr.remove(scope.toastId); - }, time, 1); - } - - function hideAndStopProgressBar() { - scope.progressBar = false; - toastCtrl.stopProgressBar(); - } - - function wantsCloseButton() { - return scope.options.closeHtml; - } - } - } -}()); - -(function() { - 'use strict'; - - angular.module('toastr', []) - .factory('toastr', toastr); - - toastr.$inject = ['$animate', '$injector', '$document', '$rootScope', '$sce', 'toastrConfig', '$q']; - - function toastr($animate, $injector, $document, $rootScope, $sce, toastrConfig, $q) { - var container; - var index = 0; - var toasts = []; - - var previousToastMessage = ''; - var openToasts = {}; - - var containerDefer = $q.defer(); - - var toast = { - active: active, - clear: clear, - error: error, - info: info, - remove: remove, - success: success, - warning: warning, - refreshTimer: refreshTimer - }; - - return toast; - - /* Public API */ - function active() { - return toasts.length; - } - - function clear(toast) { - // Bit of a hack, I will remove this soon with a BC - if (arguments.length === 1 && !toast) { return; } - - if (toast) { - remove(toast.toastId); - } else { - for (var i = 0; i < toasts.length; i++) { - remove(toasts[i].toastId); - } - } - } - - function error(message, title, optionsOverride) { - var type = _getOptions().iconClasses.error; - return _buildNotification(type, message, title, optionsOverride); - } - - function info(message, title, optionsOverride) { - var type = _getOptions().iconClasses.info; - return _buildNotification(type, message, title, optionsOverride); - } - - function success(message, title, optionsOverride) { - var type = _getOptions().iconClasses.success; - return _buildNotification(type, message, title, optionsOverride); - } - - function warning(message, title, optionsOverride) { - var type = _getOptions().iconClasses.warning; - return _buildNotification(type, message, title, optionsOverride); - } - - function refreshTimer(toast, newTime) { - if (toast && toast.isOpened && toasts.indexOf(toast) >= 0) { - toast.scope.refreshTimer(newTime); - } - } - - function remove(toastId, wasClicked) { - var toast = findToast(toastId); - - if (toast && ! toast.deleting) { // Avoid clicking when fading out - toast.deleting = true; - toast.isOpened = false; - $animate.leave(toast.el).then(function() { - if (toast.scope.options.onHidden) { - toast.scope.options.onHidden(!!wasClicked, toast); - } - toast.scope.$destroy(); - var index = toasts.indexOf(toast); - delete openToasts[toast.scope.message]; - toasts.splice(index, 1); - var maxOpened = toastrConfig.maxOpened; - if (maxOpened && toasts.length >= maxOpened) { - toasts[maxOpened - 1].open.resolve(); - } - if (lastToast()) { - container.remove(); - container = null; - containerDefer = $q.defer(); - } - }); - } - - function findToast(toastId) { - for (var i = 0; i < toasts.length; i++) { - if (toasts[i].toastId === toastId) { - return toasts[i]; - } - } - } - - function lastToast() { - return !toasts.length; - } - } - - /* Internal functions */ - function _buildNotification(type, message, title, optionsOverride) { - if (angular.isObject(title)) { - optionsOverride = title; - title = null; - } - - return _notify({ - iconClass: type, - message: message, - optionsOverride: optionsOverride, - title: title - }); - } - - function _getOptions() { - return angular.extend({}, toastrConfig); - } - - function _createOrGetContainer(options) { - if(container) { return containerDefer.promise; } - - container = angular.element('
'); - container.attr('id', options.containerId); - container.addClass(options.positionClass); - container.css({'pointer-events': 'auto'}); - - var target = angular.element(document.querySelector(options.target)); - - if ( ! target || ! target.length) { - throw 'Target for toasts doesn\'t exist'; - } - - $animate.enter(container, target).then(function() { - containerDefer.resolve(); - }); - - return containerDefer.promise; - } - - function _notify(map) { - var options = _getOptions(); - - if (shouldExit()) { return; } - - var newToast = createToast(); - - toasts.push(newToast); - - if (ifMaxOpenedAndAutoDismiss()) { - var oldToasts = toasts.slice(0, (toasts.length - options.maxOpened)); - for (var i = 0, len = oldToasts.length; i < len; i++) { - remove(oldToasts[i].toastId); - } - } - - if (maxOpenedNotReached()) { - newToast.open.resolve(); - } - - newToast.open.promise.then(function() { - _createOrGetContainer(options).then(function() { - newToast.isOpened = true; - if (options.newestOnTop) { - $animate.enter(newToast.el, container).then(function() { - newToast.scope.init(); - }); - } else { - var sibling = container[0].lastChild ? angular.element(container[0].lastChild) : null; - $animate.enter(newToast.el, container, sibling).then(function() { - newToast.scope.init(); - }); - } - }); - }); - - return newToast; - - function ifMaxOpenedAndAutoDismiss() { - return options.autoDismiss && options.maxOpened && toasts.length > options.maxOpened; - } - - function createScope(toast, map, options) { - if (options.allowHtml) { - toast.scope.allowHtml = true; - toast.scope.title = $sce.trustAsHtml(map.title); - toast.scope.message = $sce.trustAsHtml(map.message); - } else { - toast.scope.title = map.title; - toast.scope.message = map.message; - } - - toast.scope.toastType = toast.iconClass; - toast.scope.toastId = toast.toastId; - toast.scope.extraData = options.extraData; - - toast.scope.options = { - extendedTimeOut: options.extendedTimeOut, - messageClass: options.messageClass, - onHidden: options.onHidden, - onShown: generateEvent('onShown'), - onTap: generateEvent('onTap'), - progressBar: options.progressBar, - tapToDismiss: options.tapToDismiss, - timeOut: options.timeOut, - titleClass: options.titleClass, - toastClass: options.toastClass - }; - - if (options.closeButton) { - toast.scope.options.closeHtml = options.closeHtml; - } - - function generateEvent(event) { - if (options[event]) { - return function() { - options[event](toast); - }; - } - } - } - - function createToast() { - var newToast = { - toastId: index++, - isOpened: false, - scope: $rootScope.$new(), - open: $q.defer() - }; - newToast.iconClass = map.iconClass; - if (map.optionsOverride) { - angular.extend(options, cleanOptionsOverride(map.optionsOverride)); - newToast.iconClass = map.optionsOverride.iconClass || newToast.iconClass; - } - - createScope(newToast, map, options); - - newToast.el = createToastEl(newToast.scope); - - return newToast; - - function cleanOptionsOverride(options) { - var badOptions = ['containerId', 'iconClasses', 'maxOpened', 'newestOnTop', - 'positionClass', 'preventDuplicates', 'preventOpenDuplicates', 'templates']; - for (var i = 0, l = badOptions.length; i < l; i++) { - delete options[badOptions[i]]; - } - - return options; - } - } - - function createToastEl(scope) { - var angularDomEl = angular.element('
'), - $compile = $injector.get('$compile'); - return $compile(angularDomEl)(scope); - } - - function maxOpenedNotReached() { - return options.maxOpened && toasts.length <= options.maxOpened || !options.maxOpened; - } - - function shouldExit() { - var isDuplicateOfLast = options.preventDuplicates && map.message === previousToastMessage; - var isDuplicateOpen = options.preventOpenDuplicates && openToasts[map.message]; - - if (isDuplicateOfLast || isDuplicateOpen) { - return true; - } - - previousToastMessage = map.message; - openToasts[map.message] = true; - - return false; - } - } - } -}()); - -(function() { - 'use strict'; - - angular.module('toastr') - .constant('toastrConfig', { - allowHtml: false, - autoDismiss: false, - closeButton: false, - closeHtml: '', - containerId: 'toast-container', - extendedTimeOut: 1000, - iconClasses: { - error: 'toast-error', - info: 'toast-info', - success: 'toast-success', - warning: 'toast-warning' - }, - maxOpened: 0, - messageClass: 'toast-message', - newestOnTop: true, - onHidden: null, - onShown: null, - onTap: null, - positionClass: 'toast-top-right', - preventDuplicates: false, - preventOpenDuplicates: false, - progressBar: false, - tapToDismiss: true, - target: 'body', - templates: { - toast: 'directives/toast/toast.html', - progressbar: 'directives/progressbar/progressbar.html' - }, - timeOut: 5000, - titleClass: 'toast-title', - toastClass: 'toast' - }); -}()); - -(function() { - 'use strict'; - - angular.module('toastr') - .directive('progressBar', progressBar); - - progressBar.$inject = ['toastrConfig']; - - function progressBar(toastrConfig) { - return { - require: '^toast', - templateUrl: function() { - return toastrConfig.templates.progressbar; - }, - link: linkFunction - }; - - function linkFunction(scope, element, attrs, toastCtrl) { - var intervalId, currentTimeOut, hideTime; - - toastCtrl.progressBar = scope; - - scope.start = function(duration) { - if (intervalId) { - clearInterval(intervalId); - } - - currentTimeOut = parseFloat(duration); - hideTime = new Date().getTime() + currentTimeOut; - intervalId = setInterval(updateProgress, 10); - }; - - scope.stop = function() { - if (intervalId) { - clearInterval(intervalId); - } - }; - - function updateProgress() { - var percentage = ((hideTime - (new Date().getTime())) / currentTimeOut) * 100; - element.css('width', percentage + '%'); - } - - scope.$on('$destroy', function() { - // Failsafe stop - clearInterval(intervalId); - }); - } - } -}()); - -(function() { - 'use strict'; - - angular.module('toastr') - .controller('ToastController', ToastController); - - function ToastController() { - this.progressBar = null; - - this.startProgressBar = function(duration) { - if (this.progressBar) { - this.progressBar.start(duration); - } - }; - - this.stopProgressBar = function() { - if (this.progressBar) { - this.progressBar.stop(); - } - }; - } -}()); - -(function() { - 'use strict'; - - angular.module('toastr') - .directive('toast', toast); - - toast.$inject = ['$injector', '$interval', 'toastrConfig', 'toastr']; - - function toast($injector, $interval, toastrConfig, toastr) { - return { - templateUrl: function() { - return toastrConfig.templates.toast; - }, - controller: 'ToastController', - link: toastLinkFunction - }; - - function toastLinkFunction(scope, element, attrs, toastCtrl) { - var timeout; - - scope.toastClass = scope.options.toastClass; - scope.titleClass = scope.options.titleClass; - scope.messageClass = scope.options.messageClass; - scope.progressBar = scope.options.progressBar; - - if (wantsCloseButton()) { - var button = angular.element(scope.options.closeHtml), - $compile = $injector.get('$compile'); - button.addClass('toast-close-button'); - button.attr('ng-click', 'close(true, $event)'); - $compile(button)(scope); - element.children().prepend(button); - } - - scope.init = function() { - if (scope.options.timeOut) { - timeout = createTimeout(scope.options.timeOut); - } - if (scope.options.onShown) { - scope.options.onShown(); - } - }; - - element.on('mouseenter', function() { - hideAndStopProgressBar(); - if (timeout) { - $interval.cancel(timeout); - } - }); - - scope.tapToast = function () { - if (angular.isFunction(scope.options.onTap)) { - scope.options.onTap(); - } - if (scope.options.tapToDismiss) { - scope.close(true); - } - }; - - scope.close = function (wasClicked, $event) { - if ($event && angular.isFunction($event.stopPropagation)) { - $event.stopPropagation(); - } - toastr.remove(scope.toastId, wasClicked); - }; - - scope.refreshTimer = function(newTime) { - if (timeout) { - $interval.cancel(timeout); - timeout = createTimeout(newTime || scope.options.timeOut); - } - }; - - element.on('mouseleave', function() { - if (scope.options.timeOut === 0 && scope.options.extendedTimeOut === 0) { return; } - scope.$apply(function() { - scope.progressBar = scope.options.progressBar; - }); - timeout = createTimeout(scope.options.extendedTimeOut); - }); - - function createTimeout(time) { - toastCtrl.startProgressBar(time); - return $interval(function() { - toastCtrl.stopProgressBar(); - toastr.remove(scope.toastId); - }, time, 1); - } - - function hideAndStopProgressBar() { - scope.progressBar = false; - toastCtrl.stopProgressBar(); - } - - function wantsCloseButton() { - return scope.options.closeHtml; - } - } - } -}()); - -angular.module("toastr").run(["$templateCache", function($templateCache) {$templateCache.put("directives/progressbar/progressbar.html","
\n"); -$templateCache.put("directives/toast/toast.html","
\n
\n
{{title}}
\n
{{message}}
\n
\n
\n
\n \n
\n");}]); -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.angularStripe = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o - * - * Copyright (c) 2014 Jon Schlinkert, contributors. - * Licensed under the MIT license. - */ - -var isNumber = _dereq_('is-number'); -var slice = _dereq_('array-slice'); - -module.exports = function last(arr, num) { - if (!Array.isArray(arr)) { - throw new Error('array-last expects an array as the first argument.'); - } - - if (arr.length === 0) { - return null; - } - - var res = slice(arr, arr.length - (isNumber(num) ? +num : 1)); - if (+num === 1 || num == null) { - return res[0]; - } - return res; -}; - -},{"array-slice":8,"is-number":20}],8:[function(_dereq_,module,exports){ -/*! - * array-slice - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -module.exports = function slice(arr, start, end) { - var len = arr.length >>> 0; - var range = []; - - start = idx(arr, start); - end = idx(arr, end, len); - - while (start < end) { - range.push(arr[start++]); - } - return range; -}; - - -function idx(arr, pos, end) { - var len = arr.length >>> 0; - - if (pos == null) { - pos = end || 0; - } else if (pos < 0) { - pos = Math.max(len + pos, 0); - } else { - pos = Math.min(pos, len); - } - - return pos; -} -},{}],9:[function(_dereq_,module,exports){ -"use strict"; - -// rawAsap provides everything we need except exception management. -var rawAsap = _dereq_("./raw"); -// RawTasks are recycled to reduce GC churn. -var freeTasks = []; -// We queue errors to ensure they are thrown in right order (FIFO). -// Array-as-queue is good enough here, since we are just dealing with exceptions. -var pendingErrors = []; -var requestErrorThrow = rawAsap.makeRequestCallFromTimer(throwFirstError); - -function throwFirstError() { - if (pendingErrors.length) { - throw pendingErrors.shift(); - } -} - -/** - * Calls a task as soon as possible after returning, in its own event, with priority - * over other events like animation, reflow, and repaint. An error thrown from an - * event will not interrupt, nor even substantially slow down the processing of - * other events, but will be rather postponed to a lower priority event. - * @param {{call}} task A callable object, typically a function that takes no - * arguments. - */ -module.exports = asap; -function asap(task) { - var rawTask; - if (freeTasks.length) { - rawTask = freeTasks.pop(); - } else { - rawTask = new RawTask(); - } - rawTask.task = task; - rawAsap(rawTask); -} - -// We wrap tasks with recyclable task objects. A task object implements -// `call`, just like a function. -function RawTask() { - this.task = null; -} - -// The sole purpose of wrapping the task is to catch the exception and recycle -// the task object after its single use. -RawTask.prototype.call = function () { - try { - this.task.call(); - } catch (error) { - if (asap.onerror) { - // This hook exists purely for testing purposes. - // Its name will be periodically randomized to break any code that - // depends on its existence. - asap.onerror(error); - } else { - // In a web browser, exceptions are not fatal. However, to avoid - // slowing down the queue of pending tasks, we rethrow the error in a - // lower priority turn. - pendingErrors.push(error); - requestErrorThrow(); + if ($attributes.ccType) { + $scope.$watch($attributes.ccType, function () { + ngModel.$validate() + }) } - } finally { - this.task = null; - freeTasks[freeTasks.length] = this; - } -}; -},{"./raw":10}],10:[function(_dereq_,module,exports){ -(function (global){ -"use strict"; + if ($attributes.ccFormat != null) { + ngModel.$formatters.unshift(card.format) + $element.on('input', function formatInput () { + var input = $element.val() + var previous = $viewValue() + if (!input) return + var element = $element[0] + var formatted = card.format(card.parse(input)) -// Use the fastest means possible to execute a task in its own turn, with -// priority over other events including IO, animation, reflow, and redraw -// events in browsers. -// -// An exception thrown by a task will permanently interrupt the processing of -// subsequent tasks. The higher level `asap` function ensures that if an -// exception is thrown by a task, that the task queue will continue flushing as -// soon as possible, but if you use `rawAsap` directly, you are responsible to -// either ensure that no exceptions are thrown from your task, or to manually -// call `rawAsap.requestFlush` if an exception is thrown. -module.exports = rawAsap; -function rawAsap(task) { - if (!queue.length) { - requestFlush(); - flushing = true; - } - // Equivalent to push, but avoids a function call. - queue[queue.length] = task; -} + var selectionEnd = element.selectionEnd + ngModel.$setViewValue(formatted) + ngModel.$render() -var queue = []; -// Once a flush has been requested, no further calls to `requestFlush` are -// necessary until the next `flush` completes. -var flushing = false; -// `requestFlush` is an implementation-specific method that attempts to kick -// off a `flush` event as quickly as possible. `flush` will attempt to exhaust -// the event queue before yielding to the browser's own event loop. -var requestFlush; -// The position of the next task to execute in the task queue. This is -// preserved between calls to `flush` so that it can be resumed if -// a task throws an exception. -var index = 0; -// If a task schedules additional tasks recursively, the task queue can grow -// unbounded. To prevent memory exhaustion, the task queue will periodically -// truncate already-completed tasks. -var capacity = 1024; - -// The flush function processes all tasks that have been scheduled with -// `rawAsap` unless and until one of those tasks throws an exception. -// If a task throws an exception, `flush` ensures that its state will remain -// consistent and will resume where it left off when called again. -// However, `flush` does not make any arrangements to be called again if an -// exception is thrown. -function flush() { - while (index < queue.length) { - var currentIndex = index; - // Advance the index before calling the task. This ensures that we will - // begin flushing on the next task the task throws an error. - index = index + 1; - queue[currentIndex].call(); - // Prevent leaking memory for long chains of recursive calls to `asap`. - // If we call `asap` within tasks scheduled by `asap`, the queue will - // grow, but to avoid an O(n) walk for every task we execute, we don't - // shift tasks off the queue after they have been executed. - // Instead, we periodically shift 1024 tasks off the queue. - if (index > capacity) { - // Manually shift all values starting at the index back to the - // beginning of the queue. - for (var scan = 0, newLength = queue.length - index; scan < newLength; scan++) { - queue[scan] = queue[scan + index]; + if (previous && previous.length < formatted.length) { + selectionEnd = formatted.length } - queue.length -= index; - index = 0; - } - } - queue.length = 0; - index = 0; - flushing = false; -} - -// `requestFlush` is implemented using a strategy based on data collected from -// every available SauceLabs Selenium web driver worker at time of writing. -// https://docs.google.com/spreadsheets/d/1mG-5UYGup5qxGdEMWkhP6BWCz053NUb2E1QoUTU16uA/edit#gid=783724593 - -// Safari 6 and 6.1 for desktop, iPad, and iPhone are the only browsers that -// have WebKitMutationObserver but not un-prefixed MutationObserver. -// Must use `global` or `self` instead of `window` to work in both frames and web -// workers. `global` is a provision of Browserify, Mr, Mrs, or Mop. - -/* globals self */ -var scope = typeof global !== "undefined" ? global : self; -var BrowserMutationObserver = scope.MutationObserver || scope.WebKitMutationObserver; - -// MutationObservers are desirable because they have high priority and work -// reliably everywhere they are implemented. -// They are implemented in all modern browsers. -// -// - Android 4-4.3 -// - Chrome 26-34 -// - Firefox 14-29 -// - Internet Explorer 11 -// - iPad Safari 6-7.1 -// - iPhone Safari 7-7.1 -// - Safari 6-7 -if (typeof BrowserMutationObserver === "function") { - requestFlush = makeRequestCallFromMutationObserver(flush); - -// MessageChannels are desirable because they give direct access to the HTML -// task queue, are implemented in Internet Explorer 10, Safari 5.0-1, and Opera -// 11-12, and in web workers in many engines. -// Although message channels yield to any queued rendering and IO tasks, they -// would be better than imposing the 4ms delay of timers. -// However, they do not work reliably in Internet Explorer or Safari. - -// Internet Explorer 10 is the only browser that has setImmediate but does -// not have MutationObservers. -// Although setImmediate yields to the browser's renderer, it would be -// preferrable to falling back to setTimeout since it does not have -// the minimum 4ms penalty. -// Unfortunately there appears to be a bug in Internet Explorer 10 Mobile (and -// Desktop to a lesser extent) that renders both setImmediate and -// MessageChannel useless for the purposes of ASAP. -// https://github.com/kriskowal/q/issues/396 - -// Timers are implemented universally. -// We fall back to timers in workers in most engines, and in foreground -// contexts in the following browsers. -// However, note that even this simple case requires nuances to operate in a -// broad spectrum of browsers. -// -// - Firefox 3-13 -// - Internet Explorer 6-9 -// - iPad Safari 4.3 -// - Lynx 2.8.7 -} else { - requestFlush = makeRequestCallFromTimer(flush); -} - -// `requestFlush` requests that the high priority event queue be flushed as -// soon as possible. -// This is useful to prevent an error thrown in a task from stalling the event -// queue if the exception handled by Node.js’s -// `process.on("uncaughtException")` or by a domain. -rawAsap.requestFlush = requestFlush; - -// To request a high priority event, we induce a mutation observer by toggling -// the text of a text node between "1" and "-1". -function makeRequestCallFromMutationObserver(callback) { - var toggle = 1; - var observer = new BrowserMutationObserver(callback); - var node = document.createTextNode(""); - observer.observe(node, {characterData: true}); - return function requestCall() { - toggle = -toggle; - node.data = toggle; - }; -} - -// The message channel technique was discovered by Malte Ubl and was the -// original foundation for this library. -// http://www.nonblocking.io/2011/06/windownexttick.html - -// Safari 6.0.5 (at least) intermittently fails to create message ports on a -// page's first load. Thankfully, this version of Safari supports -// MutationObservers, so we don't need to fall back in that case. - -// function makeRequestCallFromMessageChannel(callback) { -// var channel = new MessageChannel(); -// channel.port1.onmessage = callback; -// return function requestCall() { -// channel.port2.postMessage(0); -// }; -// } - -// For reasons explained above, we are also unable to use `setImmediate` -// under any circumstances. -// Even if we were, there is another bug in Internet Explorer 10. -// It is not sufficient to assign `setImmediate` to `requestFlush` because -// `setImmediate` must be called *by name* and therefore must be wrapped in a -// closure. -// Never forget. - -// function makeRequestCallFromSetImmediate(callback) { -// return function requestCall() { -// setImmediate(callback); -// }; -// } - -// Safari 6.0 has a problem where timers will get lost while the user is -// scrolling. This problem does not impact ASAP because Safari 6.0 supports -// mutation observers, so that implementation is used instead. -// However, if we ever elect to use timers in Safari, the prevalent work-around -// is to add a scroll event listener that calls for a flush. - -// `setTimeout` does not call the passed callback if the delay is less than -// approximately 7 in web workers in Firefox 8 through 18, and sometimes not -// even then. - -function makeRequestCallFromTimer(callback) { - return function requestCall() { - // We dispatch a timeout with a specified delay of 0 for engines that - // can reliably accommodate that request. This will usually be snapped - // to a 4 milisecond delay, but once we're flushing, there's no delay - // between events. - var timeoutHandle = setTimeout(handleTimer, 0); - // However, since this timer gets frequently dropped in Firefox - // workers, we enlist an interval handle that will try to fire - // an event 20 times per second until it succeeds. - var intervalHandle = setInterval(handleTimer, 50); - - function handleTimer() { - // Whichever timer succeeds will cancel both timers and - // execute the callback. - clearTimeout(timeoutHandle); - clearInterval(intervalHandle); - callback(); - } - }; -} - -// This is for `asap.js` only. -// Its name will be periodically randomized to break any code that depends on -// its existence. -rawAsap.makeRequestCallFromTimer = makeRequestCallFromTimer; - -// ASAP was originally a nextTick shim included in Q. This was factored out -// into this ASAP package. It was later adapted to RSVP which made further -// amendments. These decisions, particularly to marginalize MessageChannel and -// to capture the MutationObserver implementation in a closure, were integrated -// back into ASAP proper. -// https://github.com/tildeio/rsvp.js/blob/cddf7232546a9cf858524b75cde6f9edf72620a7/lib/rsvp/asap.js - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],11:[function(_dereq_,module,exports){ -'use strict' - -var assert = _dereq_('assert-ok') -var format = _dereq_('simple-format') -var print = _dereq_('print-value') - -module.exports = function assertEqual (a, b) { - assert(a === b, format('expected `%s` to equal `%s`', print(a), print(b))) -} - -},{"assert-ok":13,"print-value":30,"simple-format":32}],12:[function(_dereq_,module,exports){ -'use strict' - -module.exports = function assertFunction (value) { - if (typeof value !== 'function') { - throw new TypeError('Expected function, got: ' + value) - } -} - -},{}],13:[function(_dereq_,module,exports){ -'use strict' - -module.exports = function assertOk (value, message) { - if (!value) { - throw new Error(message || 'Expected true, got ' + value) - } -} - -},{}],14:[function(_dereq_,module,exports){ -'use strict' - -module.exports = CallAll - -function CallAll (fns) { - fns = Array.isArray(fns) ? fns : arguments - return function callAll () { - var args = arguments - var ret = new Array(fns.length) - for (var i = 0, ii = fns.length; i < ii; i++) { - ret[i] = fns[i].apply(null, args) - } - return ret - } -} - -},{}],15:[function(_dereq_,module,exports){ -/** - * cuid.js - * Collision-resistant UID generator for browsers and node. - * Sequential for fast db lookups and recency sorting. - * Safe for element IDs and server-side lookups. - * - * Extracted from CLCTR - * - * Copyright (c) Eric Elliott 2012 - * MIT License - */ - -/*global window, navigator, document, require, process, module */ -(function (app) { - 'use strict'; - var namespace = 'cuid', - c = 0, - blockSize = 4, - base = 36, - discreteValues = Math.pow(base, blockSize), - - pad = function pad(num, size) { - var s = "000000000" + num; - return s.substr(s.length-size); - }, - - randomBlock = function randomBlock() { - return pad((Math.random() * - discreteValues << 0) - .toString(base), blockSize); - }, - - safeCounter = function () { - c = (c < discreteValues) ? c : 0; - c++; // this is not subliminal - return c - 1; - }, - - api = function cuid() { - // Starting with a lowercase letter makes - // it HTML element ID friendly. - var letter = 'c', // hard-coded allows for sequential access - - // timestamp - // warning: this exposes the exact date and time - // that the uid was created. - timestamp = (new Date().getTime()).toString(base), - - // Prevent same-machine collisions. - counter, - - // A few chars to generate distinct ids for different - // clients (so different computers are far less - // likely to generate the same id) - fingerprint = api.fingerprint(), - - // Grab some more chars from Math.random() - random = randomBlock() + randomBlock(); - - counter = pad(safeCounter().toString(base), blockSize); - - return (letter + timestamp + counter + fingerprint + random); - }; - - api.slug = function slug() { - var date = new Date().getTime().toString(36), - counter, - print = api.fingerprint().slice(0,1) + - api.fingerprint().slice(-1), - random = randomBlock().slice(-2); - - counter = safeCounter().toString(36).slice(-4); - - return date.slice(-2) + - counter + print + random; - }; - - api.globalCount = function globalCount() { - // We want to cache the results of this - var cache = (function calc() { - var i, - count = 0; - - for (i in window) { - count++; + setCursorPostion(element, selectionEnd) + }) } - return count; - }()); + ngModel.$parsers.unshift(card.parse) - api.globalCount = function () { return cache; }; - return cache; - }; + ngModel.$validators.ccNumber = function validateCcNumber (number) { + return ngModel.$isEmpty(ngModel.$viewValue) || card.isValid(number) + } - api.fingerprint = function browserPrint() { - return pad((navigator.mimeTypes.length + - navigator.userAgent.length).toString(36) + - api.globalCount().toString(36), 4); - }; - - // don't change anything from here down. - if (app.register) { - app.register(namespace, api); - } else if (typeof module !== 'undefined') { - module.exports = api; - } else { - app[namespace] = api; - } - -}(this.applitude || this)); - -},{}],16:[function(_dereq_,module,exports){ -var wrappy = _dereq_('wrappy') -module.exports = wrappy(dezalgo) - -var asap = _dereq_('asap') - -function dezalgo (cb) { - var sync = true - asap(function () { - sync = false - }) - - return function zalgoSafe() { - var args = arguments - var me = this - if (sync) - asap(function() { - cb.apply(me, args) - }) - else - cb.apply(me, args) - } -} - -},{"asap":9,"wrappy":35}],17:[function(_dereq_,module,exports){ -'use strict'; -var isObj = _dereq_('is-obj'); - -module.exports.get = function (obj, path) { - if (!isObj(obj) || typeof path !== 'string') { - return obj; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - var descriptor = Object.getOwnPropertyDescriptor(obj, pathArr[i]) || Object.getOwnPropertyDescriptor(Object.prototype, pathArr[i]); - if (descriptor && !descriptor.enumerable) { - return; - } - - obj = obj[pathArr[i]]; - - if (obj === undefined || obj === null) { - // `obj` is either `undefined` or `null` so we want to stop the loop, and - // if this is not the last bit of the path, and - // if it did't return `undefined` - // it would return `null` if `obj` is `null` - // but we want `get({foo: null}, 'foo.bar')` to equal `undefined` not `null` - if (i !== pathArr.length - 1) { - return undefined; - } - - break; - } - } - - return obj; -}; - -module.exports.set = function (obj, path, value) { - if (!isObj(obj) || typeof path !== 'string') { - return; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - var p = pathArr[i]; - - if (!isObj(obj[p])) { - obj[p] = {}; - } - - if (i === pathArr.length - 1) { - obj[p] = value; - } - - obj = obj[p]; - } -}; - -module.exports.delete = function (obj, path) { - if (!isObj(obj) || typeof path !== 'string') { - return; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - var p = pathArr[i]; - - if (i === pathArr.length - 1) { - delete obj[p]; - return; - } - - obj = obj[p]; - } -}; - -module.exports.has = function (obj, path) { - if (!isObj(obj) || typeof path !== 'string') { - return false; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - obj = obj[pathArr[i]]; - - if (obj === undefined) { - return false; - } - } - - return true; -}; - -function getPathSegments(path) { - var pathArr = path.split('.'); - var parts = []; - - for (var i = 0; i < pathArr.length; i++) { - var p = pathArr[i]; - - while (p[p.length - 1] === '\\' && pathArr[i + 1] !== undefined) { - p = p.slice(0, -1) + '.'; - p += pathArr[++i]; - } - - parts.push(p); - } - - return parts; -} - -},{"is-obj":21}],18:[function(_dereq_,module,exports){ -'use strict' - -var assertFn = _dereq_('assert-function') - -module.exports = Ear - -function Ear () { - var callbacks = [] - - function listeners () { - var args = arguments - var i = 0 - var length = callbacks.length - for (; i < length; i++) { - var callback = callbacks[i] - callback.apply(null, args) - } - } - - listeners.add = function (listener) { - assertFn(listener) - callbacks.push(listener) - return function remove () { - var i = 0 - var length = callbacks.length - for (; i < length; i++) { - if (callbacks[i] === listener) { - callbacks.splice(i, 1) - return + ngModel.$validators.ccNumberType = function validateCcNumberType (number) { + if (ngModel.$isEmpty(ngModel.$viewValue)) return true + var type = $parse($attributes.ccType)($scope) + if (!type) card.isValid(number) + return array(type).some(partial(card.isValid, number)) } } } } - - return listeners } -},{"assert-function":12}],19:[function(_dereq_,module,exports){ -(function (global){ -var win; - -if (typeof window !== "undefined") { - win = window; -} else if (typeof global !== "undefined") { - win = global; -} else if (typeof self !== "undefined"){ - win = self; -} else { - win = {}; +},{"ap":5,"cast-array":6,"creditcards":13}],5:[function(_dereq_,module,exports){ +exports = module.exports = ap; +function ap (args, fn) { + return function () { + var rest = [].slice.call(arguments) + , first = args.slice() + first.push.apply(first, rest) + return fn.apply(this, first); + }; } -module.exports = win; +exports.pa = pa; +function pa (args, fn) { + return function () { + var rest = [].slice.call(arguments) + rest.push.apply(rest, args) + return fn.apply(this, rest); + }; +} -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],20:[function(_dereq_,module,exports){ -/*! - * is-number - * - * Copyright (c) 2014 Jon Schlinkert, contributors. - * Licensed under the MIT License - */ +exports.apa = apa; +function apa (left, right, fn) { + return function () { + return fn.apply(this, + left.concat.apply(left, arguments).concat(right) + ); + }; +} +exports.partial = partial; +function partial (fn) { + var args = [].slice.call(arguments, 1); + return ap(args, fn); +} + +exports.partialRight = partialRight; +function partialRight (fn) { + var args = [].slice.call(arguments, 1); + return pa(args, fn); +} + +exports.curry = curry; +function curry (fn) { + return partial(partial, fn); +} + +exports.curryRight = function curryRight (fn) { + return partial(partialRight, fn); +} + +},{}],6:[function(_dereq_,module,exports){ +'use strict' + +var isArray = _dereq_('isarray') + +module.exports = function castArray (value) { + return isArray(value) ? value : [value] +} + +},{"isarray":22}],7:[function(_dereq_,module,exports){ +'use strict' + +var types = exports.types = _dereq_('./src/types') +exports.Type = _dereq_('./src/type') + +exports.find = function findCardType (callback) { + for (var typeName in types) { + var type = types[typeName] + var result = callback(type) + if (result) return type + } +} + +},{"./src/type":8,"./src/types":9}],8:[function(_dereq_,module,exports){ +'use strict' + +var extend = _dereq_('xtend/mutable') + +module.exports = CardType + +function CardType (name, config) { + extend(this, {name: name}, config) +} + +CardType.prototype.cvcLength = 3 +CardType.prototype.luhn = true +CardType.prototype.groupPattern = /(\d{1,4})(\d{1,4})?(\d{1,4})?(\d{1,4})?/ + +CardType.prototype.group = function (number) { + return (number.match(this.groupPattern) || []) + .slice(1) + .filter(Boolean) +} + +CardType.prototype.test = function (number, eager) { + return this[eager ? 'eagerPattern' : 'pattern'].test(number) +} + +},{"xtend/mutable":30}],9:[function(_dereq_,module,exports){ +'use strict' + +var Type = _dereq_('./type') + +var group19 = /(\d{1,4})(\d{1,4})?(\d{1,4})?(\d{1,4})?(\d{1,3})?/ + +exports.visa = new Type('Visa', { + pattern: /^4\d{12}(\d{3}|\d{6})?$/, + eagerPattern: /^4/, + groupPattern: group19 +}) + +exports.maestro = new Type('Maestro', { + pattern: /^(?:5[06789]\d\d|(?!6011[0234])(?!60117[4789])(?!60118[6789])(?!60119)(?!64[456789])(?!65)6\d{3})\d{8,15}$/, + eagerPattern: /^(5(018|0[23]|[68])|6[37]|60111|60115|60117([56]|7[56])|60118[0-5]|64[0-3]|66)/, + groupPattern: group19 +}) + +exports.forbrugsforeningen = new Type('Forbrugsforeningen', { + pattern: /^600722\d{10}$/, + eagerPattern: /^600/ +}) + +exports.dankort = new Type('Dankort', { + pattern: /^5019\d{12}$/, + eagerPattern: /^5019/ +}) + +exports.masterCard = new Type('MasterCard', { + pattern: /^(5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)\d{12}$/, + eagerPattern: /^(2|5[1-5])/ +}) + +exports.americanExpress = new Type('American Express', { + pattern: /^3[47]\d{13}$/, + eagerPattern: /^3[47]/, + groupPattern: /(\d{1,4})(\d{1,6})?(\d{1,5})?/, + cvcLength: 4 +}) + +exports.dinersClub = new Type('Diners Club', { + pattern: /^3(0[0-5]|[68]\d)\d{11}$/, + eagerPattern: /^3(0|[68])/, + groupPattern: /(\d{1,4})?(\d{1,6})?(\d{1,4})?/ +}) + +exports.discover = new Type('Discover', { + pattern: /^6(011(0[0-9]|[2-4]\d|74|7[7-9]|8[6-9]|9[0-9])|4[4-9]\d{3}|5\d{4})\d{10}$/, + eagerPattern: /^6(011(0[0-9]|[2-4]|74|7[7-9]|8[6-9]|9[0-9])|4[4-9]|5)/ +}) + +exports.jcb = new Type('JCB', { + pattern: /^35\d{14}$/, + eagerPattern: /^35/ +}) + +exports.unionPay = new Type('UnionPay', { + pattern: /^62[0-5]\d{13,16}$/, + eagerPattern: /^62/, + groupPattern: group19, + luhn: false +}) + +exports.troy = new Type('Troy', { + pattern: /^9792\d{12}$/, + eagerPattern: /^9792/ +}) + +},{"./type":8}],10:[function(_dereq_,module,exports){ +'use strict' + +var luhn = _dereq_('fast-luhn') +var types = _dereq_('./types') + +module.exports = { + types: types, + parse: parseCard, + format: formatCard, + type: cardType, + luhn: luhn, + isValid: isCardValid +} + +function parseCard (number) { + if (typeof number !== 'string') return '' + return number.replace(/[^\d]/g, '') +} + +function formatCard (number, separator) { + var type = getType(number, true) + if (!type) return number + return type.group(number).join(separator || ' ') +} + +function cardType (number, eager) { + var type = getType(number, eager) + return type ? type.name : undefined +} + +function isCardValid (number, type) { + if (type) { + type = types.get(type) + } else { + type = getType(number) + } + if (!type) return false + return (!type.luhn || luhn(number)) && type.test(number) +} + +function getType (number, eager) { + return types.find(function (type) { + return type.test(number, eager) + }) +} + +},{"./types":14,"fast-luhn":16}],11:[function(_dereq_,module,exports){ +'use strict' + +var types = _dereq_('./types') +var cvcRegex = /^\d{3,4}$/ + +module.exports = { + isValid: cvcIsValid +} + +function cvcIsValid (cvc, type) { + if (typeof cvc !== 'string') return false + if (!cvcRegex.test(cvc)) return false + if (!type) return true + return types.get(type).cvcLength === cvc.length +} + +},{"./types":14}],12:[function(_dereq_,module,exports){ +'use strict' + +var isValidMonth = _dereq_('is-valid-month') +var parseIntStrict = _dereq_('parse-int') +var parseYear = _dereq_('parse-year') + +module.exports = { + isPast: isPast, + month: { + parse: parseMonth, + isValid: isValidMonth + }, + year: { + parse: parseYear, + format: formatExpYear, + isValid: isExpYearValid, + isPast: isExpYearPast + } +} + +function isPast (month, year) { + return Date.now() >= new Date(year, month) +} + +function parseMonth (month) { + return parseIntStrict(month) +} + +function formatExpYear (year, strip) { + year = year.toString() + return strip ? year.substr(2, 4) : year +} + +function isExpYearValid (year) { + if (typeof year !== 'number') return false + year = parseIntStrict(year) + return year > 0 +} + +function isExpYearPast (year) { + return new Date().getFullYear() > year +} + +},{"is-valid-month":21,"parse-int":24,"parse-year":25}],13:[function(_dereq_,module,exports){ +'use strict' + +module.exports = { + card: _dereq_('./card'), + cvc: _dereq_('./cvc'), + expiration: _dereq_('./expiration') +} + +},{"./card":10,"./cvc":11,"./expiration":12}],14:[function(_dereq_,module,exports){ +'use strict' + +var ccTypes = _dereq_('creditcards-types') +var camel = _dereq_('to-camel-case') +var extend = _dereq_('xtend') + +module.exports = extend(ccTypes, { + get: function getTypeByName (name) { + return ccTypes.types[camel(name)] + } +}) + +},{"creditcards-types":7,"to-camel-case":26,"xtend":29}],15:[function(_dereq_,module,exports){ +'use strict' + +var zeroFill = _dereq_('zero-fill') +var parseIntStrict = _dereq_('parse-int') + +var pad = zeroFill(2) + +module.exports = function expandYear (year, now) { + now = now || new Date() + var base = now.getFullYear().toString().substr(0, 2) + year = parseIntStrict(year) + return parseIntStrict(base + pad(year)) +} + +},{"parse-int":24,"zero-fill":31}],16:[function(_dereq_,module,exports){ +'use strict' + +module.exports = (function (array) { + return function luhn (number) { + if (typeof number !== 'string') throw new TypeError('Expected string input') + if (!number) return false + var length = number.length + var bit = 1 + var sum = 0 + var value + + while (length) { + value = parseInt(number.charAt(--length), 10) + sum += (bit ^= 1) ? array[value] : value + } + + return !!sum && sum % 10 === 0 + } +}([0, 2, 4, 6, 8, 1, 3, 5, 7, 9])) + +},{}],17:[function(_dereq_,module,exports){ 'use strict'; -module.exports = function isNumber(n) { - return !!(+n) || n === 0 || n === '0'; +/* eslint no-invalid-this: 1 */ + +var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible '; +var slice = Array.prototype.slice; +var toStr = Object.prototype.toString; +var funcType = '[object Function]'; + +module.exports = function bind(that) { + var target = this; + if (typeof target !== 'function' || toStr.call(target) !== funcType) { + throw new TypeError(ERROR_MESSAGE + target); + } + var args = slice.call(arguments, 1); + + var bound; + var binder = function () { + if (this instanceof bound) { + var result = target.apply( + this, + args.concat(slice.call(arguments)) + ); + if (Object(result) === result) { + return result; + } + return this; + } else { + return target.apply( + that, + args.concat(slice.call(arguments)) + ); + } + }; + + var boundLength = Math.max(0, target.length - args.length); + var boundArgs = []; + for (var i = 0; i < boundLength; i++) { + boundArgs.push('$' + i); + } + + bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder); + + if (target.prototype) { + var Empty = function Empty() {}; + Empty.prototype = target.prototype; + bound.prototype = new Empty(); + Empty.prototype = null; + } + + return bound; }; -},{}],21:[function(_dereq_,module,exports){ +},{}],18:[function(_dereq_,module,exports){ 'use strict'; -module.exports = function (x) { - var type = typeof x; - return x !== null && (type === 'object' || type === 'function'); + +var implementation = _dereq_('./implementation'); + +module.exports = Function.prototype.bind || implementation; + +},{"./implementation":17}],19:[function(_dereq_,module,exports){ +'use strict'; +var numberIsNan = _dereq_('number-is-nan'); + +module.exports = Number.isFinite || function (val) { + return !(typeof val !== 'number' || numberIsNan(val) || val === Infinity || val === -Infinity); }; -},{}],22:[function(_dereq_,module,exports){ +},{"number-is-nan":23}],20:[function(_dereq_,module,exports){ +// https://github.com/paulmillr/es6-shim +// http://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.isinteger +var isFinite = _dereq_("is-finite"); +module.exports = Number.isInteger || function(val) { + return typeof val === "number" && + isFinite(val) && + Math.floor(val) === val; +}; + +},{"is-finite":19}],21:[function(_dereq_,module,exports){ +'use strict' + +var isInteger = _dereq_('is-integer') + +module.exports = function isValidMonth (month) { + if (typeof month !== 'number' || !isInteger(month)) return false + return month >= 1 && month <= 12 +} + +},{"is-integer":20}],22:[function(_dereq_,module,exports){ module.exports = Array.isArray || function (arr) { return Object.prototype.toString.call(arr) == '[object Array]'; }; },{}],23:[function(_dereq_,module,exports){ -/*! - * isobject - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. +'use strict'; +module.exports = Number.isNaN || function (x) { + return x !== x; +}; + +},{}],24:[function(_dereq_,module,exports){ +'use strict' + +var isInteger = _dereq_('is-integer') + +module.exports = function parseIntStrict (integer) { + if (typeof integer === 'number') { + return isInteger(integer) ? integer : undefined + } + if (typeof integer === 'string') { + return /^-?\d+$/.test(integer) ? parseInt(integer, 10) : undefined + } +} + +},{"is-integer":20}],25:[function(_dereq_,module,exports){ +'use strict' + +var parseIntStrict = _dereq_('parse-int') +var expandYear = _dereq_('expand-year') + +module.exports = function parseYear (year, expand, now) { + year = parseIntStrict(year) + if (year == null) return + if (!expand) return year + return expandYear(year, now) +} + +},{"expand-year":15,"parse-int":24}],26:[function(_dereq_,module,exports){ + +var space = _dereq_('to-space-case') + +/** + * Export. */ -'use strict'; +module.exports = toCamelCase -var isArray = _dereq_('isarray'); +/** + * Convert a `string` to camel case. + * + * @param {String} string + * @return {String} + */ -module.exports = function isObject(o) { - return o != null && typeof o === 'object' && !isArray(o); -}; - -},{"isarray":22}],24:[function(_dereq_,module,exports){ -exports = module.exports = stringify -exports.getSerialize = serializer - -function stringify(obj, replacer, spaces, cycleReplacer) { - return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces) -} - -function serializer(replacer, cycleReplacer) { - var stack = [], keys = [] - - if (cycleReplacer == null) cycleReplacer = function(key, value) { - if (stack[0] === value) return "[Circular ~]" - return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]" - } - - return function(key, value) { - if (stack.length > 0) { - var thisPos = stack.indexOf(this) - ~thisPos ? stack.splice(thisPos + 1) : stack.push(this) - ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key) - if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value) - } - else stack.push(value) - - return replacer == null ? value : replacer.call(this, key, value) - } -} - -},{}],25:[function(_dereq_,module,exports){ -'use strict' - -var assert = _dereq_('assert-ok') -var assertEqual = _dereq_('assert-equal') -var dot = _dereq_('dot-prop') -var toArray = _dereq_('to-array') -var last = _dereq_('array-last') -var dezalgo = _dereq_('dezalgo') -var all = _dereq_('call-all-fns') - -module.exports = Lazy - -function Lazy (methods, load) { - assert(Array.isArray(methods), 'methods are required') - assertEqual(typeof load, 'function', 'load fn is required') - - var api = null - var error = null - var queue = [] - - load(function (err, lib) { - error = err - api = lib - all(queue)(err, lib) - queue = null - }) - - return methods.reduce(function (lazy, method) { - dot.set(lazy, method, Deferred(method)) - return lazy - }, {}) - - function Deferred (method) { - return function deferred () { - var args = arguments - onReady(function (err, api) { - if (!err) return dot.get(api, method).apply(null, args) - var callback = last(toArray(args)) - if (typeof callback === 'function') { - return callback(err) - } - }) - } - } - - function onReady (callback) { - callback = dezalgo(callback) - - if (api || error) return callback(error, api) - queue.push(callback) - } -} - -},{"array-last":7,"assert-equal":11,"assert-ok":13,"call-all-fns":14,"dezalgo":16,"dot-prop":26,"to-array":34}],26:[function(_dereq_,module,exports){ -'use strict'; -var isObj = _dereq_('is-obj'); - -module.exports.get = function (obj, path) { - if (!isObj(obj) || typeof path !== 'string') { - return obj; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - obj = obj[pathArr[i]]; - - if (obj === undefined) { - break; - } - } - - return obj; -}; - -module.exports.set = function (obj, path, value) { - if (!isObj(obj) || typeof path !== 'string') { - return; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - var p = pathArr[i]; - - if (!isObj(obj[p])) { - obj[p] = {}; - } - - if (i === pathArr.length - 1) { - obj[p] = value; - } - - obj = obj[p]; - } -}; - -module.exports.delete = function (obj, path) { - if (!isObj(obj) || typeof path !== 'string') { - return; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - var p = pathArr[i]; - - if (i === pathArr.length - 1) { - delete obj[p]; - return; - } - - obj = obj[p]; - } -}; - -module.exports.has = function (obj, path) { - if (!isObj(obj) || typeof path !== 'string') { - return false; - } - - var pathArr = getPathSegments(path); - - for (var i = 0; i < pathArr.length; i++) { - obj = obj[pathArr[i]]; - - if (obj === undefined) { - return false; - } - } - - return true; -}; - -function getPathSegments(path) { - var pathArr = path.split('.'); - var parts = []; - - for (var i = 0; i < pathArr.length; i++) { - var p = pathArr[i]; - - while (p[p.length - 1] === '\\') { - p = p.slice(0, -1) + '.'; - p += pathArr[++i]; - } - - parts.push(p); - } - - return parts; -} - -},{"is-obj":21}],27:[function(_dereq_,module,exports){ -'use strict' - -var load = _dereq_('load-script') -var window = _dereq_('global/window') -var extend = _dereq_('xtend') -var assert = _dereq_('assert-ok') -var dezalgo = _dereq_('dezalgo') -var Listeners = _dereq_('ear') -var extendQuery = _dereq_('query-extend') -var cuid = _dereq_('cuid') - -module.exports = loadGlobal - -var listeners = {} - -function loadGlobal (options, callback) { - assert(options, 'options required') - assert(options.url, 'url required') - assert(options.global, 'global required') - assert(callback, 'callback required') - - options = extend(options) - callback = dezalgo(callback) - - if (getGlobal(options)) { - return callback(null, getGlobal(options)) - } - - callback = cache(options, callback) - if (!callback) return - - if (options.jsonp) { - var id = jsonpCallback(options, callback) - options.url = extendQuery(options.url, {callback: id}) - } - - load(options.url, options, function (err) { - if (err) return callback(err) - if (!options.jsonp) { - var library = getGlobal(options) - if (!library) return callback(new Error('expected: `window.' + options.global + '`, actual: `' + library + '`')) - callback(null, library) - } +function toCamelCase(string) { + return space(string).replace(/\s(\w)/g, function (matches, letter) { + return letter.toUpperCase() }) } -function cache (options, callback) { - if (!get()) { - set(Listeners()) - get().add(callback) - return function onComplete (err, lib) { - get()(err, lib) - set(Listeners()) - } - } +},{"to-space-case":28}],27:[function(_dereq_,module,exports){ - get().add(callback) - return undefined +/** + * Export. + */ - function get () { - return listeners[options.global] - } +module.exports = toNoCase - function set (value) { - listeners[options.global] = value - } +/** + * Test whether a string is camel-case. + */ + +var hasSpace = /\s/ +var hasSeparator = /(_|-|\.|:)/ +var hasCamel = /([a-z][A-Z]|[A-Z][a-z])/ + +/** + * Remove any starting case from a `string`, like camel or snake, but keep + * spaces and punctuation that may be important otherwise. + * + * @param {String} string + * @return {String} + */ + +function toNoCase(string) { + if (hasSpace.test(string)) return string.toLowerCase() + if (hasSeparator.test(string)) return (unseparate(string) || string).toLowerCase() + if (hasCamel.test(string)) return uncamelize(string).toLowerCase() + return string.toLowerCase() } -function getGlobal (options) { - return window[options.global] -} +/** + * Separator splitter. + */ -function jsonpCallback (options, callback) { - var id = cuid() - window[id] = function jsonpCallback () { - callback(null, getGlobal(options)) - delete window[id] - } - return id -} +var separatorSplitter = /[\W_]+(.|$)/g -},{"assert-ok":13,"cuid":15,"dezalgo":16,"ear":18,"global/window":19,"load-script":28,"query-extend":31,"xtend":36}],28:[function(_dereq_,module,exports){ +/** + * Un-separate a `string`. + * + * @param {String} string + * @return {String} + */ -module.exports = function load (src, opts, cb) { - var head = document.head || document.getElementsByTagName('head')[0] - var script = document.createElement('script') - - if (typeof opts === 'function') { - cb = opts - opts = {} - } - - opts = opts || {} - cb = cb || function() {} - - script.type = opts.type || 'text/javascript' - script.charset = opts.charset || 'utf8'; - script.async = 'async' in opts ? !!opts.async : true - script.src = src - - if (opts.attrs) { - setAttributes(script, opts.attrs) - } - - if (opts.text) { - script.text = '' + opts.text - } - - var onend = 'onload' in script ? stdOnEnd : ieOnEnd - onend(script, cb) - - // some good legacy browsers (firefox) fail the 'in' detection above - // so as a fallback we always set onload - // old IE will ignore this and new IE will set onload - if (!script.onload) { - stdOnEnd(script, cb); - } - - head.appendChild(script) -} - -function setAttributes(script, attrs) { - for (var attr in attrs) { - script.setAttribute(attr, attrs[attr]); - } -} - -function stdOnEnd (script, cb) { - script.onload = function () { - this.onerror = this.onload = null - cb(null, script) - } - script.onerror = function () { - // this.onload = null here is necessary - // because even IE9 works not like others - this.onerror = this.onload = null - cb(new Error('Failed to load ' + this.src), script) - } -} - -function ieOnEnd (script, cb) { - script.onreadystatechange = function () { - if (this.readyState != 'complete' && this.readyState != 'loaded') return - this.onreadystatechange = null - cb(null, script) // there is no way to catch loading errors in IE8 - } -} - -},{}],29:[function(_dereq_,module,exports){ -'use strict'; - -module.exports = function split(str) { - var a = 1, - res = ''; - - var parts = str.split('%'), - len = parts.length; - - if (len > 0) { res += parts[0]; } - - for (var i = 1; i < len; i++) { - if (parts[i][0] === 's' || parts[i][0] === 'd') { - var value = arguments[a++]; - res += parts[i][0] === 'd' ? Math.floor(value) : value; - } else if (parts[i][0]) { - res += '%' + parts[i][0]; - } else { - i++; - res += '%' + parts[i][0]; - } - - res += parts[i].substring(1); - } - - return res; -}; - -},{}],30:[function(_dereq_,module,exports){ -'use strict' - -var isObject = _dereq_('isobject') -var safeStringify = _dereq_('json-stringify-safe') - -module.exports = function print (value) { - var toString = isJson(value) ? stringify : String - return toString(value) -} - -function isJson (value) { - return isObject(value) || Array.isArray(value) -} - -function stringify (value) { - return safeStringify(value, null, '') -} - -},{"isobject":23,"json-stringify-safe":24}],31:[function(_dereq_,module,exports){ -!function(glob) { - - var queryToObject = function(query) { - var obj = {}; - if (!query) return obj; - each(query.split('&'), function(val) { - var pieces = val.split('='); - var key = parseKey(pieces[0]); - var keyDecoded = decodeURIComponent(key.val); - var valDecoded = pieces[1] && decodeURIComponent(pieces[1]); - - if (key.type === 'array') { - if (!obj[keyDecoded]) obj[keyDecoded] = []; - obj[keyDecoded].push(valDecoded); - } else if (key.type === 'string') { - obj[keyDecoded] = valDecoded; - } - }); - return obj; - }; - - var objectToQuery = function(obj) { - var pieces = [], encodedKey; - for (var k in obj) { - if (!obj.hasOwnProperty(k)) continue; - if (typeof obj[k] === 'undefined') { - pieces.push(encodeURIComponent(k)); - continue; - } - encodedKey = encodeURIComponent(k); - if (isArray(obj[k])) { - each(obj[k], function(val) { - pieces.push(encodedKey + '[]=' + encodeURIComponent(val)); - }); - continue; - } - pieces.push(encodedKey + '=' + encodeURIComponent(obj[k])); - } - return pieces.length ? ('?' + pieces.join('&')) : ''; - }; - - // for now we will only support string and arrays - var parseKey = function(key) { - var pos = key.indexOf('['); - if (pos === -1) return { type: 'string', val: key }; - return { type: 'array', val: key.substr(0, pos) }; - }; - - var isArray = function(val) { - return Object.prototype.toString.call(val) === '[object Array]'; - }; - - var extract = function(url) { - var pos = url.lastIndexOf('?'); - var hasQuery = pos !== -1; - var base = void 0; - - if (hasQuery && pos > 0) { - base = url.substring(0, pos); - } else if (!hasQuery && (url && url.length > 0)) { - base = url; - } - - return { - base: base, - query: hasQuery ? url.substring(pos+1) : void 0 - }; - }; - - // thanks raynos! - // https://github.com/Raynos/xtend - var extend = function() { - var target = {}; - for (var i = 0; i < arguments.length; i++) { - var source = arguments[i]; - for (var key in source) { - if (source.hasOwnProperty(key)) { - target[key] = source[key]; - } - } - } - return target; - }; - - var queryExtend = function() { - var args = Array.prototype.slice.call(arguments, 0); - var asObject = args[args.length-1] === true; - var base = ''; - - if (!args.length) { - return base; - } - - if (asObject) { - args.pop(); - } - - var normalized = map(args, function(param) { - if (typeof param === 'string') { - var extracted = extract(param); - if (extracted.base) base = extracted.base; - return queryToObject(extracted.query); - } - return param; - }); - - if (asObject) { - return extend.apply({}, normalized); - } else { - return base + objectToQuery(extend.apply({}, normalized)); - } - - }; - - var each = function(arr, fn) { - for (var i = 0, l = arr.length; i < l; i++) { - fn(arr[i], i); - } - }; - - var map = function(arr, fn) { - var res = []; - for (var i = 0, l = arr.length; i < l; i++) { - res.push( fn(arr[i], i) ); - } - return res; - }; - - if (typeof module !== 'undefined' && module.exports) { - // Node.js / browserify - module.exports = queryExtend; - } else if (typeof define === 'function' && define.amd) { - // require.js / AMD - define(function() { - return queryExtend; - }); - } else { - // - * - * - * - * - * - * - * - * - */ -angular.module('ui.router', ['ui.router.state']); - -angular.module('ui.router.compat', ['ui.router']); - -/** - * @ngdoc object - * @name ui.router.util.$resolve - * - * @requires $q - * @requires $injector - * - * @description - * Manages resolution of (acyclic) graphs of promises. - */ -$Resolve.$inject = ['$q', '$injector']; -function $Resolve( $q, $injector) { - - var VISIT_IN_PROGRESS = 1, - VISIT_DONE = 2, - NOTHING = {}, - NO_DEPENDENCIES = [], - NO_LOCALS = NOTHING, - NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING }); - - - /** - * @ngdoc function - * @name ui.router.util.$resolve#study - * @methodOf ui.router.util.$resolve - * - * @description - * Studies a set of invocables that are likely to be used multiple times. - *
-   * $resolve.study(invocables)(locals, parent, self)
-   * 
- * is equivalent to - *
-   * $resolve.resolve(invocables, locals, parent, self)
-   * 
- * but the former is more efficient (in fact `resolve` just calls `study` - * internally). - * - * @param {object} invocables Invocable objects - * @return {function} a function to pass in locals, parent and self - */ - this.study = function (invocables) { - if (!isObject(invocables)) throw new Error("'invocables' must be an object"); - var invocableKeys = objectKeys(invocables || {}); - - // Perform a topological sort of invocables to build an ordered plan - var plan = [], cycle = [], visited = {}; - function visit(value, key) { - if (visited[key] === VISIT_DONE) return; - - cycle.push(key); - if (visited[key] === VISIT_IN_PROGRESS) { - cycle.splice(0, indexOf(cycle, key)); - throw new Error("Cyclic dependency: " + cycle.join(" -> ")); - } - visited[key] = VISIT_IN_PROGRESS; - - if (isString(value)) { - plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES); - } else { - var params = $injector.annotate(value); - forEach(params, function (param) { - if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param); - }); - plan.push(key, value, params); - } - - cycle.pop(); - visited[key] = VISIT_DONE; - } - forEach(invocables, visit); - invocables = cycle = visited = null; // plan is all that's required - - function isResolve(value) { - return isObject(value) && value.then && value.$$promises; - } - - return function (locals, parent, self) { - if (isResolve(locals) && self === undefined) { - self = parent; parent = locals; locals = null; - } - if (!locals) locals = NO_LOCALS; - else if (!isObject(locals)) { - throw new Error("'locals' must be an object"); - } - if (!parent) parent = NO_PARENT; - else if (!isResolve(parent)) { - throw new Error("'parent' must be a promise returned by $resolve.resolve()"); - } - - // To complete the overall resolution, we have to wait for the parent - // promise and for the promise for each invokable in our plan. - var resolution = $q.defer(), - result = silenceUncaughtInPromise(resolution.promise), - promises = result.$$promises = {}, - values = extend({}, locals), - wait = 1 + plan.length/3, - merged = false; - - silenceUncaughtInPromise(result); - - function done() { - // Merge parent values we haven't got yet and publish our own $$values - if (!--wait) { - if (!merged) merge(values, parent.$$values); - result.$$values = values; - result.$$promises = result.$$promises || true; // keep for isResolve() - delete result.$$inheritedValues; - resolution.resolve(values); - } - } - - function fail(reason) { - result.$$failure = reason; - resolution.reject(reason); - } - - // Short-circuit if parent has already failed - if (isDefined(parent.$$failure)) { - fail(parent.$$failure); - return result; - } - - if (parent.$$inheritedValues) { - merge(values, omit(parent.$$inheritedValues, invocableKeys)); - } - - // Merge parent values if the parent has already resolved, or merge - // parent promises and wait if the parent resolve is still in progress. - extend(promises, parent.$$promises); - if (parent.$$values) { - merged = merge(values, omit(parent.$$values, invocableKeys)); - result.$$inheritedValues = omit(parent.$$values, invocableKeys); - done(); - } else { - if (parent.$$inheritedValues) { - result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys); - } - parent.then(done, fail); - } - - // Process each invocable in the plan, but ignore any where a local of the same name exists. - for (var i=0, ii=plan.length; i - * Impact on loading templates for more details about this mechanism. - * - * @param {boolean} value - */ - this.shouldUnsafelyUseHttp = function(value) { - shouldUnsafelyUseHttp = !!value; - }; - - /** - * @ngdoc object - * @name ui.router.util.$templateFactory - * - * @requires $http - * @requires $templateCache - * @requires $injector - * - * @description - * Service. Manages loading of templates. - */ - this.$get = ['$http', '$templateCache', '$injector', function($http, $templateCache, $injector){ - return new TemplateFactory($http, $templateCache, $injector, shouldUnsafelyUseHttp);}]; -} - - -/** - * @ngdoc object - * @name ui.router.util.$templateFactory - * - * @requires $http - * @requires $templateCache - * @requires $injector - * - * @description - * Service. Manages loading of templates. - */ -function TemplateFactory($http, $templateCache, $injector, shouldUnsafelyUseHttp) { - - /** - * @ngdoc function - * @name ui.router.util.$templateFactory#fromConfig - * @methodOf ui.router.util.$templateFactory - * - * @description - * Creates a template from a configuration object. - * - * @param {object} config Configuration object for which to load a template. - * The following properties are search in the specified order, and the first one - * that is defined is used to create the template: - * - * @param {string|object} config.template html string template or function to - * load via {@link ui.router.util.$templateFactory#fromString fromString}. - * @param {string|object} config.templateUrl url to load or a function returning - * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}. - * @param {Function} config.templateProvider function to invoke via - * {@link ui.router.util.$templateFactory#fromProvider fromProvider}. - * @param {object} params Parameters to pass to the template function. - * @param {object} locals Locals to pass to `invoke` if the template is loaded - * via a `templateProvider`. Defaults to `{ params: params }`. - * - * @return {string|object} The template html as a string, or a promise for - * that string,or `null` if no template is configured. - */ - this.fromConfig = function (config, params, locals) { - return ( - isDefined(config.template) ? this.fromString(config.template, params) : - isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) : - isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) : - null - ); - }; - - /** - * @ngdoc function - * @name ui.router.util.$templateFactory#fromString - * @methodOf ui.router.util.$templateFactory - * - * @description - * Creates a template from a string or a function returning a string. - * - * @param {string|object} template html template as a string or function that - * returns an html template as a string. - * @param {object} params Parameters to pass to the template function. - * - * @return {string|object} The template html as a string, or a promise for that - * string. - */ - this.fromString = function (template, params) { - return isFunction(template) ? template(params) : template; - }; - - /** - * @ngdoc function - * @name ui.router.util.$templateFactory#fromUrl - * @methodOf ui.router.util.$templateFactory - * - * @description - * Loads a template from the a URL via `$http` and `$templateCache`. - * - * @param {string|Function} url url of the template to load, or a function - * that returns a url. - * @param {Object} params Parameters to pass to the url function. - * @return {string|Promise.} The template html as a string, or a promise - * for that string. - */ - this.fromUrl = function (url, params) { - if (isFunction(url)) url = url(params); - if (url == null) return null; - else { - if(!shouldUnsafelyUseHttp) { - return $injector.get('$templateRequest')(url); - } else { - return $http - .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }}) - .then(function(response) { return response.data; }); - } - } - }; - - /** - * @ngdoc function - * @name ui.router.util.$templateFactory#fromProvider - * @methodOf ui.router.util.$templateFactory - * - * @description - * Creates a template by invoking an injectable provider function. - * - * @param {Function} provider Function to invoke via `$injector.invoke` - * @param {Object} params Parameters for the template. - * @param {Object} locals Locals to pass to `invoke`. Defaults to - * `{ params: params }`. - * @return {string|Promise.} The template html as a string, or a promise - * for that string. - */ - this.fromProvider = function (provider, params, locals) { - return $injector.invoke(provider, null, locals || { params: params }); - }; -} - -angular.module('ui.router.util').provider('$templateFactory', TemplateFactoryProvider); - -var $$UMFP; // reference to $UrlMatcherFactoryProvider - -/** - * @ngdoc object - * @name ui.router.util.type:UrlMatcher - * - * @description - * Matches URLs against patterns and extracts named parameters from the path or the search - * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list - * of search parameters. Multiple search parameter names are separated by '&'. Search parameters - * do not influence whether or not a URL is matched, but their values are passed through into - * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}. - * - * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace - * syntax, which optionally allows a regular expression for the parameter to be specified: - * - * * `':'` name - colon placeholder - * * `'*'` name - catch-all placeholder - * * `'{' name '}'` - curly placeholder - * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the - * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash. - * - * Parameter names may contain only word characters (latin letters, digits, and underscore) and - * must be unique within the pattern (across both path and search parameters). For colon - * placeholders or curly placeholders without an explicit regexp, a path parameter matches any - * number of characters other than '/'. For catch-all placeholders the path parameter matches - * any number of characters. - * - * Examples: - * - * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for - * trailing slashes, and patterns have to match the entire path, not just a prefix. - * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or - * '/user/bob/details'. The second path segment will be captured as the parameter 'id'. - * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax. - * * `'/user/{id:[^/]*}'` - Same as the previous example. - * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id - * parameter consists of 1 to 8 hex digits. - * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the - * path into the parameter 'path'. - * * `'/files/*path'` - ditto. - * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined - * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start - * - * @param {string} pattern The pattern to compile into a matcher. - * @param {Object} config A configuration object hash: - * @param {Object=} parentMatcher Used to concatenate the pattern/config onto - * an existing UrlMatcher - * - * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`. - * * `strict` - `false` if matching against a URL with a trailing slash should be treated as equivalent to a URL without a trailing slash, the default value is `true`. - * - * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any - * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns - * non-null) will start with this prefix. - * - * @property {string} source The pattern that was passed into the constructor - * - * @property {string} sourcePath The path portion of the source property - * - * @property {string} sourceSearch The search portion of the source property - * - * @property {string} regex The constructed regex that will be used to match against the url when - * it is time to determine which url will match. - * - * @returns {Object} New `UrlMatcher` object - */ -function UrlMatcher(pattern, config, parentMatcher) { - config = extend({ params: {} }, isObject(config) ? config : {}); - - // Find all placeholders and create a compiled pattern, using either classic or curly syntax: - // '*' name - // ':' name - // '{' name '}' - // '{' name ':' regexp '}' - // The regular expression is somewhat complicated due to the need to allow curly braces - // inside the regular expression. The placeholder regexp breaks down as follows: - // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case) - // \{([\w\[\]]+)(?:\:\s*( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case - // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either - // [^{}\\]+ - anything other than curly braces or backslash - // \\. - a backslash escape - // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms - var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, - searchPlaceholder = /([:]?)([\w\[\].-]+)|\{([\w\[\].-]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, - compiled = '^', last = 0, m, - segments = this.segments = [], - parentParams = parentMatcher ? parentMatcher.params : {}, - params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(), - paramNames = []; - - function addParameter(id, type, config, location) { - paramNames.push(id); - if (parentParams[id]) return parentParams[id]; - if (!/^\w+([-.]+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'"); - if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'"); - params[id] = new $$UMFP.Param(id, type, config, location); - return params[id]; - } - - function quoteRegExp(string, pattern, squash, optional) { - var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&"); - if (!pattern) return result; - switch(squash) { - case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break; - case true: - result = result.replace(/\/$/, ''); - surroundPattern = ['(?:\/(', ')|\/)?']; - break; - default: surroundPattern = ['(' + squash + "|", ')?']; break; - } - return result + surroundPattern[0] + pattern + surroundPattern[1]; - } - - this.source = pattern; - - // Split into static segments separated by path parameter placeholders. - // The number of segments is always 1 more than the number of parameters. - function matchDetails(m, isSearch) { - var id, regexp, segment, type, cfg, arrayMode; - id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null - cfg = config.params[id]; - segment = pattern.substring(last, m.index); - regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null); - - if (regexp) { - type = $$UMFP.type(regexp) || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) }); - } - - return { - id: id, regexp: regexp, segment: segment, type: type, cfg: cfg - }; - } - - var p, param, segment; - while ((m = placeholder.exec(pattern))) { - p = matchDetails(m, false); - if (p.segment.indexOf('?') >= 0) break; // we're into the search part - - param = addParameter(p.id, p.type, p.cfg, "path"); - compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional); - segments.push(p.segment); - last = placeholder.lastIndex; - } - segment = pattern.substring(last); - - // Find any search parameter names and remove them from the last segment - var i = segment.indexOf('?'); - - if (i >= 0) { - var search = this.sourceSearch = segment.substring(i); - segment = segment.substring(0, i); - this.sourcePath = pattern.substring(0, last + i); - - if (search.length > 0) { - last = 0; - while ((m = searchPlaceholder.exec(search))) { - p = matchDetails(m, true); - param = addParameter(p.id, p.type, p.cfg, "search"); - last = placeholder.lastIndex; - // check if ?& - } - } - } else { - this.sourcePath = pattern; - this.sourceSearch = ''; - } - - compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$'; - segments.push(segment); - - this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined); - this.prefix = segments[0]; - this.$$paramNames = paramNames; -} - -/** - * @ngdoc function - * @name ui.router.util.type:UrlMatcher#concat - * @methodOf ui.router.util.type:UrlMatcher - * - * @description - * Returns a new matcher for a pattern constructed by appending the path part and adding the - * search parameters of the specified pattern to this pattern. The current pattern is not - * modified. This can be understood as creating a pattern for URLs that are relative to (or - * suffixes of) the current pattern. - * - * @example - * The following two matchers are equivalent: - *
- * new UrlMatcher('/user/{id}?q').concat('/details?date');
- * new UrlMatcher('/user/{id}/details?q&date');
- * 
- * - * @param {string} pattern The pattern to append. - * @param {Object} config An object hash of the configuration for the matcher. - * @returns {UrlMatcher} A matcher for the concatenated pattern. - */ -UrlMatcher.prototype.concat = function (pattern, config) { - // Because order of search parameters is irrelevant, we can add our own search - // parameters to the end of the new pattern. Parse the new pattern by itself - // and then join the bits together, but it's much easier to do this on a string level. - var defaultConfig = { - caseInsensitive: $$UMFP.caseInsensitive(), - strict: $$UMFP.strictMode(), - squash: $$UMFP.defaultSquashPolicy() - }; - return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this); -}; - -UrlMatcher.prototype.toString = function () { - return this.source; -}; - -/** - * @ngdoc function - * @name ui.router.util.type:UrlMatcher#exec - * @methodOf ui.router.util.type:UrlMatcher - * - * @description - * Tests the specified path against this matcher, and returns an object containing the captured - * parameter values, or null if the path does not match. The returned object contains the values - * of any search parameters that are mentioned in the pattern, but their value may be null if - * they are not present in `searchParams`. This means that search parameters are always treated - * as optional. - * - * @example - *
- * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
- *   x: '1', q: 'hello'
- * });
- * // returns { id: 'bob', q: 'hello', r: null }
- * 
- * - * @param {string} path The URL path to match, e.g. `$location.path()`. - * @param {Object} searchParams URL search parameters, e.g. `$location.search()`. - * @returns {Object} The captured parameter values. - */ -UrlMatcher.prototype.exec = function (path, searchParams) { - var m = this.regexp.exec(path); - if (!m) return null; - searchParams = searchParams || {}; - - var paramNames = this.parameters(), nTotal = paramNames.length, - nPath = this.segments.length - 1, - values = {}, i, j, cfg, paramName; - - if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'"); - - function decodePathArray(string) { - function reverseString(str) { return str.split("").reverse().join(""); } - function unquoteDashes(str) { return str.replace(/\\-/g, "-"); } - - var split = reverseString(string).split(/-(?!\\)/); - var allReversed = map(split, reverseString); - return map(allReversed, unquoteDashes).reverse(); - } - - var param, paramVal; - for (i = 0; i < nPath; i++) { - paramName = paramNames[i]; - param = this.params[paramName]; - paramVal = m[i+1]; - // if the param value matches a pre-replace pair, replace the value before decoding. - for (j = 0; j < param.replace.length; j++) { - if (param.replace[j].from === paramVal) paramVal = param.replace[j].to; - } - if (paramVal && param.array === true) paramVal = decodePathArray(paramVal); - if (isDefined(paramVal)) paramVal = param.type.decode(paramVal); - values[paramName] = param.value(paramVal); - } - for (/**/; i < nTotal; i++) { - paramName = paramNames[i]; - values[paramName] = this.params[paramName].value(searchParams[paramName]); - param = this.params[paramName]; - paramVal = searchParams[paramName]; - for (j = 0; j < param.replace.length; j++) { - if (param.replace[j].from === paramVal) paramVal = param.replace[j].to; - } - if (isDefined(paramVal)) paramVal = param.type.decode(paramVal); - values[paramName] = param.value(paramVal); - } - - return values; -}; - -/** - * @ngdoc function - * @name ui.router.util.type:UrlMatcher#parameters - * @methodOf ui.router.util.type:UrlMatcher - * - * @description - * Returns the names of all path and search parameters of this pattern in an unspecified order. - * - * @returns {Array.} An array of parameter names. Must be treated as read-only. If the - * pattern has no parameters, an empty array is returned. - */ -UrlMatcher.prototype.parameters = function (param) { - if (!isDefined(param)) return this.$$paramNames; - return this.params[param] || null; -}; - -/** - * @ngdoc function - * @name ui.router.util.type:UrlMatcher#validates - * @methodOf ui.router.util.type:UrlMatcher - * - * @description - * Checks an object hash of parameters to validate their correctness according to the parameter - * types of this `UrlMatcher`. - * - * @param {Object} params The object hash of parameters to validate. - * @returns {boolean} Returns `true` if `params` validates, otherwise `false`. - */ -UrlMatcher.prototype.validates = function (params) { - return this.params.$$validates(params); -}; - -/** - * @ngdoc function - * @name ui.router.util.type:UrlMatcher#format - * @methodOf ui.router.util.type:UrlMatcher - * - * @description - * Creates a URL that matches this pattern by substituting the specified values - * for the path and search parameters. Null values for path parameters are - * treated as empty strings. - * - * @example - *
- * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
- * // returns '/user/bob?q=yes'
- * 
- * - * @param {Object} values the values to substitute for the parameters in this pattern. - * @returns {string} the formatted URL (path and optionally search part). - */ -UrlMatcher.prototype.format = function (values) { - values = values || {}; - var segments = this.segments, params = this.parameters(), paramset = this.params; - if (!this.validates(values)) return null; - - var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0]; - - function encodeDashes(str) { // Replace dashes with encoded "\-" - return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); }); - } - - for (i = 0; i < nTotal; i++) { - var isPathParam = i < nPath; - var name = params[i], param = paramset[name], value = param.value(values[name]); - var isDefaultValue = param.isOptional && param.type.equals(param.value(), value); - var squash = isDefaultValue ? param.squash : false; - var encoded = param.type.encode(value); - - if (isPathParam) { - var nextSegment = segments[i + 1]; - var isFinalPathParam = i + 1 === nPath; - - if (squash === false) { - if (encoded != null) { - if (isArray(encoded)) { - result += map(encoded, encodeDashes).join("-"); - } else { - result += encodeURIComponent(encoded); - } - } - result += nextSegment; - } else if (squash === true) { - var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/; - result += nextSegment.match(capture)[1]; - } else if (isString(squash)) { - result += squash + nextSegment; - } - - if (isFinalPathParam && param.squash === true && result.slice(-1) === '/') result = result.slice(0, -1); - } else { - if (encoded == null || (isDefaultValue && squash !== false)) continue; - if (!isArray(encoded)) encoded = [ encoded ]; - if (encoded.length === 0) continue; - encoded = map(encoded, encodeURIComponent).join('&' + name + '='); - result += (search ? '&' : '?') + (name + '=' + encoded); - search = true; - } - } - - return result; -}; - -/** - * @ngdoc object - * @name ui.router.util.type:Type - * - * @description - * Implements an interface to define custom parameter types that can be decoded from and encoded to - * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`} - * objects when matching or formatting URLs, or comparing or validating parameter values. - * - * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more - * information on registering custom types. - * - * @param {Object} config A configuration object which contains the custom type definition. The object's - * properties will override the default methods and/or pattern in `Type`'s public interface. - * @example - *
- * {
- *   decode: function(val) { return parseInt(val, 10); },
- *   encode: function(val) { return val && val.toString(); },
- *   equals: function(a, b) { return this.is(a) && a === b; },
- *   is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
- *   pattern: /\d+/
- * }
- * 
- * - * @property {RegExp} pattern The regular expression pattern used to match values of this type when - * coming from a substring of a URL. - * - * @returns {Object} Returns a new `Type` object. - */ -function Type(config) { - extend(this, config); -} - -/** - * @ngdoc function - * @name ui.router.util.type:Type#is - * @methodOf ui.router.util.type:Type - * - * @description - * Detects whether a value is of a particular type. Accepts a native (decoded) value - * and determines whether it matches the current `Type` object. - * - * @param {*} val The value to check. - * @param {string} key Optional. If the type check is happening in the context of a specific - * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the - * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects. - * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`. - */ -Type.prototype.is = function(val, key) { - return true; -}; - -/** - * @ngdoc function - * @name ui.router.util.type:Type#encode - * @methodOf ui.router.util.type:Type - * - * @description - * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the - * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it - * only needs to be a representation of `val` that has been coerced to a string. - * - * @param {*} val The value to encode. - * @param {string} key The name of the parameter in which `val` is stored. Can be used for - * meta-programming of `Type` objects. - * @returns {string} Returns a string representation of `val` that can be encoded in a URL. - */ -Type.prototype.encode = function(val, key) { - return val; -}; - -/** - * @ngdoc function - * @name ui.router.util.type:Type#decode - * @methodOf ui.router.util.type:Type - * - * @description - * Converts a parameter value (from URL string or transition param) to a custom/native value. - * - * @param {string} val The URL parameter value to decode. - * @param {string} key The name of the parameter in which `val` is stored. Can be used for - * meta-programming of `Type` objects. - * @returns {*} Returns a custom representation of the URL parameter value. - */ -Type.prototype.decode = function(val, key) { - return val; -}; - -/** - * @ngdoc function - * @name ui.router.util.type:Type#equals - * @methodOf ui.router.util.type:Type - * - * @description - * Determines whether two decoded values are equivalent. - * - * @param {*} a A value to compare against. - * @param {*} b A value to compare against. - * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`. - */ -Type.prototype.equals = function(a, b) { - return a == b; -}; - -Type.prototype.$subPattern = function() { - var sub = this.pattern.toString(); - return sub.substr(1, sub.length - 2); -}; - -Type.prototype.pattern = /.*/; - -Type.prototype.toString = function() { return "{Type:" + this.name + "}"; }; - -/** Given an encoded string, or a decoded object, returns a decoded object */ -Type.prototype.$normalize = function(val) { - return this.is(val) ? val : this.decode(val); -}; - -/* - * Wraps an existing custom Type as an array of Type, depending on 'mode'. - * e.g.: - * - urlmatcher pattern "/path?{queryParam[]:int}" - * - url: "/path?queryParam=1&queryParam=2 - * - $stateParams.queryParam will be [1, 2] - * if `mode` is "auto", then - * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1 - * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2] - */ -Type.prototype.$asArray = function(mode, isSearch) { - if (!mode) return this; - if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only"); - - function ArrayType(type, mode) { - function bindTo(type, callbackName) { - return function() { - return type[callbackName].apply(type, arguments); - }; - } - - // Wrap non-array value as array - function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); } - // Unwrap array value for "auto" mode. Return undefined for empty array. - function arrayUnwrap(val) { - switch(val.length) { - case 0: return undefined; - case 1: return mode === "auto" ? val[0] : val; - default: return val; - } - } - function falsey(val) { return !val; } - - // Wraps type (.is/.encode/.decode) functions to operate on each value of an array - function arrayHandler(callback, allTruthyMode) { - return function handleArray(val) { - if (isArray(val) && val.length === 0) return val; - val = arrayWrap(val); - var result = map(val, callback); - if (allTruthyMode === true) - return filter(result, falsey).length === 0; - return arrayUnwrap(result); - }; - } - - // Wraps type (.equals) functions to operate on each value of an array - function arrayEqualsHandler(callback) { - return function handleArray(val1, val2) { - var left = arrayWrap(val1), right = arrayWrap(val2); - if (left.length !== right.length) return false; - for (var i = 0; i < left.length; i++) { - if (!callback(left[i], right[i])) return false; - } - return true; - }; - } - - this.encode = arrayHandler(bindTo(type, 'encode')); - this.decode = arrayHandler(bindTo(type, 'decode')); - this.is = arrayHandler(bindTo(type, 'is'), true); - this.equals = arrayEqualsHandler(bindTo(type, 'equals')); - this.pattern = type.pattern; - this.$normalize = arrayHandler(bindTo(type, '$normalize')); - this.name = type.name; - this.$arrayMode = mode; - } - - return new ArrayType(this, mode); -}; - - - -/** - * @ngdoc object - * @name ui.router.util.$urlMatcherFactory - * - * @description - * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory - * is also available to providers under the name `$urlMatcherFactoryProvider`. - */ -function $UrlMatcherFactory() { - $$UMFP = this; - - var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false; - - // Use tildes to pre-encode slashes. - // If the slashes are simply URLEncoded, the browser can choose to pre-decode them, - // and bidirectional encoding/decoding fails. - // Tilde was chosen because it's not a RFC 3986 section 2.2 Reserved Character - function valToString(val) { return val != null ? val.toString().replace(/(~|\/)/g, function (m) { return {'~':'~~', '/':'~2F'}[m]; }) : val; } - function valFromString(val) { return val != null ? val.toString().replace(/(~~|~2F)/g, function (m) { return {'~~':'~', '~2F':'/'}[m]; }) : val; } - - var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = { - "string": { - encode: valToString, - decode: valFromString, - // TODO: in 1.0, make string .is() return false if value is undefined/null by default. - // In 0.2.x, string params are optional by default for backwards compat - is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; }, - pattern: /[^/]*/ - }, - "int": { - encode: valToString, - decode: function(val) { return parseInt(val, 10); }, - is: function(val) { return val !== undefined && val !== null && this.decode(val.toString()) === val; }, - pattern: /\d+/ - }, - "bool": { - encode: function(val) { return val ? 1 : 0; }, - decode: function(val) { return parseInt(val, 10) !== 0; }, - is: function(val) { return val === true || val === false; }, - pattern: /0|1/ - }, - "date": { - encode: function (val) { - if (!this.is(val)) - return undefined; - return [ val.getFullYear(), - ('0' + (val.getMonth() + 1)).slice(-2), - ('0' + val.getDate()).slice(-2) - ].join("-"); - }, - decode: function (val) { - if (this.is(val)) return val; - var match = this.capture.exec(val); - return match ? new Date(match[1], match[2] - 1, match[3]) : undefined; - }, - is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); }, - equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); }, - pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/, - capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/ - }, - "json": { - encode: angular.toJson, - decode: angular.fromJson, - is: angular.isObject, - equals: angular.equals, - pattern: /[^/]*/ - }, - "any": { // does not encode/decode - encode: angular.identity, - decode: angular.identity, - equals: angular.equals, - pattern: /.*/ - } - }; - - function getDefaultConfig() { - return { - strict: isStrictMode, - caseInsensitive: isCaseInsensitive - }; - } - - function isInjectable(value) { - return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1]))); - } - - /** - * [Internal] Get the default value of a parameter, which may be an injectable function. - */ - $UrlMatcherFactory.$$getDefaultValue = function(config) { - if (!isInjectable(config.value)) return config.value; - if (!injector) throw new Error("Injectable functions cannot be called at configuration time"); - return injector.invoke(config.value); - }; - - /** - * @ngdoc function - * @name ui.router.util.$urlMatcherFactory#caseInsensitive - * @methodOf ui.router.util.$urlMatcherFactory - * - * @description - * Defines whether URL matching should be case sensitive (the default behavior), or not. - * - * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`; - * @returns {boolean} the current value of caseInsensitive - */ - this.caseInsensitive = function(value) { - if (isDefined(value)) - isCaseInsensitive = value; - return isCaseInsensitive; - }; - - /** - * @ngdoc function - * @name ui.router.util.$urlMatcherFactory#strictMode - * @methodOf ui.router.util.$urlMatcherFactory - * - * @description - * Defines whether URLs should match trailing slashes, or not (the default behavior). - * - * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`. - * @returns {boolean} the current value of strictMode - */ - this.strictMode = function(value) { - if (isDefined(value)) - isStrictMode = value; - return isStrictMode; - }; - - /** - * @ngdoc function - * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy - * @methodOf ui.router.util.$urlMatcherFactory - * - * @description - * Sets the default behavior when generating or matching URLs with default parameter values. - * - * @param {string} value A string that defines the default parameter URL squashing behavior. - * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL - * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the - * parameter is surrounded by slashes, squash (remove) one slash from the URL - * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove) - * the parameter value from the URL and replace it with this string. - */ - this.defaultSquashPolicy = function(value) { - if (!isDefined(value)) return defaultSquashPolicy; - if (value !== true && value !== false && !isString(value)) - throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string"); - defaultSquashPolicy = value; - return value; - }; - - /** - * @ngdoc function - * @name ui.router.util.$urlMatcherFactory#compile - * @methodOf ui.router.util.$urlMatcherFactory - * - * @description - * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern. - * - * @param {string} pattern The URL pattern. - * @param {Object} config The config object hash. - * @returns {UrlMatcher} The UrlMatcher. - */ - this.compile = function (pattern, config) { - return new UrlMatcher(pattern, extend(getDefaultConfig(), config)); - }; - - /** - * @ngdoc function - * @name ui.router.util.$urlMatcherFactory#isMatcher - * @methodOf ui.router.util.$urlMatcherFactory - * - * @description - * Returns true if the specified object is a `UrlMatcher`, or false otherwise. - * - * @param {Object} object The object to perform the type check against. - * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by - * implementing all the same methods. - */ - this.isMatcher = function (o) { - if (!isObject(o)) return false; - var result = true; - - forEach(UrlMatcher.prototype, function(val, name) { - if (isFunction(val)) { - result = result && (isDefined(o[name]) && isFunction(o[name])); - } - }); - return result; - }; - - /** - * @ngdoc function - * @name ui.router.util.$urlMatcherFactory#type - * @methodOf ui.router.util.$urlMatcherFactory - * - * @description - * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to - * generate URLs with typed parameters. - * - * @param {string} name The type name. - * @param {Object|Function} definition The type definition. See - * {@link ui.router.util.type:Type `Type`} for information on the values accepted. - * @param {Object|Function} definitionFn (optional) A function that is injected before the app - * runtime starts. The result of this function is merged into the existing `definition`. - * See {@link ui.router.util.type:Type `Type`} for information on the values accepted. - * - * @returns {Object} Returns `$urlMatcherFactoryProvider`. - * - * @example - * This is a simple example of a custom type that encodes and decodes items from an - * array, using the array index as the URL-encoded value: - * - *
-   * var list = ['John', 'Paul', 'George', 'Ringo'];
-   *
-   * $urlMatcherFactoryProvider.type('listItem', {
-   *   encode: function(item) {
-   *     // Represent the list item in the URL using its corresponding index
-   *     return list.indexOf(item);
-   *   },
-   *   decode: function(item) {
-   *     // Look up the list item by index
-   *     return list[parseInt(item, 10)];
-   *   },
-   *   is: function(item) {
-   *     // Ensure the item is valid by checking to see that it appears
-   *     // in the list
-   *     return list.indexOf(item) > -1;
-   *   }
-   * });
-   *
-   * $stateProvider.state('list', {
-   *   url: "/list/{item:listItem}",
-   *   controller: function($scope, $stateParams) {
-   *     console.log($stateParams.item);
-   *   }
-   * });
-   *
-   * // ...
-   *
-   * // Changes URL to '/list/3', logs "Ringo" to the console
-   * $state.go('list', { item: "Ringo" });
-   * 
- * - * This is a more complex example of a type that relies on dependency injection to - * interact with services, and uses the parameter name from the URL to infer how to - * handle encoding and decoding parameter values: - * - *
-   * // Defines a custom type that gets a value from a service,
-   * // where each service gets different types of values from
-   * // a backend API:
-   * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
-   *
-   *   // Matches up services to URL parameter names
-   *   var services = {
-   *     user: Users,
-   *     post: Posts
-   *   };
-   *
-   *   return {
-   *     encode: function(object) {
-   *       // Represent the object in the URL using its unique ID
-   *       return object.id;
-   *     },
-   *     decode: function(value, key) {
-   *       // Look up the object by ID, using the parameter
-   *       // name (key) to call the correct service
-   *       return services[key].findById(value);
-   *     },
-   *     is: function(object, key) {
-   *       // Check that object is a valid dbObject
-   *       return angular.isObject(object) && object.id && services[key];
-   *     }
-   *     equals: function(a, b) {
-   *       // Check the equality of decoded objects by comparing
-   *       // their unique IDs
-   *       return a.id === b.id;
-   *     }
-   *   };
-   * });
-   *
-   * // In a config() block, you can then attach URLs with
-   * // type-annotated parameters:
-   * $stateProvider.state('users', {
-   *   url: "/users",
-   *   // ...
-   * }).state('users.item', {
-   *   url: "/{user:dbObject}",
-   *   controller: function($scope, $stateParams) {
-   *     // $stateParams.user will now be an object returned from
-   *     // the Users service
-   *   },
-   *   // ...
-   * });
-   * 
- */ - this.type = function (name, definition, definitionFn) { - if (!isDefined(definition)) return $types[name]; - if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined."); - - $types[name] = new Type(extend({ name: name }, definition)); - if (definitionFn) { - typeQueue.push({ name: name, def: definitionFn }); - if (!enqueue) flushTypeQueue(); - } - return this; - }; - - // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s - function flushTypeQueue() { - while(typeQueue.length) { - var type = typeQueue.shift(); - if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime."); - angular.extend($types[type.name], injector.invoke(type.def)); - } - } - - // Register default types. Store them in the prototype of $types. - forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); }); - $types = inherit($types, {}); - - /* No need to document $get, since it returns this */ - this.$get = ['$injector', function ($injector) { - injector = $injector; - enqueue = false; - flushTypeQueue(); - - forEach(defaultTypes, function(type, name) { - if (!$types[name]) $types[name] = new Type(type); - }); - return this; - }]; - - this.Param = function Param(id, type, config, location) { - var self = this; - config = unwrapShorthand(config); - type = getType(config, type, location); - var arrayMode = getArrayMode(); - type = arrayMode ? type.$asArray(arrayMode, location === "search") : type; - if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined) - config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to "" - var isOptional = config.value !== undefined; - var squash = getSquashPolicy(config, isOptional); - var replace = getReplace(config, arrayMode, isOptional, squash); - - function unwrapShorthand(config) { - var keys = isObject(config) ? objectKeys(config) : []; - var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 && - indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1; - if (isShorthand) config = { value: config }; - config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; }; - return config; - } - - function getType(config, urlType, location) { - if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations."); - if (urlType) return urlType; - if (!config.type) return (location === "config" ? $types.any : $types.string); - - if (angular.isString(config.type)) - return $types[config.type]; - if (config.type instanceof Type) - return config.type; - return new Type(config.type); - } - - // array config: param name (param[]) overrides default settings. explicit config overrides param name. - function getArrayMode() { - var arrayDefaults = { array: (location === "search" ? "auto" : false) }; - var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {}; - return extend(arrayDefaults, arrayParamNomenclature, config).array; - } - - /** - * returns false, true, or the squash value to indicate the "default parameter url squash policy". - */ - function getSquashPolicy(config, isOptional) { - var squash = config.squash; - if (!isOptional || squash === false) return false; - if (!isDefined(squash) || squash == null) return defaultSquashPolicy; - if (squash === true || isString(squash)) return squash; - throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string"); - } - - function getReplace(config, arrayMode, isOptional, squash) { - var replace, configuredKeys, defaultPolicy = [ - { from: "", to: (isOptional || arrayMode ? undefined : "") }, - { from: null, to: (isOptional || arrayMode ? undefined : "") } - ]; - replace = isArray(config.replace) ? config.replace : []; - if (isString(squash)) - replace.push({ from: squash, to: undefined }); - configuredKeys = map(replace, function(item) { return item.from; } ); - return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace); - } - - /** - * [Internal] Get the default value of a parameter, which may be an injectable function. - */ - function $$getDefaultValue() { - if (!injector) throw new Error("Injectable functions cannot be called at configuration time"); - var defaultValue = injector.invoke(config.$$fn); - if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue)) - throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")"); - return defaultValue; - } - - /** - * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the - * default value, which may be the result of an injectable function. - */ - function $value(value) { - function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; } - function $replace(value) { - var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; }); - return replacement.length ? replacement[0] : value; - } - value = $replace(value); - return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value); - } - - function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; } - - extend(this, { - id: id, - type: type, - location: location, - array: arrayMode, - squash: squash, - replace: replace, - isOptional: isOptional, - value: $value, - dynamic: undefined, - config: config, - toString: toString - }); - }; - - function ParamSet(params) { - extend(this, params || {}); - } - - ParamSet.prototype = { - $$new: function() { - return inherit(this, extend(new ParamSet(), { $$parent: this})); - }, - $$keys: function () { - var keys = [], chain = [], parent = this, - ignore = objectKeys(ParamSet.prototype); - while (parent) { chain.push(parent); parent = parent.$$parent; } - chain.reverse(); - forEach(chain, function(paramset) { - forEach(objectKeys(paramset), function(key) { - if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key); - }); - }); - return keys; - }, - $$values: function(paramValues) { - var values = {}, self = this; - forEach(self.$$keys(), function(key) { - values[key] = self[key].value(paramValues && paramValues[key]); - }); - return values; - }, - $$equals: function(paramValues1, paramValues2) { - var equal = true, self = this; - forEach(self.$$keys(), function(key) { - var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key]; - if (!self[key].type.equals(left, right)) equal = false; - }); - return equal; - }, - $$validates: function $$validate(paramValues) { - var keys = this.$$keys(), i, param, rawVal, normalized, encoded; - for (i = 0; i < keys.length; i++) { - param = this[keys[i]]; - rawVal = paramValues[keys[i]]; - if ((rawVal === undefined || rawVal === null) && param.isOptional) - break; // There was no parameter value, but the param is optional - normalized = param.type.$normalize(rawVal); - if (!param.type.is(normalized)) - return false; // The value was not of the correct Type, and could not be decoded to the correct Type - encoded = param.type.encode(normalized); - if (angular.isString(encoded) && !param.type.pattern.exec(encoded)) - return false; // The value was of the correct type, but when encoded, did not match the Type's regexp - } - return true; - }, - $$parent: undefined - }; - - this.ParamSet = ParamSet; -} - -// Register as a provider so it's available to other providers -angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory); -angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]); - -/** - * @ngdoc object - * @name ui.router.router.$urlRouterProvider - * - * @requires ui.router.util.$urlMatcherFactoryProvider - * @requires $locationProvider - * - * @description - * `$urlRouterProvider` has the responsibility of watching `$location`. - * When `$location` changes it runs through a list of rules one by one until a - * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify - * a url in a state configuration. All urls are compiled into a UrlMatcher object. - * - * There are several methods on `$urlRouterProvider` that make it useful to use directly - * in your module config. - */ -$UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider']; -function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) { - var rules = [], otherwise = null, interceptDeferred = false, listener; - - // Returns a string that is a prefix of all strings matching the RegExp - function regExpPrefix(re) { - var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source); - return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : ''; - } - - // Interpolates matched values into a String.replace()-style pattern - function interpolate(pattern, match) { - return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) { - return match[what === '$' ? 0 : Number(what)]; - }); - } - - /** - * @ngdoc function - * @name ui.router.router.$urlRouterProvider#rule - * @methodOf ui.router.router.$urlRouterProvider - * - * @description - * Defines rules that are used by `$urlRouterProvider` to find matches for - * specific URLs. - * - * @example - *
-   * var app = angular.module('app', ['ui.router.router']);
-   *
-   * app.config(function ($urlRouterProvider) {
-   *   // Here's an example of how you might allow case insensitive urls
-   *   $urlRouterProvider.rule(function ($injector, $location) {
-   *     var path = $location.path(),
-   *         normalized = path.toLowerCase();
-   *
-   *     if (path !== normalized) {
-   *       return normalized;
-   *     }
-   *   });
-   * });
-   * 
- * - * @param {function} rule Handler function that takes `$injector` and `$location` - * services as arguments. You can use them to return a valid path as a string. - * - * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance - */ - this.rule = function (rule) { - if (!isFunction(rule)) throw new Error("'rule' must be a function"); - rules.push(rule); - return this; - }; - - /** - * @ngdoc object - * @name ui.router.router.$urlRouterProvider#otherwise - * @methodOf ui.router.router.$urlRouterProvider - * - * @description - * Defines a path that is used when an invalid route is requested. - * - * @example - *
-   * var app = angular.module('app', ['ui.router.router']);
-   *
-   * app.config(function ($urlRouterProvider) {
-   *   // if the path doesn't match any of the urls you configured
-   *   // otherwise will take care of routing the user to the
-   *   // specified url
-   *   $urlRouterProvider.otherwise('/index');
-   *
-   *   // Example of using function rule as param
-   *   $urlRouterProvider.otherwise(function ($injector, $location) {
-   *     return '/a/valid/url';
-   *   });
-   * });
-   * 
- * - * @param {string|function} rule The url path you want to redirect to or a function - * rule that returns the url path. The function version is passed two params: - * `$injector` and `$location` services, and must return a url string. - * - * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance - */ - this.otherwise = function (rule) { - if (isString(rule)) { - var redirect = rule; - rule = function () { return redirect; }; - } - else if (!isFunction(rule)) throw new Error("'rule' must be a function"); - otherwise = rule; - return this; - }; - - - function handleIfMatch($injector, handler, match) { - if (!match) return false; - var result = $injector.invoke(handler, handler, { $match: match }); - return isDefined(result) ? result : true; - } - - /** - * @ngdoc function - * @name ui.router.router.$urlRouterProvider#when - * @methodOf ui.router.router.$urlRouterProvider - * - * @description - * Registers a handler for a given url matching. - * - * If the handler is a string, it is - * treated as a redirect, and is interpolated according to the syntax of match - * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise). - * - * If the handler is a function, it is injectable. It gets invoked if `$location` - * matches. You have the option of inject the match object as `$match`. - * - * The handler can return - * - * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter` - * will continue trying to find another one that matches. - * - **string** which is treated as a redirect and passed to `$location.url()` - * - **void** or any **truthy** value tells `$urlRouter` that the url was handled. - * - * @example - *
-   * var app = angular.module('app', ['ui.router.router']);
-   *
-   * app.config(function ($urlRouterProvider) {
-   *   $urlRouterProvider.when($state.url, function ($match, $stateParams) {
-   *     if ($state.$current.navigable !== state ||
-   *         !equalForKeys($match, $stateParams) {
-   *      $state.transitionTo(state, $match, false);
-   *     }
-   *   });
-   * });
-   * 
- * - * @param {string|object} what The incoming path that you want to redirect. - * @param {string|function} handler The path you want to redirect your user to. - */ - this.when = function (what, handler) { - var redirect, handlerIsString = isString(handler); - if (isString(what)) what = $urlMatcherFactory.compile(what); - - if (!handlerIsString && !isFunction(handler) && !isArray(handler)) - throw new Error("invalid 'handler' in when()"); - - var strategies = { - matcher: function (what, handler) { - if (handlerIsString) { - redirect = $urlMatcherFactory.compile(handler); - handler = ['$match', function ($match) { return redirect.format($match); }]; - } - return extend(function ($injector, $location) { - return handleIfMatch($injector, handler, what.exec($location.path(), $location.search())); - }, { - prefix: isString(what.prefix) ? what.prefix : '' - }); - }, - regex: function (what, handler) { - if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky"); - - if (handlerIsString) { - redirect = handler; - handler = ['$match', function ($match) { return interpolate(redirect, $match); }]; - } - return extend(function ($injector, $location) { - return handleIfMatch($injector, handler, what.exec($location.path())); - }, { - prefix: regExpPrefix(what) - }); - } - }; - - var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp }; - - for (var n in check) { - if (check[n]) return this.rule(strategies[n](what, handler)); - } - - throw new Error("invalid 'what' in when()"); - }; - - /** - * @ngdoc function - * @name ui.router.router.$urlRouterProvider#deferIntercept - * @methodOf ui.router.router.$urlRouterProvider - * - * @description - * Disables (or enables) deferring location change interception. - * - * If you wish to customize the behavior of syncing the URL (for example, if you wish to - * defer a transition but maintain the current URL), call this method at configuration time. - * Then, at run time, call `$urlRouter.listen()` after you have configured your own - * `$locationChangeSuccess` event handler. - * - * @example - *
-   * var app = angular.module('app', ['ui.router.router']);
-   *
-   * app.config(function ($urlRouterProvider) {
-   *
-   *   // Prevent $urlRouter from automatically intercepting URL changes;
-   *   // this allows you to configure custom behavior in between
-   *   // location changes and route synchronization:
-   *   $urlRouterProvider.deferIntercept();
-   *
-   * }).run(function ($rootScope, $urlRouter, UserService) {
-   *
-   *   $rootScope.$on('$locationChangeSuccess', function(e) {
-   *     // UserService is an example service for managing user state
-   *     if (UserService.isLoggedIn()) return;
-   *
-   *     // Prevent $urlRouter's default handler from firing
-   *     e.preventDefault();
-   *
-   *     UserService.handleLogin().then(function() {
-   *       // Once the user has logged in, sync the current URL
-   *       // to the router:
-   *       $urlRouter.sync();
-   *     });
-   *   });
-   *
-   *   // Configures $urlRouter's listener *after* your custom listener
-   *   $urlRouter.listen();
-   * });
-   * 
- * - * @param {boolean} defer Indicates whether to defer location change interception. Passing - no parameter is equivalent to `true`. - */ - this.deferIntercept = function (defer) { - if (defer === undefined) defer = true; - interceptDeferred = defer; - }; - - /** - * @ngdoc object - * @name ui.router.router.$urlRouter - * - * @requires $location - * @requires $rootScope - * @requires $injector - * @requires $browser - * - * @description - * - */ - this.$get = $get; - $get.$inject = ['$location', '$rootScope', '$injector', '$browser', '$sniffer']; - function $get( $location, $rootScope, $injector, $browser, $sniffer) { - - var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl; - - function appendBasePath(url, isHtml5, absolute) { - if (baseHref === '/') return url; - if (isHtml5) return baseHref.slice(0, -1) + url; - if (absolute) return baseHref.slice(1) + url; - return url; - } - - // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree - function update(evt) { - if (evt && evt.defaultPrevented) return; - var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl; - lastPushedUrl = undefined; - // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573 - //if (ignoreUpdate) return true; - - function check(rule) { - var handled = rule($injector, $location); - - if (!handled) return false; - if (isString(handled)) $location.replace().url(handled); - return true; - } - var n = rules.length, i; - - for (i = 0; i < n; i++) { - if (check(rules[i])) return; - } - // always check otherwise last to allow dynamic updates to the set of rules - if (otherwise) check(otherwise); - } - - function listen() { - listener = listener || $rootScope.$on('$locationChangeSuccess', update); - return listener; - } - - if (!interceptDeferred) listen(); - - return { - /** - * @ngdoc function - * @name ui.router.router.$urlRouter#sync - * @methodOf ui.router.router.$urlRouter - * - * @description - * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`. - * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event, - * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed - * with the transition by calling `$urlRouter.sync()`. - * - * @example - *
-       * angular.module('app', ['ui.router'])
-       *   .run(function($rootScope, $urlRouter) {
-       *     $rootScope.$on('$locationChangeSuccess', function(evt) {
-       *       // Halt state change from even starting
-       *       evt.preventDefault();
-       *       // Perform custom logic
-       *       var meetsRequirement = ...
-       *       // Continue with the update and state transition if logic allows
-       *       if (meetsRequirement) $urlRouter.sync();
-       *     });
-       * });
-       * 
- */ - sync: function() { - update(); - }, - - listen: function() { - return listen(); - }, - - update: function(read) { - if (read) { - location = $location.url(); - return; - } - if ($location.url() === location) return; - - $location.url(location); - $location.replace(); - }, - - push: function(urlMatcher, params, options) { - var url = urlMatcher.format(params || {}); - - // Handle the special hash param, if needed - if (url !== null && params && params['#']) { - url += '#' + params['#']; - } - - $location.url(url); - lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined; - if (options && options.replace) $location.replace(); - }, - - /** - * @ngdoc function - * @name ui.router.router.$urlRouter#href - * @methodOf ui.router.router.$urlRouter - * - * @description - * A URL generation method that returns the compiled URL for a given - * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters. - * - * @example - *
-       * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
-       *   person: "bob"
-       * });
-       * // $bob == "/about/bob";
-       * 
- * - * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate. - * @param {object=} params An object of parameter values to fill the matcher's required parameters. - * @param {object=} options Options object. The options are: - * - * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". - * - * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher` - */ - href: function(urlMatcher, params, options) { - if (!urlMatcher.validates(params)) return null; - - var isHtml5 = $locationProvider.html5Mode(); - if (angular.isObject(isHtml5)) { - isHtml5 = isHtml5.enabled; - } - - isHtml5 = isHtml5 && $sniffer.history; - - var url = urlMatcher.format(params); - options = options || {}; - - if (!isHtml5 && url !== null) { - url = "#" + $locationProvider.hashPrefix() + url; - } - - // Handle special hash param, if needed - if (url !== null && params && params['#']) { - url += '#' + params['#']; - } - - url = appendBasePath(url, isHtml5, options.absolute); - - if (!options.absolute || !url) { - return url; - } - - var slash = (!isHtml5 && url ? '/' : ''), port = $location.port(); - port = (port === 80 || port === 443 ? '' : ':' + port); - - return [$location.protocol(), '://', $location.host(), port, slash, url].join(''); - } - }; - } -} - -angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider); - -/** - * @ngdoc object - * @name ui.router.state.$stateProvider - * - * @requires ui.router.router.$urlRouterProvider - * @requires ui.router.util.$urlMatcherFactoryProvider - * - * @description - * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely - * on state. - * - * A state corresponds to a "place" in the application in terms of the overall UI and - * navigation. A state describes (via the controller / template / view properties) what - * the UI looks like and does at that place. - * - * States often have things in common, and the primary way of factoring out these - * commonalities in this model is via the state hierarchy, i.e. parent/child states aka - * nested states. - * - * The `$stateProvider` provides interfaces to declare these states for your app. - */ -$StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider']; -function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { - - var root, states = {}, $state, queue = {}, abstractKey = 'abstract'; - - // Builds state properties from definition passed to registerState() - var stateBuilder = { - - // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined. - // state.children = []; - // if (parent) parent.children.push(state); - parent: function(state) { - if (isDefined(state.parent) && state.parent) return findState(state.parent); - // regex matches any valid composite state name - // would match "contact.list" but not "contacts" - var compositeName = /^(.+)\.[^.]+$/.exec(state.name); - return compositeName ? findState(compositeName[1]) : root; - }, - - // inherit 'data' from parent and override by own values (if any) - data: function(state) { - if (state.parent && state.parent.data) { - state.data = state.self.data = inherit(state.parent.data, state.data); - } - return state.data; - }, - - // Build a URLMatcher if necessary, either via a relative or absolute URL - url: function(state) { - var url = state.url, config = { params: state.params || {} }; - - if (isString(url)) { - if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config); - return (state.parent.navigable || root).url.concat(url, config); - } - - if (!url || $urlMatcherFactory.isMatcher(url)) return url; - throw new Error("Invalid url '" + url + "' in state '" + state + "'"); - }, - - // Keep track of the closest ancestor state that has a URL (i.e. is navigable) - navigable: function(state) { - return state.url ? state : (state.parent ? state.parent.navigable : null); - }, - - // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params - ownParams: function(state) { - var params = state.url && state.url.params || new $$UMFP.ParamSet(); - forEach(state.params || {}, function(config, id) { - if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config"); - }); - return params; - }, - - // Derive parameters for this state and ensure they're a super-set of parent's parameters - params: function(state) { - var ownParams = pick(state.ownParams, state.ownParams.$$keys()); - return state.parent && state.parent.params ? extend(state.parent.params.$$new(), ownParams) : new $$UMFP.ParamSet(); - }, - - // If there is no explicit multi-view configuration, make one up so we don't have - // to handle both cases in the view directive later. Note that having an explicit - // 'views' property will mean the default unnamed view properties are ignored. This - // is also a good time to resolve view names to absolute names, so everything is a - // straight lookup at link time. - views: function(state) { - var views = {}; - - forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) { - if (name.indexOf('@') < 0) name += '@' + state.parent.name; - view.resolveAs = view.resolveAs || state.resolveAs || '$resolve'; - views[name] = view; - }); - return views; - }, - - // Keep a full path from the root down to this state as this is needed for state activation. - path: function(state) { - return state.parent ? state.parent.path.concat(state) : []; // exclude root from path - }, - - // Speed up $state.contains() as it's used a lot - includes: function(state) { - var includes = state.parent ? extend({}, state.parent.includes) : {}; - includes[state.name] = true; - return includes; - }, - - $delegates: {} - }; - - function isRelative(stateName) { - return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0; - } - - function findState(stateOrName, base) { - if (!stateOrName) return undefined; - - var isStr = isString(stateOrName), - name = isStr ? stateOrName : stateOrName.name, - path = isRelative(name); - - if (path) { - if (!base) throw new Error("No reference point given for path '" + name + "'"); - base = findState(base); - - var rel = name.split("."), i = 0, pathLength = rel.length, current = base; - - for (; i < pathLength; i++) { - if (rel[i] === "" && i === 0) { - current = base; - continue; - } - if (rel[i] === "^") { - if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'"); - current = current.parent; - continue; - } - break; - } - rel = rel.slice(i).join("."); - name = current.name + (current.name && rel ? "." : "") + rel; - } - var state = states[name]; - - if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) { - return state; - } - return undefined; - } - - function queueState(parentName, state) { - if (!queue[parentName]) { - queue[parentName] = []; - } - queue[parentName].push(state); - } - - function flushQueuedChildren(parentName) { - var queued = queue[parentName] || []; - while(queued.length) { - registerState(queued.shift()); - } - } - - function registerState(state) { - // Wrap a new object around the state so we can store our private details easily. - state = inherit(state, { - self: state, - resolve: state.resolve || {}, - toString: function() { return this.name; } - }); - - var name = state.name; - if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name"); - if (states.hasOwnProperty(name)) throw new Error("State '" + name + "' is already defined"); - - // Get parent name - var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.')) - : (isString(state.parent)) ? state.parent - : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name - : ''; - - // If parent is not registered yet, add state to queue and register later - if (parentName && !states[parentName]) { - return queueState(parentName, state.self); - } - - for (var key in stateBuilder) { - if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]); - } - states[name] = state; - - // Register the state in the global state list and with $urlRouter if necessary. - if (!state[abstractKey] && state.url) { - $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) { - if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) { - $state.transitionTo(state, $match, { inherit: true, location: false }); - } - }]); - } - - // Register any queued children - flushQueuedChildren(name); - - return state; - } - - // Checks text to see if it looks like a glob. - function isGlob (text) { - return text.indexOf('*') > -1; - } - - // Returns true if glob matches current $state name. - function doesStateMatchGlob (glob) { - var globSegments = glob.split('.'), - segments = $state.$current.name.split('.'); - - //match single stars - for (var i = 0, l = globSegments.length; i < l; i++) { - if (globSegments[i] === '*') { - segments[i] = '*'; - } - } - - //match greedy starts - if (globSegments[0] === '**') { - segments = segments.slice(indexOf(segments, globSegments[1])); - segments.unshift('**'); - } - //match greedy ends - if (globSegments[globSegments.length - 1] === '**') { - segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE); - segments.push('**'); - } - - if (globSegments.length != segments.length) { - return false; - } - - return segments.join('') === globSegments.join(''); - } - - - // Implicit root state that is always active - root = registerState({ - name: '', - url: '^', - views: null, - 'abstract': true - }); - root.navigable = null; - - - /** - * @ngdoc function - * @name ui.router.state.$stateProvider#decorator - * @methodOf ui.router.state.$stateProvider - * - * @description - * Allows you to extend (carefully) or override (at your own peril) the - * `stateBuilder` object used internally by `$stateProvider`. This can be used - * to add custom functionality to ui-router, for example inferring templateUrl - * based on the state name. - * - * When passing only a name, it returns the current (original or decorated) builder - * function that matches `name`. - * - * The builder functions that can be decorated are listed below. Though not all - * necessarily have a good use case for decoration, that is up to you to decide. - * - * In addition, users can attach custom decorators, which will generate new - * properties within the state's internal definition. There is currently no clear - * use-case for this beyond accessing internal states (i.e. $state.$current), - * however, expect this to become increasingly relevant as we introduce additional - * meta-programming features. - * - * **Warning**: Decorators should not be interdependent because the order of - * execution of the builder functions in non-deterministic. Builder functions - * should only be dependent on the state definition object and super function. - * - * - * Existing builder functions and current return values: - * - * - **parent** `{object}` - returns the parent state object. - * - **data** `{object}` - returns state data, including any inherited data that is not - * overridden by own values (if any). - * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher} - * or `null`. - * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is - * navigable). - * - **params** `{object}` - returns an array of state params that are ensured to - * be a super-set of parent's params. - * - **views** `{object}` - returns a views object where each key is an absolute view - * name (i.e. "viewName@stateName") and each value is the config object - * (template, controller) for the view. Even when you don't use the views object - * explicitly on a state config, one is still created for you internally. - * So by decorating this builder function you have access to decorating template - * and controller properties. - * - **ownParams** `{object}` - returns an array of params that belong to the state, - * not including any params defined by ancestor states. - * - **path** `{string}` - returns the full path from the root down to this state. - * Needed for state activation. - * - **includes** `{object}` - returns an object that includes every state that - * would pass a `$state.includes()` test. - * - * @example - *
-   * // Override the internal 'views' builder with a function that takes the state
-   * // definition, and a reference to the internal function being overridden:
-   * $stateProvider.decorator('views', function (state, parent) {
-   *   var result = {},
-   *       views = parent(state);
-   *
-   *   angular.forEach(views, function (config, name) {
-   *     var autoName = (state.name + '.' + name).replace('.', '/');
-   *     config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
-   *     result[name] = config;
-   *   });
-   *   return result;
-   * });
-   *
-   * $stateProvider.state('home', {
-   *   views: {
-   *     'contact.list': { controller: 'ListController' },
-   *     'contact.item': { controller: 'ItemController' }
-   *   }
-   * });
-   *
-   * // ...
-   *
-   * $state.go('home');
-   * // Auto-populates list and item views with /partials/home/contact/list.html,
-   * // and /partials/home/contact/item.html, respectively.
-   * 
- * - * @param {string} name The name of the builder function to decorate. - * @param {object} func A function that is responsible for decorating the original - * builder function. The function receives two parameters: - * - * - `{object}` - state - The state config object. - * - `{object}` - super - The original builder function. - * - * @return {object} $stateProvider - $stateProvider instance - */ - this.decorator = decorator; - function decorator(name, func) { - /*jshint validthis: true */ - if (isString(name) && !isDefined(func)) { - return stateBuilder[name]; - } - if (!isFunction(func) || !isString(name)) { - return this; - } - if (stateBuilder[name] && !stateBuilder.$delegates[name]) { - stateBuilder.$delegates[name] = stateBuilder[name]; - } - stateBuilder[name] = func; - return this; - } - - /** - * @ngdoc function - * @name ui.router.state.$stateProvider#state - * @methodOf ui.router.state.$stateProvider - * - * @description - * Registers a state configuration under a given state name. The stateConfig object - * has the following acceptable properties. - * - * @param {string} name A unique state name, e.g. "home", "about", "contacts". - * To create a parent/child state use a dot, e.g. "about.sales", "home.newest". - * @param {object} stateConfig State configuration object. - * @param {string|function=} stateConfig.template - * - * html template as a string or a function that returns - * an html template as a string which should be used by the uiView directives. This property - * takes precedence over templateUrl. - * - * If `template` is a function, it will be called with the following parameters: - * - * - {array.<object>} - state parameters extracted from the current $location.path() by - * applying the current state - * - *
template:
-   *   "

inline template definition

" + - * "
"
- *
template: function(params) {
-   *       return "

generated template

"; }
- * - * - * @param {string|function=} stateConfig.templateUrl - * - * - * path or function that returns a path to an html - * template that should be used by uiView. - * - * If `templateUrl` is a function, it will be called with the following parameters: - * - * - {array.<object>} - state parameters extracted from the current $location.path() by - * applying the current state - * - *
templateUrl: "home.html"
- *
templateUrl: function(params) {
-   *     return myTemplates[params.pageId]; }
- * - * @param {function=} stateConfig.templateProvider - * - * Provider function that returns HTML content string. - *
 templateProvider:
-   *       function(MyTemplateService, params) {
-   *         return MyTemplateService.getTemplate(params.pageId);
-   *       }
- * - * @param {string|function=} stateConfig.controller - * - * - * Controller fn that should be associated with newly - * related scope or the name of a registered controller if passed as a string. - * Optionally, the ControllerAs may be declared here. - *
controller: "MyRegisteredController"
- *
controller:
-   *     "MyRegisteredController as fooCtrl"}
- *
controller: function($scope, MyService) {
-   *     $scope.data = MyService.getData(); }
- * - * @param {function=} stateConfig.controllerProvider - * - * - * Injectable provider function that returns the actual controller or string. - *
controllerProvider:
-   *   function(MyResolveData) {
-   *     if (MyResolveData.foo)
-   *       return "FooCtrl"
-   *     else if (MyResolveData.bar)
-   *       return "BarCtrl";
-   *     else return function($scope) {
-   *       $scope.baz = "Qux";
-   *     }
-   *   }
- * - * @param {string=} stateConfig.controllerAs - * - * - * A controller alias name. If present the controller will be - * published to scope under the controllerAs name. - *
controllerAs: "myCtrl"
- * - * @param {string|object=} stateConfig.parent - * - * Optionally specifies the parent state of this state. - * - *
parent: 'parentState'
- *
parent: parentState // JS variable
- * - * @param {object=} stateConfig.resolve - * - * - * An optional map<string, function> 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 before the controller is instantiated. - * If all the promises are resolved successfully, the $stateChangeSuccess event is fired - * and the values of the resolved promises are injected into any controllers that reference them. - * If any of the promises are rejected the $stateChangeError event is fired. - * - * The map object is: - * - * - key - {string}: name of dependency to be injected into controller - * - factory - {string|function}: If string then it is alias for service. Otherwise if function, - * it is injected and return value it treated as dependency. If result is a promise, it is - * resolved before its value is injected into controller. - * - *
resolve: {
-   *     myResolve1:
-   *       function($http, $stateParams) {
-   *         return $http.get("/api/foos/"+stateParams.fooID);
-   *       }
-   *     }
- * - * @param {string=} stateConfig.url - * - * - * A url fragment with optional parameters. When a state is navigated or - * transitioned to, the `$stateParams` service will be populated with any - * parameters that were passed. - * - * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for - * more details on acceptable patterns ) - * - * examples: - *
url: "/home"
-   * url: "/users/:userid"
-   * url: "/books/{bookid:[a-zA-Z_-]}"
-   * url: "/books/{categoryid:int}"
-   * url: "/books/{publishername:string}/{categoryid:int}"
-   * url: "/messages?before&after"
-   * url: "/messages?{before:date}&{after:date}"
-   * url: "/messages/:mailboxid?{before:date}&{after:date}"
-   * 
- * - * @param {object=} stateConfig.views - * - * an optional map<string, object> which defined multiple views, or targets views - * manually/explicitly. - * - * Examples: - * - * Targets three named `ui-view`s in the parent state's template - *
views: {
-   *     header: {
-   *       controller: "headerCtrl",
-   *       templateUrl: "header.html"
-   *     }, body: {
-   *       controller: "bodyCtrl",
-   *       templateUrl: "body.html"
-   *     }, footer: {
-   *       controller: "footCtrl",
-   *       templateUrl: "footer.html"
-   *     }
-   *   }
- * - * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template. - *
views: {
-   *     'header@top': {
-   *       controller: "msgHeaderCtrl",
-   *       templateUrl: "msgHeader.html"
-   *     }, 'body': {
-   *       controller: "messagesCtrl",
-   *       templateUrl: "messages.html"
-   *     }
-   *   }
- * - * @param {boolean=} [stateConfig.abstract=false] - * - * An abstract state will never be directly activated, - * but can provide inherited properties to its common children states. - *
abstract: true
- * - * @param {function=} stateConfig.onEnter - * - * - * Callback function for when a state is entered. Good way - * to trigger an action or dispatch an event, such as opening a dialog. - * If minifying your scripts, make sure to explicitly annotate this function, - * because it won't be automatically annotated by your build tools. - * - *
onEnter: function(MyService, $stateParams) {
-   *     MyService.foo($stateParams.myParam);
-   * }
- * - * @param {function=} stateConfig.onExit - * - * - * Callback function for when a state is exited. Good way to - * trigger an action or dispatch an event, such as opening a dialog. - * If minifying your scripts, make sure to explicitly annotate this function, - * because it won't be automatically annotated by your build tools. - * - *
onExit: function(MyService, $stateParams) {
-   *     MyService.cleanup($stateParams.myParam);
-   * }
- * - * @param {boolean=} [stateConfig.reloadOnSearch=true] - * - * - * If `false`, will not retrigger the same state - * just because a search/query parameter has changed (via $location.search() or $location.hash()). - * Useful for when you'd like to modify $location.search() without triggering a reload. - *
reloadOnSearch: false
- * - * @param {object=} stateConfig.data - * - * - * Arbitrary data object, useful for custom configuration. The parent state's `data` is - * prototypally inherited. In other words, adding a data property to a state adds it to - * the entire subtree via prototypal inheritance. - * - *
data: {
-   *     requiredRole: 'foo'
-   * } 
- * - * @param {object=} stateConfig.params - * - * - * A map which optionally configures parameters declared in the `url`, or - * defines additional non-url parameters. For each parameter being - * configured, add a configuration object keyed to the name of the parameter. - * - * Each parameter configuration object may contain the following properties: - * - * - ** value ** - {object|function=}: specifies the default value for this - * parameter. This implicitly sets this parameter as optional. - * - * When UI-Router routes to a state and no value is - * specified for this parameter in the URL or transition, the - * default value will be used instead. If `value` is a function, - * it will be injected and invoked, and the return value used. - * - * *Note*: `undefined` is treated as "no default value" while `null` - * is treated as "the default value is `null`". - * - * *Shorthand*: If you only need to configure the default value of the - * parameter, you may use a shorthand syntax. In the **`params`** - * map, instead mapping the param name to a full parameter configuration - * object, simply set map it to the default parameter value, e.g.: - * - *
// define a parameter's default value
-   * params: {
-   *     param1: { value: "defaultValue" }
-   * }
-   * // shorthand default values
-   * params: {
-   *     param1: "defaultValue",
-   *     param2: "param2Default"
-   * }
- * - * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be - * treated as an array of values. If you specified a Type, the value will be - * treated as an array of the specified Type. Note: query parameter values - * default to a special `"auto"` mode. - * - * For query parameters in `"auto"` mode, if multiple values for a single parameter - * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values - * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if - * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single - * value (e.g.: `{ foo: '1' }`). - * - *
params: {
-   *     param1: { array: true }
-   * }
- * - * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when - * the current parameter value is the same as the default value. If `squash` is not set, it uses the - * configured default squash policy. - * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`}) - * - * There are three squash settings: - * - * - false: The parameter's default value is not squashed. It is encoded and included in the URL - * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed - * by slashes in the state's `url` declaration, then one of those slashes are omitted. - * This can allow for cleaner looking URLs. - * - `""`: The parameter's default value is replaced with an arbitrary placeholder of your choice. - * - *
params: {
-   *     param1: {
-   *       value: "defaultId",
-   *       squash: true
-   * } }
-   * // squash "defaultValue" to "~"
-   * params: {
-   *     param1: {
-   *       value: "defaultValue",
-   *       squash: "~"
-   * } }
-   * 
- * - * - * @example - *
-   * // Some state name examples
-   *
-   * // stateName can be a single top-level name (must be unique).
-   * $stateProvider.state("home", {});
-   *
-   * // Or it can be a nested state name. This state is a child of the
-   * // above "home" state.
-   * $stateProvider.state("home.newest", {});
-   *
-   * // Nest states as deeply as needed.
-   * $stateProvider.state("home.newest.abc.xyz.inception", {});
-   *
-   * // state() returns $stateProvider, so you can chain state declarations.
-   * $stateProvider
-   *   .state("home", {})
-   *   .state("about", {})
-   *   .state("contacts", {});
-   * 
- * - */ - this.state = state; - function state(name, definition) { - /*jshint validthis: true */ - if (isObject(name)) definition = name; - else definition.name = name; - registerState(definition); - return this; - } - - /** - * @ngdoc object - * @name ui.router.state.$state - * - * @requires $rootScope - * @requires $q - * @requires ui.router.state.$view - * @requires $injector - * @requires ui.router.util.$resolve - * @requires ui.router.state.$stateParams - * @requires ui.router.router.$urlRouter - * - * @property {object} params A param object, e.g. {sectionId: section.id)}, that - * you'd like to test against the current active state. - * @property {object} current A reference to the state's config object. However - * you passed it in. Useful for accessing custom data. - * @property {object} transition Currently pending transition. A promise that'll - * resolve or reject. - * - * @description - * `$state` service is responsible for representing states as well as transitioning - * between them. It also provides interfaces to ask for current state or even states - * you're coming from. - */ - this.$get = $get; - $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory']; - function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) { - - var TransitionSupersededError = new Error('transition superseded'); - - var TransitionSuperseded = silenceUncaughtInPromise($q.reject(TransitionSupersededError)); - var TransitionPrevented = silenceUncaughtInPromise($q.reject(new Error('transition prevented'))); - var TransitionAborted = silenceUncaughtInPromise($q.reject(new Error('transition aborted'))); - var TransitionFailed = silenceUncaughtInPromise($q.reject(new Error('transition failed'))); - - // Handles the case where a state which is the target of a transition is not found, and the user - // can optionally retry or defer the transition - function handleRedirect(redirect, state, params, options) { - /** - * @ngdoc event - * @name ui.router.state.$state#$stateNotFound - * @eventOf ui.router.state.$state - * @eventType broadcast on root scope - * @description - * Fired when a requested state **cannot be found** using the provided state name during transition. - * The event is broadcast allowing any handlers a single chance to deal with the error (usually by - * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler, - * you can see its three properties in the example. You can use `event.preventDefault()` to abort the - * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value. - * - * @param {Object} event Event object. - * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties. - * @param {State} fromState Current state object. - * @param {Object} fromParams Current state params. - * - * @example - * - *
-       * // somewhere, assume lazy.state has not been defined
-       * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
-       *
-       * // somewhere else
-       * $scope.$on('$stateNotFound',
-       * function(event, unfoundState, fromState, fromParams){
-       *     console.log(unfoundState.to); // "lazy.state"
-       *     console.log(unfoundState.toParams); // {a:1, b:2}
-       *     console.log(unfoundState.options); // {inherit:false} + default options
-       * })
-       * 
- */ - var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params); - - if (evt.defaultPrevented) { - $urlRouter.update(); - return TransitionAborted; - } - - if (!evt.retry) { - return null; - } - - // Allow the handler to return a promise to defer state lookup retry - if (options.$retry) { - $urlRouter.update(); - return TransitionFailed; - } - var retryTransition = $state.transition = $q.when(evt.retry); - - retryTransition.then(function() { - if (retryTransition !== $state.transition) { - $rootScope.$broadcast('$stateChangeCancel', redirect.to, redirect.toParams, state, params); - return TransitionSuperseded; - } - redirect.options.$retry = true; - return $state.transitionTo(redirect.to, redirect.toParams, redirect.options); - }, function() { - return TransitionAborted; - }); - $urlRouter.update(); - - return retryTransition; - } - - root.locals = { resolve: null, globals: { $stateParams: {} } }; - - $state = { - params: {}, - current: root.self, - $current: root, - transition: null - }; - - /** - * @ngdoc function - * @name ui.router.state.$state#reload - * @methodOf ui.router.state.$state - * - * @description - * A method that force reloads the current state. All resolves are re-resolved, - * controllers reinstantiated, and events re-fired. - * - * @example - *
-     * var app angular.module('app', ['ui.router']);
-     *
-     * app.controller('ctrl', function ($scope, $state) {
-     *   $scope.reload = function(){
-     *     $state.reload();
-     *   }
-     * });
-     * 
- * - * `reload()` is just an alias for: - *
-     * $state.transitionTo($state.current, $stateParams, { 
-     *   reload: true, inherit: false, notify: true
-     * });
-     * 
- * - * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved. - * @example - *
-     * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item' 
-     * //and current state is 'contacts.detail.item'
-     * var app angular.module('app', ['ui.router']);
-     *
-     * app.controller('ctrl', function ($scope, $state) {
-     *   $scope.reload = function(){
-     *     //will reload 'contact.detail' and 'contact.detail.item' states
-     *     $state.reload('contact.detail');
-     *   }
-     * });
-     * 
- * - * `reload()` is just an alias for: - *
-     * $state.transitionTo($state.current, $stateParams, { 
-     *   reload: true, inherit: false, notify: true
-     * });
-     * 
- - * @returns {promise} A promise representing the state of the new transition. See - * {@link ui.router.state.$state#methods_go $state.go}. - */ - $state.reload = function reload(state) { - return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true}); - }; - - /** - * @ngdoc function - * @name ui.router.state.$state#go - * @methodOf ui.router.state.$state - * - * @description - * Convenience method for transitioning to a new state. `$state.go` calls - * `$state.transitionTo` internally but automatically sets options to - * `{ location: true, inherit: true, relative: $state.$current, notify: true }`. - * This allows you to easily use an absolute or relative to path and specify - * only the parameters you'd like to update (while letting unspecified parameters - * inherit from the currently active ancestor states). - * - * @example - *
-     * var app = angular.module('app', ['ui.router']);
-     *
-     * app.controller('ctrl', function ($scope, $state) {
-     *   $scope.changeState = function () {
-     *     $state.go('contact.detail');
-     *   };
-     * });
-     * 
- * - * - * @param {string} to Absolute state name or relative state path. Some examples: - * - * - `$state.go('contact.detail')` - will go to the `contact.detail` state - * - `$state.go('^')` - will go to a parent state - * - `$state.go('^.sibling')` - will go to a sibling state - * - `$state.go('.child.grandchild')` - will go to grandchild state - * - * @param {object=} params A map of the parameters that will be sent to the state, - * will populate $stateParams. Any parameters that are not specified will be inherited from currently - * defined parameters. Only parameters specified in the state definition can be overridden, new - * parameters will be ignored. This allows, for example, going to a sibling state that shares parameters - * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e. - * transitioning to a sibling will get you the parameters for all parents, transitioning to a child - * will get you all current parameters, etc. - * @param {object=} options Options object. The options are: - * - * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` - * will not. If string, must be `"replace"`, which will update url and also replace last history record. - * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. - * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), - * defines which state to be relative from. - * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. - * - **`reload`** (v0.2.5) - {boolean=false|string|object}, If `true` will force transition even if no state or params - * have changed. It will reload the resolves and views of the current state and parent states. - * If `reload` is a string (or state object), the state object is fetched (by name, or object reference); and \ - * the transition reloads the resolves and views for that matched state, and all its children states. - * - * @returns {promise} A promise representing the state of the new transition. - * - * Possible success values: - * - * - $state.current - * - *
Possible rejection values: - * - * - 'transition superseded' - when a newer transition has been started after this one - * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener - * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or - * when a `$stateNotFound` `event.retry` promise errors. - * - 'transition failed' - when a state has been unsuccessfully found after 2 tries. - * - *resolve error* - when an error has occurred with a `resolve` - * - */ - $state.go = function go(to, params, options) { - return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options)); - }; - - /** - * @ngdoc function - * @name ui.router.state.$state#transitionTo - * @methodOf ui.router.state.$state - * - * @description - * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go} - * uses `transitionTo` internally. `$state.go` is recommended in most situations. - * - * @example - *
-     * var app = angular.module('app', ['ui.router']);
-     *
-     * app.controller('ctrl', function ($scope, $state) {
-     *   $scope.changeState = function () {
-     *     $state.transitionTo('contact.detail');
-     *   };
-     * });
-     * 
- * - * @param {string} to State name. - * @param {object=} toParams A map of the parameters that will be sent to the state, - * will populate $stateParams. - * @param {object=} options Options object. The options are: - * - * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` - * will not. If string, must be `"replace"`, which will update url and also replace last history record. - * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url. - * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'), - * defines which state to be relative from. - * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. - * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params - * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd - * use this when you want to force a reload when *everything* is the same, including search params. - * if String, then will reload the state with the name given in reload, and any children. - * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children. - * - * @returns {promise} A promise representing the state of the new transition. See - * {@link ui.router.state.$state#methods_go $state.go}. - */ - $state.transitionTo = function transitionTo(to, toParams, options) { - toParams = toParams || {}; - options = extend({ - location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false - }, options || {}); - - var from = $state.$current, fromParams = $state.params, fromPath = from.path; - var evt, toState = findState(to, options.relative); - - // Store the hash param for later (since it will be stripped out by various methods) - var hash = toParams['#']; - - if (!isDefined(toState)) { - var redirect = { to: to, toParams: toParams, options: options }; - var redirectResult = handleRedirect(redirect, from.self, fromParams, options); - - if (redirectResult) { - return redirectResult; - } - - // Always retry once if the $stateNotFound was not prevented - // (handles either redirect changed or state lazy-definition) - to = redirect.to; - toParams = redirect.toParams; - options = redirect.options; - toState = findState(to, options.relative); - - if (!isDefined(toState)) { - if (!options.relative) throw new Error("No such state '" + to + "'"); - throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'"); - } - } - if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'"); - if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState); - if (!toState.params.$$validates(toParams)) return TransitionFailed; - - toParams = toState.params.$$values(toParams); - to = toState; - - var toPath = to.path; - - // Starting from the root of the path, keep all levels that haven't changed - var keep = 0, state = toPath[keep], locals = root.locals, toLocals = []; - - if (!options.reload) { - while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) { - locals = toLocals[keep] = state.locals; - keep++; - state = toPath[keep]; - } - } else if (isString(options.reload) || isObject(options.reload)) { - if (isObject(options.reload) && !options.reload.name) { - throw new Error('Invalid reload state object'); - } - - var reloadState = options.reload === true ? fromPath[0] : findState(options.reload); - if (options.reload && !reloadState) { - throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'"); - } - - while (state && state === fromPath[keep] && state !== reloadState) { - locals = toLocals[keep] = state.locals; - keep++; - state = toPath[keep]; - } - } - - // If we're going to the same state and all locals are kept, we've got nothing to do. - // But clear 'transition', as we still want to cancel any other pending transitions. - // TODO: We may not want to bump 'transition' if we're called from a location change - // that we've initiated ourselves, because we might accidentally abort a legitimate - // transition initiated from code? - if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) { - if (hash) toParams['#'] = hash; - $state.params = toParams; - copy($state.params, $stateParams); - copy(filterByKeys(to.params.$$keys(), $stateParams), to.locals.globals.$stateParams); - if (options.location && to.navigable && to.navigable.url) { - $urlRouter.push(to.navigable.url, toParams, { - $$avoidResync: true, replace: options.location === 'replace' - }); - $urlRouter.update(true); - } - $state.transition = null; - return $q.when($state.current); - } - - // Filter parameters before we pass them to event handlers etc. - toParams = filterByKeys(to.params.$$keys(), toParams || {}); - - // Re-add the saved hash before we start returning things or broadcasting $stateChangeStart - if (hash) toParams['#'] = hash; - - // Broadcast start event and cancel the transition if requested - if (options.notify) { - /** - * @ngdoc event - * @name ui.router.state.$state#$stateChangeStart - * @eventOf ui.router.state.$state - * @eventType broadcast on root scope - * @description - * Fired when the state transition **begins**. You can use `event.preventDefault()` - * to prevent the transition from happening and then the transition promise will be - * rejected with a `'transition prevented'` value. - * - * @param {Object} event Event object. - * @param {State} toState The state being transitioned to. - * @param {Object} toParams The params supplied to the `toState`. - * @param {State} fromState The current state, pre-transition. - * @param {Object} fromParams The params supplied to the `fromState`. - * - * @example - * - *
-         * $rootScope.$on('$stateChangeStart',
-         * function(event, toState, toParams, fromState, fromParams){
-         *     event.preventDefault();
-         *     // transitionTo() promise will be rejected with
-         *     // a 'transition prevented' error
-         * })
-         * 
- */ - if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams, options).defaultPrevented) { - $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); - //Don't update and resync url if there's been a new transition started. see issue #2238, #600 - if ($state.transition == null) $urlRouter.update(); - return TransitionPrevented; - } - } - - // Resolve locals for the remaining states, but don't update any global state just - // yet -- if anything fails to resolve the current state needs to remain untouched. - // We also set up an inheritance chain for the locals here. This allows the view directive - // to quickly look up the correct definition for each view in the current state. Even - // though we create the locals object itself outside resolveState(), it is initially - // empty and gets filled asynchronously. We need to keep track of the promise for the - // (fully resolved) current locals, and pass this down the chain. - var resolved = $q.when(locals); - - for (var l = keep; l < toPath.length; l++, state = toPath[l]) { - locals = toLocals[l] = inherit(locals); - resolved = resolveState(state, toParams, state === to, resolved, locals, options); - } - - // Once everything is resolved, we are ready to perform the actual transition - // and return a promise for the new state. We also keep track of what the - // current promise is, so that we can detect overlapping transitions and - // keep only the outcome of the last transition. - var transition = $state.transition = resolved.then(function () { - var l, entering, exiting; - - if ($state.transition !== transition) { - $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); - return TransitionSuperseded; - } - - // Exit 'from' states not kept - for (l = fromPath.length - 1; l >= keep; l--) { - exiting = fromPath[l]; - if (exiting.self.onExit) { - $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals); - } - exiting.locals = null; - } - - // Enter 'to' states not kept - for (l = keep; l < toPath.length; l++) { - entering = toPath[l]; - entering.locals = toLocals[l]; - if (entering.self.onEnter) { - $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals); - } - } - - // Run it again, to catch any transitions in callbacks - if ($state.transition !== transition) { - $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); - return TransitionSuperseded; - } - - // Update globals in $state - $state.$current = to; - $state.current = to.self; - $state.params = toParams; - copy($state.params, $stateParams); - $state.transition = null; - - if (options.location && to.navigable) { - $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, { - $$avoidResync: true, replace: options.location === 'replace' - }); - } - - if (options.notify) { - /** - * @ngdoc event - * @name ui.router.state.$state#$stateChangeSuccess - * @eventOf ui.router.state.$state - * @eventType broadcast on root scope - * @description - * Fired once the state transition is **complete**. - * - * @param {Object} event Event object. - * @param {State} toState The state being transitioned to. - * @param {Object} toParams The params supplied to the `toState`. - * @param {State} fromState The current state, pre-transition. - * @param {Object} fromParams The params supplied to the `fromState`. - */ - $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams); - } - $urlRouter.update(true); - - return $state.current; - }).then(null, function (error) { - // propagate TransitionSuperseded error without emitting $stateChangeCancel - // as it was already emitted in the success handler above - if (error === TransitionSupersededError) return TransitionSuperseded; - - if ($state.transition !== transition) { - $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); - return TransitionSuperseded; - } - - $state.transition = null; - /** - * @ngdoc event - * @name ui.router.state.$state#$stateChangeError - * @eventOf ui.router.state.$state - * @eventType broadcast on root scope - * @description - * Fired when an **error occurs** during transition. It's important to note that if you - * have any errors in your resolve functions (javascript errors, non-existent services, etc) - * they will not throw traditionally. You must listen for this $stateChangeError event to - * catch **ALL** errors. - * - * @param {Object} event Event object. - * @param {State} toState The state being transitioned to. - * @param {Object} toParams The params supplied to the `toState`. - * @param {State} fromState The current state, pre-transition. - * @param {Object} fromParams The params supplied to the `fromState`. - * @param {Error} error The resolve error object. - */ - evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error); - - if (!evt.defaultPrevented) { - $urlRouter.update(); - } - - return $q.reject(error); - }); - - silenceUncaughtInPromise(transition); - return transition; - }; - - /** - * @ngdoc function - * @name ui.router.state.$state#is - * @methodOf ui.router.state.$state - * - * @description - * Similar to {@link ui.router.state.$state#methods_includes $state.includes}, - * but only checks for the full state name. If params is supplied then it will be - * tested for strict equality against the current active params object, so all params - * must match with none missing and no extras. - * - * @example - *
-     * $state.$current.name = 'contacts.details.item';
-     *
-     * // absolute name
-     * $state.is('contact.details.item'); // returns true
-     * $state.is(contactDetailItemStateObject); // returns true
-     *
-     * // relative name (. and ^), typically from a template
-     * // E.g. from the 'contacts.details' template
-     * 
Item
- *
- * - * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check. - * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like - * to test against the current active state. - * @param {object=} options An options object. The options are: - * - * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will - * test relative to `options.relative` state (or name). - * - * @returns {boolean} Returns true if it is the state. - */ - $state.is = function is(stateOrName, params, options) { - options = extend({ relative: $state.$current }, options || {}); - var state = findState(stateOrName, options.relative); - - if (!isDefined(state)) { return undefined; } - if ($state.$current !== state) { return false; } - - return !params || objectKeys(params).reduce(function(acc, key) { - var paramDef = state.params[key]; - return acc && !paramDef || paramDef.type.equals($stateParams[key], params[key]); - }, true); - }; - - /** - * @ngdoc function - * @name ui.router.state.$state#includes - * @methodOf ui.router.state.$state - * - * @description - * A method to determine if the current active state is equal to or is the child of the - * state stateName. If any params are passed then they will be tested for a match as well. - * Not all the parameters need to be passed, just the ones you'd like to test for equality. - * - * @example - * Partial and relative names - *
-     * $state.$current.name = 'contacts.details.item';
-     *
-     * // Using partial names
-     * $state.includes("contacts"); // returns true
-     * $state.includes("contacts.details"); // returns true
-     * $state.includes("contacts.details.item"); // returns true
-     * $state.includes("contacts.list"); // returns false
-     * $state.includes("about"); // returns false
-     *
-     * // Using relative names (. and ^), typically from a template
-     * // E.g. from the 'contacts.details' template
-     * 
Item
- *
- * - * Basic globbing patterns - *
-     * $state.$current.name = 'contacts.details.item.url';
-     *
-     * $state.includes("*.details.*.*"); // returns true
-     * $state.includes("*.details.**"); // returns true
-     * $state.includes("**.item.**"); // returns true
-     * $state.includes("*.details.item.url"); // returns true
-     * $state.includes("*.details.*.url"); // returns true
-     * $state.includes("*.details.*"); // returns false
-     * $state.includes("item.**"); // returns false
-     * 
- * - * @param {string} stateOrName A partial name, relative name, or glob pattern - * to be searched for within the current state name. - * @param {object=} params A param object, e.g. `{sectionId: section.id}`, - * that you'd like to test against the current active state. - * @param {object=} options An options object. The options are: - * - * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set, - * .includes will test relative to `options.relative` state (or name). - * - * @returns {boolean} Returns true if it does include the state - */ - $state.includes = function includes(stateOrName, params, options) { - options = extend({ relative: $state.$current }, options || {}); - if (isString(stateOrName) && isGlob(stateOrName)) { - if (!doesStateMatchGlob(stateOrName)) { - return false; - } - stateOrName = $state.$current.name; - } - - var state = findState(stateOrName, options.relative); - if (!isDefined(state)) { return undefined; } - if (!isDefined($state.$current.includes[state.name])) { return false; } - if (!params) { return true; } - - var keys = objectKeys(params); - for (var i = 0; i < keys.length; i++) { - var key = keys[i], paramDef = state.params[key]; - if (paramDef && !paramDef.type.equals($stateParams[key], params[key])) { - return false; - } - } - - return objectKeys(params).reduce(function(acc, key) { - var paramDef = state.params[key]; - return acc && !paramDef || paramDef.type.equals($stateParams[key], params[key]); - }, true); - }; - - - /** - * @ngdoc function - * @name ui.router.state.$state#href - * @methodOf ui.router.state.$state - * - * @description - * A url generation method that returns the compiled url for the given state populated with the given params. - * - * @example - *
-     * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
-     * 
- * - * @param {string|object} stateOrName The state name or state object you'd like to generate a url from. - * @param {object=} params An object of parameter values to fill the state's required parameters. - * @param {object=} options Options object. The options are: - * - * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the - * first parameter, then the constructed href url will be built from the first navigable ancestor (aka - * ancestor with a valid url). - * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. - * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), - * defines which state to be relative from. - * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". - * - * @returns {string} compiled state url - */ - $state.href = function href(stateOrName, params, options) { - options = extend({ - lossy: true, - inherit: true, - absolute: false, - relative: $state.$current - }, options || {}); - - var state = findState(stateOrName, options.relative); - - if (!isDefined(state)) return null; - if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state); - - var nav = (state && options.lossy) ? state.navigable : state; - - if (!nav || nav.url === undefined || nav.url === null) { - return null; - } - return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), { - absolute: options.absolute - }); - }; - - /** - * @ngdoc function - * @name ui.router.state.$state#get - * @methodOf ui.router.state.$state - * - * @description - * Returns the state configuration object for any specific state or all states. - * - * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for - * the requested state. If not provided, returns an array of ALL state configs. - * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context. - * @returns {Object|Array} State configuration object or array of all objects. - */ - $state.get = function (stateOrName, context) { - if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; }); - var state = findState(stateOrName, context || $state.$current); - return (state && state.self) ? state.self : null; - }; - - function resolveState(state, params, paramsAreFiltered, inherited, dst, options) { - // Make a restricted $stateParams with only the parameters that apply to this state if - // necessary. In addition to being available to the controller and onEnter/onExit callbacks, - // we also need $stateParams to be available for any $injector calls we make during the - // dependency resolution process. - var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params); - var locals = { $stateParams: $stateParams }; - - // Resolve 'global' dependencies for the state, i.e. those not specific to a view. - // We're also including $stateParams in this; that way the parameters are restricted - // to the set that should be visible to the state, and are independent of when we update - // the global $state and $stateParams values. - dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state); - var promises = [dst.resolve.then(function (globals) { - dst.globals = globals; - })]; - if (inherited) promises.push(inherited); - - function resolveViews() { - var viewsPromises = []; - - // Resolve template and dependencies for all views. - forEach(state.views, function (view, name) { - var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {}); - injectables.$template = [ function () { - return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || ''; - }]; - - viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) { - // References to the controller (only instantiated at link time) - if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) { - var injectLocals = angular.extend({}, injectables, dst.globals); - result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals); - } else { - result.$$controller = view.controller; - } - // Provide access to the state itself for internal use - result.$$state = state; - result.$$controllerAs = view.controllerAs; - result.$$resolveAs = view.resolveAs; - dst[name] = result; - })); - }); - - return $q.all(viewsPromises).then(function(){ - return dst.globals; - }); - } - - // Wait for all the promises and then return the activation object - return $q.all(promises).then(resolveViews).then(function (values) { - return dst; - }); - } - - return $state; - } - - function shouldSkipReload(to, toParams, from, fromParams, locals, options) { - // Return true if there are no differences in non-search (path/object) params, false if there are differences - function nonSearchParamsEqual(fromAndToState, fromParams, toParams) { - // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params. - function notSearchParam(key) { - return fromAndToState.params[key].location != "search"; - } - var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam); - var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys)); - var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams); - return nonQueryParamSet.$$equals(fromParams, toParams); - } - - // If reload was not explicitly requested - // and we're transitioning to the same state we're already in - // and the locals didn't change - // or they changed in a way that doesn't merit reloading - // (reloadOnParams:false, or reloadOnSearch.false and only search params changed) - // Then return true. - if (!options.reload && to === from && - (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) { - return true; - } - } -} - -angular.module('ui.router.state') - .factory('$stateParams', function () { return {}; }) - .constant("$state.runtime", { autoinject: true }) - .provider('$state', $StateProvider) - // Inject $state to initialize when entering runtime. #2574 - .run(['$injector', function ($injector) { - // Allow tests (stateSpec.js) to turn this off by defining this constant - if ($injector.get("$state.runtime").autoinject) { - $injector.get('$state'); - } - }]); - - -$ViewProvider.$inject = []; -function $ViewProvider() { - - this.$get = $get; - /** - * @ngdoc object - * @name ui.router.state.$view - * - * @requires ui.router.util.$templateFactory - * @requires $rootScope - * - * @description - * - */ - $get.$inject = ['$rootScope', '$templateFactory']; - function $get( $rootScope, $templateFactory) { - return { - // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... }) - /** - * @ngdoc function - * @name ui.router.state.$view#load - * @methodOf ui.router.state.$view - * - * @description - * - * @param {string} name name - * @param {object} options option object. - */ - load: function load(name, options) { - var result, defaults = { - template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {} - }; - options = extend(defaults, options); - - if (options.view) { - result = $templateFactory.fromConfig(options.view, options.params, options.locals); - } - return result; - } - }; - } -} - -angular.module('ui.router.state').provider('$view', $ViewProvider); - -/** - * @ngdoc object - * @name ui.router.state.$uiViewScrollProvider - * - * @description - * Provider that returns the {@link ui.router.state.$uiViewScroll} service function. - */ -function $ViewScrollProvider() { - - var useAnchorScroll = false; - - /** - * @ngdoc function - * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll - * @methodOf ui.router.state.$uiViewScrollProvider - * - * @description - * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for - * scrolling based on the url anchor. - */ - this.useAnchorScroll = function () { - useAnchorScroll = true; - }; - - /** - * @ngdoc object - * @name ui.router.state.$uiViewScroll - * - * @requires $anchorScroll - * @requires $timeout - * - * @description - * When called with a jqLite element, it scrolls the element into view (after a - * `$timeout` so the DOM has time to refresh). - * - * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor, - * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}. - */ - this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) { - if (useAnchorScroll) { - return $anchorScroll; - } - - return function ($element) { - return $timeout(function () { - $element[0].scrollIntoView(); - }, 0, false); - }; - }]; -} - -angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider); - -/** - * @ngdoc directive - * @name ui.router.state.directive:ui-view - * - * @requires ui.router.state.$state - * @requires $compile - * @requires $controller - * @requires $injector - * @requires ui.router.state.$uiViewScroll - * @requires $document - * - * @restrict ECA - * - * @description - * The ui-view directive tells $state where to place your templates. - * - * @param {string=} name A view name. The name should be unique amongst the other views in the - * same state. You can have views of the same name that live in different states. - * - * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window - * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll - * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you - * scroll ui-view elements into view when they are populated during a state activation. - * - * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) - * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.* - * - * @param {string=} onload Expression to evaluate whenever the view updates. - * - * @example - * A view can be unnamed or named. - *
- * 
- * 
- * - * - *
- *
- * - * You can only have one unnamed view within any template (or root html). If you are only using a - * single view and it is unnamed then you can populate it like so: - *
- * 
- * $stateProvider.state("home", { - * template: "

HELLO!

" - * }) - *
- * - * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#methods_state `views`} - * config property, by name, in this case an empty name: - *
- * $stateProvider.state("home", {
- *   views: {
- *     "": {
- *       template: "

HELLO!

" - * } - * } - * }) - *
- * - * But typically you'll only use the views property if you name your view or have more than one view - * in the same template. There's not really a compelling reason to name a view if its the only one, - * but you could if you wanted, like so: - *
- * 
- *
- *
- * $stateProvider.state("home", {
- *   views: {
- *     "main": {
- *       template: "

HELLO!

" - * } - * } - * }) - *
- * - * Really though, you'll use views to set up multiple views: - *
- * 
- *
- *
- *
- * - *
- * $stateProvider.state("home", {
- *   views: {
- *     "": {
- *       template: "

HELLO!

" - * }, - * "chart": { - * template: "" - * }, - * "data": { - * template: "" - * } - * } - * }) - *
- * - * Examples for `autoscroll`: - * - *
- * 
- * 
- *
- * 
- * 
- * 
- * 
- * 
- * - * Resolve data: - * - * The resolved data from the state's `resolve` block is placed on the scope as `$resolve` (this - * can be customized using [[ViewDeclaration.resolveAs]]). This can be then accessed from the template. - * - * Note that when `controllerAs` is being used, `$resolve` is set on the controller instance *after* the - * controller is instantiated. The `$onInit()` hook can be used to perform initialization code which - * depends on `$resolve` data. - * - * Example usage of $resolve in a view template - *
- * $stateProvider.state('home', {
- *   template: '',
- *   resolve: {
- *     user: function(UserService) { return UserService.fetchUser(); }
- *   }
- * });
- * 
- */ -$ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate', '$q']; -function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate, $q) { - - function getService() { - return ($injector.has) ? function(service) { - return $injector.has(service) ? $injector.get(service) : null; - } : function(service) { - try { - return $injector.get(service); - } catch (e) { - return null; - } - }; - } - - var service = getService(), - $animator = service('$animator'), - $animate = service('$animate'); - - // Returns a set of DOM manipulation functions based on which Angular version - // it should use - function getRenderer(attrs, scope) { - var statics = function() { - return { - enter: function (element, target, cb) { target.after(element); cb(); }, - leave: function (element, cb) { element.remove(); cb(); } - }; - }; - - if ($animate) { - return { - enter: function(element, target, cb) { - if (angular.version.minor > 2) { - $animate.enter(element, null, target).then(cb); - } else { - $animate.enter(element, null, target, cb); - } - }, - leave: function(element, cb) { - if (angular.version.minor > 2) { - $animate.leave(element).then(cb); - } else { - $animate.leave(element, cb); - } - } - }; - } - - if ($animator) { - var animate = $animator && $animator(scope, attrs); - - return { - enter: function(element, target, cb) {animate.enter(element, null, target); cb(); }, - leave: function(element, cb) { animate.leave(element); cb(); } - }; - } - - return statics(); - } - - var directive = { - restrict: 'ECA', - terminal: true, - priority: 400, - transclude: 'element', - compile: function (tElement, tAttrs, $transclude) { - return function (scope, $element, attrs) { - var previousEl, currentEl, currentScope, latestLocals, - onloadExp = attrs.onload || '', - autoScrollExp = attrs.autoscroll, - renderer = getRenderer(attrs, scope), - inherited = $element.inheritedData('$uiView'); - - scope.$on('$stateChangeSuccess', function() { - updateView(false); - }); - - updateView(true); - - function cleanupLastView() { - if (previousEl) { - previousEl.remove(); - previousEl = null; - } - - if (currentScope) { - currentScope.$destroy(); - currentScope = null; - } - - if (currentEl) { - var $uiViewData = currentEl.data('$uiViewAnim'); - renderer.leave(currentEl, function() { - $uiViewData.$$animLeave.resolve(); - previousEl = null; - }); - - previousEl = currentEl; - currentEl = null; - } - } - - function updateView(firstTime) { - var newScope, - name = getUiViewName(scope, attrs, $element, $interpolate), - previousLocals = name && $state.$current && $state.$current.locals[name]; - - if (!firstTime && previousLocals === latestLocals) return; // nothing to do - newScope = scope.$new(); - latestLocals = $state.$current.locals[name]; - - /** - * @ngdoc event - * @name ui.router.state.directive:ui-view#$viewContentLoading - * @eventOf ui.router.state.directive:ui-view - * @eventType emits on ui-view directive scope - * @description - * - * Fired once the view **begins loading**, *before* the DOM is rendered. - * - * @param {Object} event Event object. - * @param {string} viewName Name of the view. - */ - newScope.$emit('$viewContentLoading', name); - - var clone = $transclude(newScope, function(clone) { - var animEnter = $q.defer(), animLeave = $q.defer(); - var viewAnimData = { - $animEnter: animEnter.promise, - $animLeave: animLeave.promise, - $$animLeave: animLeave - }; - - clone.data('$uiViewAnim', viewAnimData); - renderer.enter(clone, $element, function onUiViewEnter() { - animEnter.resolve(); - if(currentScope) { - currentScope.$emit('$viewContentAnimationEnded'); - } - - if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) { - $uiViewScroll(clone); - } - }); - cleanupLastView(); - }); - - currentEl = clone; - currentScope = newScope; - /** - * @ngdoc event - * @name ui.router.state.directive:ui-view#$viewContentLoaded - * @eventOf ui.router.state.directive:ui-view - * @eventType emits on ui-view directive scope - * @description - * Fired once the view is **loaded**, *after* the DOM is rendered. - * - * @param {Object} event Event object. - * @param {string} viewName Name of the view. - */ - currentScope.$emit('$viewContentLoaded', name); - currentScope.$eval(onloadExp); - } - }; - } - }; - - return directive; -} - -$ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate']; -function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) { - return { - restrict: 'ECA', - priority: -400, - compile: function (tElement) { - var initial = tElement.html(); - if (tElement.empty) { - tElement.empty(); - } else { - // ng 1.0.0 doesn't have empty(), which cleans up data and handlers - tElement[0].innerHTML = null; - } - - return function (scope, $element, attrs) { - var current = $state.$current, - name = getUiViewName(scope, attrs, $element, $interpolate), - locals = current && current.locals[name]; - - if (! locals) { - $element.html(initial); - $compile($element.contents())(scope); - return; - } - - $element.data('$uiView', { name: name, state: locals.$$state }); - $element.html(locals.$template ? locals.$template : initial); - - var resolveData = angular.extend({}, locals); - scope[locals.$$resolveAs] = resolveData; - - var link = $compile($element.contents()); - - if (locals.$$controller) { - locals.$scope = scope; - locals.$element = $element; - var controller = $controller(locals.$$controller, locals); - if (locals.$$controllerAs) { - scope[locals.$$controllerAs] = controller; - scope[locals.$$controllerAs][locals.$$resolveAs] = resolveData; - } - if (isFunction(controller.$onInit)) controller.$onInit(); - $element.data('$ngControllerController', controller); - $element.children().data('$ngControllerController', controller); - } - - link(scope); - }; - } - }; -} - -/** - * Shared ui-view code for both directives: - * Given scope, element, and its attributes, return the view's name - */ -function getUiViewName(scope, attrs, element, $interpolate) { - var name = $interpolate(attrs.uiView || attrs.name || '')(scope); - var uiViewCreatedBy = element.inheritedData('$uiView'); - return name.indexOf('@') >= 0 ? name : (name + '@' + (uiViewCreatedBy ? uiViewCreatedBy.state.name : '')); -} - -angular.module('ui.router.state').directive('uiView', $ViewDirective); -angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill); - -function parseStateRef(ref, current) { - var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed; - if (preparsed) ref = current + '(' + preparsed[1] + ')'; - parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/); - if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'"); - return { state: parsed[1], paramExpr: parsed[3] || null }; -} - -function stateContext(el) { - var stateData = el.parent().inheritedData('$uiView'); - - if (stateData && stateData.state && stateData.state.name) { - return stateData.state; - } -} - -function getTypeInfo(el) { - // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. - var isSvg = Object.prototype.toString.call(el.prop('href')) === '[object SVGAnimatedString]'; - var isForm = el[0].nodeName === "FORM"; - - return { - attr: isForm ? "action" : (isSvg ? 'xlink:href' : 'href'), - isAnchor: el.prop("tagName").toUpperCase() === "A", - clickable: !isForm - }; -} - -function clickHook(el, $state, $timeout, type, current) { - return function(e) { - var button = e.which || e.button, target = current(); - - if (!(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || el.attr('target'))) { - // HACK: This is to allow ng-clicks to be processed before the transition is initiated: - var transition = $timeout(function() { - $state.go(target.state, target.params, target.options); - }); - e.preventDefault(); - - // if the state has no URL, ignore one preventDefault from the directive. - var ignorePreventDefaultCount = type.isAnchor && !target.href ? 1: 0; - - e.preventDefault = function() { - if (ignorePreventDefaultCount-- <= 0) $timeout.cancel(transition); - }; - } - }; -} - -function defaultOpts(el, $state) { - return { relative: stateContext(el) || $state.$current, inherit: true }; -} - -/** - * @ngdoc directive - * @name ui.router.state.directive:ui-sref - * - * @requires ui.router.state.$state - * @requires $timeout - * - * @restrict A - * - * @description - * A directive that binds a link (`` tag) to a state. If the state has an associated - * URL, the directive will automatically generate & update the `href` attribute via - * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking - * the link will trigger a state transition with optional parameters. - * - * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be - * handled natively by the browser. - * - * You can also use relative state paths within ui-sref, just like the relative - * paths passed to `$state.go()`. You just need to be aware that the path is relative - * to the state that the link lives in, in other words the state that loaded the - * template containing the link. - * - * You can specify options to pass to {@link ui.router.state.$state#methods_go $state.go()} - * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`, - * and `reload`. - * - * @example - * Here's an example of how you'd use ui-sref and how it would compile. If you have the - * following template: - *
- * Home | About | Next page
- *
- * 
- * 
- * - * Then the compiled html would be (assuming Html5Mode is off and current state is contacts): - *
- * Home | About | Next page
- *
- * 
    - *
  • - * Joe - *
  • - *
  • - * Alice - *
  • - *
  • - * Bob - *
  • - *
- * - * Home - *
- * - * @param {string} ui-sref 'stateName' can be any valid absolute or relative state - * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#methods_go $state.go()} - */ -$StateRefDirective.$inject = ['$state', '$timeout']; -function $StateRefDirective($state, $timeout) { - return { - restrict: 'A', - require: ['?^uiSrefActive', '?^uiSrefActiveEq'], - link: function(scope, element, attrs, uiSrefActive) { - var ref = parseStateRef(attrs.uiSref, $state.current.name); - var def = { state: ref.state, href: null, params: null }; - var type = getTypeInfo(element); - var active = uiSrefActive[1] || uiSrefActive[0]; - var unlinkInfoFn = null; - var hookFn; - - def.options = extend(defaultOpts(element, $state), attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {}); - - var update = function(val) { - if (val) def.params = angular.copy(val); - def.href = $state.href(ref.state, def.params, def.options); - - if (unlinkInfoFn) unlinkInfoFn(); - if (active) unlinkInfoFn = active.$$addStateInfo(ref.state, def.params); - if (def.href !== null) attrs.$set(type.attr, def.href); - }; - - if (ref.paramExpr) { - scope.$watch(ref.paramExpr, function(val) { if (val !== def.params) update(val); }, true); - def.params = angular.copy(scope.$eval(ref.paramExpr)); - } - update(); - - if (!type.clickable) return; - hookFn = clickHook(element, $state, $timeout, type, function() { return def; }); - element[element.on ? 'on' : 'bind']("click", hookFn); - scope.$on('$destroy', function() { - element[element.off ? 'off' : 'unbind']("click", hookFn); - }); - } - }; -} - -/** - * @ngdoc directive - * @name ui.router.state.directive:ui-state - * - * @requires ui.router.state.uiSref - * - * @restrict A - * - * @description - * Much like ui-sref, but will accept named $scope properties to evaluate for a state definition, - * params and override options. - * - * @param {string} ui-state 'stateName' can be any valid absolute or relative state - * @param {Object} ui-state-params params to pass to {@link ui.router.state.$state#methods_href $state.href()} - * @param {Object} ui-state-opts options to pass to {@link ui.router.state.$state#methods_go $state.go()} - */ -$StateRefDynamicDirective.$inject = ['$state', '$timeout']; -function $StateRefDynamicDirective($state, $timeout) { - return { - restrict: 'A', - require: ['?^uiSrefActive', '?^uiSrefActiveEq'], - link: function(scope, element, attrs, uiSrefActive) { - var type = getTypeInfo(element); - var active = uiSrefActive[1] || uiSrefActive[0]; - var group = [attrs.uiState, attrs.uiStateParams || null, attrs.uiStateOpts || null]; - var watch = '[' + group.map(function(val) { return val || 'null'; }).join(', ') + ']'; - var def = { state: null, params: null, options: null, href: null }; - var unlinkInfoFn = null; - var hookFn; - - function runStateRefLink (group) { - def.state = group[0]; def.params = group[1]; def.options = group[2]; - def.href = $state.href(def.state, def.params, def.options); - - if (unlinkInfoFn) unlinkInfoFn(); - if (active) unlinkInfoFn = active.$$addStateInfo(def.state, def.params); - if (def.href) attrs.$set(type.attr, def.href); - } - - scope.$watch(watch, runStateRefLink, true); - runStateRefLink(scope.$eval(watch)); - - if (!type.clickable) return; - hookFn = clickHook(element, $state, $timeout, type, function() { return def; }); - element[element.on ? 'on' : 'bind']("click", hookFn); - scope.$on('$destroy', function() { - element[element.off ? 'off' : 'unbind']("click", hookFn); - }); - } - }; -} - - -/** - * @ngdoc directive - * @name ui.router.state.directive:ui-sref-active - * - * @requires ui.router.state.$state - * @requires ui.router.state.$stateParams - * @requires $interpolate - * - * @restrict A - * - * @description - * A directive working alongside ui-sref to add classes to an element when the - * related ui-sref directive's state is active, and removing them when it is inactive. - * The primary use-case is to simplify the special appearance of navigation menus - * relying on `ui-sref`, by having the "active" state's menu button appear different, - * distinguishing it from the inactive menu items. - * - * ui-sref-active can live on the same element as ui-sref or on a parent element. The first - * ui-sref-active found at the same level or above the ui-sref will be used. - * - * Will activate when the ui-sref's target state or any child state is active. If you - * need to activate only when the ui-sref target state is active and *not* any of - * it's children, then you will use - * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq} - * - * @example - * Given the following template: - *
- * 
- * 
- * - * - * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins", - * the resulting HTML will appear as (note the 'active' class): - *
- * 
- * 
- * - * The class name is interpolated **once** during the directives link time (any further changes to the - * interpolated value are ignored). - * - * Multiple classes may be specified in a space-separated format: - *
- * 
    - *
  • - * link - *
  • - *
- *
- * - * It is also possible to pass ui-sref-active an expression that evaluates - * to an object hash, whose keys represent active class names and whose - * values represent the respective state names/globs. - * ui-sref-active will match if the current active state **includes** any of - * the specified state names/globs, even the abstract ones. - * - * @Example - * Given the following template, with "admin" being an abstract state: - *
- * 
- * Roles - *
- *
- * - * When the current state is "admin.roles" the "active" class will be applied - * to both the
and elements. It is important to note that the state - * names/globs passed to ui-sref-active shadow the state provided by ui-sref. - */ - -/** - * @ngdoc directive - * @name ui.router.state.directive:ui-sref-active-eq - * - * @requires ui.router.state.$state - * @requires ui.router.state.$stateParams - * @requires $interpolate - * - * @restrict A - * - * @description - * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate - * when the exact target state used in the `ui-sref` is active; no child states. - * - */ -$StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate']; -function $StateRefActiveDirective($state, $stateParams, $interpolate) { - return { - restrict: "A", - controller: ['$scope', '$element', '$attrs', '$timeout', function ($scope, $element, $attrs, $timeout) { - var states = [], activeClasses = {}, activeEqClass, uiSrefActive; - - // There probably isn't much point in $observing this - // uiSrefActive and uiSrefActiveEq share the same directive object with some - // slight difference in logic routing - activeEqClass = $interpolate($attrs.uiSrefActiveEq || '', false)($scope); - - try { - uiSrefActive = $scope.$eval($attrs.uiSrefActive); - } catch (e) { - // Do nothing. uiSrefActive is not a valid expression. - // Fall back to using $interpolate below - } - uiSrefActive = uiSrefActive || $interpolate($attrs.uiSrefActive || '', false)($scope); - if (isObject(uiSrefActive)) { - forEach(uiSrefActive, function(stateOrName, activeClass) { - if (isString(stateOrName)) { - var ref = parseStateRef(stateOrName, $state.current.name); - addState(ref.state, $scope.$eval(ref.paramExpr), activeClass); - } - }); - } - - // Allow uiSref to communicate with uiSrefActive[Equals] - this.$$addStateInfo = function (newState, newParams) { - // we already got an explicit state provided by ui-sref-active, so we - // shadow the one that comes from ui-sref - if (isObject(uiSrefActive) && states.length > 0) { - return; - } - var deregister = addState(newState, newParams, uiSrefActive); - update(); - return deregister; - }; - - $scope.$on('$stateChangeSuccess', update); - - function addState(stateName, stateParams, activeClass) { - var state = $state.get(stateName, stateContext($element)); - var stateHash = createStateHash(stateName, stateParams); - - var stateInfo = { - state: state || { name: stateName }, - params: stateParams, - hash: stateHash - }; - - states.push(stateInfo); - activeClasses[stateHash] = activeClass; - - return function removeState() { - var idx = states.indexOf(stateInfo); - if (idx !== -1) states.splice(idx, 1); - }; - } - - /** - * @param {string} state - * @param {Object|string} [params] - * @return {string} - */ - function createStateHash(state, params) { - if (!isString(state)) { - throw new Error('state should be a string'); - } - if (isObject(params)) { - return state + toJson(params); - } - params = $scope.$eval(params); - if (isObject(params)) { - return state + toJson(params); - } - return state; - } - - // Update route state - function update() { - for (var i = 0; i < states.length; i++) { - if (anyMatch(states[i].state, states[i].params)) { - addClass($element, activeClasses[states[i].hash]); - } else { - removeClass($element, activeClasses[states[i].hash]); - } - - if (exactMatch(states[i].state, states[i].params)) { - addClass($element, activeEqClass); - } else { - removeClass($element, activeEqClass); - } - } - } - - function addClass(el, className) { $timeout(function () { el.addClass(className); }); } - function removeClass(el, className) { el.removeClass(className); } - function anyMatch(state, params) { return $state.includes(state.name, params); } - function exactMatch(state, params) { return $state.is(state.name, params); } - - update(); - }] - }; -} - -angular.module('ui.router.state') - .directive('uiSref', $StateRefDirective) - .directive('uiSrefActive', $StateRefActiveDirective) - .directive('uiSrefActiveEq', $StateRefActiveDirective) - .directive('uiState', $StateRefDynamicDirective); - -/** - * @ngdoc filter - * @name ui.router.state.filter:isState - * - * @requires ui.router.state.$state - * - * @description - * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}. - */ -$IsStateFilter.$inject = ['$state']; -function $IsStateFilter($state) { - var isFilter = function (state, params) { - return $state.is(state, params); - }; - isFilter.$stateful = true; - return isFilter; -} - -/** - * @ngdoc filter - * @name ui.router.state.filter:includedByState - * - * @requires ui.router.state.$state - * - * @description - * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}. - */ -$IncludedByStateFilter.$inject = ['$state']; -function $IncludedByStateFilter($state) { - var includesFilter = function (state, params, options) { - return $state.includes(state, params, options); - }; - includesFilter.$stateful = true; - return includesFilter; -} - -angular.module('ui.router.state') - .filter('isState', $IsStateFilter) - .filter('includedByState', $IncludedByStateFilter); -})(window, window.angular); -/** - * Duo Web SDK v2 - * Copyright 2017, Duo Security - */ - -(function (root, factory) { - /*eslint-disable */ - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define([], factory); - /*eslint-enable */ - } else if (typeof module === 'object' && module.exports) { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like environments that support module.exports, - // like Node. - module.exports = factory(); - } else { - // Browser globals (root is window) - var Duo = factory(); - // If the Javascript was loaded via a script tag, attempt to autoload - // the frame. - Duo._onReady(Duo.init); - - // Attach Duo to the `window` object - root.Duo = Duo; - } -}(this, function() { - var DUO_MESSAGE_FORMAT = /^(?:AUTH|ENROLL)+\|[A-Za-z0-9\+\/=]+\|[A-Za-z0-9\+\/=]+$/; - var DUO_ERROR_FORMAT = /^ERR\|[\w\s\.\(\)]+$/; - var DUO_OPEN_WINDOW_FORMAT = /^DUO_OPEN_WINDOW\|/; - var VALID_OPEN_WINDOW_DOMAINS = [ - 'duo.com', - 'duosecurity.com', - 'duomobile.s3-us-west-1.amazonaws.com' - ]; - - var iframeId = 'duo_iframe', - postAction = '', - postArgument = 'sig_response', - host, - sigRequest, - duoSig, - appSig, - iframe, - submitCallback; - - function throwError(message, url) { - throw new Error( - 'Duo Web SDK error: ' + message + - (url ? ('\n' + 'See ' + url + ' for more information') : '') - ); - } - - function hyphenize(str) { - return str.replace(/([a-z])([A-Z])/, '$1-$2').toLowerCase(); - } - - // cross-browser data attributes - function getDataAttribute(element, name) { - if ('dataset' in element) { - return element.dataset[name]; - } else { - return element.getAttribute('data-' + hyphenize(name)); - } - } - - // cross-browser event binding/unbinding - function on(context, event, fallbackEvent, callback) { - if ('addEventListener' in window) { - context.addEventListener(event, callback, false); - } else { - context.attachEvent(fallbackEvent, callback); - } - } - - function off(context, event, fallbackEvent, callback) { - if ('removeEventListener' in window) { - context.removeEventListener(event, callback, false); - } else { - context.detachEvent(fallbackEvent, callback); - } - } - - function onReady(callback) { - on(document, 'DOMContentLoaded', 'onreadystatechange', callback); - } - - function offReady(callback) { - off(document, 'DOMContentLoaded', 'onreadystatechange', callback); - } - - function onMessage(callback) { - on(window, 'message', 'onmessage', callback); - } - - function offMessage(callback) { - off(window, 'message', 'onmessage', callback); - } - - /** - * Parse the sig_request parameter, throwing errors if the token contains - * a server error or if the token is invalid. - * - * @param {String} sig Request token - */ - function parseSigRequest(sig) { - if (!sig) { - // nothing to do - return; - } - - // see if the token contains an error, throwing it if it does - if (sig.indexOf('ERR|') === 0) { - throwError(sig.split('|')[1]); - } - - // validate the token - if (sig.indexOf(':') === -1 || sig.split(':').length !== 2) { - throwError( - 'Duo was given a bad token. This might indicate a configuration ' + - 'problem with one of Duo\'s client libraries.', - 'https://www.duosecurity.com/docs/duoweb#first-steps' - ); - } - - var sigParts = sig.split(':'); - - // hang on to the token, and the parsed duo and app sigs - sigRequest = sig; - duoSig = sigParts[0]; - appSig = sigParts[1]; - - return { - sigRequest: sig, - duoSig: sigParts[0], - appSig: sigParts[1] - }; - } - - /** - * This function is set up to run when the DOM is ready, if the iframe was - * not available during `init`. - */ - function onDOMReady() { - iframe = document.getElementById(iframeId); - - if (!iframe) { - throw new Error( - 'This page does not contain an iframe for Duo to use.' + - 'Add an element like ' + - 'to this page. ' + - 'See https://www.duosecurity.com/docs/duoweb#3.-show-the-iframe ' + - 'for more information.' - ); - } - - // we've got an iframe, away we go! - ready(); - - // always clean up after yourself - offReady(onDOMReady); - } - - /** - * Validate that a MessageEvent came from the Duo service, and that it - * is a properly formatted payload. - * - * The Google Chrome sign-in page injects some JS into pages that also - * make use of postMessage, so we need to do additional validation above - * and beyond the origin. - * - * @param {MessageEvent} event Message received via postMessage - */ - function isDuoMessage(event) { - return Boolean( - event.origin === ('https://' + host) && - typeof event.data === 'string' && - ( - event.data.match(DUO_MESSAGE_FORMAT) || - event.data.match(DUO_ERROR_FORMAT) || - event.data.match(DUO_OPEN_WINDOW_FORMAT) - ) - ); - } - - /** - * Validate the request token and prepare for the iframe to become ready. - * - * All options below can be passed into an options hash to `Duo.init`, or - * specified on the iframe using `data-` attributes. - * - * Options specified using the options hash will take precedence over - * `data-` attributes. - * - * Example using options hash: - * ```javascript - * Duo.init({ - * iframe: "some_other_id", - * host: "api-main.duo.test", - * sig_request: "...", - * post_action: "/auth", - * post_argument: "resp" - * }); - * ``` - * - * Example using `data-` attributes: - * ``` - * - * ``` - * - * @param {Object} options - * @param {String} options.iframe The iframe, or id of an iframe to set up - * @param {String} options.host Hostname - * @param {String} options.sig_request Request token - * @param {String} [options.post_action=''] URL to POST back to after successful auth - * @param {String} [options.post_argument='sig_response'] Parameter name to use for response token - * @param {Function} [options.submit_callback] If provided, duo will not submit the form instead execute - * the callback function with reference to the "duo_form" form object - * submit_callback can be used to prevent the webpage from reloading. - */ - function init(options) { - if (options) { - if (options.host) { - host = options.host; - } - - if (options.sig_request) { - parseSigRequest(options.sig_request); - } - - if (options.post_action) { - postAction = options.post_action; - } - - if (options.post_argument) { - postArgument = options.post_argument; - } - - if (options.iframe) { - if (options.iframe.tagName) { - iframe = options.iframe; - } else if (typeof options.iframe === 'string') { - iframeId = options.iframe; - } - } - - if (typeof options.submit_callback === 'function') { - submitCallback = options.submit_callback; - } - } - - // if we were given an iframe, no need to wait for the rest of the DOM - if (false && iframe) { - ready(); - } else { - // try to find the iframe in the DOM - iframe = document.getElementById(iframeId); - - // iframe is in the DOM, away we go! - if (iframe) { - ready(); - } else { - // wait until the DOM is ready, then try again - onReady(onDOMReady); - } - } - - // always clean up after yourself! - offReady(init); - } - - /** - * This function is called when a message was received from another domain - * using the `postMessage` API. Check that the event came from the Duo - * service domain, and that the message is a properly formatted payload, - * then perform the post back to the primary service. - * - * @param event Event object (contains origin and data) - */ - function onReceivedMessage(event) { - if (isDuoMessage(event)) { - if (event.data.match(DUO_OPEN_WINDOW_FORMAT)) { - var url = event.data.substring("DUO_OPEN_WINDOW|".length); - if (isValidUrlToOpen(url)) { - // Open the URL that comes after the DUO_WINDOW_OPEN token. - window.open(url, "_self"); - } - } - else { - // the event came from duo, do the post back - doPostBack(event.data); - - // always clean up after yourself! - offMessage(onReceivedMessage); - } - } - } - - /** - * Validate that this passed in URL is one that we will actually allow to - * be opened. - * @param url String URL that the message poster wants to open - * @returns {boolean} true if we allow this url to be opened in the window - */ - function isValidUrlToOpen(url) { - if (!url) { - return false; - } - - var parser = document.createElement('a'); - parser.href = url; - - if (parser.protocol === "duotrustedendpoints:") { - return true; - } else if (parser.protocol !== "https:") { - return false; - } - - for (var i = 0; i < VALID_OPEN_WINDOW_DOMAINS.length; i++) { - if (parser.hostname.endsWith("." + VALID_OPEN_WINDOW_DOMAINS[i]) || - parser.hostname === VALID_OPEN_WINDOW_DOMAINS[i]) { - return true; - } - } - return false; - } - - /** - * Point the iframe at Duo, then wait for it to postMessage back to us. - */ - function ready() { - if (!host) { - host = getDataAttribute(iframe, 'host'); - - if (!host) { - throwError( - 'No API hostname is given for Duo to use. Be sure to pass ' + - 'a `host` parameter to Duo.init, or through the `data-host` ' + - 'attribute on the iframe element.', - 'https://www.duosecurity.com/docs/duoweb#3.-show-the-iframe' - ); - } - } - - if (!duoSig || !appSig) { - parseSigRequest(getDataAttribute(iframe, 'sigRequest')); - - if (!duoSig || !appSig) { - throwError( - 'No valid signed request is given. Be sure to give the ' + - '`sig_request` parameter to Duo.init, or use the ' + - '`data-sig-request` attribute on the iframe element.', - 'https://www.duosecurity.com/docs/duoweb#3.-show-the-iframe' - ); - } - } - - // if postAction/Argument are defaults, see if they are specified - // as data attributes on the iframe - if (postAction === '') { - postAction = getDataAttribute(iframe, 'postAction') || postAction; - } - - if (postArgument === 'sig_response') { - postArgument = getDataAttribute(iframe, 'postArgument') || postArgument; - } - - // point the iframe at Duo - iframe.src = [ - 'https://', host, '/frame/web/v1/auth?tx=', duoSig, - '&parent=', encodeURIComponent(document.location.href), - '&v=2.6' - ].join(''); - - // listen for the 'message' event - onMessage(onReceivedMessage); - } - - /** - * We received a postMessage from Duo. POST back to the primary service - * with the response token, and any additional user-supplied parameters - * given in form#duo_form. - */ - function doPostBack(response) { - // create a hidden input to contain the response token - var input = document.createElement('input'); - input.type = 'hidden'; - input.name = postArgument; - input.value = response + ':' + appSig; - - // user may supply their own form with additional inputs - var form = document.getElementById('duo_form'); - - // if the form doesn't exist, create one - if (!form) { - form = document.createElement('form'); - - // insert the new form after the iframe - iframe.parentElement.insertBefore(form, iframe.nextSibling); - } - - // make sure we are actually posting to the right place - form.method = 'POST'; - form.action = postAction; - - // add the response token input to the form - form.appendChild(input); - - // away we go! - if (typeof submitCallback === "function") { - submitCallback.call(null, form); - } else { - form.submit(); - } - } - - return { - init: init, - _onReady: onReady, - _parseSigRequest: parseSigRequest, - _isDuoMessage: isDuoMessage, - _doPostBack: doPostBack - }; -})); - (function(window, angular, undefined) {'use strict'; /** @@ -20803,1834 +13041,6 @@ angular.module('angulartics.google.analytics', ['angulartics']) }]); })(window, window.angular); -/*! - Papa Parse - v4.3.6 - https://github.com/mholt/PapaParse - License: MIT -*/ -(function(root, factory) -{ - if (typeof define === 'function' && define.amd) - { - // AMD. Register as an anonymous module. - define([], factory); - } - else if (typeof module === 'object' && typeof exports !== 'undefined') - { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like environments that support module.exports, - // like Node. - module.exports = factory(); - } - else - { - // Browser globals (root is window) - root.Papa = factory(); - } -}(this, function() -{ - 'use strict'; - - var global = (function () { - // alternative method, similar to `Function('return this')()` - // but without using `eval` (which is disabled when - // using Content Security Policy). - - if (typeof self !== 'undefined') { return self; } - if (typeof window !== 'undefined') { return window; } - if (typeof global !== 'undefined') { return global; } - - // When running tests none of the above have been defined - return {}; - })(); - - - var IS_WORKER = !global.document && !!global.postMessage, - IS_PAPA_WORKER = IS_WORKER && /(\?|&)papaworker(=|&|$)/.test(global.location.search), - LOADED_SYNC = false, AUTO_SCRIPT_PATH; - var workers = {}, workerIdCounter = 0; - - var Papa = {}; - - Papa.parse = CsvToJson; - Papa.unparse = JsonToCsv; - - Papa.RECORD_SEP = String.fromCharCode(30); - Papa.UNIT_SEP = String.fromCharCode(31); - Papa.BYTE_ORDER_MARK = '\ufeff'; - Papa.BAD_DELIMITERS = ['\r', '\n', '"', Papa.BYTE_ORDER_MARK]; - Papa.WORKERS_SUPPORTED = !IS_WORKER && !!global.Worker; - Papa.SCRIPT_PATH = null; // Must be set by your code if you use workers and this lib is loaded asynchronously - - // Configurable chunk sizes for local and remote files, respectively - Papa.LocalChunkSize = 1024 * 1024 * 10; // 10 MB - Papa.RemoteChunkSize = 1024 * 1024 * 5; // 5 MB - Papa.DefaultDelimiter = ','; // Used if not specified and detection fails - - // Exposed for testing and development only - Papa.Parser = Parser; - Papa.ParserHandle = ParserHandle; - Papa.NetworkStreamer = NetworkStreamer; - Papa.FileStreamer = FileStreamer; - Papa.StringStreamer = StringStreamer; - Papa.ReadableStreamStreamer = ReadableStreamStreamer; - - if (global.jQuery) - { - var $ = global.jQuery; - $.fn.parse = function(options) - { - var config = options.config || {}; - var queue = []; - - this.each(function(idx) - { - var supported = $(this).prop('tagName').toUpperCase() === 'INPUT' - && $(this).attr('type').toLowerCase() === 'file' - && global.FileReader; - - if (!supported || !this.files || this.files.length === 0) - return true; // continue to next input element - - for (var i = 0; i < this.files.length; i++) - { - queue.push({ - file: this.files[i], - inputElem: this, - instanceConfig: $.extend({}, config) - }); - } - }); - - parseNextFile(); // begin parsing - return this; // maintains chainability - - - function parseNextFile() - { - if (queue.length === 0) - { - if (isFunction(options.complete)) - options.complete(); - return; - } - - var f = queue[0]; - - if (isFunction(options.before)) - { - var returned = options.before(f.file, f.inputElem); - - if (typeof returned === 'object') - { - if (returned.action === 'abort') - { - error('AbortError', f.file, f.inputElem, returned.reason); - return; // Aborts all queued files immediately - } - else if (returned.action === 'skip') - { - fileComplete(); // parse the next file in the queue, if any - return; - } - else if (typeof returned.config === 'object') - f.instanceConfig = $.extend(f.instanceConfig, returned.config); - } - else if (returned === 'skip') - { - fileComplete(); // parse the next file in the queue, if any - return; - } - } - - // Wrap up the user's complete callback, if any, so that ours also gets executed - var userCompleteFunc = f.instanceConfig.complete; - f.instanceConfig.complete = function(results) - { - if (isFunction(userCompleteFunc)) - userCompleteFunc(results, f.file, f.inputElem); - fileComplete(); - }; - - Papa.parse(f.file, f.instanceConfig); - } - - function error(name, file, elem, reason) - { - if (isFunction(options.error)) - options.error({name: name}, file, elem, reason); - } - - function fileComplete() - { - queue.splice(0, 1); - parseNextFile(); - } - } - } - - - if (IS_PAPA_WORKER) - { - global.onmessage = workerThreadReceivedMessage; - } - else if (Papa.WORKERS_SUPPORTED) - { - AUTO_SCRIPT_PATH = getScriptPath(); - - // Check if the script was loaded synchronously - if (!document.body) - { - // Body doesn't exist yet, must be synchronous - LOADED_SYNC = true; - } - else - { - document.addEventListener('DOMContentLoaded', function () { - LOADED_SYNC = true; - }, true); - } - } - - - - - function CsvToJson(_input, _config) - { - _config = _config || {}; - var dynamicTyping = _config.dynamicTyping || false; - if (isFunction(dynamicTyping)) { - _config.dynamicTypingFunction = dynamicTyping; - // Will be filled on first row call - dynamicTyping = {}; - } - _config.dynamicTyping = dynamicTyping; - - if (_config.worker && Papa.WORKERS_SUPPORTED) - { - var w = newWorker(); - - w.userStep = _config.step; - w.userChunk = _config.chunk; - w.userComplete = _config.complete; - w.userError = _config.error; - - _config.step = isFunction(_config.step); - _config.chunk = isFunction(_config.chunk); - _config.complete = isFunction(_config.complete); - _config.error = isFunction(_config.error); - delete _config.worker; // prevent infinite loop - - w.postMessage({ - input: _input, - config: _config, - workerId: w.id - }); - - return; - } - - var streamer = null; - if (typeof _input === 'string') - { - if (_config.download) - streamer = new NetworkStreamer(_config); - else - streamer = new StringStreamer(_config); - } - else if (_input.readable === true && isFunction(_input.read) && isFunction(_input.on)) - { - streamer = new ReadableStreamStreamer(_config); - } - else if ((global.File && _input instanceof File) || _input instanceof Object) // ...Safari. (see issue #106) - streamer = new FileStreamer(_config); - - return streamer.stream(_input); - } - - - - - - - function JsonToCsv(_input, _config) - { - var _output = ''; - var _fields = []; - - // Default configuration - - /** whether to surround every datum with quotes */ - var _quotes = false; - - /** whether to write headers */ - var _writeHeader = true; - - /** delimiting character */ - var _delimiter = ','; - - /** newline character(s) */ - var _newline = '\r\n'; - - /** quote character */ - var _quoteChar = '"'; - - unpackConfig(); - - var quoteCharRegex = new RegExp(_quoteChar, 'g'); - - if (typeof _input === 'string') - _input = JSON.parse(_input); - - if (_input instanceof Array) - { - if (!_input.length || _input[0] instanceof Array) - return serialize(null, _input); - else if (typeof _input[0] === 'object') - return serialize(objectKeys(_input[0]), _input); - } - else if (typeof _input === 'object') - { - if (typeof _input.data === 'string') - _input.data = JSON.parse(_input.data); - - if (_input.data instanceof Array) - { - if (!_input.fields) - _input.fields = _input.meta && _input.meta.fields; - - if (!_input.fields) - _input.fields = _input.data[0] instanceof Array - ? _input.fields - : objectKeys(_input.data[0]); - - if (!(_input.data[0] instanceof Array) && typeof _input.data[0] !== 'object') - _input.data = [_input.data]; // handles input like [1,2,3] or ['asdf'] - } - - return serialize(_input.fields || [], _input.data || []); - } - - // Default (any valid paths should return before this) - throw 'exception: Unable to serialize unrecognized input'; - - - function unpackConfig() - { - if (typeof _config !== 'object') - return; - - if (typeof _config.delimiter === 'string' - && _config.delimiter.length === 1 - && Papa.BAD_DELIMITERS.indexOf(_config.delimiter) === -1) - { - _delimiter = _config.delimiter; - } - - if (typeof _config.quotes === 'boolean' - || _config.quotes instanceof Array) - _quotes = _config.quotes; - - if (typeof _config.newline === 'string') - _newline = _config.newline; - - if (typeof _config.quoteChar === 'string') - _quoteChar = _config.quoteChar; - - if (typeof _config.header === 'boolean') - _writeHeader = _config.header; - } - - - /** Turns an object's keys into an array */ - function objectKeys(obj) - { - if (typeof obj !== 'object') - return []; - var keys = []; - for (var key in obj) - keys.push(key); - return keys; - } - - /** The double for loop that iterates the data and writes out a CSV string including header row */ - function serialize(fields, data) - { - var csv = ''; - - if (typeof fields === 'string') - fields = JSON.parse(fields); - if (typeof data === 'string') - data = JSON.parse(data); - - var hasHeader = fields instanceof Array && fields.length > 0; - var dataKeyedByField = !(data[0] instanceof Array); - - // If there a header row, write it first - if (hasHeader && _writeHeader) - { - for (var i = 0; i < fields.length; i++) - { - if (i > 0) - csv += _delimiter; - csv += safe(fields[i], i); - } - if (data.length > 0) - csv += _newline; - } - - // Then write out the data - for (var row = 0; row < data.length; row++) - { - var maxCol = hasHeader ? fields.length : data[row].length; - - for (var col = 0; col < maxCol; col++) - { - if (col > 0) - csv += _delimiter; - var colIdx = hasHeader && dataKeyedByField ? fields[col] : col; - csv += safe(data[row][colIdx], col); - } - - if (row < data.length - 1) - csv += _newline; - } - - return csv; - } - - /** Encloses a value around quotes if needed (makes a value safe for CSV insertion) */ - function safe(str, col) - { - if (typeof str === 'undefined' || str === null) - return ''; - - str = str.toString().replace(quoteCharRegex, _quoteChar+_quoteChar); - - var needsQuotes = (typeof _quotes === 'boolean' && _quotes) - || (_quotes instanceof Array && _quotes[col]) - || hasAny(str, Papa.BAD_DELIMITERS) - || str.indexOf(_delimiter) > -1 - || str.charAt(0) === ' ' - || str.charAt(str.length - 1) === ' '; - - return needsQuotes ? _quoteChar + str + _quoteChar : str; - } - - function hasAny(str, substrings) - { - for (var i = 0; i < substrings.length; i++) - if (str.indexOf(substrings[i]) > -1) - return true; - return false; - } - } - - /** ChunkStreamer is the base prototype for various streamer implementations. */ - function ChunkStreamer(config) - { - this._handle = null; - this._paused = false; - this._finished = false; - this._input = null; - this._baseIndex = 0; - this._partialLine = ''; - this._rowCount = 0; - this._start = 0; - this._nextChunk = null; - this.isFirstChunk = true; - this._completeResults = { - data: [], - errors: [], - meta: {} - }; - replaceConfig.call(this, config); - - this.parseChunk = function(chunk) - { - // First chunk pre-processing - if (this.isFirstChunk && isFunction(this._config.beforeFirstChunk)) - { - var modifiedChunk = this._config.beforeFirstChunk(chunk); - if (modifiedChunk !== undefined) - chunk = modifiedChunk; - } - this.isFirstChunk = false; - - // Rejoin the line we likely just split in two by chunking the file - var aggregate = this._partialLine + chunk; - this._partialLine = ''; - - var results = this._handle.parse(aggregate, this._baseIndex, !this._finished); - - if (this._handle.paused() || this._handle.aborted()) - return; - - var lastIndex = results.meta.cursor; - - if (!this._finished) - { - this._partialLine = aggregate.substring(lastIndex - this._baseIndex); - this._baseIndex = lastIndex; - } - - if (results && results.data) - this._rowCount += results.data.length; - - var finishedIncludingPreview = this._finished || (this._config.preview && this._rowCount >= this._config.preview); - - if (IS_PAPA_WORKER) - { - global.postMessage({ - results: results, - workerId: Papa.WORKER_ID, - finished: finishedIncludingPreview - }); - } - else if (isFunction(this._config.chunk)) - { - this._config.chunk(results, this._handle); - if (this._paused) - return; - results = undefined; - this._completeResults = undefined; - } - - if (!this._config.step && !this._config.chunk) { - this._completeResults.data = this._completeResults.data.concat(results.data); - this._completeResults.errors = this._completeResults.errors.concat(results.errors); - this._completeResults.meta = results.meta; - } - - if (finishedIncludingPreview && isFunction(this._config.complete) && (!results || !results.meta.aborted)) - this._config.complete(this._completeResults, this._input); - - if (!finishedIncludingPreview && (!results || !results.meta.paused)) - this._nextChunk(); - - return results; - }; - - this._sendError = function(error) - { - if (isFunction(this._config.error)) - this._config.error(error); - else if (IS_PAPA_WORKER && this._config.error) - { - global.postMessage({ - workerId: Papa.WORKER_ID, - error: error, - finished: false - }); - } - }; - - function replaceConfig(config) - { - // Deep-copy the config so we can edit it - var configCopy = copy(config); - configCopy.chunkSize = parseInt(configCopy.chunkSize); // parseInt VERY important so we don't concatenate strings! - if (!config.step && !config.chunk) - configCopy.chunkSize = null; // disable Range header if not streaming; bad values break IIS - see issue #196 - this._handle = new ParserHandle(configCopy); - this._handle.streamer = this; - this._config = configCopy; // persist the copy to the caller - } - } - - - function NetworkStreamer(config) - { - config = config || {}; - if (!config.chunkSize) - config.chunkSize = Papa.RemoteChunkSize; - ChunkStreamer.call(this, config); - - var xhr; - - if (IS_WORKER) - { - this._nextChunk = function() - { - this._readChunk(); - this._chunkLoaded(); - }; - } - else - { - this._nextChunk = function() - { - this._readChunk(); - }; - } - - this.stream = function(url) - { - this._input = url; - this._nextChunk(); // Starts streaming - }; - - this._readChunk = function() - { - if (this._finished) - { - this._chunkLoaded(); - return; - } - - xhr = new XMLHttpRequest(); - - if (this._config.withCredentials) - { - xhr.withCredentials = this._config.withCredentials; - } - - if (!IS_WORKER) - { - xhr.onload = bindFunction(this._chunkLoaded, this); - xhr.onerror = bindFunction(this._chunkError, this); - } - - xhr.open('GET', this._input, !IS_WORKER); - // Headers can only be set when once the request state is OPENED - if (this._config.downloadRequestHeaders) - { - var headers = this._config.downloadRequestHeaders; - - for (var headerName in headers) - { - xhr.setRequestHeader(headerName, headers[headerName]); - } - } - - if (this._config.chunkSize) - { - var end = this._start + this._config.chunkSize - 1; // minus one because byte range is inclusive - xhr.setRequestHeader('Range', 'bytes='+this._start+'-'+end); - xhr.setRequestHeader('If-None-Match', 'webkit-no-cache'); // https://bugs.webkit.org/show_bug.cgi?id=82672 - } - - try { - xhr.send(); - } - catch (err) { - this._chunkError(err.message); - } - - if (IS_WORKER && xhr.status === 0) - this._chunkError(); - else - this._start += this._config.chunkSize; - } - - this._chunkLoaded = function() - { - if (xhr.readyState != 4) - return; - - if (xhr.status < 200 || xhr.status >= 400) - { - this._chunkError(); - return; - } - - this._finished = !this._config.chunkSize || this._start > getFileSize(xhr); - this.parseChunk(xhr.responseText); - } - - this._chunkError = function(errorMessage) - { - var errorText = xhr.statusText || errorMessage; - this._sendError(errorText); - } - - function getFileSize(xhr) - { - var contentRange = xhr.getResponseHeader('Content-Range'); - if (contentRange === null) { // no content range, then finish! - return -1; - } - return parseInt(contentRange.substr(contentRange.lastIndexOf('/') + 1)); - } - } - NetworkStreamer.prototype = Object.create(ChunkStreamer.prototype); - NetworkStreamer.prototype.constructor = NetworkStreamer; - - - function FileStreamer(config) - { - config = config || {}; - if (!config.chunkSize) - config.chunkSize = Papa.LocalChunkSize; - ChunkStreamer.call(this, config); - - var reader, slice; - - // FileReader is better than FileReaderSync (even in worker) - see http://stackoverflow.com/q/24708649/1048862 - // But Firefox is a pill, too - see issue #76: https://github.com/mholt/PapaParse/issues/76 - var usingAsyncReader = typeof FileReader !== 'undefined'; // Safari doesn't consider it a function - see issue #105 - - this.stream = function(file) - { - this._input = file; - slice = file.slice || file.webkitSlice || file.mozSlice; - - if (usingAsyncReader) - { - reader = new FileReader(); // Preferred method of reading files, even in workers - reader.onload = bindFunction(this._chunkLoaded, this); - reader.onerror = bindFunction(this._chunkError, this); - } - else - reader = new FileReaderSync(); // Hack for running in a web worker in Firefox - - this._nextChunk(); // Starts streaming - }; - - this._nextChunk = function() - { - if (!this._finished && (!this._config.preview || this._rowCount < this._config.preview)) - this._readChunk(); - } - - this._readChunk = function() - { - var input = this._input; - if (this._config.chunkSize) - { - var end = Math.min(this._start + this._config.chunkSize, this._input.size); - input = slice.call(input, this._start, end); - } - var txt = reader.readAsText(input, this._config.encoding); - if (!usingAsyncReader) - this._chunkLoaded({ target: { result: txt } }); // mimic the async signature - } - - this._chunkLoaded = function(event) - { - // Very important to increment start each time before handling results - this._start += this._config.chunkSize; - this._finished = !this._config.chunkSize || this._start >= this._input.size; - this.parseChunk(event.target.result); - } - - this._chunkError = function() - { - this._sendError(reader.error); - } - - } - FileStreamer.prototype = Object.create(ChunkStreamer.prototype); - FileStreamer.prototype.constructor = FileStreamer; - - - function StringStreamer(config) - { - config = config || {}; - ChunkStreamer.call(this, config); - - var string; - var remaining; - this.stream = function(s) - { - string = s; - remaining = s; - return this._nextChunk(); - } - this._nextChunk = function() - { - if (this._finished) return; - var size = this._config.chunkSize; - var chunk = size ? remaining.substr(0, size) : remaining; - remaining = size ? remaining.substr(size) : ''; - this._finished = !remaining; - return this.parseChunk(chunk); - } - } - StringStreamer.prototype = Object.create(StringStreamer.prototype); - StringStreamer.prototype.constructor = StringStreamer; - - - function ReadableStreamStreamer(config) - { - config = config || {}; - - ChunkStreamer.call(this, config); - - var queue = []; - var parseOnData = true; - - this.stream = function(stream) - { - this._input = stream; - - this._input.on('data', this._streamData); - this._input.on('end', this._streamEnd); - this._input.on('error', this._streamError); - } - - this._nextChunk = function() - { - if (queue.length) - { - this.parseChunk(queue.shift()); - } - else - { - parseOnData = true; - } - } - - this._streamData = bindFunction(function(chunk) - { - try - { - queue.push(typeof chunk === 'string' ? chunk : chunk.toString(this._config.encoding)); - - if (parseOnData) - { - parseOnData = false; - this.parseChunk(queue.shift()); - } - } - catch (error) - { - this._streamError(error); - } - }, this); - - this._streamError = bindFunction(function(error) - { - this._streamCleanUp(); - this._sendError(error.message); - }, this); - - this._streamEnd = bindFunction(function() - { - this._streamCleanUp(); - this._finished = true; - this._streamData(''); - }, this); - - this._streamCleanUp = bindFunction(function() - { - this._input.removeListener('data', this._streamData); - this._input.removeListener('end', this._streamEnd); - this._input.removeListener('error', this._streamError); - }, this); - } - ReadableStreamStreamer.prototype = Object.create(ChunkStreamer.prototype); - ReadableStreamStreamer.prototype.constructor = ReadableStreamStreamer; - - - // Use one ParserHandle per entire CSV file or string - function ParserHandle(_config) - { - // One goal is to minimize the use of regular expressions... - var FLOAT = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i; - - var self = this; - var _stepCounter = 0; // Number of times step was called (number of rows parsed) - var _input; // The input being parsed - var _parser; // The core parser being used - var _paused = false; // Whether we are paused or not - var _aborted = false; // Whether the parser has aborted or not - var _delimiterError; // Temporary state between delimiter detection and processing results - var _fields = []; // Fields are from the header row of the input, if there is one - var _results = { // The last results returned from the parser - data: [], - errors: [], - meta: {} - }; - - if (isFunction(_config.step)) - { - var userStep = _config.step; - _config.step = function(results) - { - _results = results; - - if (needsHeaderRow()) - processResults(); - else // only call user's step function after header row - { - processResults(); - - // It's possbile that this line was empty and there's no row here after all - if (_results.data.length === 0) - return; - - _stepCounter += results.data.length; - if (_config.preview && _stepCounter > _config.preview) - _parser.abort(); - else - userStep(_results, self); - } - }; - } - - /** - * Parses input. Most users won't need, and shouldn't mess with, the baseIndex - * and ignoreLastRow parameters. They are used by streamers (wrapper functions) - * when an input comes in multiple chunks, like from a file. - */ - this.parse = function(input, baseIndex, ignoreLastRow) - { - if (!_config.newline) - _config.newline = guessLineEndings(input); - - _delimiterError = false; - if (!_config.delimiter) - { - var delimGuess = guessDelimiter(input, _config.newline, _config.skipEmptyLines); - if (delimGuess.successful) - _config.delimiter = delimGuess.bestDelimiter; - else - { - _delimiterError = true; // add error after parsing (otherwise it would be overwritten) - _config.delimiter = Papa.DefaultDelimiter; - } - _results.meta.delimiter = _config.delimiter; - } - else if(isFunction(_config.delimiter)) - { - _config.delimiter = _config.delimiter(input); - _results.meta.delimiter = _config.delimiter; - } - - var parserConfig = copy(_config); - if (_config.preview && _config.header) - parserConfig.preview++; // to compensate for header row - - _input = input; - _parser = new Parser(parserConfig); - _results = _parser.parse(_input, baseIndex, ignoreLastRow); - processResults(); - return _paused ? { meta: { paused: true } } : (_results || { meta: { paused: false } }); - }; - - this.paused = function() - { - return _paused; - }; - - this.pause = function() - { - _paused = true; - _parser.abort(); - _input = _input.substr(_parser.getCharIndex()); - }; - - this.resume = function() - { - _paused = false; - self.streamer.parseChunk(_input); - }; - - this.aborted = function () - { - return _aborted; - }; - - this.abort = function() - { - _aborted = true; - _parser.abort(); - _results.meta.aborted = true; - if (isFunction(_config.complete)) - _config.complete(_results); - _input = ''; - }; - - function processResults() - { - if (_results && _delimiterError) - { - addError('Delimiter', 'UndetectableDelimiter', 'Unable to auto-detect delimiting character; defaulted to \''+Papa.DefaultDelimiter+'\''); - _delimiterError = false; - } - - if (_config.skipEmptyLines) - { - for (var i = 0; i < _results.data.length; i++) - if (_results.data[i].length === 1 && _results.data[i][0] === '') - _results.data.splice(i--, 1); - } - - if (needsHeaderRow()) - fillHeaderFields(); - - return applyHeaderAndDynamicTyping(); - } - - function needsHeaderRow() - { - return _config.header && _fields.length === 0; - } - - function fillHeaderFields() - { - if (!_results) - return; - for (var i = 0; needsHeaderRow() && i < _results.data.length; i++) - for (var j = 0; j < _results.data[i].length; j++) - _fields.push(_results.data[i][j]); - _results.data.splice(0, 1); - } - - function shouldApplyDynamicTyping(field) { - // Cache function values to avoid calling it for each row - if (_config.dynamicTypingFunction && _config.dynamicTyping[field] === undefined) { - _config.dynamicTyping[field] = _config.dynamicTypingFunction(field); - } - return (_config.dynamicTyping[field] || _config.dynamicTyping) === true - } - - function parseDynamic(field, value) - { - if (shouldApplyDynamicTyping(field)) - { - if (value === 'true' || value === 'TRUE') - return true; - else if (value === 'false' || value === 'FALSE') - return false; - else - return tryParseFloat(value); - } - return value; - } - - function applyHeaderAndDynamicTyping() - { - if (!_results || (!_config.header && !_config.dynamicTyping)) - return _results; - - for (var i = 0; i < _results.data.length; i++) - { - var row = _config.header ? {} : []; - - for (var j = 0; j < _results.data[i].length; j++) - { - var field = j; - var value = _results.data[i][j]; - - if (_config.header) - field = j >= _fields.length ? '__parsed_extra' : _fields[j]; - - value = parseDynamic(field, value); - - if (field === '__parsed_extra') - { - row[field] = row[field] || []; - row[field].push(value); - } - else - row[field] = value; - } - - _results.data[i] = row; - - if (_config.header) - { - if (j > _fields.length) - addError('FieldMismatch', 'TooManyFields', 'Too many fields: expected ' + _fields.length + ' fields but parsed ' + j, i); - else if (j < _fields.length) - addError('FieldMismatch', 'TooFewFields', 'Too few fields: expected ' + _fields.length + ' fields but parsed ' + j, i); - } - } - - if (_config.header && _results.meta) - _results.meta.fields = _fields; - return _results; - } - - function guessDelimiter(input, newline, skipEmptyLines) - { - var delimChoices = [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP]; - var bestDelim, bestDelta, fieldCountPrevRow; - - for (var i = 0; i < delimChoices.length; i++) - { - var delim = delimChoices[i]; - var delta = 0, avgFieldCount = 0, emptyLinesCount = 0; - fieldCountPrevRow = undefined; - - var preview = new Parser({ - delimiter: delim, - newline: newline, - preview: 10 - }).parse(input); - - for (var j = 0; j < preview.data.length; j++) - { - if (skipEmptyLines && preview.data[j].length === 1 && preview.data[j][0].length === 0) { - emptyLinesCount++ - continue - } - var fieldCount = preview.data[j].length; - avgFieldCount += fieldCount; - - if (typeof fieldCountPrevRow === 'undefined') - { - fieldCountPrevRow = fieldCount; - continue; - } - else if (fieldCount > 1) - { - delta += Math.abs(fieldCount - fieldCountPrevRow); - fieldCountPrevRow = fieldCount; - } - } - - if (preview.data.length > 0) - avgFieldCount /= (preview.data.length - emptyLinesCount); - - if ((typeof bestDelta === 'undefined' || delta < bestDelta) - && avgFieldCount > 1.99) - { - bestDelta = delta; - bestDelim = delim; - } - } - - _config.delimiter = bestDelim; - - return { - successful: !!bestDelim, - bestDelimiter: bestDelim - } - } - - function guessLineEndings(input) - { - input = input.substr(0, 1024*1024); // max length 1 MB - - var r = input.split('\r'); - - var n = input.split('\n'); - - var nAppearsFirst = (n.length > 1 && n[0].length < r[0].length); - - if (r.length === 1 || nAppearsFirst) - return '\n'; - - var numWithN = 0; - for (var i = 0; i < r.length; i++) - { - if (r[i][0] === '\n') - numWithN++; - } - - return numWithN >= r.length / 2 ? '\r\n' : '\r'; - } - - function tryParseFloat(val) - { - var isNumber = FLOAT.test(val); - return isNumber ? parseFloat(val) : val; - } - - function addError(type, code, msg, row) - { - _results.errors.push({ - type: type, - code: code, - message: msg, - row: row - }); - } - } - - - - - - /** The core parser implements speedy and correct CSV parsing */ - function Parser(config) - { - // Unpack the config object - config = config || {}; - var delim = config.delimiter; - var newline = config.newline; - var comments = config.comments; - var step = config.step; - var preview = config.preview; - var fastMode = config.fastMode; - var quoteChar = config.quoteChar || '"'; - - // Delimiter must be valid - if (typeof delim !== 'string' - || Papa.BAD_DELIMITERS.indexOf(delim) > -1) - delim = ','; - - // Comment character must be valid - if (comments === delim) - throw 'Comment character same as delimiter'; - else if (comments === true) - comments = '#'; - else if (typeof comments !== 'string' - || Papa.BAD_DELIMITERS.indexOf(comments) > -1) - comments = false; - - // Newline must be valid: \r, \n, or \r\n - if (newline != '\n' && newline != '\r' && newline != '\r\n') - newline = '\n'; - - // We're gonna need these at the Parser scope - var cursor = 0; - var aborted = false; - - this.parse = function(input, baseIndex, ignoreLastRow) - { - // For some reason, in Chrome, this speeds things up (!?) - if (typeof input !== 'string') - throw 'Input must be a string'; - - // We don't need to compute some of these every time parse() is called, - // but having them in a more local scope seems to perform better - var inputLen = input.length, - delimLen = delim.length, - newlineLen = newline.length, - commentsLen = comments.length; - var stepIsFunction = isFunction(step); - - // Establish starting state - cursor = 0; - var data = [], errors = [], row = [], lastCursor = 0; - - if (!input) - return returnable(); - - if (fastMode || (fastMode !== false && input.indexOf(quoteChar) === -1)) - { - var rows = input.split(newline); - for (var i = 0; i < rows.length; i++) - { - var row = rows[i]; - cursor += row.length; - if (i !== rows.length - 1) - cursor += newline.length; - else if (ignoreLastRow) - return returnable(); - if (comments && row.substr(0, commentsLen) === comments) - continue; - if (stepIsFunction) - { - data = []; - pushRow(row.split(delim)); - doStep(); - if (aborted) - return returnable(); - } - else - pushRow(row.split(delim)); - if (preview && i >= preview) - { - data = data.slice(0, preview); - return returnable(true); - } - } - return returnable(); - } - - var nextDelim = input.indexOf(delim, cursor); - var nextNewline = input.indexOf(newline, cursor); - var quoteCharRegex = new RegExp(quoteChar+quoteChar, 'g'); - - // Parser loop - for (;;) - { - // Field has opening quote - if (input[cursor] === quoteChar) - { - // Start our search for the closing quote where the cursor is - var quoteSearch = cursor; - - // Skip the opening quote - cursor++; - - for (;;) - { - // Find closing quote - var quoteSearch = input.indexOf(quoteChar, quoteSearch+1); - - //No other quotes are found - no other delimiters - if (quoteSearch === -1) - { - if (!ignoreLastRow) { - // No closing quote... what a pity - errors.push({ - type: 'Quotes', - code: 'MissingQuotes', - message: 'Quoted field unterminated', - row: data.length, // row has yet to be inserted - index: cursor - }); - } - return finish(); - } - - // Closing quote at EOF - if (quoteSearch === inputLen-1) - { - var value = input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar); - return finish(value); - } - - // If this quote is escaped, it's part of the data; skip it - if (input[quoteSearch+1] === quoteChar) - { - quoteSearch++; - continue; - } - - // Closing quote followed by delimiter - if (input[quoteSearch+1] === delim) - { - row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar)); - cursor = quoteSearch + 1 + delimLen; - nextDelim = input.indexOf(delim, cursor); - nextNewline = input.indexOf(newline, cursor); - break; - } - - // Closing quote followed by newline - if (input.substr(quoteSearch+1, newlineLen) === newline) - { - row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar)); - saveRow(quoteSearch + 1 + newlineLen); - nextDelim = input.indexOf(delim, cursor); // because we may have skipped the nextDelim in the quoted field - - if (stepIsFunction) - { - doStep(); - if (aborted) - return returnable(); - } - - if (preview && data.length >= preview) - return returnable(true); - - break; - } - - - // Checks for valid closing quotes are complete (escaped quotes or quote followed by EOF/delimiter/newline) -- assume these quotes are part of an invalid text string - errors.push({ - type: 'Quotes', - code: 'InvalidQuotes', - message: 'Trailing quote on quoted field is malformed', - row: data.length, // row has yet to be inserted - index: cursor - }); - - quoteSearch++; - continue; - - } - - continue; - } - - // Comment found at start of new line - if (comments && row.length === 0 && input.substr(cursor, commentsLen) === comments) - { - if (nextNewline === -1) // Comment ends at EOF - return returnable(); - cursor = nextNewline + newlineLen; - nextNewline = input.indexOf(newline, cursor); - nextDelim = input.indexOf(delim, cursor); - continue; - } - - // Next delimiter comes before next newline, so we've reached end of field - if (nextDelim !== -1 && (nextDelim < nextNewline || nextNewline === -1)) - { - row.push(input.substring(cursor, nextDelim)); - cursor = nextDelim + delimLen; - nextDelim = input.indexOf(delim, cursor); - continue; - } - - // End of row - if (nextNewline !== -1) - { - row.push(input.substring(cursor, nextNewline)); - saveRow(nextNewline + newlineLen); - - if (stepIsFunction) - { - doStep(); - if (aborted) - return returnable(); - } - - if (preview && data.length >= preview) - return returnable(true); - - continue; - } - - break; - } - - - return finish(); - - - function pushRow(row) - { - data.push(row); - lastCursor = cursor; - } - - /** - * Appends the remaining input from cursor to the end into - * row, saves the row, calls step, and returns the results. - */ - function finish(value) - { - if (ignoreLastRow) - return returnable(); - if (typeof value === 'undefined') - value = input.substr(cursor); - row.push(value); - cursor = inputLen; // important in case parsing is paused - pushRow(row); - if (stepIsFunction) - doStep(); - return returnable(); - } - - /** - * Appends the current row to the results. It sets the cursor - * to newCursor and finds the nextNewline. The caller should - * take care to execute user's step function and check for - * preview and end parsing if necessary. - */ - function saveRow(newCursor) - { - cursor = newCursor; - pushRow(row); - row = []; - nextNewline = input.indexOf(newline, cursor); - } - - /** Returns an object with the results, errors, and meta. */ - function returnable(stopped) - { - return { - data: data, - errors: errors, - meta: { - delimiter: delim, - linebreak: newline, - aborted: aborted, - truncated: !!stopped, - cursor: lastCursor + (baseIndex || 0) - } - }; - } - - /** Executes the user's step function and resets data & errors. */ - function doStep() - { - step(returnable()); - data = [], errors = []; - } - }; - - /** Sets the abort flag */ - this.abort = function() - { - aborted = true; - }; - - /** Gets the cursor position */ - this.getCharIndex = function() - { - return cursor; - }; - } - - - // If you need to load Papa Parse asynchronously and you also need worker threads, hard-code - // the script path here. See: https://github.com/mholt/PapaParse/issues/87#issuecomment-57885358 - function getScriptPath() - { - var scripts = document.getElementsByTagName('script'); - return scripts.length ? scripts[scripts.length - 1].src : ''; - } - - function newWorker() - { - if (!Papa.WORKERS_SUPPORTED) - return false; - if (!LOADED_SYNC && Papa.SCRIPT_PATH === null) - throw new Error( - 'Script path cannot be determined automatically when Papa Parse is loaded asynchronously. ' + - 'You need to set Papa.SCRIPT_PATH manually.' - ); - var workerUrl = Papa.SCRIPT_PATH || AUTO_SCRIPT_PATH; - // Append 'papaworker' to the search string to tell papaparse that this is our worker. - workerUrl += (workerUrl.indexOf('?') !== -1 ? '&' : '?') + 'papaworker'; - var w = new global.Worker(workerUrl); - w.onmessage = mainThreadReceivedMessage; - w.id = workerIdCounter++; - workers[w.id] = w; - return w; - } - - /** Callback when main thread receives a message */ - function mainThreadReceivedMessage(e) - { - var msg = e.data; - var worker = workers[msg.workerId]; - var aborted = false; - - if (msg.error) - worker.userError(msg.error, msg.file); - else if (msg.results && msg.results.data) - { - var abort = function() { - aborted = true; - completeWorker(msg.workerId, { data: [], errors: [], meta: { aborted: true } }); - }; - - var handle = { - abort: abort, - pause: notImplemented, - resume: notImplemented - }; - - if (isFunction(worker.userStep)) - { - for (var i = 0; i < msg.results.data.length; i++) - { - worker.userStep({ - data: [msg.results.data[i]], - errors: msg.results.errors, - meta: msg.results.meta - }, handle); - if (aborted) - break; - } - delete msg.results; // free memory ASAP - } - else if (isFunction(worker.userChunk)) - { - worker.userChunk(msg.results, handle, msg.file); - delete msg.results; - } - } - - if (msg.finished && !aborted) - completeWorker(msg.workerId, msg.results); - } - - function completeWorker(workerId, results) { - var worker = workers[workerId]; - if (isFunction(worker.userComplete)) - worker.userComplete(results); - worker.terminate(); - delete workers[workerId]; - } - - function notImplemented() { - throw 'Not implemented.'; - } - - /** Callback when worker thread receives a message */ - function workerThreadReceivedMessage(e) - { - var msg = e.data; - - if (typeof Papa.WORKER_ID === 'undefined' && msg) - Papa.WORKER_ID = msg.workerId; - - if (typeof msg.input === 'string') - { - global.postMessage({ - workerId: Papa.WORKER_ID, - results: Papa.parse(msg.input, msg.config), - finished: true - }); - } - else if ((global.File && msg.input instanceof File) || msg.input instanceof Object) // thank you, Safari (see issue #106) - { - var results = Papa.parse(msg.input, msg.config); - if (results) - global.postMessage({ - workerId: Papa.WORKER_ID, - results: results, - finished: true - }); - } - } - - /** Makes a deep copy of an array or object (mostly) */ - function copy(obj) - { - if (typeof obj !== 'object') - return obj; - var cpy = obj instanceof Array ? [] : {}; - for (var key in obj) - cpy[key] = copy(obj[key]); - return cpy; - } - - function bindFunction(f, self) - { - return function() { f.apply(self, arguments); }; - } - - function isFunction(func) - { - return typeof func === 'function'; - } - - return Papa; -})); - -(function (root, factory) { - 'use strict'; - - if (typeof define === 'function' && define.amd) { - define(['angular'], factory); - } else if (root.hasOwnProperty('angular')) { - // Browser globals (root is window), we don't register it. - factory(root.angular); - } else if (typeof exports === 'object') { - module.exports = factory(require('angular')); - } -}(this , function (angular) { - 'use strict'; - - // In cases where Angular does not get passed or angular is a truthy value - // but misses .module we can fall back to using window. - angular = (angular && angular.module ) ? angular : window.angular; - - - function isStorageSupported($window, storageType) { - - // Some installations of IE, for an unknown reason, throw "SCRIPT5: Error: Access is denied" - // when accessing window.localStorage. This happens before you try to do anything with it. Catch - // that error and allow execution to continue. - - // fix 'SecurityError: DOM Exception 18' exception in Desktop Safari, Mobile Safari - // when "Block cookies": "Always block" is turned on - var supported; - try { - supported = $window[storageType]; - } - catch(err) { - supported = false; - } - - // When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage and sessionStorage - // is available, but trying to call .setItem throws an exception below: - // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota." - if(supported) { - var key = '__' + Math.round(Math.random() * 1e7); - try { - $window[storageType].setItem(key, key); - $window[storageType].removeItem(key, key); - } - catch(err) { - supported = false; - } - } - - return supported; - } - - /** - * @ngdoc overview - * @name ngStorage - */ - - return angular.module('ngStorage', []) - - /** - * @ngdoc object - * @name ngStorage.$localStorage - * @requires $rootScope - * @requires $window - */ - - .provider('$localStorage', _storageProvider('localStorage')) - - /** - * @ngdoc object - * @name ngStorage.$sessionStorage - * @requires $rootScope - * @requires $window - */ - - .provider('$sessionStorage', _storageProvider('sessionStorage')); - - function _storageProvider(storageType) { - var providerWebStorage = isStorageSupported(window, storageType); - - return function () { - var storageKeyPrefix = 'ngStorage-'; - - this.setKeyPrefix = function (prefix) { - if (typeof prefix !== 'string') { - throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setKeyPrefix() expects a String.'); - } - storageKeyPrefix = prefix; - }; - - var serializer = angular.toJson; - var deserializer = angular.fromJson; - - this.setSerializer = function (s) { - if (typeof s !== 'function') { - throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setSerializer expects a function.'); - } - - serializer = s; - }; - - this.setDeserializer = function (d) { - if (typeof d !== 'function') { - throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setDeserializer expects a function.'); - } - - deserializer = d; - }; - - this.supported = function() { - return !!providerWebStorage; - }; - - // Note: This is not very elegant at all. - this.get = function (key) { - return providerWebStorage && deserializer(providerWebStorage.getItem(storageKeyPrefix + key)); - }; - - // Note: This is not very elegant at all. - this.set = function (key, value) { - return providerWebStorage && providerWebStorage.setItem(storageKeyPrefix + key, serializer(value)); - }; - - this.remove = function (key) { - providerWebStorage && providerWebStorage.removeItem(storageKeyPrefix + key); - } - - this.$get = [ - '$rootScope', - '$window', - '$log', - '$timeout', - '$document', - - function( - $rootScope, - $window, - $log, - $timeout, - $document - ){ - - // The magic number 10 is used which only works for some keyPrefixes... - // See https://github.com/gsklee/ngStorage/issues/137 - var prefixLength = storageKeyPrefix.length; - - // #9: Assign a placeholder object if Web Storage is unavailable to prevent breaking the entire AngularJS app - // Note: recheck mainly for testing (so we can use $window[storageType] rather than window[storageType]) - var isSupported = isStorageSupported($window, storageType), - webStorage = isSupported || ($log.warn('This browser does not support Web Storage!'), {setItem: angular.noop, getItem: angular.noop, removeItem: angular.noop}), - $storage = { - $default: function(items) { - for (var k in items) { - angular.isDefined($storage[k]) || ($storage[k] = angular.copy(items[k]) ); - } - - $storage.$sync(); - return $storage; - }, - $reset: function(items) { - for (var k in $storage) { - '$' === k[0] || (delete $storage[k] && webStorage.removeItem(storageKeyPrefix + k)); - } - - return $storage.$default(items); - }, - $sync: function () { - for (var i = 0, l = webStorage.length, k; i < l; i++) { - // #8, #10: `webStorage.key(i)` may be an empty string (or throw an exception in IE9 if `webStorage` is empty) - (k = webStorage.key(i)) && storageKeyPrefix === k.slice(0, prefixLength) && ($storage[k.slice(prefixLength)] = deserializer(webStorage.getItem(k))); - } - }, - $apply: function() { - var temp$storage; - - _debounce = null; - - if (!angular.equals($storage, _last$storage)) { - temp$storage = angular.copy(_last$storage); - angular.forEach($storage, function(v, k) { - if (angular.isDefined(v) && '$' !== k[0]) { - webStorage.setItem(storageKeyPrefix + k, serializer(v)); - delete temp$storage[k]; - } - }); - - for (var k in temp$storage) { - webStorage.removeItem(storageKeyPrefix + k); - } - - _last$storage = angular.copy($storage); - } - }, - $supported: function() { - return !!isSupported; - } - }, - _last$storage, - _debounce; - - $storage.$sync(); - - _last$storage = angular.copy($storage); - - $rootScope.$watch(function() { - _debounce || (_debounce = $timeout($storage.$apply, 100, false)); - }); - - // #6: Use `$window.addEventListener` instead of `angular.element` to avoid the jQuery-specific `event.originalEvent` - $window.addEventListener && $window.addEventListener('storage', function(event) { - if (!event.key) { - return; - } - - // Reference doc. - var doc = $document[0]; - - if ( (!doc.hasFocus || !doc.hasFocus()) && storageKeyPrefix === event.key.slice(0, prefixLength) ) { - event.newValue ? $storage[event.key.slice(prefixLength)] = deserializer(event.newValue) : delete $storage[event.key.slice(prefixLength)]; - - _last$storage = angular.copy($storage); - - $rootScope.$apply(); - } - }); - - $window.addEventListener && $window.addEventListener('beforeunload', function() { - $storage.$apply(); - }); - - return $storage; - } - ]; - }; - } - -})); - /*! * clipboard.js v1.7.1 * https://zenorocha.github.io/clipboard.js @@ -23421,6 +13831,437 @@ module.exports = E; },{"./clipboard-action":7,"good-listener":4,"tiny-emitter":6}]},{},[8])(8) }); +/** + * Duo Web SDK v2 + * Copyright 2017, Duo Security + */ + +(function (root, factory) { + /*eslint-disable */ + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], factory); + /*eslint-enable */ + } else if (typeof module === 'object' && module.exports) { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + // Browser globals (root is window) + var Duo = factory(); + // If the Javascript was loaded via a script tag, attempt to autoload + // the frame. + Duo._onReady(Duo.init); + + // Attach Duo to the `window` object + root.Duo = Duo; + } +}(this, function() { + var DUO_MESSAGE_FORMAT = /^(?:AUTH|ENROLL)+\|[A-Za-z0-9\+\/=]+\|[A-Za-z0-9\+\/=]+$/; + var DUO_ERROR_FORMAT = /^ERR\|[\w\s\.\(\)]+$/; + var DUO_OPEN_WINDOW_FORMAT = /^DUO_OPEN_WINDOW\|/; + var VALID_OPEN_WINDOW_DOMAINS = [ + 'duo.com', + 'duosecurity.com', + 'duomobile.s3-us-west-1.amazonaws.com' + ]; + + var iframeId = 'duo_iframe', + postAction = '', + postArgument = 'sig_response', + host, + sigRequest, + duoSig, + appSig, + iframe, + submitCallback; + + function throwError(message, url) { + throw new Error( + 'Duo Web SDK error: ' + message + + (url ? ('\n' + 'See ' + url + ' for more information') : '') + ); + } + + function hyphenize(str) { + return str.replace(/([a-z])([A-Z])/, '$1-$2').toLowerCase(); + } + + // cross-browser data attributes + function getDataAttribute(element, name) { + if ('dataset' in element) { + return element.dataset[name]; + } else { + return element.getAttribute('data-' + hyphenize(name)); + } + } + + // cross-browser event binding/unbinding + function on(context, event, fallbackEvent, callback) { + if ('addEventListener' in window) { + context.addEventListener(event, callback, false); + } else { + context.attachEvent(fallbackEvent, callback); + } + } + + function off(context, event, fallbackEvent, callback) { + if ('removeEventListener' in window) { + context.removeEventListener(event, callback, false); + } else { + context.detachEvent(fallbackEvent, callback); + } + } + + function onReady(callback) { + on(document, 'DOMContentLoaded', 'onreadystatechange', callback); + } + + function offReady(callback) { + off(document, 'DOMContentLoaded', 'onreadystatechange', callback); + } + + function onMessage(callback) { + on(window, 'message', 'onmessage', callback); + } + + function offMessage(callback) { + off(window, 'message', 'onmessage', callback); + } + + /** + * Parse the sig_request parameter, throwing errors if the token contains + * a server error or if the token is invalid. + * + * @param {String} sig Request token + */ + function parseSigRequest(sig) { + if (!sig) { + // nothing to do + return; + } + + // see if the token contains an error, throwing it if it does + if (sig.indexOf('ERR|') === 0) { + throwError(sig.split('|')[1]); + } + + // validate the token + if (sig.indexOf(':') === -1 || sig.split(':').length !== 2) { + throwError( + 'Duo was given a bad token. This might indicate a configuration ' + + 'problem with one of Duo\'s client libraries.', + 'https://www.duosecurity.com/docs/duoweb#first-steps' + ); + } + + var sigParts = sig.split(':'); + + // hang on to the token, and the parsed duo and app sigs + sigRequest = sig; + duoSig = sigParts[0]; + appSig = sigParts[1]; + + return { + sigRequest: sig, + duoSig: sigParts[0], + appSig: sigParts[1] + }; + } + + /** + * This function is set up to run when the DOM is ready, if the iframe was + * not available during `init`. + */ + function onDOMReady() { + iframe = document.getElementById(iframeId); + + if (!iframe) { + throw new Error( + 'This page does not contain an iframe for Duo to use.' + + 'Add an element like ' + + 'to this page. ' + + 'See https://www.duosecurity.com/docs/duoweb#3.-show-the-iframe ' + + 'for more information.' + ); + } + + // we've got an iframe, away we go! + ready(); + + // always clean up after yourself + offReady(onDOMReady); + } + + /** + * Validate that a MessageEvent came from the Duo service, and that it + * is a properly formatted payload. + * + * The Google Chrome sign-in page injects some JS into pages that also + * make use of postMessage, so we need to do additional validation above + * and beyond the origin. + * + * @param {MessageEvent} event Message received via postMessage + */ + function isDuoMessage(event) { + return Boolean( + event.origin === ('https://' + host) && + typeof event.data === 'string' && + ( + event.data.match(DUO_MESSAGE_FORMAT) || + event.data.match(DUO_ERROR_FORMAT) || + event.data.match(DUO_OPEN_WINDOW_FORMAT) + ) + ); + } + + /** + * Validate the request token and prepare for the iframe to become ready. + * + * All options below can be passed into an options hash to `Duo.init`, or + * specified on the iframe using `data-` attributes. + * + * Options specified using the options hash will take precedence over + * `data-` attributes. + * + * Example using options hash: + * ```javascript + * Duo.init({ + * iframe: "some_other_id", + * host: "api-main.duo.test", + * sig_request: "...", + * post_action: "/auth", + * post_argument: "resp" + * }); + * ``` + * + * Example using `data-` attributes: + * ``` + * + * ``` + * + * @param {Object} options + * @param {String} options.iframe The iframe, or id of an iframe to set up + * @param {String} options.host Hostname + * @param {String} options.sig_request Request token + * @param {String} [options.post_action=''] URL to POST back to after successful auth + * @param {String} [options.post_argument='sig_response'] Parameter name to use for response token + * @param {Function} [options.submit_callback] If provided, duo will not submit the form instead execute + * the callback function with reference to the "duo_form" form object + * submit_callback can be used to prevent the webpage from reloading. + */ + function init(options) { + if (options) { + if (options.host) { + host = options.host; + } + + if (options.sig_request) { + parseSigRequest(options.sig_request); + } + + if (options.post_action) { + postAction = options.post_action; + } + + if (options.post_argument) { + postArgument = options.post_argument; + } + + if (options.iframe) { + if (options.iframe.tagName) { + iframe = options.iframe; + } else if (typeof options.iframe === 'string') { + iframeId = options.iframe; + } + } + + if (typeof options.submit_callback === 'function') { + submitCallback = options.submit_callback; + } + } + + // if we were given an iframe, no need to wait for the rest of the DOM + if (false && iframe) { + ready(); + } else { + // try to find the iframe in the DOM + iframe = document.getElementById(iframeId); + + // iframe is in the DOM, away we go! + if (iframe) { + ready(); + } else { + // wait until the DOM is ready, then try again + onReady(onDOMReady); + } + } + + // always clean up after yourself! + offReady(init); + } + + /** + * This function is called when a message was received from another domain + * using the `postMessage` API. Check that the event came from the Duo + * service domain, and that the message is a properly formatted payload, + * then perform the post back to the primary service. + * + * @param event Event object (contains origin and data) + */ + function onReceivedMessage(event) { + if (isDuoMessage(event)) { + if (event.data.match(DUO_OPEN_WINDOW_FORMAT)) { + var url = event.data.substring("DUO_OPEN_WINDOW|".length); + if (isValidUrlToOpen(url)) { + // Open the URL that comes after the DUO_WINDOW_OPEN token. + window.open(url, "_self"); + } + } + else { + // the event came from duo, do the post back + doPostBack(event.data); + + // always clean up after yourself! + offMessage(onReceivedMessage); + } + } + } + + /** + * Validate that this passed in URL is one that we will actually allow to + * be opened. + * @param url String URL that the message poster wants to open + * @returns {boolean} true if we allow this url to be opened in the window + */ + function isValidUrlToOpen(url) { + if (!url) { + return false; + } + + var parser = document.createElement('a'); + parser.href = url; + + if (parser.protocol === "duotrustedendpoints:") { + return true; + } else if (parser.protocol !== "https:") { + return false; + } + + for (var i = 0; i < VALID_OPEN_WINDOW_DOMAINS.length; i++) { + if (parser.hostname.endsWith("." + VALID_OPEN_WINDOW_DOMAINS[i]) || + parser.hostname === VALID_OPEN_WINDOW_DOMAINS[i]) { + return true; + } + } + return false; + } + + /** + * Point the iframe at Duo, then wait for it to postMessage back to us. + */ + function ready() { + if (!host) { + host = getDataAttribute(iframe, 'host'); + + if (!host) { + throwError( + 'No API hostname is given for Duo to use. Be sure to pass ' + + 'a `host` parameter to Duo.init, or through the `data-host` ' + + 'attribute on the iframe element.', + 'https://www.duosecurity.com/docs/duoweb#3.-show-the-iframe' + ); + } + } + + if (!duoSig || !appSig) { + parseSigRequest(getDataAttribute(iframe, 'sigRequest')); + + if (!duoSig || !appSig) { + throwError( + 'No valid signed request is given. Be sure to give the ' + + '`sig_request` parameter to Duo.init, or use the ' + + '`data-sig-request` attribute on the iframe element.', + 'https://www.duosecurity.com/docs/duoweb#3.-show-the-iframe' + ); + } + } + + // if postAction/Argument are defaults, see if they are specified + // as data attributes on the iframe + if (postAction === '') { + postAction = getDataAttribute(iframe, 'postAction') || postAction; + } + + if (postArgument === 'sig_response') { + postArgument = getDataAttribute(iframe, 'postArgument') || postArgument; + } + + // point the iframe at Duo + iframe.src = [ + 'https://', host, '/frame/web/v1/auth?tx=', duoSig, + '&parent=', encodeURIComponent(document.location.href), + '&v=2.6' + ].join(''); + + // listen for the 'message' event + onMessage(onReceivedMessage); + } + + /** + * We received a postMessage from Duo. POST back to the primary service + * with the response token, and any additional user-supplied parameters + * given in form#duo_form. + */ + function doPostBack(response) { + // create a hidden input to contain the response token + var input = document.createElement('input'); + input.type = 'hidden'; + input.name = postArgument; + input.value = response + ':' + appSig; + + // user may supply their own form with additional inputs + var form = document.getElementById('duo_form'); + + // if the form doesn't exist, create one + if (!form) { + form = document.createElement('form'); + + // insert the new form after the iframe + iframe.parentElement.insertBefore(form, iframe.nextSibling); + } + + // make sure we are actually posting to the right place + form.method = 'POST'; + form.action = postAction; + + // add the response token input to the form + form.appendChild(input); + + // away we go! + if (typeof submitCallback === "function") { + submitCallback.call(null, form); + } else { + form.submit(); + } + } + + return { + init: init, + _onReady: onReady, + _parseSigRequest: parseSigRequest, + _isDuoMessage: isDuoMessage, + _doPostBack: doPostBack + }; +})); + (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); @@ -35761,6 +26602,9165 @@ function getMillerRabinTests(bits) { }); }()); +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.angularStripe = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o + * + * Copyright (c) 2014 Jon Schlinkert, contributors. + * Licensed under the MIT license. + */ + +var isNumber = _dereq_('is-number'); +var slice = _dereq_('array-slice'); + +module.exports = function last(arr, num) { + if (!Array.isArray(arr)) { + throw new Error('array-last expects an array as the first argument.'); + } + + if (arr.length === 0) { + return null; + } + + var res = slice(arr, arr.length - (isNumber(num) ? +num : 1)); + if (+num === 1 || num == null) { + return res[0]; + } + return res; +}; + +},{"array-slice":8,"is-number":20}],8:[function(_dereq_,module,exports){ +/*! + * array-slice + * + * Copyright (c) 2014-2015, Jon Schlinkert. + * Licensed under the MIT License. + */ + +'use strict'; + +module.exports = function slice(arr, start, end) { + var len = arr.length >>> 0; + var range = []; + + start = idx(arr, start); + end = idx(arr, end, len); + + while (start < end) { + range.push(arr[start++]); + } + return range; +}; + + +function idx(arr, pos, end) { + var len = arr.length >>> 0; + + if (pos == null) { + pos = end || 0; + } else if (pos < 0) { + pos = Math.max(len + pos, 0); + } else { + pos = Math.min(pos, len); + } + + return pos; +} +},{}],9:[function(_dereq_,module,exports){ +"use strict"; + +// rawAsap provides everything we need except exception management. +var rawAsap = _dereq_("./raw"); +// RawTasks are recycled to reduce GC churn. +var freeTasks = []; +// We queue errors to ensure they are thrown in right order (FIFO). +// Array-as-queue is good enough here, since we are just dealing with exceptions. +var pendingErrors = []; +var requestErrorThrow = rawAsap.makeRequestCallFromTimer(throwFirstError); + +function throwFirstError() { + if (pendingErrors.length) { + throw pendingErrors.shift(); + } +} + +/** + * Calls a task as soon as possible after returning, in its own event, with priority + * over other events like animation, reflow, and repaint. An error thrown from an + * event will not interrupt, nor even substantially slow down the processing of + * other events, but will be rather postponed to a lower priority event. + * @param {{call}} task A callable object, typically a function that takes no + * arguments. + */ +module.exports = asap; +function asap(task) { + var rawTask; + if (freeTasks.length) { + rawTask = freeTasks.pop(); + } else { + rawTask = new RawTask(); + } + rawTask.task = task; + rawAsap(rawTask); +} + +// We wrap tasks with recyclable task objects. A task object implements +// `call`, just like a function. +function RawTask() { + this.task = null; +} + +// The sole purpose of wrapping the task is to catch the exception and recycle +// the task object after its single use. +RawTask.prototype.call = function () { + try { + this.task.call(); + } catch (error) { + if (asap.onerror) { + // This hook exists purely for testing purposes. + // Its name will be periodically randomized to break any code that + // depends on its existence. + asap.onerror(error); + } else { + // In a web browser, exceptions are not fatal. However, to avoid + // slowing down the queue of pending tasks, we rethrow the error in a + // lower priority turn. + pendingErrors.push(error); + requestErrorThrow(); + } + } finally { + this.task = null; + freeTasks[freeTasks.length] = this; + } +}; + +},{"./raw":10}],10:[function(_dereq_,module,exports){ +(function (global){ +"use strict"; + +// Use the fastest means possible to execute a task in its own turn, with +// priority over other events including IO, animation, reflow, and redraw +// events in browsers. +// +// An exception thrown by a task will permanently interrupt the processing of +// subsequent tasks. The higher level `asap` function ensures that if an +// exception is thrown by a task, that the task queue will continue flushing as +// soon as possible, but if you use `rawAsap` directly, you are responsible to +// either ensure that no exceptions are thrown from your task, or to manually +// call `rawAsap.requestFlush` if an exception is thrown. +module.exports = rawAsap; +function rawAsap(task) { + if (!queue.length) { + requestFlush(); + flushing = true; + } + // Equivalent to push, but avoids a function call. + queue[queue.length] = task; +} + +var queue = []; +// Once a flush has been requested, no further calls to `requestFlush` are +// necessary until the next `flush` completes. +var flushing = false; +// `requestFlush` is an implementation-specific method that attempts to kick +// off a `flush` event as quickly as possible. `flush` will attempt to exhaust +// the event queue before yielding to the browser's own event loop. +var requestFlush; +// The position of the next task to execute in the task queue. This is +// preserved between calls to `flush` so that it can be resumed if +// a task throws an exception. +var index = 0; +// If a task schedules additional tasks recursively, the task queue can grow +// unbounded. To prevent memory exhaustion, the task queue will periodically +// truncate already-completed tasks. +var capacity = 1024; + +// The flush function processes all tasks that have been scheduled with +// `rawAsap` unless and until one of those tasks throws an exception. +// If a task throws an exception, `flush` ensures that its state will remain +// consistent and will resume where it left off when called again. +// However, `flush` does not make any arrangements to be called again if an +// exception is thrown. +function flush() { + while (index < queue.length) { + var currentIndex = index; + // Advance the index before calling the task. This ensures that we will + // begin flushing on the next task the task throws an error. + index = index + 1; + queue[currentIndex].call(); + // Prevent leaking memory for long chains of recursive calls to `asap`. + // If we call `asap` within tasks scheduled by `asap`, the queue will + // grow, but to avoid an O(n) walk for every task we execute, we don't + // shift tasks off the queue after they have been executed. + // Instead, we periodically shift 1024 tasks off the queue. + if (index > capacity) { + // Manually shift all values starting at the index back to the + // beginning of the queue. + for (var scan = 0, newLength = queue.length - index; scan < newLength; scan++) { + queue[scan] = queue[scan + index]; + } + queue.length -= index; + index = 0; + } + } + queue.length = 0; + index = 0; + flushing = false; +} + +// `requestFlush` is implemented using a strategy based on data collected from +// every available SauceLabs Selenium web driver worker at time of writing. +// https://docs.google.com/spreadsheets/d/1mG-5UYGup5qxGdEMWkhP6BWCz053NUb2E1QoUTU16uA/edit#gid=783724593 + +// Safari 6 and 6.1 for desktop, iPad, and iPhone are the only browsers that +// have WebKitMutationObserver but not un-prefixed MutationObserver. +// Must use `global` or `self` instead of `window` to work in both frames and web +// workers. `global` is a provision of Browserify, Mr, Mrs, or Mop. + +/* globals self */ +var scope = typeof global !== "undefined" ? global : self; +var BrowserMutationObserver = scope.MutationObserver || scope.WebKitMutationObserver; + +// MutationObservers are desirable because they have high priority and work +// reliably everywhere they are implemented. +// They are implemented in all modern browsers. +// +// - Android 4-4.3 +// - Chrome 26-34 +// - Firefox 14-29 +// - Internet Explorer 11 +// - iPad Safari 6-7.1 +// - iPhone Safari 7-7.1 +// - Safari 6-7 +if (typeof BrowserMutationObserver === "function") { + requestFlush = makeRequestCallFromMutationObserver(flush); + +// MessageChannels are desirable because they give direct access to the HTML +// task queue, are implemented in Internet Explorer 10, Safari 5.0-1, and Opera +// 11-12, and in web workers in many engines. +// Although message channels yield to any queued rendering and IO tasks, they +// would be better than imposing the 4ms delay of timers. +// However, they do not work reliably in Internet Explorer or Safari. + +// Internet Explorer 10 is the only browser that has setImmediate but does +// not have MutationObservers. +// Although setImmediate yields to the browser's renderer, it would be +// preferrable to falling back to setTimeout since it does not have +// the minimum 4ms penalty. +// Unfortunately there appears to be a bug in Internet Explorer 10 Mobile (and +// Desktop to a lesser extent) that renders both setImmediate and +// MessageChannel useless for the purposes of ASAP. +// https://github.com/kriskowal/q/issues/396 + +// Timers are implemented universally. +// We fall back to timers in workers in most engines, and in foreground +// contexts in the following browsers. +// However, note that even this simple case requires nuances to operate in a +// broad spectrum of browsers. +// +// - Firefox 3-13 +// - Internet Explorer 6-9 +// - iPad Safari 4.3 +// - Lynx 2.8.7 +} else { + requestFlush = makeRequestCallFromTimer(flush); +} + +// `requestFlush` requests that the high priority event queue be flushed as +// soon as possible. +// This is useful to prevent an error thrown in a task from stalling the event +// queue if the exception handled by Node.js’s +// `process.on("uncaughtException")` or by a domain. +rawAsap.requestFlush = requestFlush; + +// To request a high priority event, we induce a mutation observer by toggling +// the text of a text node between "1" and "-1". +function makeRequestCallFromMutationObserver(callback) { + var toggle = 1; + var observer = new BrowserMutationObserver(callback); + var node = document.createTextNode(""); + observer.observe(node, {characterData: true}); + return function requestCall() { + toggle = -toggle; + node.data = toggle; + }; +} + +// The message channel technique was discovered by Malte Ubl and was the +// original foundation for this library. +// http://www.nonblocking.io/2011/06/windownexttick.html + +// Safari 6.0.5 (at least) intermittently fails to create message ports on a +// page's first load. Thankfully, this version of Safari supports +// MutationObservers, so we don't need to fall back in that case. + +// function makeRequestCallFromMessageChannel(callback) { +// var channel = new MessageChannel(); +// channel.port1.onmessage = callback; +// return function requestCall() { +// channel.port2.postMessage(0); +// }; +// } + +// For reasons explained above, we are also unable to use `setImmediate` +// under any circumstances. +// Even if we were, there is another bug in Internet Explorer 10. +// It is not sufficient to assign `setImmediate` to `requestFlush` because +// `setImmediate` must be called *by name* and therefore must be wrapped in a +// closure. +// Never forget. + +// function makeRequestCallFromSetImmediate(callback) { +// return function requestCall() { +// setImmediate(callback); +// }; +// } + +// Safari 6.0 has a problem where timers will get lost while the user is +// scrolling. This problem does not impact ASAP because Safari 6.0 supports +// mutation observers, so that implementation is used instead. +// However, if we ever elect to use timers in Safari, the prevalent work-around +// is to add a scroll event listener that calls for a flush. + +// `setTimeout` does not call the passed callback if the delay is less than +// approximately 7 in web workers in Firefox 8 through 18, and sometimes not +// even then. + +function makeRequestCallFromTimer(callback) { + return function requestCall() { + // We dispatch a timeout with a specified delay of 0 for engines that + // can reliably accommodate that request. This will usually be snapped + // to a 4 milisecond delay, but once we're flushing, there's no delay + // between events. + var timeoutHandle = setTimeout(handleTimer, 0); + // However, since this timer gets frequently dropped in Firefox + // workers, we enlist an interval handle that will try to fire + // an event 20 times per second until it succeeds. + var intervalHandle = setInterval(handleTimer, 50); + + function handleTimer() { + // Whichever timer succeeds will cancel both timers and + // execute the callback. + clearTimeout(timeoutHandle); + clearInterval(intervalHandle); + callback(); + } + }; +} + +// This is for `asap.js` only. +// Its name will be periodically randomized to break any code that depends on +// its existence. +rawAsap.makeRequestCallFromTimer = makeRequestCallFromTimer; + +// ASAP was originally a nextTick shim included in Q. This was factored out +// into this ASAP package. It was later adapted to RSVP which made further +// amendments. These decisions, particularly to marginalize MessageChannel and +// to capture the MutationObserver implementation in a closure, were integrated +// back into ASAP proper. +// https://github.com/tildeio/rsvp.js/blob/cddf7232546a9cf858524b75cde6f9edf72620a7/lib/rsvp/asap.js + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],11:[function(_dereq_,module,exports){ +'use strict' + +var assert = _dereq_('assert-ok') +var format = _dereq_('simple-format') +var print = _dereq_('print-value') + +module.exports = function assertEqual (a, b) { + assert(a === b, format('expected `%s` to equal `%s`', print(a), print(b))) +} + +},{"assert-ok":13,"print-value":30,"simple-format":32}],12:[function(_dereq_,module,exports){ +'use strict' + +module.exports = function assertFunction (value) { + if (typeof value !== 'function') { + throw new TypeError('Expected function, got: ' + value) + } +} + +},{}],13:[function(_dereq_,module,exports){ +'use strict' + +module.exports = function assertOk (value, message) { + if (!value) { + throw new Error(message || 'Expected true, got ' + value) + } +} + +},{}],14:[function(_dereq_,module,exports){ +'use strict' + +module.exports = CallAll + +function CallAll (fns) { + fns = Array.isArray(fns) ? fns : arguments + return function callAll () { + var args = arguments + var ret = new Array(fns.length) + for (var i = 0, ii = fns.length; i < ii; i++) { + ret[i] = fns[i].apply(null, args) + } + return ret + } +} + +},{}],15:[function(_dereq_,module,exports){ +/** + * cuid.js + * Collision-resistant UID generator for browsers and node. + * Sequential for fast db lookups and recency sorting. + * Safe for element IDs and server-side lookups. + * + * Extracted from CLCTR + * + * Copyright (c) Eric Elliott 2012 + * MIT License + */ + +/*global window, navigator, document, require, process, module */ +(function (app) { + 'use strict'; + var namespace = 'cuid', + c = 0, + blockSize = 4, + base = 36, + discreteValues = Math.pow(base, blockSize), + + pad = function pad(num, size) { + var s = "000000000" + num; + return s.substr(s.length-size); + }, + + randomBlock = function randomBlock() { + return pad((Math.random() * + discreteValues << 0) + .toString(base), blockSize); + }, + + safeCounter = function () { + c = (c < discreteValues) ? c : 0; + c++; // this is not subliminal + return c - 1; + }, + + api = function cuid() { + // Starting with a lowercase letter makes + // it HTML element ID friendly. + var letter = 'c', // hard-coded allows for sequential access + + // timestamp + // warning: this exposes the exact date and time + // that the uid was created. + timestamp = (new Date().getTime()).toString(base), + + // Prevent same-machine collisions. + counter, + + // A few chars to generate distinct ids for different + // clients (so different computers are far less + // likely to generate the same id) + fingerprint = api.fingerprint(), + + // Grab some more chars from Math.random() + random = randomBlock() + randomBlock(); + + counter = pad(safeCounter().toString(base), blockSize); + + return (letter + timestamp + counter + fingerprint + random); + }; + + api.slug = function slug() { + var date = new Date().getTime().toString(36), + counter, + print = api.fingerprint().slice(0,1) + + api.fingerprint().slice(-1), + random = randomBlock().slice(-2); + + counter = safeCounter().toString(36).slice(-4); + + return date.slice(-2) + + counter + print + random; + }; + + api.globalCount = function globalCount() { + // We want to cache the results of this + var cache = (function calc() { + var i, + count = 0; + + for (i in window) { + count++; + } + + return count; + }()); + + api.globalCount = function () { return cache; }; + return cache; + }; + + api.fingerprint = function browserPrint() { + return pad((navigator.mimeTypes.length + + navigator.userAgent.length).toString(36) + + api.globalCount().toString(36), 4); + }; + + // don't change anything from here down. + if (app.register) { + app.register(namespace, api); + } else if (typeof module !== 'undefined') { + module.exports = api; + } else { + app[namespace] = api; + } + +}(this.applitude || this)); + +},{}],16:[function(_dereq_,module,exports){ +var wrappy = _dereq_('wrappy') +module.exports = wrappy(dezalgo) + +var asap = _dereq_('asap') + +function dezalgo (cb) { + var sync = true + asap(function () { + sync = false + }) + + return function zalgoSafe() { + var args = arguments + var me = this + if (sync) + asap(function() { + cb.apply(me, args) + }) + else + cb.apply(me, args) + } +} + +},{"asap":9,"wrappy":35}],17:[function(_dereq_,module,exports){ +'use strict'; +var isObj = _dereq_('is-obj'); + +module.exports.get = function (obj, path) { + if (!isObj(obj) || typeof path !== 'string') { + return obj; + } + + var pathArr = getPathSegments(path); + + for (var i = 0; i < pathArr.length; i++) { + var descriptor = Object.getOwnPropertyDescriptor(obj, pathArr[i]) || Object.getOwnPropertyDescriptor(Object.prototype, pathArr[i]); + if (descriptor && !descriptor.enumerable) { + return; + } + + obj = obj[pathArr[i]]; + + if (obj === undefined || obj === null) { + // `obj` is either `undefined` or `null` so we want to stop the loop, and + // if this is not the last bit of the path, and + // if it did't return `undefined` + // it would return `null` if `obj` is `null` + // but we want `get({foo: null}, 'foo.bar')` to equal `undefined` not `null` + if (i !== pathArr.length - 1) { + return undefined; + } + + break; + } + } + + return obj; +}; + +module.exports.set = function (obj, path, value) { + if (!isObj(obj) || typeof path !== 'string') { + return; + } + + var pathArr = getPathSegments(path); + + for (var i = 0; i < pathArr.length; i++) { + var p = pathArr[i]; + + if (!isObj(obj[p])) { + obj[p] = {}; + } + + if (i === pathArr.length - 1) { + obj[p] = value; + } + + obj = obj[p]; + } +}; + +module.exports.delete = function (obj, path) { + if (!isObj(obj) || typeof path !== 'string') { + return; + } + + var pathArr = getPathSegments(path); + + for (var i = 0; i < pathArr.length; i++) { + var p = pathArr[i]; + + if (i === pathArr.length - 1) { + delete obj[p]; + return; + } + + obj = obj[p]; + } +}; + +module.exports.has = function (obj, path) { + if (!isObj(obj) || typeof path !== 'string') { + return false; + } + + var pathArr = getPathSegments(path); + + for (var i = 0; i < pathArr.length; i++) { + obj = obj[pathArr[i]]; + + if (obj === undefined) { + return false; + } + } + + return true; +}; + +function getPathSegments(path) { + var pathArr = path.split('.'); + var parts = []; + + for (var i = 0; i < pathArr.length; i++) { + var p = pathArr[i]; + + while (p[p.length - 1] === '\\' && pathArr[i + 1] !== undefined) { + p = p.slice(0, -1) + '.'; + p += pathArr[++i]; + } + + parts.push(p); + } + + return parts; +} + +},{"is-obj":21}],18:[function(_dereq_,module,exports){ +'use strict' + +var assertFn = _dereq_('assert-function') + +module.exports = Ear + +function Ear () { + var callbacks = [] + + function listeners () { + var args = arguments + var i = 0 + var length = callbacks.length + for (; i < length; i++) { + var callback = callbacks[i] + callback.apply(null, args) + } + } + + listeners.add = function (listener) { + assertFn(listener) + callbacks.push(listener) + return function remove () { + var i = 0 + var length = callbacks.length + for (; i < length; i++) { + if (callbacks[i] === listener) { + callbacks.splice(i, 1) + return + } + } + } + } + + return listeners +} + +},{"assert-function":12}],19:[function(_dereq_,module,exports){ +(function (global){ +var win; + +if (typeof window !== "undefined") { + win = window; +} else if (typeof global !== "undefined") { + win = global; +} else if (typeof self !== "undefined"){ + win = self; +} else { + win = {}; +} + +module.exports = win; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],20:[function(_dereq_,module,exports){ +/*! + * is-number + * + * Copyright (c) 2014 Jon Schlinkert, contributors. + * Licensed under the MIT License + */ + +'use strict'; + +module.exports = function isNumber(n) { + return !!(+n) || n === 0 || n === '0'; +}; + +},{}],21:[function(_dereq_,module,exports){ +'use strict'; +module.exports = function (x) { + var type = typeof x; + return x !== null && (type === 'object' || type === 'function'); +}; + +},{}],22:[function(_dereq_,module,exports){ +module.exports = Array.isArray || function (arr) { + return Object.prototype.toString.call(arr) == '[object Array]'; +}; + +},{}],23:[function(_dereq_,module,exports){ +/*! + * isobject + * + * Copyright (c) 2014-2015, Jon Schlinkert. + * Licensed under the MIT License. + */ + +'use strict'; + +var isArray = _dereq_('isarray'); + +module.exports = function isObject(o) { + return o != null && typeof o === 'object' && !isArray(o); +}; + +},{"isarray":22}],24:[function(_dereq_,module,exports){ +exports = module.exports = stringify +exports.getSerialize = serializer + +function stringify(obj, replacer, spaces, cycleReplacer) { + return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces) +} + +function serializer(replacer, cycleReplacer) { + var stack = [], keys = [] + + if (cycleReplacer == null) cycleReplacer = function(key, value) { + if (stack[0] === value) return "[Circular ~]" + return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]" + } + + return function(key, value) { + if (stack.length > 0) { + var thisPos = stack.indexOf(this) + ~thisPos ? stack.splice(thisPos + 1) : stack.push(this) + ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key) + if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value) + } + else stack.push(value) + + return replacer == null ? value : replacer.call(this, key, value) + } +} + +},{}],25:[function(_dereq_,module,exports){ +'use strict' + +var assert = _dereq_('assert-ok') +var assertEqual = _dereq_('assert-equal') +var dot = _dereq_('dot-prop') +var toArray = _dereq_('to-array') +var last = _dereq_('array-last') +var dezalgo = _dereq_('dezalgo') +var all = _dereq_('call-all-fns') + +module.exports = Lazy + +function Lazy (methods, load) { + assert(Array.isArray(methods), 'methods are required') + assertEqual(typeof load, 'function', 'load fn is required') + + var api = null + var error = null + var queue = [] + + load(function (err, lib) { + error = err + api = lib + all(queue)(err, lib) + queue = null + }) + + return methods.reduce(function (lazy, method) { + dot.set(lazy, method, Deferred(method)) + return lazy + }, {}) + + function Deferred (method) { + return function deferred () { + var args = arguments + onReady(function (err, api) { + if (!err) return dot.get(api, method).apply(null, args) + var callback = last(toArray(args)) + if (typeof callback === 'function') { + return callback(err) + } + }) + } + } + + function onReady (callback) { + callback = dezalgo(callback) + + if (api || error) return callback(error, api) + queue.push(callback) + } +} + +},{"array-last":7,"assert-equal":11,"assert-ok":13,"call-all-fns":14,"dezalgo":16,"dot-prop":26,"to-array":34}],26:[function(_dereq_,module,exports){ +'use strict'; +var isObj = _dereq_('is-obj'); + +module.exports.get = function (obj, path) { + if (!isObj(obj) || typeof path !== 'string') { + return obj; + } + + var pathArr = getPathSegments(path); + + for (var i = 0; i < pathArr.length; i++) { + obj = obj[pathArr[i]]; + + if (obj === undefined) { + break; + } + } + + return obj; +}; + +module.exports.set = function (obj, path, value) { + if (!isObj(obj) || typeof path !== 'string') { + return; + } + + var pathArr = getPathSegments(path); + + for (var i = 0; i < pathArr.length; i++) { + var p = pathArr[i]; + + if (!isObj(obj[p])) { + obj[p] = {}; + } + + if (i === pathArr.length - 1) { + obj[p] = value; + } + + obj = obj[p]; + } +}; + +module.exports.delete = function (obj, path) { + if (!isObj(obj) || typeof path !== 'string') { + return; + } + + var pathArr = getPathSegments(path); + + for (var i = 0; i < pathArr.length; i++) { + var p = pathArr[i]; + + if (i === pathArr.length - 1) { + delete obj[p]; + return; + } + + obj = obj[p]; + } +}; + +module.exports.has = function (obj, path) { + if (!isObj(obj) || typeof path !== 'string') { + return false; + } + + var pathArr = getPathSegments(path); + + for (var i = 0; i < pathArr.length; i++) { + obj = obj[pathArr[i]]; + + if (obj === undefined) { + return false; + } + } + + return true; +}; + +function getPathSegments(path) { + var pathArr = path.split('.'); + var parts = []; + + for (var i = 0; i < pathArr.length; i++) { + var p = pathArr[i]; + + while (p[p.length - 1] === '\\') { + p = p.slice(0, -1) + '.'; + p += pathArr[++i]; + } + + parts.push(p); + } + + return parts; +} + +},{"is-obj":21}],27:[function(_dereq_,module,exports){ +'use strict' + +var load = _dereq_('load-script') +var window = _dereq_('global/window') +var extend = _dereq_('xtend') +var assert = _dereq_('assert-ok') +var dezalgo = _dereq_('dezalgo') +var Listeners = _dereq_('ear') +var extendQuery = _dereq_('query-extend') +var cuid = _dereq_('cuid') + +module.exports = loadGlobal + +var listeners = {} + +function loadGlobal (options, callback) { + assert(options, 'options required') + assert(options.url, 'url required') + assert(options.global, 'global required') + assert(callback, 'callback required') + + options = extend(options) + callback = dezalgo(callback) + + if (getGlobal(options)) { + return callback(null, getGlobal(options)) + } + + callback = cache(options, callback) + if (!callback) return + + if (options.jsonp) { + var id = jsonpCallback(options, callback) + options.url = extendQuery(options.url, {callback: id}) + } + + load(options.url, options, function (err) { + if (err) return callback(err) + if (!options.jsonp) { + var library = getGlobal(options) + if (!library) return callback(new Error('expected: `window.' + options.global + '`, actual: `' + library + '`')) + callback(null, library) + } + }) +} + +function cache (options, callback) { + if (!get()) { + set(Listeners()) + get().add(callback) + return function onComplete (err, lib) { + get()(err, lib) + set(Listeners()) + } + } + + get().add(callback) + return undefined + + function get () { + return listeners[options.global] + } + + function set (value) { + listeners[options.global] = value + } +} + +function getGlobal (options) { + return window[options.global] +} + +function jsonpCallback (options, callback) { + var id = cuid() + window[id] = function jsonpCallback () { + callback(null, getGlobal(options)) + delete window[id] + } + return id +} + +},{"assert-ok":13,"cuid":15,"dezalgo":16,"ear":18,"global/window":19,"load-script":28,"query-extend":31,"xtend":36}],28:[function(_dereq_,module,exports){ + +module.exports = function load (src, opts, cb) { + var head = document.head || document.getElementsByTagName('head')[0] + var script = document.createElement('script') + + if (typeof opts === 'function') { + cb = opts + opts = {} + } + + opts = opts || {} + cb = cb || function() {} + + script.type = opts.type || 'text/javascript' + script.charset = opts.charset || 'utf8'; + script.async = 'async' in opts ? !!opts.async : true + script.src = src + + if (opts.attrs) { + setAttributes(script, opts.attrs) + } + + if (opts.text) { + script.text = '' + opts.text + } + + var onend = 'onload' in script ? stdOnEnd : ieOnEnd + onend(script, cb) + + // some good legacy browsers (firefox) fail the 'in' detection above + // so as a fallback we always set onload + // old IE will ignore this and new IE will set onload + if (!script.onload) { + stdOnEnd(script, cb); + } + + head.appendChild(script) +} + +function setAttributes(script, attrs) { + for (var attr in attrs) { + script.setAttribute(attr, attrs[attr]); + } +} + +function stdOnEnd (script, cb) { + script.onload = function () { + this.onerror = this.onload = null + cb(null, script) + } + script.onerror = function () { + // this.onload = null here is necessary + // because even IE9 works not like others + this.onerror = this.onload = null + cb(new Error('Failed to load ' + this.src), script) + } +} + +function ieOnEnd (script, cb) { + script.onreadystatechange = function () { + if (this.readyState != 'complete' && this.readyState != 'loaded') return + this.onreadystatechange = null + cb(null, script) // there is no way to catch loading errors in IE8 + } +} + +},{}],29:[function(_dereq_,module,exports){ +'use strict'; + +module.exports = function split(str) { + var a = 1, + res = ''; + + var parts = str.split('%'), + len = parts.length; + + if (len > 0) { res += parts[0]; } + + for (var i = 1; i < len; i++) { + if (parts[i][0] === 's' || parts[i][0] === 'd') { + var value = arguments[a++]; + res += parts[i][0] === 'd' ? Math.floor(value) : value; + } else if (parts[i][0]) { + res += '%' + parts[i][0]; + } else { + i++; + res += '%' + parts[i][0]; + } + + res += parts[i].substring(1); + } + + return res; +}; + +},{}],30:[function(_dereq_,module,exports){ +'use strict' + +var isObject = _dereq_('isobject') +var safeStringify = _dereq_('json-stringify-safe') + +module.exports = function print (value) { + var toString = isJson(value) ? stringify : String + return toString(value) +} + +function isJson (value) { + return isObject(value) || Array.isArray(value) +} + +function stringify (value) { + return safeStringify(value, null, '') +} + +},{"isobject":23,"json-stringify-safe":24}],31:[function(_dereq_,module,exports){ +!function(glob) { + + var queryToObject = function(query) { + var obj = {}; + if (!query) return obj; + each(query.split('&'), function(val) { + var pieces = val.split('='); + var key = parseKey(pieces[0]); + var keyDecoded = decodeURIComponent(key.val); + var valDecoded = pieces[1] && decodeURIComponent(pieces[1]); + + if (key.type === 'array') { + if (!obj[keyDecoded]) obj[keyDecoded] = []; + obj[keyDecoded].push(valDecoded); + } else if (key.type === 'string') { + obj[keyDecoded] = valDecoded; + } + }); + return obj; + }; + + var objectToQuery = function(obj) { + var pieces = [], encodedKey; + for (var k in obj) { + if (!obj.hasOwnProperty(k)) continue; + if (typeof obj[k] === 'undefined') { + pieces.push(encodeURIComponent(k)); + continue; + } + encodedKey = encodeURIComponent(k); + if (isArray(obj[k])) { + each(obj[k], function(val) { + pieces.push(encodedKey + '[]=' + encodeURIComponent(val)); + }); + continue; + } + pieces.push(encodedKey + '=' + encodeURIComponent(obj[k])); + } + return pieces.length ? ('?' + pieces.join('&')) : ''; + }; + + // for now we will only support string and arrays + var parseKey = function(key) { + var pos = key.indexOf('['); + if (pos === -1) return { type: 'string', val: key }; + return { type: 'array', val: key.substr(0, pos) }; + }; + + var isArray = function(val) { + return Object.prototype.toString.call(val) === '[object Array]'; + }; + + var extract = function(url) { + var pos = url.lastIndexOf('?'); + var hasQuery = pos !== -1; + var base = void 0; + + if (hasQuery && pos > 0) { + base = url.substring(0, pos); + } else if (!hasQuery && (url && url.length > 0)) { + base = url; + } + + return { + base: base, + query: hasQuery ? url.substring(pos+1) : void 0 + }; + }; + + // thanks raynos! + // https://github.com/Raynos/xtend + var extend = function() { + var target = {}; + for (var i = 0; i < arguments.length; i++) { + var source = arguments[i]; + for (var key in source) { + if (source.hasOwnProperty(key)) { + target[key] = source[key]; + } + } + } + return target; + }; + + var queryExtend = function() { + var args = Array.prototype.slice.call(arguments, 0); + var asObject = args[args.length-1] === true; + var base = ''; + + if (!args.length) { + return base; + } + + if (asObject) { + args.pop(); + } + + var normalized = map(args, function(param) { + if (typeof param === 'string') { + var extracted = extract(param); + if (extracted.base) base = extracted.base; + return queryToObject(extracted.query); + } + return param; + }); + + if (asObject) { + return extend.apply({}, normalized); + } else { + return base + objectToQuery(extend.apply({}, normalized)); + } + + }; + + var each = function(arr, fn) { + for (var i = 0, l = arr.length; i < l; i++) { + fn(arr[i], i); + } + }; + + var map = function(arr, fn) { + var res = []; + for (var i = 0, l = arr.length; i < l; i++) { + res.push( fn(arr[i], i) ); + } + return res; + }; + + if (typeof module !== 'undefined' && module.exports) { + // Node.js / browserify + module.exports = queryExtend; + } else if (typeof define === 'function' && define.amd) { + // require.js / AMD + define(function() { + return queryExtend; + }); + } else { + // + * + * + * + * + * + * + * + * + */ +angular.module('ui.router', ['ui.router.state']); + +angular.module('ui.router.compat', ['ui.router']); + +/** + * @ngdoc object + * @name ui.router.util.$resolve + * + * @requires $q + * @requires $injector + * + * @description + * Manages resolution of (acyclic) graphs of promises. + */ +$Resolve.$inject = ['$q', '$injector']; +function $Resolve( $q, $injector) { + + var VISIT_IN_PROGRESS = 1, + VISIT_DONE = 2, + NOTHING = {}, + NO_DEPENDENCIES = [], + NO_LOCALS = NOTHING, + NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING }); + + + /** + * @ngdoc function + * @name ui.router.util.$resolve#study + * @methodOf ui.router.util.$resolve + * + * @description + * Studies a set of invocables that are likely to be used multiple times. + *
+   * $resolve.study(invocables)(locals, parent, self)
+   * 
+ * is equivalent to + *
+   * $resolve.resolve(invocables, locals, parent, self)
+   * 
+ * but the former is more efficient (in fact `resolve` just calls `study` + * internally). + * + * @param {object} invocables Invocable objects + * @return {function} a function to pass in locals, parent and self + */ + this.study = function (invocables) { + if (!isObject(invocables)) throw new Error("'invocables' must be an object"); + var invocableKeys = objectKeys(invocables || {}); + + // Perform a topological sort of invocables to build an ordered plan + var plan = [], cycle = [], visited = {}; + function visit(value, key) { + if (visited[key] === VISIT_DONE) return; + + cycle.push(key); + if (visited[key] === VISIT_IN_PROGRESS) { + cycle.splice(0, indexOf(cycle, key)); + throw new Error("Cyclic dependency: " + cycle.join(" -> ")); + } + visited[key] = VISIT_IN_PROGRESS; + + if (isString(value)) { + plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES); + } else { + var params = $injector.annotate(value); + forEach(params, function (param) { + if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param); + }); + plan.push(key, value, params); + } + + cycle.pop(); + visited[key] = VISIT_DONE; + } + forEach(invocables, visit); + invocables = cycle = visited = null; // plan is all that's required + + function isResolve(value) { + return isObject(value) && value.then && value.$$promises; + } + + return function (locals, parent, self) { + if (isResolve(locals) && self === undefined) { + self = parent; parent = locals; locals = null; + } + if (!locals) locals = NO_LOCALS; + else if (!isObject(locals)) { + throw new Error("'locals' must be an object"); + } + if (!parent) parent = NO_PARENT; + else if (!isResolve(parent)) { + throw new Error("'parent' must be a promise returned by $resolve.resolve()"); + } + + // To complete the overall resolution, we have to wait for the parent + // promise and for the promise for each invokable in our plan. + var resolution = $q.defer(), + result = silenceUncaughtInPromise(resolution.promise), + promises = result.$$promises = {}, + values = extend({}, locals), + wait = 1 + plan.length/3, + merged = false; + + silenceUncaughtInPromise(result); + + function done() { + // Merge parent values we haven't got yet and publish our own $$values + if (!--wait) { + if (!merged) merge(values, parent.$$values); + result.$$values = values; + result.$$promises = result.$$promises || true; // keep for isResolve() + delete result.$$inheritedValues; + resolution.resolve(values); + } + } + + function fail(reason) { + result.$$failure = reason; + resolution.reject(reason); + } + + // Short-circuit if parent has already failed + if (isDefined(parent.$$failure)) { + fail(parent.$$failure); + return result; + } + + if (parent.$$inheritedValues) { + merge(values, omit(parent.$$inheritedValues, invocableKeys)); + } + + // Merge parent values if the parent has already resolved, or merge + // parent promises and wait if the parent resolve is still in progress. + extend(promises, parent.$$promises); + if (parent.$$values) { + merged = merge(values, omit(parent.$$values, invocableKeys)); + result.$$inheritedValues = omit(parent.$$values, invocableKeys); + done(); + } else { + if (parent.$$inheritedValues) { + result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys); + } + parent.then(done, fail); + } + + // Process each invocable in the plan, but ignore any where a local of the same name exists. + for (var i=0, ii=plan.length; i + * Impact on loading templates
for more details about this mechanism. + * + * @param {boolean} value + */ + this.shouldUnsafelyUseHttp = function(value) { + shouldUnsafelyUseHttp = !!value; + }; + + /** + * @ngdoc object + * @name ui.router.util.$templateFactory + * + * @requires $http + * @requires $templateCache + * @requires $injector + * + * @description + * Service. Manages loading of templates. + */ + this.$get = ['$http', '$templateCache', '$injector', function($http, $templateCache, $injector){ + return new TemplateFactory($http, $templateCache, $injector, shouldUnsafelyUseHttp);}]; +} + + +/** + * @ngdoc object + * @name ui.router.util.$templateFactory + * + * @requires $http + * @requires $templateCache + * @requires $injector + * + * @description + * Service. Manages loading of templates. + */ +function TemplateFactory($http, $templateCache, $injector, shouldUnsafelyUseHttp) { + + /** + * @ngdoc function + * @name ui.router.util.$templateFactory#fromConfig + * @methodOf ui.router.util.$templateFactory + * + * @description + * Creates a template from a configuration object. + * + * @param {object} config Configuration object for which to load a template. + * The following properties are search in the specified order, and the first one + * that is defined is used to create the template: + * + * @param {string|object} config.template html string template or function to + * load via {@link ui.router.util.$templateFactory#fromString fromString}. + * @param {string|object} config.templateUrl url to load or a function returning + * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}. + * @param {Function} config.templateProvider function to invoke via + * {@link ui.router.util.$templateFactory#fromProvider fromProvider}. + * @param {object} params Parameters to pass to the template function. + * @param {object} locals Locals to pass to `invoke` if the template is loaded + * via a `templateProvider`. Defaults to `{ params: params }`. + * + * @return {string|object} The template html as a string, or a promise for + * that string,or `null` if no template is configured. + */ + this.fromConfig = function (config, params, locals) { + return ( + isDefined(config.template) ? this.fromString(config.template, params) : + isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) : + isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) : + null + ); + }; + + /** + * @ngdoc function + * @name ui.router.util.$templateFactory#fromString + * @methodOf ui.router.util.$templateFactory + * + * @description + * Creates a template from a string or a function returning a string. + * + * @param {string|object} template html template as a string or function that + * returns an html template as a string. + * @param {object} params Parameters to pass to the template function. + * + * @return {string|object} The template html as a string, or a promise for that + * string. + */ + this.fromString = function (template, params) { + return isFunction(template) ? template(params) : template; + }; + + /** + * @ngdoc function + * @name ui.router.util.$templateFactory#fromUrl + * @methodOf ui.router.util.$templateFactory + * + * @description + * Loads a template from the a URL via `$http` and `$templateCache`. + * + * @param {string|Function} url url of the template to load, or a function + * that returns a url. + * @param {Object} params Parameters to pass to the url function. + * @return {string|Promise.} The template html as a string, or a promise + * for that string. + */ + this.fromUrl = function (url, params) { + if (isFunction(url)) url = url(params); + if (url == null) return null; + else { + if(!shouldUnsafelyUseHttp) { + return $injector.get('$templateRequest')(url); + } else { + return $http + .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }}) + .then(function(response) { return response.data; }); + } + } + }; + + /** + * @ngdoc function + * @name ui.router.util.$templateFactory#fromProvider + * @methodOf ui.router.util.$templateFactory + * + * @description + * Creates a template by invoking an injectable provider function. + * + * @param {Function} provider Function to invoke via `$injector.invoke` + * @param {Object} params Parameters for the template. + * @param {Object} locals Locals to pass to `invoke`. Defaults to + * `{ params: params }`. + * @return {string|Promise.} The template html as a string, or a promise + * for that string. + */ + this.fromProvider = function (provider, params, locals) { + return $injector.invoke(provider, null, locals || { params: params }); + }; +} + +angular.module('ui.router.util').provider('$templateFactory', TemplateFactoryProvider); + +var $$UMFP; // reference to $UrlMatcherFactoryProvider + +/** + * @ngdoc object + * @name ui.router.util.type:UrlMatcher + * + * @description + * Matches URLs against patterns and extracts named parameters from the path or the search + * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list + * of search parameters. Multiple search parameter names are separated by '&'. Search parameters + * do not influence whether or not a URL is matched, but their values are passed through into + * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}. + * + * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace + * syntax, which optionally allows a regular expression for the parameter to be specified: + * + * * `':'` name - colon placeholder + * * `'*'` name - catch-all placeholder + * * `'{' name '}'` - curly placeholder + * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the + * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash. + * + * Parameter names may contain only word characters (latin letters, digits, and underscore) and + * must be unique within the pattern (across both path and search parameters). For colon + * placeholders or curly placeholders without an explicit regexp, a path parameter matches any + * number of characters other than '/'. For catch-all placeholders the path parameter matches + * any number of characters. + * + * Examples: + * + * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for + * trailing slashes, and patterns have to match the entire path, not just a prefix. + * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or + * '/user/bob/details'. The second path segment will be captured as the parameter 'id'. + * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax. + * * `'/user/{id:[^/]*}'` - Same as the previous example. + * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id + * parameter consists of 1 to 8 hex digits. + * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the + * path into the parameter 'path'. + * * `'/files/*path'` - ditto. + * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined + * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start + * + * @param {string} pattern The pattern to compile into a matcher. + * @param {Object} config A configuration object hash: + * @param {Object=} parentMatcher Used to concatenate the pattern/config onto + * an existing UrlMatcher + * + * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`. + * * `strict` - `false` if matching against a URL with a trailing slash should be treated as equivalent to a URL without a trailing slash, the default value is `true`. + * + * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any + * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns + * non-null) will start with this prefix. + * + * @property {string} source The pattern that was passed into the constructor + * + * @property {string} sourcePath The path portion of the source property + * + * @property {string} sourceSearch The search portion of the source property + * + * @property {string} regex The constructed regex that will be used to match against the url when + * it is time to determine which url will match. + * + * @returns {Object} New `UrlMatcher` object + */ +function UrlMatcher(pattern, config, parentMatcher) { + config = extend({ params: {} }, isObject(config) ? config : {}); + + // Find all placeholders and create a compiled pattern, using either classic or curly syntax: + // '*' name + // ':' name + // '{' name '}' + // '{' name ':' regexp '}' + // The regular expression is somewhat complicated due to the need to allow curly braces + // inside the regular expression. The placeholder regexp breaks down as follows: + // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case) + // \{([\w\[\]]+)(?:\:\s*( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case + // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either + // [^{}\\]+ - anything other than curly braces or backslash + // \\. - a backslash escape + // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms + var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, + searchPlaceholder = /([:]?)([\w\[\].-]+)|\{([\w\[\].-]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, + compiled = '^', last = 0, m, + segments = this.segments = [], + parentParams = parentMatcher ? parentMatcher.params : {}, + params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(), + paramNames = []; + + function addParameter(id, type, config, location) { + paramNames.push(id); + if (parentParams[id]) return parentParams[id]; + if (!/^\w+([-.]+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'"); + if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'"); + params[id] = new $$UMFP.Param(id, type, config, location); + return params[id]; + } + + function quoteRegExp(string, pattern, squash, optional) { + var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&"); + if (!pattern) return result; + switch(squash) { + case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break; + case true: + result = result.replace(/\/$/, ''); + surroundPattern = ['(?:\/(', ')|\/)?']; + break; + default: surroundPattern = ['(' + squash + "|", ')?']; break; + } + return result + surroundPattern[0] + pattern + surroundPattern[1]; + } + + this.source = pattern; + + // Split into static segments separated by path parameter placeholders. + // The number of segments is always 1 more than the number of parameters. + function matchDetails(m, isSearch) { + var id, regexp, segment, type, cfg, arrayMode; + id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null + cfg = config.params[id]; + segment = pattern.substring(last, m.index); + regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null); + + if (regexp) { + type = $$UMFP.type(regexp) || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) }); + } + + return { + id: id, regexp: regexp, segment: segment, type: type, cfg: cfg + }; + } + + var p, param, segment; + while ((m = placeholder.exec(pattern))) { + p = matchDetails(m, false); + if (p.segment.indexOf('?') >= 0) break; // we're into the search part + + param = addParameter(p.id, p.type, p.cfg, "path"); + compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional); + segments.push(p.segment); + last = placeholder.lastIndex; + } + segment = pattern.substring(last); + + // Find any search parameter names and remove them from the last segment + var i = segment.indexOf('?'); + + if (i >= 0) { + var search = this.sourceSearch = segment.substring(i); + segment = segment.substring(0, i); + this.sourcePath = pattern.substring(0, last + i); + + if (search.length > 0) { + last = 0; + while ((m = searchPlaceholder.exec(search))) { + p = matchDetails(m, true); + param = addParameter(p.id, p.type, p.cfg, "search"); + last = placeholder.lastIndex; + // check if ?& + } + } + } else { + this.sourcePath = pattern; + this.sourceSearch = ''; + } + + compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$'; + segments.push(segment); + + this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined); + this.prefix = segments[0]; + this.$$paramNames = paramNames; +} + +/** + * @ngdoc function + * @name ui.router.util.type:UrlMatcher#concat + * @methodOf ui.router.util.type:UrlMatcher + * + * @description + * Returns a new matcher for a pattern constructed by appending the path part and adding the + * search parameters of the specified pattern to this pattern. The current pattern is not + * modified. This can be understood as creating a pattern for URLs that are relative to (or + * suffixes of) the current pattern. + * + * @example + * The following two matchers are equivalent: + *
+ * new UrlMatcher('/user/{id}?q').concat('/details?date');
+ * new UrlMatcher('/user/{id}/details?q&date');
+ * 
+ * + * @param {string} pattern The pattern to append. + * @param {Object} config An object hash of the configuration for the matcher. + * @returns {UrlMatcher} A matcher for the concatenated pattern. + */ +UrlMatcher.prototype.concat = function (pattern, config) { + // Because order of search parameters is irrelevant, we can add our own search + // parameters to the end of the new pattern. Parse the new pattern by itself + // and then join the bits together, but it's much easier to do this on a string level. + var defaultConfig = { + caseInsensitive: $$UMFP.caseInsensitive(), + strict: $$UMFP.strictMode(), + squash: $$UMFP.defaultSquashPolicy() + }; + return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this); +}; + +UrlMatcher.prototype.toString = function () { + return this.source; +}; + +/** + * @ngdoc function + * @name ui.router.util.type:UrlMatcher#exec + * @methodOf ui.router.util.type:UrlMatcher + * + * @description + * Tests the specified path against this matcher, and returns an object containing the captured + * parameter values, or null if the path does not match. The returned object contains the values + * of any search parameters that are mentioned in the pattern, but their value may be null if + * they are not present in `searchParams`. This means that search parameters are always treated + * as optional. + * + * @example + *
+ * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
+ *   x: '1', q: 'hello'
+ * });
+ * // returns { id: 'bob', q: 'hello', r: null }
+ * 
+ * + * @param {string} path The URL path to match, e.g. `$location.path()`. + * @param {Object} searchParams URL search parameters, e.g. `$location.search()`. + * @returns {Object} The captured parameter values. + */ +UrlMatcher.prototype.exec = function (path, searchParams) { + var m = this.regexp.exec(path); + if (!m) return null; + searchParams = searchParams || {}; + + var paramNames = this.parameters(), nTotal = paramNames.length, + nPath = this.segments.length - 1, + values = {}, i, j, cfg, paramName; + + if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'"); + + function decodePathArray(string) { + function reverseString(str) { return str.split("").reverse().join(""); } + function unquoteDashes(str) { return str.replace(/\\-/g, "-"); } + + var split = reverseString(string).split(/-(?!\\)/); + var allReversed = map(split, reverseString); + return map(allReversed, unquoteDashes).reverse(); + } + + var param, paramVal; + for (i = 0; i < nPath; i++) { + paramName = paramNames[i]; + param = this.params[paramName]; + paramVal = m[i+1]; + // if the param value matches a pre-replace pair, replace the value before decoding. + for (j = 0; j < param.replace.length; j++) { + if (param.replace[j].from === paramVal) paramVal = param.replace[j].to; + } + if (paramVal && param.array === true) paramVal = decodePathArray(paramVal); + if (isDefined(paramVal)) paramVal = param.type.decode(paramVal); + values[paramName] = param.value(paramVal); + } + for (/**/; i < nTotal; i++) { + paramName = paramNames[i]; + values[paramName] = this.params[paramName].value(searchParams[paramName]); + param = this.params[paramName]; + paramVal = searchParams[paramName]; + for (j = 0; j < param.replace.length; j++) { + if (param.replace[j].from === paramVal) paramVal = param.replace[j].to; + } + if (isDefined(paramVal)) paramVal = param.type.decode(paramVal); + values[paramName] = param.value(paramVal); + } + + return values; +}; + +/** + * @ngdoc function + * @name ui.router.util.type:UrlMatcher#parameters + * @methodOf ui.router.util.type:UrlMatcher + * + * @description + * Returns the names of all path and search parameters of this pattern in an unspecified order. + * + * @returns {Array.} An array of parameter names. Must be treated as read-only. If the + * pattern has no parameters, an empty array is returned. + */ +UrlMatcher.prototype.parameters = function (param) { + if (!isDefined(param)) return this.$$paramNames; + return this.params[param] || null; +}; + +/** + * @ngdoc function + * @name ui.router.util.type:UrlMatcher#validates + * @methodOf ui.router.util.type:UrlMatcher + * + * @description + * Checks an object hash of parameters to validate their correctness according to the parameter + * types of this `UrlMatcher`. + * + * @param {Object} params The object hash of parameters to validate. + * @returns {boolean} Returns `true` if `params` validates, otherwise `false`. + */ +UrlMatcher.prototype.validates = function (params) { + return this.params.$$validates(params); +}; + +/** + * @ngdoc function + * @name ui.router.util.type:UrlMatcher#format + * @methodOf ui.router.util.type:UrlMatcher + * + * @description + * Creates a URL that matches this pattern by substituting the specified values + * for the path and search parameters. Null values for path parameters are + * treated as empty strings. + * + * @example + *
+ * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
+ * // returns '/user/bob?q=yes'
+ * 
+ * + * @param {Object} values the values to substitute for the parameters in this pattern. + * @returns {string} the formatted URL (path and optionally search part). + */ +UrlMatcher.prototype.format = function (values) { + values = values || {}; + var segments = this.segments, params = this.parameters(), paramset = this.params; + if (!this.validates(values)) return null; + + var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0]; + + function encodeDashes(str) { // Replace dashes with encoded "\-" + return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); }); + } + + for (i = 0; i < nTotal; i++) { + var isPathParam = i < nPath; + var name = params[i], param = paramset[name], value = param.value(values[name]); + var isDefaultValue = param.isOptional && param.type.equals(param.value(), value); + var squash = isDefaultValue ? param.squash : false; + var encoded = param.type.encode(value); + + if (isPathParam) { + var nextSegment = segments[i + 1]; + var isFinalPathParam = i + 1 === nPath; + + if (squash === false) { + if (encoded != null) { + if (isArray(encoded)) { + result += map(encoded, encodeDashes).join("-"); + } else { + result += encodeURIComponent(encoded); + } + } + result += nextSegment; + } else if (squash === true) { + var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/; + result += nextSegment.match(capture)[1]; + } else if (isString(squash)) { + result += squash + nextSegment; + } + + if (isFinalPathParam && param.squash === true && result.slice(-1) === '/') result = result.slice(0, -1); + } else { + if (encoded == null || (isDefaultValue && squash !== false)) continue; + if (!isArray(encoded)) encoded = [ encoded ]; + if (encoded.length === 0) continue; + encoded = map(encoded, encodeURIComponent).join('&' + name + '='); + result += (search ? '&' : '?') + (name + '=' + encoded); + search = true; + } + } + + return result; +}; + +/** + * @ngdoc object + * @name ui.router.util.type:Type + * + * @description + * Implements an interface to define custom parameter types that can be decoded from and encoded to + * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`} + * objects when matching or formatting URLs, or comparing or validating parameter values. + * + * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more + * information on registering custom types. + * + * @param {Object} config A configuration object which contains the custom type definition. The object's + * properties will override the default methods and/or pattern in `Type`'s public interface. + * @example + *
+ * {
+ *   decode: function(val) { return parseInt(val, 10); },
+ *   encode: function(val) { return val && val.toString(); },
+ *   equals: function(a, b) { return this.is(a) && a === b; },
+ *   is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
+ *   pattern: /\d+/
+ * }
+ * 
+ * + * @property {RegExp} pattern The regular expression pattern used to match values of this type when + * coming from a substring of a URL. + * + * @returns {Object} Returns a new `Type` object. + */ +function Type(config) { + extend(this, config); +} + +/** + * @ngdoc function + * @name ui.router.util.type:Type#is + * @methodOf ui.router.util.type:Type + * + * @description + * Detects whether a value is of a particular type. Accepts a native (decoded) value + * and determines whether it matches the current `Type` object. + * + * @param {*} val The value to check. + * @param {string} key Optional. If the type check is happening in the context of a specific + * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the + * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects. + * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`. + */ +Type.prototype.is = function(val, key) { + return true; +}; + +/** + * @ngdoc function + * @name ui.router.util.type:Type#encode + * @methodOf ui.router.util.type:Type + * + * @description + * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the + * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it + * only needs to be a representation of `val` that has been coerced to a string. + * + * @param {*} val The value to encode. + * @param {string} key The name of the parameter in which `val` is stored. Can be used for + * meta-programming of `Type` objects. + * @returns {string} Returns a string representation of `val` that can be encoded in a URL. + */ +Type.prototype.encode = function(val, key) { + return val; +}; + +/** + * @ngdoc function + * @name ui.router.util.type:Type#decode + * @methodOf ui.router.util.type:Type + * + * @description + * Converts a parameter value (from URL string or transition param) to a custom/native value. + * + * @param {string} val The URL parameter value to decode. + * @param {string} key The name of the parameter in which `val` is stored. Can be used for + * meta-programming of `Type` objects. + * @returns {*} Returns a custom representation of the URL parameter value. + */ +Type.prototype.decode = function(val, key) { + return val; +}; + +/** + * @ngdoc function + * @name ui.router.util.type:Type#equals + * @methodOf ui.router.util.type:Type + * + * @description + * Determines whether two decoded values are equivalent. + * + * @param {*} a A value to compare against. + * @param {*} b A value to compare against. + * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`. + */ +Type.prototype.equals = function(a, b) { + return a == b; +}; + +Type.prototype.$subPattern = function() { + var sub = this.pattern.toString(); + return sub.substr(1, sub.length - 2); +}; + +Type.prototype.pattern = /.*/; + +Type.prototype.toString = function() { return "{Type:" + this.name + "}"; }; + +/** Given an encoded string, or a decoded object, returns a decoded object */ +Type.prototype.$normalize = function(val) { + return this.is(val) ? val : this.decode(val); +}; + +/* + * Wraps an existing custom Type as an array of Type, depending on 'mode'. + * e.g.: + * - urlmatcher pattern "/path?{queryParam[]:int}" + * - url: "/path?queryParam=1&queryParam=2 + * - $stateParams.queryParam will be [1, 2] + * if `mode` is "auto", then + * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1 + * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2] + */ +Type.prototype.$asArray = function(mode, isSearch) { + if (!mode) return this; + if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only"); + + function ArrayType(type, mode) { + function bindTo(type, callbackName) { + return function() { + return type[callbackName].apply(type, arguments); + }; + } + + // Wrap non-array value as array + function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); } + // Unwrap array value for "auto" mode. Return undefined for empty array. + function arrayUnwrap(val) { + switch(val.length) { + case 0: return undefined; + case 1: return mode === "auto" ? val[0] : val; + default: return val; + } + } + function falsey(val) { return !val; } + + // Wraps type (.is/.encode/.decode) functions to operate on each value of an array + function arrayHandler(callback, allTruthyMode) { + return function handleArray(val) { + if (isArray(val) && val.length === 0) return val; + val = arrayWrap(val); + var result = map(val, callback); + if (allTruthyMode === true) + return filter(result, falsey).length === 0; + return arrayUnwrap(result); + }; + } + + // Wraps type (.equals) functions to operate on each value of an array + function arrayEqualsHandler(callback) { + return function handleArray(val1, val2) { + var left = arrayWrap(val1), right = arrayWrap(val2); + if (left.length !== right.length) return false; + for (var i = 0; i < left.length; i++) { + if (!callback(left[i], right[i])) return false; + } + return true; + }; + } + + this.encode = arrayHandler(bindTo(type, 'encode')); + this.decode = arrayHandler(bindTo(type, 'decode')); + this.is = arrayHandler(bindTo(type, 'is'), true); + this.equals = arrayEqualsHandler(bindTo(type, 'equals')); + this.pattern = type.pattern; + this.$normalize = arrayHandler(bindTo(type, '$normalize')); + this.name = type.name; + this.$arrayMode = mode; + } + + return new ArrayType(this, mode); +}; + + + +/** + * @ngdoc object + * @name ui.router.util.$urlMatcherFactory + * + * @description + * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory + * is also available to providers under the name `$urlMatcherFactoryProvider`. + */ +function $UrlMatcherFactory() { + $$UMFP = this; + + var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false; + + // Use tildes to pre-encode slashes. + // If the slashes are simply URLEncoded, the browser can choose to pre-decode them, + // and bidirectional encoding/decoding fails. + // Tilde was chosen because it's not a RFC 3986 section 2.2 Reserved Character + function valToString(val) { return val != null ? val.toString().replace(/(~|\/)/g, function (m) { return {'~':'~~', '/':'~2F'}[m]; }) : val; } + function valFromString(val) { return val != null ? val.toString().replace(/(~~|~2F)/g, function (m) { return {'~~':'~', '~2F':'/'}[m]; }) : val; } + + var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = { + "string": { + encode: valToString, + decode: valFromString, + // TODO: in 1.0, make string .is() return false if value is undefined/null by default. + // In 0.2.x, string params are optional by default for backwards compat + is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; }, + pattern: /[^/]*/ + }, + "int": { + encode: valToString, + decode: function(val) { return parseInt(val, 10); }, + is: function(val) { return val !== undefined && val !== null && this.decode(val.toString()) === val; }, + pattern: /\d+/ + }, + "bool": { + encode: function(val) { return val ? 1 : 0; }, + decode: function(val) { return parseInt(val, 10) !== 0; }, + is: function(val) { return val === true || val === false; }, + pattern: /0|1/ + }, + "date": { + encode: function (val) { + if (!this.is(val)) + return undefined; + return [ val.getFullYear(), + ('0' + (val.getMonth() + 1)).slice(-2), + ('0' + val.getDate()).slice(-2) + ].join("-"); + }, + decode: function (val) { + if (this.is(val)) return val; + var match = this.capture.exec(val); + return match ? new Date(match[1], match[2] - 1, match[3]) : undefined; + }, + is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); }, + equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); }, + pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/, + capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/ + }, + "json": { + encode: angular.toJson, + decode: angular.fromJson, + is: angular.isObject, + equals: angular.equals, + pattern: /[^/]*/ + }, + "any": { // does not encode/decode + encode: angular.identity, + decode: angular.identity, + equals: angular.equals, + pattern: /.*/ + } + }; + + function getDefaultConfig() { + return { + strict: isStrictMode, + caseInsensitive: isCaseInsensitive + }; + } + + function isInjectable(value) { + return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1]))); + } + + /** + * [Internal] Get the default value of a parameter, which may be an injectable function. + */ + $UrlMatcherFactory.$$getDefaultValue = function(config) { + if (!isInjectable(config.value)) return config.value; + if (!injector) throw new Error("Injectable functions cannot be called at configuration time"); + return injector.invoke(config.value); + }; + + /** + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#caseInsensitive + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description + * Defines whether URL matching should be case sensitive (the default behavior), or not. + * + * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`; + * @returns {boolean} the current value of caseInsensitive + */ + this.caseInsensitive = function(value) { + if (isDefined(value)) + isCaseInsensitive = value; + return isCaseInsensitive; + }; + + /** + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#strictMode + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description + * Defines whether URLs should match trailing slashes, or not (the default behavior). + * + * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`. + * @returns {boolean} the current value of strictMode + */ + this.strictMode = function(value) { + if (isDefined(value)) + isStrictMode = value; + return isStrictMode; + }; + + /** + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description + * Sets the default behavior when generating or matching URLs with default parameter values. + * + * @param {string} value A string that defines the default parameter URL squashing behavior. + * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL + * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the + * parameter is surrounded by slashes, squash (remove) one slash from the URL + * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove) + * the parameter value from the URL and replace it with this string. + */ + this.defaultSquashPolicy = function(value) { + if (!isDefined(value)) return defaultSquashPolicy; + if (value !== true && value !== false && !isString(value)) + throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string"); + defaultSquashPolicy = value; + return value; + }; + + /** + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#compile + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description + * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern. + * + * @param {string} pattern The URL pattern. + * @param {Object} config The config object hash. + * @returns {UrlMatcher} The UrlMatcher. + */ + this.compile = function (pattern, config) { + return new UrlMatcher(pattern, extend(getDefaultConfig(), config)); + }; + + /** + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#isMatcher + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description + * Returns true if the specified object is a `UrlMatcher`, or false otherwise. + * + * @param {Object} object The object to perform the type check against. + * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by + * implementing all the same methods. + */ + this.isMatcher = function (o) { + if (!isObject(o)) return false; + var result = true; + + forEach(UrlMatcher.prototype, function(val, name) { + if (isFunction(val)) { + result = result && (isDefined(o[name]) && isFunction(o[name])); + } + }); + return result; + }; + + /** + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#type + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description + * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to + * generate URLs with typed parameters. + * + * @param {string} name The type name. + * @param {Object|Function} definition The type definition. See + * {@link ui.router.util.type:Type `Type`} for information on the values accepted. + * @param {Object|Function} definitionFn (optional) A function that is injected before the app + * runtime starts. The result of this function is merged into the existing `definition`. + * See {@link ui.router.util.type:Type `Type`} for information on the values accepted. + * + * @returns {Object} Returns `$urlMatcherFactoryProvider`. + * + * @example + * This is a simple example of a custom type that encodes and decodes items from an + * array, using the array index as the URL-encoded value: + * + *
+   * var list = ['John', 'Paul', 'George', 'Ringo'];
+   *
+   * $urlMatcherFactoryProvider.type('listItem', {
+   *   encode: function(item) {
+   *     // Represent the list item in the URL using its corresponding index
+   *     return list.indexOf(item);
+   *   },
+   *   decode: function(item) {
+   *     // Look up the list item by index
+   *     return list[parseInt(item, 10)];
+   *   },
+   *   is: function(item) {
+   *     // Ensure the item is valid by checking to see that it appears
+   *     // in the list
+   *     return list.indexOf(item) > -1;
+   *   }
+   * });
+   *
+   * $stateProvider.state('list', {
+   *   url: "/list/{item:listItem}",
+   *   controller: function($scope, $stateParams) {
+   *     console.log($stateParams.item);
+   *   }
+   * });
+   *
+   * // ...
+   *
+   * // Changes URL to '/list/3', logs "Ringo" to the console
+   * $state.go('list', { item: "Ringo" });
+   * 
+ * + * This is a more complex example of a type that relies on dependency injection to + * interact with services, and uses the parameter name from the URL to infer how to + * handle encoding and decoding parameter values: + * + *
+   * // Defines a custom type that gets a value from a service,
+   * // where each service gets different types of values from
+   * // a backend API:
+   * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
+   *
+   *   // Matches up services to URL parameter names
+   *   var services = {
+   *     user: Users,
+   *     post: Posts
+   *   };
+   *
+   *   return {
+   *     encode: function(object) {
+   *       // Represent the object in the URL using its unique ID
+   *       return object.id;
+   *     },
+   *     decode: function(value, key) {
+   *       // Look up the object by ID, using the parameter
+   *       // name (key) to call the correct service
+   *       return services[key].findById(value);
+   *     },
+   *     is: function(object, key) {
+   *       // Check that object is a valid dbObject
+   *       return angular.isObject(object) && object.id && services[key];
+   *     }
+   *     equals: function(a, b) {
+   *       // Check the equality of decoded objects by comparing
+   *       // their unique IDs
+   *       return a.id === b.id;
+   *     }
+   *   };
+   * });
+   *
+   * // In a config() block, you can then attach URLs with
+   * // type-annotated parameters:
+   * $stateProvider.state('users', {
+   *   url: "/users",
+   *   // ...
+   * }).state('users.item', {
+   *   url: "/{user:dbObject}",
+   *   controller: function($scope, $stateParams) {
+   *     // $stateParams.user will now be an object returned from
+   *     // the Users service
+   *   },
+   *   // ...
+   * });
+   * 
+ */ + this.type = function (name, definition, definitionFn) { + if (!isDefined(definition)) return $types[name]; + if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined."); + + $types[name] = new Type(extend({ name: name }, definition)); + if (definitionFn) { + typeQueue.push({ name: name, def: definitionFn }); + if (!enqueue) flushTypeQueue(); + } + return this; + }; + + // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s + function flushTypeQueue() { + while(typeQueue.length) { + var type = typeQueue.shift(); + if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime."); + angular.extend($types[type.name], injector.invoke(type.def)); + } + } + + // Register default types. Store them in the prototype of $types. + forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); }); + $types = inherit($types, {}); + + /* No need to document $get, since it returns this */ + this.$get = ['$injector', function ($injector) { + injector = $injector; + enqueue = false; + flushTypeQueue(); + + forEach(defaultTypes, function(type, name) { + if (!$types[name]) $types[name] = new Type(type); + }); + return this; + }]; + + this.Param = function Param(id, type, config, location) { + var self = this; + config = unwrapShorthand(config); + type = getType(config, type, location); + var arrayMode = getArrayMode(); + type = arrayMode ? type.$asArray(arrayMode, location === "search") : type; + if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined) + config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to "" + var isOptional = config.value !== undefined; + var squash = getSquashPolicy(config, isOptional); + var replace = getReplace(config, arrayMode, isOptional, squash); + + function unwrapShorthand(config) { + var keys = isObject(config) ? objectKeys(config) : []; + var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 && + indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1; + if (isShorthand) config = { value: config }; + config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; }; + return config; + } + + function getType(config, urlType, location) { + if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations."); + if (urlType) return urlType; + if (!config.type) return (location === "config" ? $types.any : $types.string); + + if (angular.isString(config.type)) + return $types[config.type]; + if (config.type instanceof Type) + return config.type; + return new Type(config.type); + } + + // array config: param name (param[]) overrides default settings. explicit config overrides param name. + function getArrayMode() { + var arrayDefaults = { array: (location === "search" ? "auto" : false) }; + var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {}; + return extend(arrayDefaults, arrayParamNomenclature, config).array; + } + + /** + * returns false, true, or the squash value to indicate the "default parameter url squash policy". + */ + function getSquashPolicy(config, isOptional) { + var squash = config.squash; + if (!isOptional || squash === false) return false; + if (!isDefined(squash) || squash == null) return defaultSquashPolicy; + if (squash === true || isString(squash)) return squash; + throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string"); + } + + function getReplace(config, arrayMode, isOptional, squash) { + var replace, configuredKeys, defaultPolicy = [ + { from: "", to: (isOptional || arrayMode ? undefined : "") }, + { from: null, to: (isOptional || arrayMode ? undefined : "") } + ]; + replace = isArray(config.replace) ? config.replace : []; + if (isString(squash)) + replace.push({ from: squash, to: undefined }); + configuredKeys = map(replace, function(item) { return item.from; } ); + return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace); + } + + /** + * [Internal] Get the default value of a parameter, which may be an injectable function. + */ + function $$getDefaultValue() { + if (!injector) throw new Error("Injectable functions cannot be called at configuration time"); + var defaultValue = injector.invoke(config.$$fn); + if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue)) + throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")"); + return defaultValue; + } + + /** + * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the + * default value, which may be the result of an injectable function. + */ + function $value(value) { + function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; } + function $replace(value) { + var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; }); + return replacement.length ? replacement[0] : value; + } + value = $replace(value); + return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value); + } + + function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; } + + extend(this, { + id: id, + type: type, + location: location, + array: arrayMode, + squash: squash, + replace: replace, + isOptional: isOptional, + value: $value, + dynamic: undefined, + config: config, + toString: toString + }); + }; + + function ParamSet(params) { + extend(this, params || {}); + } + + ParamSet.prototype = { + $$new: function() { + return inherit(this, extend(new ParamSet(), { $$parent: this})); + }, + $$keys: function () { + var keys = [], chain = [], parent = this, + ignore = objectKeys(ParamSet.prototype); + while (parent) { chain.push(parent); parent = parent.$$parent; } + chain.reverse(); + forEach(chain, function(paramset) { + forEach(objectKeys(paramset), function(key) { + if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key); + }); + }); + return keys; + }, + $$values: function(paramValues) { + var values = {}, self = this; + forEach(self.$$keys(), function(key) { + values[key] = self[key].value(paramValues && paramValues[key]); + }); + return values; + }, + $$equals: function(paramValues1, paramValues2) { + var equal = true, self = this; + forEach(self.$$keys(), function(key) { + var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key]; + if (!self[key].type.equals(left, right)) equal = false; + }); + return equal; + }, + $$validates: function $$validate(paramValues) { + var keys = this.$$keys(), i, param, rawVal, normalized, encoded; + for (i = 0; i < keys.length; i++) { + param = this[keys[i]]; + rawVal = paramValues[keys[i]]; + if ((rawVal === undefined || rawVal === null) && param.isOptional) + break; // There was no parameter value, but the param is optional + normalized = param.type.$normalize(rawVal); + if (!param.type.is(normalized)) + return false; // The value was not of the correct Type, and could not be decoded to the correct Type + encoded = param.type.encode(normalized); + if (angular.isString(encoded) && !param.type.pattern.exec(encoded)) + return false; // The value was of the correct type, but when encoded, did not match the Type's regexp + } + return true; + }, + $$parent: undefined + }; + + this.ParamSet = ParamSet; +} + +// Register as a provider so it's available to other providers +angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory); +angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]); + +/** + * @ngdoc object + * @name ui.router.router.$urlRouterProvider + * + * @requires ui.router.util.$urlMatcherFactoryProvider + * @requires $locationProvider + * + * @description + * `$urlRouterProvider` has the responsibility of watching `$location`. + * When `$location` changes it runs through a list of rules one by one until a + * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify + * a url in a state configuration. All urls are compiled into a UrlMatcher object. + * + * There are several methods on `$urlRouterProvider` that make it useful to use directly + * in your module config. + */ +$UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider']; +function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) { + var rules = [], otherwise = null, interceptDeferred = false, listener; + + // Returns a string that is a prefix of all strings matching the RegExp + function regExpPrefix(re) { + var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source); + return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : ''; + } + + // Interpolates matched values into a String.replace()-style pattern + function interpolate(pattern, match) { + return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) { + return match[what === '$' ? 0 : Number(what)]; + }); + } + + /** + * @ngdoc function + * @name ui.router.router.$urlRouterProvider#rule + * @methodOf ui.router.router.$urlRouterProvider + * + * @description + * Defines rules that are used by `$urlRouterProvider` to find matches for + * specific URLs. + * + * @example + *
+   * var app = angular.module('app', ['ui.router.router']);
+   *
+   * app.config(function ($urlRouterProvider) {
+   *   // Here's an example of how you might allow case insensitive urls
+   *   $urlRouterProvider.rule(function ($injector, $location) {
+   *     var path = $location.path(),
+   *         normalized = path.toLowerCase();
+   *
+   *     if (path !== normalized) {
+   *       return normalized;
+   *     }
+   *   });
+   * });
+   * 
+ * + * @param {function} rule Handler function that takes `$injector` and `$location` + * services as arguments. You can use them to return a valid path as a string. + * + * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance + */ + this.rule = function (rule) { + if (!isFunction(rule)) throw new Error("'rule' must be a function"); + rules.push(rule); + return this; + }; + + /** + * @ngdoc object + * @name ui.router.router.$urlRouterProvider#otherwise + * @methodOf ui.router.router.$urlRouterProvider + * + * @description + * Defines a path that is used when an invalid route is requested. + * + * @example + *
+   * var app = angular.module('app', ['ui.router.router']);
+   *
+   * app.config(function ($urlRouterProvider) {
+   *   // if the path doesn't match any of the urls you configured
+   *   // otherwise will take care of routing the user to the
+   *   // specified url
+   *   $urlRouterProvider.otherwise('/index');
+   *
+   *   // Example of using function rule as param
+   *   $urlRouterProvider.otherwise(function ($injector, $location) {
+   *     return '/a/valid/url';
+   *   });
+   * });
+   * 
+ * + * @param {string|function} rule The url path you want to redirect to or a function + * rule that returns the url path. The function version is passed two params: + * `$injector` and `$location` services, and must return a url string. + * + * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance + */ + this.otherwise = function (rule) { + if (isString(rule)) { + var redirect = rule; + rule = function () { return redirect; }; + } + else if (!isFunction(rule)) throw new Error("'rule' must be a function"); + otherwise = rule; + return this; + }; + + + function handleIfMatch($injector, handler, match) { + if (!match) return false; + var result = $injector.invoke(handler, handler, { $match: match }); + return isDefined(result) ? result : true; + } + + /** + * @ngdoc function + * @name ui.router.router.$urlRouterProvider#when + * @methodOf ui.router.router.$urlRouterProvider + * + * @description + * Registers a handler for a given url matching. + * + * If the handler is a string, it is + * treated as a redirect, and is interpolated according to the syntax of match + * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise). + * + * If the handler is a function, it is injectable. It gets invoked if `$location` + * matches. You have the option of inject the match object as `$match`. + * + * The handler can return + * + * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter` + * will continue trying to find another one that matches. + * - **string** which is treated as a redirect and passed to `$location.url()` + * - **void** or any **truthy** value tells `$urlRouter` that the url was handled. + * + * @example + *
+   * var app = angular.module('app', ['ui.router.router']);
+   *
+   * app.config(function ($urlRouterProvider) {
+   *   $urlRouterProvider.when($state.url, function ($match, $stateParams) {
+   *     if ($state.$current.navigable !== state ||
+   *         !equalForKeys($match, $stateParams) {
+   *      $state.transitionTo(state, $match, false);
+   *     }
+   *   });
+   * });
+   * 
+ * + * @param {string|object} what The incoming path that you want to redirect. + * @param {string|function} handler The path you want to redirect your user to. + */ + this.when = function (what, handler) { + var redirect, handlerIsString = isString(handler); + if (isString(what)) what = $urlMatcherFactory.compile(what); + + if (!handlerIsString && !isFunction(handler) && !isArray(handler)) + throw new Error("invalid 'handler' in when()"); + + var strategies = { + matcher: function (what, handler) { + if (handlerIsString) { + redirect = $urlMatcherFactory.compile(handler); + handler = ['$match', function ($match) { return redirect.format($match); }]; + } + return extend(function ($injector, $location) { + return handleIfMatch($injector, handler, what.exec($location.path(), $location.search())); + }, { + prefix: isString(what.prefix) ? what.prefix : '' + }); + }, + regex: function (what, handler) { + if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky"); + + if (handlerIsString) { + redirect = handler; + handler = ['$match', function ($match) { return interpolate(redirect, $match); }]; + } + return extend(function ($injector, $location) { + return handleIfMatch($injector, handler, what.exec($location.path())); + }, { + prefix: regExpPrefix(what) + }); + } + }; + + var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp }; + + for (var n in check) { + if (check[n]) return this.rule(strategies[n](what, handler)); + } + + throw new Error("invalid 'what' in when()"); + }; + + /** + * @ngdoc function + * @name ui.router.router.$urlRouterProvider#deferIntercept + * @methodOf ui.router.router.$urlRouterProvider + * + * @description + * Disables (or enables) deferring location change interception. + * + * If you wish to customize the behavior of syncing the URL (for example, if you wish to + * defer a transition but maintain the current URL), call this method at configuration time. + * Then, at run time, call `$urlRouter.listen()` after you have configured your own + * `$locationChangeSuccess` event handler. + * + * @example + *
+   * var app = angular.module('app', ['ui.router.router']);
+   *
+   * app.config(function ($urlRouterProvider) {
+   *
+   *   // Prevent $urlRouter from automatically intercepting URL changes;
+   *   // this allows you to configure custom behavior in between
+   *   // location changes and route synchronization:
+   *   $urlRouterProvider.deferIntercept();
+   *
+   * }).run(function ($rootScope, $urlRouter, UserService) {
+   *
+   *   $rootScope.$on('$locationChangeSuccess', function(e) {
+   *     // UserService is an example service for managing user state
+   *     if (UserService.isLoggedIn()) return;
+   *
+   *     // Prevent $urlRouter's default handler from firing
+   *     e.preventDefault();
+   *
+   *     UserService.handleLogin().then(function() {
+   *       // Once the user has logged in, sync the current URL
+   *       // to the router:
+   *       $urlRouter.sync();
+   *     });
+   *   });
+   *
+   *   // Configures $urlRouter's listener *after* your custom listener
+   *   $urlRouter.listen();
+   * });
+   * 
+ * + * @param {boolean} defer Indicates whether to defer location change interception. Passing + no parameter is equivalent to `true`. + */ + this.deferIntercept = function (defer) { + if (defer === undefined) defer = true; + interceptDeferred = defer; + }; + + /** + * @ngdoc object + * @name ui.router.router.$urlRouter + * + * @requires $location + * @requires $rootScope + * @requires $injector + * @requires $browser + * + * @description + * + */ + this.$get = $get; + $get.$inject = ['$location', '$rootScope', '$injector', '$browser', '$sniffer']; + function $get( $location, $rootScope, $injector, $browser, $sniffer) { + + var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl; + + function appendBasePath(url, isHtml5, absolute) { + if (baseHref === '/') return url; + if (isHtml5) return baseHref.slice(0, -1) + url; + if (absolute) return baseHref.slice(1) + url; + return url; + } + + // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree + function update(evt) { + if (evt && evt.defaultPrevented) return; + var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl; + lastPushedUrl = undefined; + // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573 + //if (ignoreUpdate) return true; + + function check(rule) { + var handled = rule($injector, $location); + + if (!handled) return false; + if (isString(handled)) $location.replace().url(handled); + return true; + } + var n = rules.length, i; + + for (i = 0; i < n; i++) { + if (check(rules[i])) return; + } + // always check otherwise last to allow dynamic updates to the set of rules + if (otherwise) check(otherwise); + } + + function listen() { + listener = listener || $rootScope.$on('$locationChangeSuccess', update); + return listener; + } + + if (!interceptDeferred) listen(); + + return { + /** + * @ngdoc function + * @name ui.router.router.$urlRouter#sync + * @methodOf ui.router.router.$urlRouter + * + * @description + * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`. + * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event, + * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed + * with the transition by calling `$urlRouter.sync()`. + * + * @example + *
+       * angular.module('app', ['ui.router'])
+       *   .run(function($rootScope, $urlRouter) {
+       *     $rootScope.$on('$locationChangeSuccess', function(evt) {
+       *       // Halt state change from even starting
+       *       evt.preventDefault();
+       *       // Perform custom logic
+       *       var meetsRequirement = ...
+       *       // Continue with the update and state transition if logic allows
+       *       if (meetsRequirement) $urlRouter.sync();
+       *     });
+       * });
+       * 
+ */ + sync: function() { + update(); + }, + + listen: function() { + return listen(); + }, + + update: function(read) { + if (read) { + location = $location.url(); + return; + } + if ($location.url() === location) return; + + $location.url(location); + $location.replace(); + }, + + push: function(urlMatcher, params, options) { + var url = urlMatcher.format(params || {}); + + // Handle the special hash param, if needed + if (url !== null && params && params['#']) { + url += '#' + params['#']; + } + + $location.url(url); + lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined; + if (options && options.replace) $location.replace(); + }, + + /** + * @ngdoc function + * @name ui.router.router.$urlRouter#href + * @methodOf ui.router.router.$urlRouter + * + * @description + * A URL generation method that returns the compiled URL for a given + * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters. + * + * @example + *
+       * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
+       *   person: "bob"
+       * });
+       * // $bob == "/about/bob";
+       * 
+ * + * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate. + * @param {object=} params An object of parameter values to fill the matcher's required parameters. + * @param {object=} options Options object. The options are: + * + * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". + * + * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher` + */ + href: function(urlMatcher, params, options) { + if (!urlMatcher.validates(params)) return null; + + var isHtml5 = $locationProvider.html5Mode(); + if (angular.isObject(isHtml5)) { + isHtml5 = isHtml5.enabled; + } + + isHtml5 = isHtml5 && $sniffer.history; + + var url = urlMatcher.format(params); + options = options || {}; + + if (!isHtml5 && url !== null) { + url = "#" + $locationProvider.hashPrefix() + url; + } + + // Handle special hash param, if needed + if (url !== null && params && params['#']) { + url += '#' + params['#']; + } + + url = appendBasePath(url, isHtml5, options.absolute); + + if (!options.absolute || !url) { + return url; + } + + var slash = (!isHtml5 && url ? '/' : ''), port = $location.port(); + port = (port === 80 || port === 443 ? '' : ':' + port); + + return [$location.protocol(), '://', $location.host(), port, slash, url].join(''); + } + }; + } +} + +angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider); + +/** + * @ngdoc object + * @name ui.router.state.$stateProvider + * + * @requires ui.router.router.$urlRouterProvider + * @requires ui.router.util.$urlMatcherFactoryProvider + * + * @description + * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely + * on state. + * + * A state corresponds to a "place" in the application in terms of the overall UI and + * navigation. A state describes (via the controller / template / view properties) what + * the UI looks like and does at that place. + * + * States often have things in common, and the primary way of factoring out these + * commonalities in this model is via the state hierarchy, i.e. parent/child states aka + * nested states. + * + * The `$stateProvider` provides interfaces to declare these states for your app. + */ +$StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider']; +function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { + + var root, states = {}, $state, queue = {}, abstractKey = 'abstract'; + + // Builds state properties from definition passed to registerState() + var stateBuilder = { + + // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined. + // state.children = []; + // if (parent) parent.children.push(state); + parent: function(state) { + if (isDefined(state.parent) && state.parent) return findState(state.parent); + // regex matches any valid composite state name + // would match "contact.list" but not "contacts" + var compositeName = /^(.+)\.[^.]+$/.exec(state.name); + return compositeName ? findState(compositeName[1]) : root; + }, + + // inherit 'data' from parent and override by own values (if any) + data: function(state) { + if (state.parent && state.parent.data) { + state.data = state.self.data = inherit(state.parent.data, state.data); + } + return state.data; + }, + + // Build a URLMatcher if necessary, either via a relative or absolute URL + url: function(state) { + var url = state.url, config = { params: state.params || {} }; + + if (isString(url)) { + if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config); + return (state.parent.navigable || root).url.concat(url, config); + } + + if (!url || $urlMatcherFactory.isMatcher(url)) return url; + throw new Error("Invalid url '" + url + "' in state '" + state + "'"); + }, + + // Keep track of the closest ancestor state that has a URL (i.e. is navigable) + navigable: function(state) { + return state.url ? state : (state.parent ? state.parent.navigable : null); + }, + + // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params + ownParams: function(state) { + var params = state.url && state.url.params || new $$UMFP.ParamSet(); + forEach(state.params || {}, function(config, id) { + if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config"); + }); + return params; + }, + + // Derive parameters for this state and ensure they're a super-set of parent's parameters + params: function(state) { + var ownParams = pick(state.ownParams, state.ownParams.$$keys()); + return state.parent && state.parent.params ? extend(state.parent.params.$$new(), ownParams) : new $$UMFP.ParamSet(); + }, + + // If there is no explicit multi-view configuration, make one up so we don't have + // to handle both cases in the view directive later. Note that having an explicit + // 'views' property will mean the default unnamed view properties are ignored. This + // is also a good time to resolve view names to absolute names, so everything is a + // straight lookup at link time. + views: function(state) { + var views = {}; + + forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) { + if (name.indexOf('@') < 0) name += '@' + state.parent.name; + view.resolveAs = view.resolveAs || state.resolveAs || '$resolve'; + views[name] = view; + }); + return views; + }, + + // Keep a full path from the root down to this state as this is needed for state activation. + path: function(state) { + return state.parent ? state.parent.path.concat(state) : []; // exclude root from path + }, + + // Speed up $state.contains() as it's used a lot + includes: function(state) { + var includes = state.parent ? extend({}, state.parent.includes) : {}; + includes[state.name] = true; + return includes; + }, + + $delegates: {} + }; + + function isRelative(stateName) { + return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0; + } + + function findState(stateOrName, base) { + if (!stateOrName) return undefined; + + var isStr = isString(stateOrName), + name = isStr ? stateOrName : stateOrName.name, + path = isRelative(name); + + if (path) { + if (!base) throw new Error("No reference point given for path '" + name + "'"); + base = findState(base); + + var rel = name.split("."), i = 0, pathLength = rel.length, current = base; + + for (; i < pathLength; i++) { + if (rel[i] === "" && i === 0) { + current = base; + continue; + } + if (rel[i] === "^") { + if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'"); + current = current.parent; + continue; + } + break; + } + rel = rel.slice(i).join("."); + name = current.name + (current.name && rel ? "." : "") + rel; + } + var state = states[name]; + + if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) { + return state; + } + return undefined; + } + + function queueState(parentName, state) { + if (!queue[parentName]) { + queue[parentName] = []; + } + queue[parentName].push(state); + } + + function flushQueuedChildren(parentName) { + var queued = queue[parentName] || []; + while(queued.length) { + registerState(queued.shift()); + } + } + + function registerState(state) { + // Wrap a new object around the state so we can store our private details easily. + state = inherit(state, { + self: state, + resolve: state.resolve || {}, + toString: function() { return this.name; } + }); + + var name = state.name; + if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name"); + if (states.hasOwnProperty(name)) throw new Error("State '" + name + "' is already defined"); + + // Get parent name + var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.')) + : (isString(state.parent)) ? state.parent + : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name + : ''; + + // If parent is not registered yet, add state to queue and register later + if (parentName && !states[parentName]) { + return queueState(parentName, state.self); + } + + for (var key in stateBuilder) { + if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]); + } + states[name] = state; + + // Register the state in the global state list and with $urlRouter if necessary. + if (!state[abstractKey] && state.url) { + $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) { + if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) { + $state.transitionTo(state, $match, { inherit: true, location: false }); + } + }]); + } + + // Register any queued children + flushQueuedChildren(name); + + return state; + } + + // Checks text to see if it looks like a glob. + function isGlob (text) { + return text.indexOf('*') > -1; + } + + // Returns true if glob matches current $state name. + function doesStateMatchGlob (glob) { + var globSegments = glob.split('.'), + segments = $state.$current.name.split('.'); + + //match single stars + for (var i = 0, l = globSegments.length; i < l; i++) { + if (globSegments[i] === '*') { + segments[i] = '*'; + } + } + + //match greedy starts + if (globSegments[0] === '**') { + segments = segments.slice(indexOf(segments, globSegments[1])); + segments.unshift('**'); + } + //match greedy ends + if (globSegments[globSegments.length - 1] === '**') { + segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE); + segments.push('**'); + } + + if (globSegments.length != segments.length) { + return false; + } + + return segments.join('') === globSegments.join(''); + } + + + // Implicit root state that is always active + root = registerState({ + name: '', + url: '^', + views: null, + 'abstract': true + }); + root.navigable = null; + + + /** + * @ngdoc function + * @name ui.router.state.$stateProvider#decorator + * @methodOf ui.router.state.$stateProvider + * + * @description + * Allows you to extend (carefully) or override (at your own peril) the + * `stateBuilder` object used internally by `$stateProvider`. This can be used + * to add custom functionality to ui-router, for example inferring templateUrl + * based on the state name. + * + * When passing only a name, it returns the current (original or decorated) builder + * function that matches `name`. + * + * The builder functions that can be decorated are listed below. Though not all + * necessarily have a good use case for decoration, that is up to you to decide. + * + * In addition, users can attach custom decorators, which will generate new + * properties within the state's internal definition. There is currently no clear + * use-case for this beyond accessing internal states (i.e. $state.$current), + * however, expect this to become increasingly relevant as we introduce additional + * meta-programming features. + * + * **Warning**: Decorators should not be interdependent because the order of + * execution of the builder functions in non-deterministic. Builder functions + * should only be dependent on the state definition object and super function. + * + * + * Existing builder functions and current return values: + * + * - **parent** `{object}` - returns the parent state object. + * - **data** `{object}` - returns state data, including any inherited data that is not + * overridden by own values (if any). + * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher} + * or `null`. + * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is + * navigable). + * - **params** `{object}` - returns an array of state params that are ensured to + * be a super-set of parent's params. + * - **views** `{object}` - returns a views object where each key is an absolute view + * name (i.e. "viewName@stateName") and each value is the config object + * (template, controller) for the view. Even when you don't use the views object + * explicitly on a state config, one is still created for you internally. + * So by decorating this builder function you have access to decorating template + * and controller properties. + * - **ownParams** `{object}` - returns an array of params that belong to the state, + * not including any params defined by ancestor states. + * - **path** `{string}` - returns the full path from the root down to this state. + * Needed for state activation. + * - **includes** `{object}` - returns an object that includes every state that + * would pass a `$state.includes()` test. + * + * @example + *
+   * // Override the internal 'views' builder with a function that takes the state
+   * // definition, and a reference to the internal function being overridden:
+   * $stateProvider.decorator('views', function (state, parent) {
+   *   var result = {},
+   *       views = parent(state);
+   *
+   *   angular.forEach(views, function (config, name) {
+   *     var autoName = (state.name + '.' + name).replace('.', '/');
+   *     config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
+   *     result[name] = config;
+   *   });
+   *   return result;
+   * });
+   *
+   * $stateProvider.state('home', {
+   *   views: {
+   *     'contact.list': { controller: 'ListController' },
+   *     'contact.item': { controller: 'ItemController' }
+   *   }
+   * });
+   *
+   * // ...
+   *
+   * $state.go('home');
+   * // Auto-populates list and item views with /partials/home/contact/list.html,
+   * // and /partials/home/contact/item.html, respectively.
+   * 
+ * + * @param {string} name The name of the builder function to decorate. + * @param {object} func A function that is responsible for decorating the original + * builder function. The function receives two parameters: + * + * - `{object}` - state - The state config object. + * - `{object}` - super - The original builder function. + * + * @return {object} $stateProvider - $stateProvider instance + */ + this.decorator = decorator; + function decorator(name, func) { + /*jshint validthis: true */ + if (isString(name) && !isDefined(func)) { + return stateBuilder[name]; + } + if (!isFunction(func) || !isString(name)) { + return this; + } + if (stateBuilder[name] && !stateBuilder.$delegates[name]) { + stateBuilder.$delegates[name] = stateBuilder[name]; + } + stateBuilder[name] = func; + return this; + } + + /** + * @ngdoc function + * @name ui.router.state.$stateProvider#state + * @methodOf ui.router.state.$stateProvider + * + * @description + * Registers a state configuration under a given state name. The stateConfig object + * has the following acceptable properties. + * + * @param {string} name A unique state name, e.g. "home", "about", "contacts". + * To create a parent/child state use a dot, e.g. "about.sales", "home.newest". + * @param {object} stateConfig State configuration object. + * @param {string|function=} stateConfig.template + * + * html template as a string or a function that returns + * an html template as a string which should be used by the uiView directives. This property + * takes precedence over templateUrl. + * + * If `template` is a function, it will be called with the following parameters: + * + * - {array.<object>} - state parameters extracted from the current $location.path() by + * applying the current state + * + *
template:
+   *   "

inline template definition

" + + * "
"
+ *
template: function(params) {
+   *       return "

generated template

"; }
+ *
+ * + * @param {string|function=} stateConfig.templateUrl + * + * + * path or function that returns a path to an html + * template that should be used by uiView. + * + * If `templateUrl` is a function, it will be called with the following parameters: + * + * - {array.<object>} - state parameters extracted from the current $location.path() by + * applying the current state + * + *
templateUrl: "home.html"
+ *
templateUrl: function(params) {
+   *     return myTemplates[params.pageId]; }
+ * + * @param {function=} stateConfig.templateProvider + * + * Provider function that returns HTML content string. + *
 templateProvider:
+   *       function(MyTemplateService, params) {
+   *         return MyTemplateService.getTemplate(params.pageId);
+   *       }
+ * + * @param {string|function=} stateConfig.controller + * + * + * Controller fn that should be associated with newly + * related scope or the name of a registered controller if passed as a string. + * Optionally, the ControllerAs may be declared here. + *
controller: "MyRegisteredController"
+ *
controller:
+   *     "MyRegisteredController as fooCtrl"}
+ *
controller: function($scope, MyService) {
+   *     $scope.data = MyService.getData(); }
+ * + * @param {function=} stateConfig.controllerProvider + * + * + * Injectable provider function that returns the actual controller or string. + *
controllerProvider:
+   *   function(MyResolveData) {
+   *     if (MyResolveData.foo)
+   *       return "FooCtrl"
+   *     else if (MyResolveData.bar)
+   *       return "BarCtrl";
+   *     else return function($scope) {
+   *       $scope.baz = "Qux";
+   *     }
+   *   }
+ * + * @param {string=} stateConfig.controllerAs + * + * + * A controller alias name. If present the controller will be + * published to scope under the controllerAs name. + *
controllerAs: "myCtrl"
+ * + * @param {string|object=} stateConfig.parent + * + * Optionally specifies the parent state of this state. + * + *
parent: 'parentState'
+ *
parent: parentState // JS variable
+ * + * @param {object=} stateConfig.resolve + * + * + * An optional map<string, function> 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 before the controller is instantiated. + * If all the promises are resolved successfully, the $stateChangeSuccess event is fired + * and the values of the resolved promises are injected into any controllers that reference them. + * If any of the promises are rejected the $stateChangeError event is fired. + * + * The map object is: + * + * - key - {string}: name of dependency to be injected into controller + * - factory - {string|function}: If string then it is alias for service. Otherwise if function, + * it is injected and return value it treated as dependency. If result is a promise, it is + * resolved before its value is injected into controller. + * + *
resolve: {
+   *     myResolve1:
+   *       function($http, $stateParams) {
+   *         return $http.get("/api/foos/"+stateParams.fooID);
+   *       }
+   *     }
+ * + * @param {string=} stateConfig.url + * + * + * A url fragment with optional parameters. When a state is navigated or + * transitioned to, the `$stateParams` service will be populated with any + * parameters that were passed. + * + * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for + * more details on acceptable patterns ) + * + * examples: + *
url: "/home"
+   * url: "/users/:userid"
+   * url: "/books/{bookid:[a-zA-Z_-]}"
+   * url: "/books/{categoryid:int}"
+   * url: "/books/{publishername:string}/{categoryid:int}"
+   * url: "/messages?before&after"
+   * url: "/messages?{before:date}&{after:date}"
+   * url: "/messages/:mailboxid?{before:date}&{after:date}"
+   * 
+ * + * @param {object=} stateConfig.views + * + * an optional map<string, object> which defined multiple views, or targets views + * manually/explicitly. + * + * Examples: + * + * Targets three named `ui-view`s in the parent state's template + *
views: {
+   *     header: {
+   *       controller: "headerCtrl",
+   *       templateUrl: "header.html"
+   *     }, body: {
+   *       controller: "bodyCtrl",
+   *       templateUrl: "body.html"
+   *     }, footer: {
+   *       controller: "footCtrl",
+   *       templateUrl: "footer.html"
+   *     }
+   *   }
+ * + * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template. + *
views: {
+   *     'header@top': {
+   *       controller: "msgHeaderCtrl",
+   *       templateUrl: "msgHeader.html"
+   *     }, 'body': {
+   *       controller: "messagesCtrl",
+   *       templateUrl: "messages.html"
+   *     }
+   *   }
+ * + * @param {boolean=} [stateConfig.abstract=false] + * + * An abstract state will never be directly activated, + * but can provide inherited properties to its common children states. + *
abstract: true
+ * + * @param {function=} stateConfig.onEnter + * + * + * Callback function for when a state is entered. Good way + * to trigger an action or dispatch an event, such as opening a dialog. + * If minifying your scripts, make sure to explicitly annotate this function, + * because it won't be automatically annotated by your build tools. + * + *
onEnter: function(MyService, $stateParams) {
+   *     MyService.foo($stateParams.myParam);
+   * }
+ * + * @param {function=} stateConfig.onExit + * + * + * Callback function for when a state is exited. Good way to + * trigger an action or dispatch an event, such as opening a dialog. + * If minifying your scripts, make sure to explicitly annotate this function, + * because it won't be automatically annotated by your build tools. + * + *
onExit: function(MyService, $stateParams) {
+   *     MyService.cleanup($stateParams.myParam);
+   * }
+ * + * @param {boolean=} [stateConfig.reloadOnSearch=true] + * + * + * If `false`, will not retrigger the same state + * just because a search/query parameter has changed (via $location.search() or $location.hash()). + * Useful for when you'd like to modify $location.search() without triggering a reload. + *
reloadOnSearch: false
+ * + * @param {object=} stateConfig.data + * + * + * Arbitrary data object, useful for custom configuration. The parent state's `data` is + * prototypally inherited. In other words, adding a data property to a state adds it to + * the entire subtree via prototypal inheritance. + * + *
data: {
+   *     requiredRole: 'foo'
+   * } 
+ * + * @param {object=} stateConfig.params + * + * + * A map which optionally configures parameters declared in the `url`, or + * defines additional non-url parameters. For each parameter being + * configured, add a configuration object keyed to the name of the parameter. + * + * Each parameter configuration object may contain the following properties: + * + * - ** value ** - {object|function=}: specifies the default value for this + * parameter. This implicitly sets this parameter as optional. + * + * When UI-Router routes to a state and no value is + * specified for this parameter in the URL or transition, the + * default value will be used instead. If `value` is a function, + * it will be injected and invoked, and the return value used. + * + * *Note*: `undefined` is treated as "no default value" while `null` + * is treated as "the default value is `null`". + * + * *Shorthand*: If you only need to configure the default value of the + * parameter, you may use a shorthand syntax. In the **`params`** + * map, instead mapping the param name to a full parameter configuration + * object, simply set map it to the default parameter value, e.g.: + * + *
// define a parameter's default value
+   * params: {
+   *     param1: { value: "defaultValue" }
+   * }
+   * // shorthand default values
+   * params: {
+   *     param1: "defaultValue",
+   *     param2: "param2Default"
+   * }
+ * + * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be + * treated as an array of values. If you specified a Type, the value will be + * treated as an array of the specified Type. Note: query parameter values + * default to a special `"auto"` mode. + * + * For query parameters in `"auto"` mode, if multiple values for a single parameter + * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values + * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if + * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single + * value (e.g.: `{ foo: '1' }`). + * + *
params: {
+   *     param1: { array: true }
+   * }
+ * + * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when + * the current parameter value is the same as the default value. If `squash` is not set, it uses the + * configured default squash policy. + * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`}) + * + * There are three squash settings: + * + * - false: The parameter's default value is not squashed. It is encoded and included in the URL + * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed + * by slashes in the state's `url` declaration, then one of those slashes are omitted. + * This can allow for cleaner looking URLs. + * - `""`: The parameter's default value is replaced with an arbitrary placeholder of your choice. + * + *
params: {
+   *     param1: {
+   *       value: "defaultId",
+   *       squash: true
+   * } }
+   * // squash "defaultValue" to "~"
+   * params: {
+   *     param1: {
+   *       value: "defaultValue",
+   *       squash: "~"
+   * } }
+   * 
+ * + * + * @example + *
+   * // Some state name examples
+   *
+   * // stateName can be a single top-level name (must be unique).
+   * $stateProvider.state("home", {});
+   *
+   * // Or it can be a nested state name. This state is a child of the
+   * // above "home" state.
+   * $stateProvider.state("home.newest", {});
+   *
+   * // Nest states as deeply as needed.
+   * $stateProvider.state("home.newest.abc.xyz.inception", {});
+   *
+   * // state() returns $stateProvider, so you can chain state declarations.
+   * $stateProvider
+   *   .state("home", {})
+   *   .state("about", {})
+   *   .state("contacts", {});
+   * 
+ * + */ + this.state = state; + function state(name, definition) { + /*jshint validthis: true */ + if (isObject(name)) definition = name; + else definition.name = name; + registerState(definition); + return this; + } + + /** + * @ngdoc object + * @name ui.router.state.$state + * + * @requires $rootScope + * @requires $q + * @requires ui.router.state.$view + * @requires $injector + * @requires ui.router.util.$resolve + * @requires ui.router.state.$stateParams + * @requires ui.router.router.$urlRouter + * + * @property {object} params A param object, e.g. {sectionId: section.id)}, that + * you'd like to test against the current active state. + * @property {object} current A reference to the state's config object. However + * you passed it in. Useful for accessing custom data. + * @property {object} transition Currently pending transition. A promise that'll + * resolve or reject. + * + * @description + * `$state` service is responsible for representing states as well as transitioning + * between them. It also provides interfaces to ask for current state or even states + * you're coming from. + */ + this.$get = $get; + $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory']; + function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) { + + var TransitionSupersededError = new Error('transition superseded'); + + var TransitionSuperseded = silenceUncaughtInPromise($q.reject(TransitionSupersededError)); + var TransitionPrevented = silenceUncaughtInPromise($q.reject(new Error('transition prevented'))); + var TransitionAborted = silenceUncaughtInPromise($q.reject(new Error('transition aborted'))); + var TransitionFailed = silenceUncaughtInPromise($q.reject(new Error('transition failed'))); + + // Handles the case where a state which is the target of a transition is not found, and the user + // can optionally retry or defer the transition + function handleRedirect(redirect, state, params, options) { + /** + * @ngdoc event + * @name ui.router.state.$state#$stateNotFound + * @eventOf ui.router.state.$state + * @eventType broadcast on root scope + * @description + * Fired when a requested state **cannot be found** using the provided state name during transition. + * The event is broadcast allowing any handlers a single chance to deal with the error (usually by + * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler, + * you can see its three properties in the example. You can use `event.preventDefault()` to abort the + * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value. + * + * @param {Object} event Event object. + * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties. + * @param {State} fromState Current state object. + * @param {Object} fromParams Current state params. + * + * @example + * + *
+       * // somewhere, assume lazy.state has not been defined
+       * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
+       *
+       * // somewhere else
+       * $scope.$on('$stateNotFound',
+       * function(event, unfoundState, fromState, fromParams){
+       *     console.log(unfoundState.to); // "lazy.state"
+       *     console.log(unfoundState.toParams); // {a:1, b:2}
+       *     console.log(unfoundState.options); // {inherit:false} + default options
+       * })
+       * 
+ */ + var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params); + + if (evt.defaultPrevented) { + $urlRouter.update(); + return TransitionAborted; + } + + if (!evt.retry) { + return null; + } + + // Allow the handler to return a promise to defer state lookup retry + if (options.$retry) { + $urlRouter.update(); + return TransitionFailed; + } + var retryTransition = $state.transition = $q.when(evt.retry); + + retryTransition.then(function() { + if (retryTransition !== $state.transition) { + $rootScope.$broadcast('$stateChangeCancel', redirect.to, redirect.toParams, state, params); + return TransitionSuperseded; + } + redirect.options.$retry = true; + return $state.transitionTo(redirect.to, redirect.toParams, redirect.options); + }, function() { + return TransitionAborted; + }); + $urlRouter.update(); + + return retryTransition; + } + + root.locals = { resolve: null, globals: { $stateParams: {} } }; + + $state = { + params: {}, + current: root.self, + $current: root, + transition: null + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#reload + * @methodOf ui.router.state.$state + * + * @description + * A method that force reloads the current state. All resolves are re-resolved, + * controllers reinstantiated, and events re-fired. + * + * @example + *
+     * var app angular.module('app', ['ui.router']);
+     *
+     * app.controller('ctrl', function ($scope, $state) {
+     *   $scope.reload = function(){
+     *     $state.reload();
+     *   }
+     * });
+     * 
+ * + * `reload()` is just an alias for: + *
+     * $state.transitionTo($state.current, $stateParams, { 
+     *   reload: true, inherit: false, notify: true
+     * });
+     * 
+ * + * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved. + * @example + *
+     * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item' 
+     * //and current state is 'contacts.detail.item'
+     * var app angular.module('app', ['ui.router']);
+     *
+     * app.controller('ctrl', function ($scope, $state) {
+     *   $scope.reload = function(){
+     *     //will reload 'contact.detail' and 'contact.detail.item' states
+     *     $state.reload('contact.detail');
+     *   }
+     * });
+     * 
+ * + * `reload()` is just an alias for: + *
+     * $state.transitionTo($state.current, $stateParams, { 
+     *   reload: true, inherit: false, notify: true
+     * });
+     * 
+ + * @returns {promise} A promise representing the state of the new transition. See + * {@link ui.router.state.$state#methods_go $state.go}. + */ + $state.reload = function reload(state) { + return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true}); + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#go + * @methodOf ui.router.state.$state + * + * @description + * Convenience method for transitioning to a new state. `$state.go` calls + * `$state.transitionTo` internally but automatically sets options to + * `{ location: true, inherit: true, relative: $state.$current, notify: true }`. + * This allows you to easily use an absolute or relative to path and specify + * only the parameters you'd like to update (while letting unspecified parameters + * inherit from the currently active ancestor states). + * + * @example + *
+     * var app = angular.module('app', ['ui.router']);
+     *
+     * app.controller('ctrl', function ($scope, $state) {
+     *   $scope.changeState = function () {
+     *     $state.go('contact.detail');
+     *   };
+     * });
+     * 
+ * + * + * @param {string} to Absolute state name or relative state path. Some examples: + * + * - `$state.go('contact.detail')` - will go to the `contact.detail` state + * - `$state.go('^')` - will go to a parent state + * - `$state.go('^.sibling')` - will go to a sibling state + * - `$state.go('.child.grandchild')` - will go to grandchild state + * + * @param {object=} params A map of the parameters that will be sent to the state, + * will populate $stateParams. Any parameters that are not specified will be inherited from currently + * defined parameters. Only parameters specified in the state definition can be overridden, new + * parameters will be ignored. This allows, for example, going to a sibling state that shares parameters + * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e. + * transitioning to a sibling will get you the parameters for all parents, transitioning to a child + * will get you all current parameters, etc. + * @param {object=} options Options object. The options are: + * + * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` + * will not. If string, must be `"replace"`, which will update url and also replace last history record. + * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. + * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), + * defines which state to be relative from. + * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. + * - **`reload`** (v0.2.5) - {boolean=false|string|object}, If `true` will force transition even if no state or params + * have changed. It will reload the resolves and views of the current state and parent states. + * If `reload` is a string (or state object), the state object is fetched (by name, or object reference); and \ + * the transition reloads the resolves and views for that matched state, and all its children states. + * + * @returns {promise} A promise representing the state of the new transition. + * + * Possible success values: + * + * - $state.current + * + *
Possible rejection values: + * + * - 'transition superseded' - when a newer transition has been started after this one + * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener + * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or + * when a `$stateNotFound` `event.retry` promise errors. + * - 'transition failed' - when a state has been unsuccessfully found after 2 tries. + * - *resolve error* - when an error has occurred with a `resolve` + * + */ + $state.go = function go(to, params, options) { + return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options)); + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#transitionTo + * @methodOf ui.router.state.$state + * + * @description + * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go} + * uses `transitionTo` internally. `$state.go` is recommended in most situations. + * + * @example + *
+     * var app = angular.module('app', ['ui.router']);
+     *
+     * app.controller('ctrl', function ($scope, $state) {
+     *   $scope.changeState = function () {
+     *     $state.transitionTo('contact.detail');
+     *   };
+     * });
+     * 
+ * + * @param {string} to State name. + * @param {object=} toParams A map of the parameters that will be sent to the state, + * will populate $stateParams. + * @param {object=} options Options object. The options are: + * + * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` + * will not. If string, must be `"replace"`, which will update url and also replace last history record. + * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url. + * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'), + * defines which state to be relative from. + * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. + * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params + * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd + * use this when you want to force a reload when *everything* is the same, including search params. + * if String, then will reload the state with the name given in reload, and any children. + * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children. + * + * @returns {promise} A promise representing the state of the new transition. See + * {@link ui.router.state.$state#methods_go $state.go}. + */ + $state.transitionTo = function transitionTo(to, toParams, options) { + toParams = toParams || {}; + options = extend({ + location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false + }, options || {}); + + var from = $state.$current, fromParams = $state.params, fromPath = from.path; + var evt, toState = findState(to, options.relative); + + // Store the hash param for later (since it will be stripped out by various methods) + var hash = toParams['#']; + + if (!isDefined(toState)) { + var redirect = { to: to, toParams: toParams, options: options }; + var redirectResult = handleRedirect(redirect, from.self, fromParams, options); + + if (redirectResult) { + return redirectResult; + } + + // Always retry once if the $stateNotFound was not prevented + // (handles either redirect changed or state lazy-definition) + to = redirect.to; + toParams = redirect.toParams; + options = redirect.options; + toState = findState(to, options.relative); + + if (!isDefined(toState)) { + if (!options.relative) throw new Error("No such state '" + to + "'"); + throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'"); + } + } + if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'"); + if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState); + if (!toState.params.$$validates(toParams)) return TransitionFailed; + + toParams = toState.params.$$values(toParams); + to = toState; + + var toPath = to.path; + + // Starting from the root of the path, keep all levels that haven't changed + var keep = 0, state = toPath[keep], locals = root.locals, toLocals = []; + + if (!options.reload) { + while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) { + locals = toLocals[keep] = state.locals; + keep++; + state = toPath[keep]; + } + } else if (isString(options.reload) || isObject(options.reload)) { + if (isObject(options.reload) && !options.reload.name) { + throw new Error('Invalid reload state object'); + } + + var reloadState = options.reload === true ? fromPath[0] : findState(options.reload); + if (options.reload && !reloadState) { + throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'"); + } + + while (state && state === fromPath[keep] && state !== reloadState) { + locals = toLocals[keep] = state.locals; + keep++; + state = toPath[keep]; + } + } + + // If we're going to the same state and all locals are kept, we've got nothing to do. + // But clear 'transition', as we still want to cancel any other pending transitions. + // TODO: We may not want to bump 'transition' if we're called from a location change + // that we've initiated ourselves, because we might accidentally abort a legitimate + // transition initiated from code? + if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) { + if (hash) toParams['#'] = hash; + $state.params = toParams; + copy($state.params, $stateParams); + copy(filterByKeys(to.params.$$keys(), $stateParams), to.locals.globals.$stateParams); + if (options.location && to.navigable && to.navigable.url) { + $urlRouter.push(to.navigable.url, toParams, { + $$avoidResync: true, replace: options.location === 'replace' + }); + $urlRouter.update(true); + } + $state.transition = null; + return $q.when($state.current); + } + + // Filter parameters before we pass them to event handlers etc. + toParams = filterByKeys(to.params.$$keys(), toParams || {}); + + // Re-add the saved hash before we start returning things or broadcasting $stateChangeStart + if (hash) toParams['#'] = hash; + + // Broadcast start event and cancel the transition if requested + if (options.notify) { + /** + * @ngdoc event + * @name ui.router.state.$state#$stateChangeStart + * @eventOf ui.router.state.$state + * @eventType broadcast on root scope + * @description + * Fired when the state transition **begins**. You can use `event.preventDefault()` + * to prevent the transition from happening and then the transition promise will be + * rejected with a `'transition prevented'` value. + * + * @param {Object} event Event object. + * @param {State} toState The state being transitioned to. + * @param {Object} toParams The params supplied to the `toState`. + * @param {State} fromState The current state, pre-transition. + * @param {Object} fromParams The params supplied to the `fromState`. + * + * @example + * + *
+         * $rootScope.$on('$stateChangeStart',
+         * function(event, toState, toParams, fromState, fromParams){
+         *     event.preventDefault();
+         *     // transitionTo() promise will be rejected with
+         *     // a 'transition prevented' error
+         * })
+         * 
+ */ + if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams, options).defaultPrevented) { + $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); + //Don't update and resync url if there's been a new transition started. see issue #2238, #600 + if ($state.transition == null) $urlRouter.update(); + return TransitionPrevented; + } + } + + // Resolve locals for the remaining states, but don't update any global state just + // yet -- if anything fails to resolve the current state needs to remain untouched. + // We also set up an inheritance chain for the locals here. This allows the view directive + // to quickly look up the correct definition for each view in the current state. Even + // though we create the locals object itself outside resolveState(), it is initially + // empty and gets filled asynchronously. We need to keep track of the promise for the + // (fully resolved) current locals, and pass this down the chain. + var resolved = $q.when(locals); + + for (var l = keep; l < toPath.length; l++, state = toPath[l]) { + locals = toLocals[l] = inherit(locals); + resolved = resolveState(state, toParams, state === to, resolved, locals, options); + } + + // Once everything is resolved, we are ready to perform the actual transition + // and return a promise for the new state. We also keep track of what the + // current promise is, so that we can detect overlapping transitions and + // keep only the outcome of the last transition. + var transition = $state.transition = resolved.then(function () { + var l, entering, exiting; + + if ($state.transition !== transition) { + $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); + return TransitionSuperseded; + } + + // Exit 'from' states not kept + for (l = fromPath.length - 1; l >= keep; l--) { + exiting = fromPath[l]; + if (exiting.self.onExit) { + $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals); + } + exiting.locals = null; + } + + // Enter 'to' states not kept + for (l = keep; l < toPath.length; l++) { + entering = toPath[l]; + entering.locals = toLocals[l]; + if (entering.self.onEnter) { + $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals); + } + } + + // Run it again, to catch any transitions in callbacks + if ($state.transition !== transition) { + $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); + return TransitionSuperseded; + } + + // Update globals in $state + $state.$current = to; + $state.current = to.self; + $state.params = toParams; + copy($state.params, $stateParams); + $state.transition = null; + + if (options.location && to.navigable) { + $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, { + $$avoidResync: true, replace: options.location === 'replace' + }); + } + + if (options.notify) { + /** + * @ngdoc event + * @name ui.router.state.$state#$stateChangeSuccess + * @eventOf ui.router.state.$state + * @eventType broadcast on root scope + * @description + * Fired once the state transition is **complete**. + * + * @param {Object} event Event object. + * @param {State} toState The state being transitioned to. + * @param {Object} toParams The params supplied to the `toState`. + * @param {State} fromState The current state, pre-transition. + * @param {Object} fromParams The params supplied to the `fromState`. + */ + $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams); + } + $urlRouter.update(true); + + return $state.current; + }).then(null, function (error) { + // propagate TransitionSuperseded error without emitting $stateChangeCancel + // as it was already emitted in the success handler above + if (error === TransitionSupersededError) return TransitionSuperseded; + + if ($state.transition !== transition) { + $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); + return TransitionSuperseded; + } + + $state.transition = null; + /** + * @ngdoc event + * @name ui.router.state.$state#$stateChangeError + * @eventOf ui.router.state.$state + * @eventType broadcast on root scope + * @description + * Fired when an **error occurs** during transition. It's important to note that if you + * have any errors in your resolve functions (javascript errors, non-existent services, etc) + * they will not throw traditionally. You must listen for this $stateChangeError event to + * catch **ALL** errors. + * + * @param {Object} event Event object. + * @param {State} toState The state being transitioned to. + * @param {Object} toParams The params supplied to the `toState`. + * @param {State} fromState The current state, pre-transition. + * @param {Object} fromParams The params supplied to the `fromState`. + * @param {Error} error The resolve error object. + */ + evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error); + + if (!evt.defaultPrevented) { + $urlRouter.update(); + } + + return $q.reject(error); + }); + + silenceUncaughtInPromise(transition); + return transition; + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#is + * @methodOf ui.router.state.$state + * + * @description + * Similar to {@link ui.router.state.$state#methods_includes $state.includes}, + * but only checks for the full state name. If params is supplied then it will be + * tested for strict equality against the current active params object, so all params + * must match with none missing and no extras. + * + * @example + *
+     * $state.$current.name = 'contacts.details.item';
+     *
+     * // absolute name
+     * $state.is('contact.details.item'); // returns true
+     * $state.is(contactDetailItemStateObject); // returns true
+     *
+     * // relative name (. and ^), typically from a template
+     * // E.g. from the 'contacts.details' template
+     * 
Item
+ *
+ * + * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check. + * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like + * to test against the current active state. + * @param {object=} options An options object. The options are: + * + * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will + * test relative to `options.relative` state (or name). + * + * @returns {boolean} Returns true if it is the state. + */ + $state.is = function is(stateOrName, params, options) { + options = extend({ relative: $state.$current }, options || {}); + var state = findState(stateOrName, options.relative); + + if (!isDefined(state)) { return undefined; } + if ($state.$current !== state) { return false; } + + return !params || objectKeys(params).reduce(function(acc, key) { + var paramDef = state.params[key]; + return acc && !paramDef || paramDef.type.equals($stateParams[key], params[key]); + }, true); + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#includes + * @methodOf ui.router.state.$state + * + * @description + * A method to determine if the current active state is equal to or is the child of the + * state stateName. If any params are passed then they will be tested for a match as well. + * Not all the parameters need to be passed, just the ones you'd like to test for equality. + * + * @example + * Partial and relative names + *
+     * $state.$current.name = 'contacts.details.item';
+     *
+     * // Using partial names
+     * $state.includes("contacts"); // returns true
+     * $state.includes("contacts.details"); // returns true
+     * $state.includes("contacts.details.item"); // returns true
+     * $state.includes("contacts.list"); // returns false
+     * $state.includes("about"); // returns false
+     *
+     * // Using relative names (. and ^), typically from a template
+     * // E.g. from the 'contacts.details' template
+     * 
Item
+ *
+ * + * Basic globbing patterns + *
+     * $state.$current.name = 'contacts.details.item.url';
+     *
+     * $state.includes("*.details.*.*"); // returns true
+     * $state.includes("*.details.**"); // returns true
+     * $state.includes("**.item.**"); // returns true
+     * $state.includes("*.details.item.url"); // returns true
+     * $state.includes("*.details.*.url"); // returns true
+     * $state.includes("*.details.*"); // returns false
+     * $state.includes("item.**"); // returns false
+     * 
+ * + * @param {string} stateOrName A partial name, relative name, or glob pattern + * to be searched for within the current state name. + * @param {object=} params A param object, e.g. `{sectionId: section.id}`, + * that you'd like to test against the current active state. + * @param {object=} options An options object. The options are: + * + * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set, + * .includes will test relative to `options.relative` state (or name). + * + * @returns {boolean} Returns true if it does include the state + */ + $state.includes = function includes(stateOrName, params, options) { + options = extend({ relative: $state.$current }, options || {}); + if (isString(stateOrName) && isGlob(stateOrName)) { + if (!doesStateMatchGlob(stateOrName)) { + return false; + } + stateOrName = $state.$current.name; + } + + var state = findState(stateOrName, options.relative); + if (!isDefined(state)) { return undefined; } + if (!isDefined($state.$current.includes[state.name])) { return false; } + if (!params) { return true; } + + var keys = objectKeys(params); + for (var i = 0; i < keys.length; i++) { + var key = keys[i], paramDef = state.params[key]; + if (paramDef && !paramDef.type.equals($stateParams[key], params[key])) { + return false; + } + } + + return objectKeys(params).reduce(function(acc, key) { + var paramDef = state.params[key]; + return acc && !paramDef || paramDef.type.equals($stateParams[key], params[key]); + }, true); + }; + + + /** + * @ngdoc function + * @name ui.router.state.$state#href + * @methodOf ui.router.state.$state + * + * @description + * A url generation method that returns the compiled url for the given state populated with the given params. + * + * @example + *
+     * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
+     * 
+ * + * @param {string|object} stateOrName The state name or state object you'd like to generate a url from. + * @param {object=} params An object of parameter values to fill the state's required parameters. + * @param {object=} options Options object. The options are: + * + * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the + * first parameter, then the constructed href url will be built from the first navigable ancestor (aka + * ancestor with a valid url). + * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. + * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), + * defines which state to be relative from. + * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". + * + * @returns {string} compiled state url + */ + $state.href = function href(stateOrName, params, options) { + options = extend({ + lossy: true, + inherit: true, + absolute: false, + relative: $state.$current + }, options || {}); + + var state = findState(stateOrName, options.relative); + + if (!isDefined(state)) return null; + if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state); + + var nav = (state && options.lossy) ? state.navigable : state; + + if (!nav || nav.url === undefined || nav.url === null) { + return null; + } + return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), { + absolute: options.absolute + }); + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#get + * @methodOf ui.router.state.$state + * + * @description + * Returns the state configuration object for any specific state or all states. + * + * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for + * the requested state. If not provided, returns an array of ALL state configs. + * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context. + * @returns {Object|Array} State configuration object or array of all objects. + */ + $state.get = function (stateOrName, context) { + if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; }); + var state = findState(stateOrName, context || $state.$current); + return (state && state.self) ? state.self : null; + }; + + function resolveState(state, params, paramsAreFiltered, inherited, dst, options) { + // Make a restricted $stateParams with only the parameters that apply to this state if + // necessary. In addition to being available to the controller and onEnter/onExit callbacks, + // we also need $stateParams to be available for any $injector calls we make during the + // dependency resolution process. + var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params); + var locals = { $stateParams: $stateParams }; + + // Resolve 'global' dependencies for the state, i.e. those not specific to a view. + // We're also including $stateParams in this; that way the parameters are restricted + // to the set that should be visible to the state, and are independent of when we update + // the global $state and $stateParams values. + dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state); + var promises = [dst.resolve.then(function (globals) { + dst.globals = globals; + })]; + if (inherited) promises.push(inherited); + + function resolveViews() { + var viewsPromises = []; + + // Resolve template and dependencies for all views. + forEach(state.views, function (view, name) { + var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {}); + injectables.$template = [ function () { + return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || ''; + }]; + + viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) { + // References to the controller (only instantiated at link time) + if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) { + var injectLocals = angular.extend({}, injectables, dst.globals); + result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals); + } else { + result.$$controller = view.controller; + } + // Provide access to the state itself for internal use + result.$$state = state; + result.$$controllerAs = view.controllerAs; + result.$$resolveAs = view.resolveAs; + dst[name] = result; + })); + }); + + return $q.all(viewsPromises).then(function(){ + return dst.globals; + }); + } + + // Wait for all the promises and then return the activation object + return $q.all(promises).then(resolveViews).then(function (values) { + return dst; + }); + } + + return $state; + } + + function shouldSkipReload(to, toParams, from, fromParams, locals, options) { + // Return true if there are no differences in non-search (path/object) params, false if there are differences + function nonSearchParamsEqual(fromAndToState, fromParams, toParams) { + // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params. + function notSearchParam(key) { + return fromAndToState.params[key].location != "search"; + } + var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam); + var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys)); + var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams); + return nonQueryParamSet.$$equals(fromParams, toParams); + } + + // If reload was not explicitly requested + // and we're transitioning to the same state we're already in + // and the locals didn't change + // or they changed in a way that doesn't merit reloading + // (reloadOnParams:false, or reloadOnSearch.false and only search params changed) + // Then return true. + if (!options.reload && to === from && + (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) { + return true; + } + } +} + +angular.module('ui.router.state') + .factory('$stateParams', function () { return {}; }) + .constant("$state.runtime", { autoinject: true }) + .provider('$state', $StateProvider) + // Inject $state to initialize when entering runtime. #2574 + .run(['$injector', function ($injector) { + // Allow tests (stateSpec.js) to turn this off by defining this constant + if ($injector.get("$state.runtime").autoinject) { + $injector.get('$state'); + } + }]); + + +$ViewProvider.$inject = []; +function $ViewProvider() { + + this.$get = $get; + /** + * @ngdoc object + * @name ui.router.state.$view + * + * @requires ui.router.util.$templateFactory + * @requires $rootScope + * + * @description + * + */ + $get.$inject = ['$rootScope', '$templateFactory']; + function $get( $rootScope, $templateFactory) { + return { + // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... }) + /** + * @ngdoc function + * @name ui.router.state.$view#load + * @methodOf ui.router.state.$view + * + * @description + * + * @param {string} name name + * @param {object} options option object. + */ + load: function load(name, options) { + var result, defaults = { + template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {} + }; + options = extend(defaults, options); + + if (options.view) { + result = $templateFactory.fromConfig(options.view, options.params, options.locals); + } + return result; + } + }; + } +} + +angular.module('ui.router.state').provider('$view', $ViewProvider); + +/** + * @ngdoc object + * @name ui.router.state.$uiViewScrollProvider + * + * @description + * Provider that returns the {@link ui.router.state.$uiViewScroll} service function. + */ +function $ViewScrollProvider() { + + var useAnchorScroll = false; + + /** + * @ngdoc function + * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll + * @methodOf ui.router.state.$uiViewScrollProvider + * + * @description + * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for + * scrolling based on the url anchor. + */ + this.useAnchorScroll = function () { + useAnchorScroll = true; + }; + + /** + * @ngdoc object + * @name ui.router.state.$uiViewScroll + * + * @requires $anchorScroll + * @requires $timeout + * + * @description + * When called with a jqLite element, it scrolls the element into view (after a + * `$timeout` so the DOM has time to refresh). + * + * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor, + * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}. + */ + this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) { + if (useAnchorScroll) { + return $anchorScroll; + } + + return function ($element) { + return $timeout(function () { + $element[0].scrollIntoView(); + }, 0, false); + }; + }]; +} + +angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider); + +/** + * @ngdoc directive + * @name ui.router.state.directive:ui-view + * + * @requires ui.router.state.$state + * @requires $compile + * @requires $controller + * @requires $injector + * @requires ui.router.state.$uiViewScroll + * @requires $document + * + * @restrict ECA + * + * @description + * The ui-view directive tells $state where to place your templates. + * + * @param {string=} name A view name. The name should be unique amongst the other views in the + * same state. You can have views of the same name that live in different states. + * + * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window + * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll + * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you + * scroll ui-view elements into view when they are populated during a state activation. + * + * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) + * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.* + * + * @param {string=} onload Expression to evaluate whenever the view updates. + * + * @example + * A view can be unnamed or named. + *
+ * 
+ * 
+ * + * + *
+ *
+ * + * You can only have one unnamed view within any template (or root html). If you are only using a + * single view and it is unnamed then you can populate it like so: + *
+ * 
+ * $stateProvider.state("home", { + * template: "

HELLO!

" + * }) + *
+ * + * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#methods_state `views`} + * config property, by name, in this case an empty name: + *
+ * $stateProvider.state("home", {
+ *   views: {
+ *     "": {
+ *       template: "

HELLO!

" + * } + * } + * }) + *
+ * + * But typically you'll only use the views property if you name your view or have more than one view + * in the same template. There's not really a compelling reason to name a view if its the only one, + * but you could if you wanted, like so: + *
+ * 
+ *
+ *
+ * $stateProvider.state("home", {
+ *   views: {
+ *     "main": {
+ *       template: "

HELLO!

" + * } + * } + * }) + *
+ * + * Really though, you'll use views to set up multiple views: + *
+ * 
+ *
+ *
+ *
+ * + *
+ * $stateProvider.state("home", {
+ *   views: {
+ *     "": {
+ *       template: "

HELLO!

" + * }, + * "chart": { + * template: "" + * }, + * "data": { + * template: "" + * } + * } + * }) + *
+ * + * Examples for `autoscroll`: + * + *
+ * 
+ * 
+ *
+ * 
+ * 
+ * 
+ * 
+ * 
+ * + * Resolve data: + * + * The resolved data from the state's `resolve` block is placed on the scope as `$resolve` (this + * can be customized using [[ViewDeclaration.resolveAs]]). This can be then accessed from the template. + * + * Note that when `controllerAs` is being used, `$resolve` is set on the controller instance *after* the + * controller is instantiated. The `$onInit()` hook can be used to perform initialization code which + * depends on `$resolve` data. + * + * Example usage of $resolve in a view template + *
+ * $stateProvider.state('home', {
+ *   template: '',
+ *   resolve: {
+ *     user: function(UserService) { return UserService.fetchUser(); }
+ *   }
+ * });
+ * 
+ */ +$ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate', '$q']; +function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate, $q) { + + function getService() { + return ($injector.has) ? function(service) { + return $injector.has(service) ? $injector.get(service) : null; + } : function(service) { + try { + return $injector.get(service); + } catch (e) { + return null; + } + }; + } + + var service = getService(), + $animator = service('$animator'), + $animate = service('$animate'); + + // Returns a set of DOM manipulation functions based on which Angular version + // it should use + function getRenderer(attrs, scope) { + var statics = function() { + return { + enter: function (element, target, cb) { target.after(element); cb(); }, + leave: function (element, cb) { element.remove(); cb(); } + }; + }; + + if ($animate) { + return { + enter: function(element, target, cb) { + if (angular.version.minor > 2) { + $animate.enter(element, null, target).then(cb); + } else { + $animate.enter(element, null, target, cb); + } + }, + leave: function(element, cb) { + if (angular.version.minor > 2) { + $animate.leave(element).then(cb); + } else { + $animate.leave(element, cb); + } + } + }; + } + + if ($animator) { + var animate = $animator && $animator(scope, attrs); + + return { + enter: function(element, target, cb) {animate.enter(element, null, target); cb(); }, + leave: function(element, cb) { animate.leave(element); cb(); } + }; + } + + return statics(); + } + + var directive = { + restrict: 'ECA', + terminal: true, + priority: 400, + transclude: 'element', + compile: function (tElement, tAttrs, $transclude) { + return function (scope, $element, attrs) { + var previousEl, currentEl, currentScope, latestLocals, + onloadExp = attrs.onload || '', + autoScrollExp = attrs.autoscroll, + renderer = getRenderer(attrs, scope), + inherited = $element.inheritedData('$uiView'); + + scope.$on('$stateChangeSuccess', function() { + updateView(false); + }); + + updateView(true); + + function cleanupLastView() { + if (previousEl) { + previousEl.remove(); + previousEl = null; + } + + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + + if (currentEl) { + var $uiViewData = currentEl.data('$uiViewAnim'); + renderer.leave(currentEl, function() { + $uiViewData.$$animLeave.resolve(); + previousEl = null; + }); + + previousEl = currentEl; + currentEl = null; + } + } + + function updateView(firstTime) { + var newScope, + name = getUiViewName(scope, attrs, $element, $interpolate), + previousLocals = name && $state.$current && $state.$current.locals[name]; + + if (!firstTime && previousLocals === latestLocals) return; // nothing to do + newScope = scope.$new(); + latestLocals = $state.$current.locals[name]; + + /** + * @ngdoc event + * @name ui.router.state.directive:ui-view#$viewContentLoading + * @eventOf ui.router.state.directive:ui-view + * @eventType emits on ui-view directive scope + * @description + * + * Fired once the view **begins loading**, *before* the DOM is rendered. + * + * @param {Object} event Event object. + * @param {string} viewName Name of the view. + */ + newScope.$emit('$viewContentLoading', name); + + var clone = $transclude(newScope, function(clone) { + var animEnter = $q.defer(), animLeave = $q.defer(); + var viewAnimData = { + $animEnter: animEnter.promise, + $animLeave: animLeave.promise, + $$animLeave: animLeave + }; + + clone.data('$uiViewAnim', viewAnimData); + renderer.enter(clone, $element, function onUiViewEnter() { + animEnter.resolve(); + if(currentScope) { + currentScope.$emit('$viewContentAnimationEnded'); + } + + if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) { + $uiViewScroll(clone); + } + }); + cleanupLastView(); + }); + + currentEl = clone; + currentScope = newScope; + /** + * @ngdoc event + * @name ui.router.state.directive:ui-view#$viewContentLoaded + * @eventOf ui.router.state.directive:ui-view + * @eventType emits on ui-view directive scope + * @description + * Fired once the view is **loaded**, *after* the DOM is rendered. + * + * @param {Object} event Event object. + * @param {string} viewName Name of the view. + */ + currentScope.$emit('$viewContentLoaded', name); + currentScope.$eval(onloadExp); + } + }; + } + }; + + return directive; +} + +$ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate']; +function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) { + return { + restrict: 'ECA', + priority: -400, + compile: function (tElement) { + var initial = tElement.html(); + if (tElement.empty) { + tElement.empty(); + } else { + // ng 1.0.0 doesn't have empty(), which cleans up data and handlers + tElement[0].innerHTML = null; + } + + return function (scope, $element, attrs) { + var current = $state.$current, + name = getUiViewName(scope, attrs, $element, $interpolate), + locals = current && current.locals[name]; + + if (! locals) { + $element.html(initial); + $compile($element.contents())(scope); + return; + } + + $element.data('$uiView', { name: name, state: locals.$$state }); + $element.html(locals.$template ? locals.$template : initial); + + var resolveData = angular.extend({}, locals); + scope[locals.$$resolveAs] = resolveData; + + var link = $compile($element.contents()); + + if (locals.$$controller) { + locals.$scope = scope; + locals.$element = $element; + var controller = $controller(locals.$$controller, locals); + if (locals.$$controllerAs) { + scope[locals.$$controllerAs] = controller; + scope[locals.$$controllerAs][locals.$$resolveAs] = resolveData; + } + if (isFunction(controller.$onInit)) controller.$onInit(); + $element.data('$ngControllerController', controller); + $element.children().data('$ngControllerController', controller); + } + + link(scope); + }; + } + }; +} + +/** + * Shared ui-view code for both directives: + * Given scope, element, and its attributes, return the view's name + */ +function getUiViewName(scope, attrs, element, $interpolate) { + var name = $interpolate(attrs.uiView || attrs.name || '')(scope); + var uiViewCreatedBy = element.inheritedData('$uiView'); + return name.indexOf('@') >= 0 ? name : (name + '@' + (uiViewCreatedBy ? uiViewCreatedBy.state.name : '')); +} + +angular.module('ui.router.state').directive('uiView', $ViewDirective); +angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill); + +function parseStateRef(ref, current) { + var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed; + if (preparsed) ref = current + '(' + preparsed[1] + ')'; + parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/); + if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'"); + return { state: parsed[1], paramExpr: parsed[3] || null }; +} + +function stateContext(el) { + var stateData = el.parent().inheritedData('$uiView'); + + if (stateData && stateData.state && stateData.state.name) { + return stateData.state; + } +} + +function getTypeInfo(el) { + // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. + var isSvg = Object.prototype.toString.call(el.prop('href')) === '[object SVGAnimatedString]'; + var isForm = el[0].nodeName === "FORM"; + + return { + attr: isForm ? "action" : (isSvg ? 'xlink:href' : 'href'), + isAnchor: el.prop("tagName").toUpperCase() === "A", + clickable: !isForm + }; +} + +function clickHook(el, $state, $timeout, type, current) { + return function(e) { + var button = e.which || e.button, target = current(); + + if (!(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || el.attr('target'))) { + // HACK: This is to allow ng-clicks to be processed before the transition is initiated: + var transition = $timeout(function() { + $state.go(target.state, target.params, target.options); + }); + e.preventDefault(); + + // if the state has no URL, ignore one preventDefault from the directive. + var ignorePreventDefaultCount = type.isAnchor && !target.href ? 1: 0; + + e.preventDefault = function() { + if (ignorePreventDefaultCount-- <= 0) $timeout.cancel(transition); + }; + } + }; +} + +function defaultOpts(el, $state) { + return { relative: stateContext(el) || $state.$current, inherit: true }; +} + +/** + * @ngdoc directive + * @name ui.router.state.directive:ui-sref + * + * @requires ui.router.state.$state + * @requires $timeout + * + * @restrict A + * + * @description + * A directive that binds a link (`` tag) to a state. If the state has an associated + * URL, the directive will automatically generate & update the `href` attribute via + * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking + * the link will trigger a state transition with optional parameters. + * + * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be + * handled natively by the browser. + * + * You can also use relative state paths within ui-sref, just like the relative + * paths passed to `$state.go()`. You just need to be aware that the path is relative + * to the state that the link lives in, in other words the state that loaded the + * template containing the link. + * + * You can specify options to pass to {@link ui.router.state.$state#methods_go $state.go()} + * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`, + * and `reload`. + * + * @example + * Here's an example of how you'd use ui-sref and how it would compile. If you have the + * following template: + *
+ * Home | About | Next page
+ *
+ * 
+ * 
+ * + * Then the compiled html would be (assuming Html5Mode is off and current state is contacts): + *
+ * Home | About | Next page
+ *
+ * 
    + *
  • + * Joe + *
  • + *
  • + * Alice + *
  • + *
  • + * Bob + *
  • + *
+ * + * Home + *
+ * + * @param {string} ui-sref 'stateName' can be any valid absolute or relative state + * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#methods_go $state.go()} + */ +$StateRefDirective.$inject = ['$state', '$timeout']; +function $StateRefDirective($state, $timeout) { + return { + restrict: 'A', + require: ['?^uiSrefActive', '?^uiSrefActiveEq'], + link: function(scope, element, attrs, uiSrefActive) { + var ref = parseStateRef(attrs.uiSref, $state.current.name); + var def = { state: ref.state, href: null, params: null }; + var type = getTypeInfo(element); + var active = uiSrefActive[1] || uiSrefActive[0]; + var unlinkInfoFn = null; + var hookFn; + + def.options = extend(defaultOpts(element, $state), attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {}); + + var update = function(val) { + if (val) def.params = angular.copy(val); + def.href = $state.href(ref.state, def.params, def.options); + + if (unlinkInfoFn) unlinkInfoFn(); + if (active) unlinkInfoFn = active.$$addStateInfo(ref.state, def.params); + if (def.href !== null) attrs.$set(type.attr, def.href); + }; + + if (ref.paramExpr) { + scope.$watch(ref.paramExpr, function(val) { if (val !== def.params) update(val); }, true); + def.params = angular.copy(scope.$eval(ref.paramExpr)); + } + update(); + + if (!type.clickable) return; + hookFn = clickHook(element, $state, $timeout, type, function() { return def; }); + element[element.on ? 'on' : 'bind']("click", hookFn); + scope.$on('$destroy', function() { + element[element.off ? 'off' : 'unbind']("click", hookFn); + }); + } + }; +} + +/** + * @ngdoc directive + * @name ui.router.state.directive:ui-state + * + * @requires ui.router.state.uiSref + * + * @restrict A + * + * @description + * Much like ui-sref, but will accept named $scope properties to evaluate for a state definition, + * params and override options. + * + * @param {string} ui-state 'stateName' can be any valid absolute or relative state + * @param {Object} ui-state-params params to pass to {@link ui.router.state.$state#methods_href $state.href()} + * @param {Object} ui-state-opts options to pass to {@link ui.router.state.$state#methods_go $state.go()} + */ +$StateRefDynamicDirective.$inject = ['$state', '$timeout']; +function $StateRefDynamicDirective($state, $timeout) { + return { + restrict: 'A', + require: ['?^uiSrefActive', '?^uiSrefActiveEq'], + link: function(scope, element, attrs, uiSrefActive) { + var type = getTypeInfo(element); + var active = uiSrefActive[1] || uiSrefActive[0]; + var group = [attrs.uiState, attrs.uiStateParams || null, attrs.uiStateOpts || null]; + var watch = '[' + group.map(function(val) { return val || 'null'; }).join(', ') + ']'; + var def = { state: null, params: null, options: null, href: null }; + var unlinkInfoFn = null; + var hookFn; + + function runStateRefLink (group) { + def.state = group[0]; def.params = group[1]; def.options = group[2]; + def.href = $state.href(def.state, def.params, def.options); + + if (unlinkInfoFn) unlinkInfoFn(); + if (active) unlinkInfoFn = active.$$addStateInfo(def.state, def.params); + if (def.href) attrs.$set(type.attr, def.href); + } + + scope.$watch(watch, runStateRefLink, true); + runStateRefLink(scope.$eval(watch)); + + if (!type.clickable) return; + hookFn = clickHook(element, $state, $timeout, type, function() { return def; }); + element[element.on ? 'on' : 'bind']("click", hookFn); + scope.$on('$destroy', function() { + element[element.off ? 'off' : 'unbind']("click", hookFn); + }); + } + }; +} + + +/** + * @ngdoc directive + * @name ui.router.state.directive:ui-sref-active + * + * @requires ui.router.state.$state + * @requires ui.router.state.$stateParams + * @requires $interpolate + * + * @restrict A + * + * @description + * A directive working alongside ui-sref to add classes to an element when the + * related ui-sref directive's state is active, and removing them when it is inactive. + * The primary use-case is to simplify the special appearance of navigation menus + * relying on `ui-sref`, by having the "active" state's menu button appear different, + * distinguishing it from the inactive menu items. + * + * ui-sref-active can live on the same element as ui-sref or on a parent element. The first + * ui-sref-active found at the same level or above the ui-sref will be used. + * + * Will activate when the ui-sref's target state or any child state is active. If you + * need to activate only when the ui-sref target state is active and *not* any of + * it's children, then you will use + * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq} + * + * @example + * Given the following template: + *
+ * 
+ * 
+ * + * + * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins", + * the resulting HTML will appear as (note the 'active' class): + *
+ * 
+ * 
+ * + * The class name is interpolated **once** during the directives link time (any further changes to the + * interpolated value are ignored). + * + * Multiple classes may be specified in a space-separated format: + *
+ * 
    + *
  • + * link + *
  • + *
+ *
+ * + * It is also possible to pass ui-sref-active an expression that evaluates + * to an object hash, whose keys represent active class names and whose + * values represent the respective state names/globs. + * ui-sref-active will match if the current active state **includes** any of + * the specified state names/globs, even the abstract ones. + * + * @Example + * Given the following template, with "admin" being an abstract state: + *
+ * 
+ * Roles + *
+ *
+ * + * When the current state is "admin.roles" the "active" class will be applied + * to both the
and elements. It is important to note that the state + * names/globs passed to ui-sref-active shadow the state provided by ui-sref. + */ + +/** + * @ngdoc directive + * @name ui.router.state.directive:ui-sref-active-eq + * + * @requires ui.router.state.$state + * @requires ui.router.state.$stateParams + * @requires $interpolate + * + * @restrict A + * + * @description + * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate + * when the exact target state used in the `ui-sref` is active; no child states. + * + */ +$StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate']; +function $StateRefActiveDirective($state, $stateParams, $interpolate) { + return { + restrict: "A", + controller: ['$scope', '$element', '$attrs', '$timeout', function ($scope, $element, $attrs, $timeout) { + var states = [], activeClasses = {}, activeEqClass, uiSrefActive; + + // There probably isn't much point in $observing this + // uiSrefActive and uiSrefActiveEq share the same directive object with some + // slight difference in logic routing + activeEqClass = $interpolate($attrs.uiSrefActiveEq || '', false)($scope); + + try { + uiSrefActive = $scope.$eval($attrs.uiSrefActive); + } catch (e) { + // Do nothing. uiSrefActive is not a valid expression. + // Fall back to using $interpolate below + } + uiSrefActive = uiSrefActive || $interpolate($attrs.uiSrefActive || '', false)($scope); + if (isObject(uiSrefActive)) { + forEach(uiSrefActive, function(stateOrName, activeClass) { + if (isString(stateOrName)) { + var ref = parseStateRef(stateOrName, $state.current.name); + addState(ref.state, $scope.$eval(ref.paramExpr), activeClass); + } + }); + } + + // Allow uiSref to communicate with uiSrefActive[Equals] + this.$$addStateInfo = function (newState, newParams) { + // we already got an explicit state provided by ui-sref-active, so we + // shadow the one that comes from ui-sref + if (isObject(uiSrefActive) && states.length > 0) { + return; + } + var deregister = addState(newState, newParams, uiSrefActive); + update(); + return deregister; + }; + + $scope.$on('$stateChangeSuccess', update); + + function addState(stateName, stateParams, activeClass) { + var state = $state.get(stateName, stateContext($element)); + var stateHash = createStateHash(stateName, stateParams); + + var stateInfo = { + state: state || { name: stateName }, + params: stateParams, + hash: stateHash + }; + + states.push(stateInfo); + activeClasses[stateHash] = activeClass; + + return function removeState() { + var idx = states.indexOf(stateInfo); + if (idx !== -1) states.splice(idx, 1); + }; + } + + /** + * @param {string} state + * @param {Object|string} [params] + * @return {string} + */ + function createStateHash(state, params) { + if (!isString(state)) { + throw new Error('state should be a string'); + } + if (isObject(params)) { + return state + toJson(params); + } + params = $scope.$eval(params); + if (isObject(params)) { + return state + toJson(params); + } + return state; + } + + // Update route state + function update() { + for (var i = 0; i < states.length; i++) { + if (anyMatch(states[i].state, states[i].params)) { + addClass($element, activeClasses[states[i].hash]); + } else { + removeClass($element, activeClasses[states[i].hash]); + } + + if (exactMatch(states[i].state, states[i].params)) { + addClass($element, activeEqClass); + } else { + removeClass($element, activeEqClass); + } + } + } + + function addClass(el, className) { $timeout(function () { el.addClass(className); }); } + function removeClass(el, className) { el.removeClass(className); } + function anyMatch(state, params) { return $state.includes(state.name, params); } + function exactMatch(state, params) { return $state.is(state.name, params); } + + update(); + }] + }; +} + +angular.module('ui.router.state') + .directive('uiSref', $StateRefDirective) + .directive('uiSrefActive', $StateRefActiveDirective) + .directive('uiSrefActiveEq', $StateRefActiveDirective) + .directive('uiState', $StateRefDynamicDirective); + +/** + * @ngdoc filter + * @name ui.router.state.filter:isState + * + * @requires ui.router.state.$state + * + * @description + * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}. + */ +$IsStateFilter.$inject = ['$state']; +function $IsStateFilter($state) { + var isFilter = function (state, params) { + return $state.is(state, params); + }; + isFilter.$stateful = true; + return isFilter; +} + +/** + * @ngdoc filter + * @name ui.router.state.filter:includedByState + * + * @requires ui.router.state.$state + * + * @description + * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}. + */ +$IncludedByStateFilter.$inject = ['$state']; +function $IncludedByStateFilter($state) { + var includesFilter = function (state, params, options) { + return $state.includes(state, params, options); + }; + includesFilter.$stateful = true; + return includesFilter; +} + +angular.module('ui.router.state') + .filter('isState', $IsStateFilter) + .filter('includedByState', $IncludedByStateFilter); +})(window, window.angular); +(function() { + 'use strict'; + + angular.module('toastr', []) + .factory('toastr', toastr); + + toastr.$inject = ['$animate', '$injector', '$document', '$rootScope', '$sce', 'toastrConfig', '$q']; + + function toastr($animate, $injector, $document, $rootScope, $sce, toastrConfig, $q) { + var container; + var index = 0; + var toasts = []; + + var previousToastMessage = ''; + var openToasts = {}; + + var containerDefer = $q.defer(); + + var toast = { + active: active, + clear: clear, + error: error, + info: info, + remove: remove, + success: success, + warning: warning, + refreshTimer: refreshTimer + }; + + return toast; + + /* Public API */ + function active() { + return toasts.length; + } + + function clear(toast) { + // Bit of a hack, I will remove this soon with a BC + if (arguments.length === 1 && !toast) { return; } + + if (toast) { + remove(toast.toastId); + } else { + for (var i = 0; i < toasts.length; i++) { + remove(toasts[i].toastId); + } + } + } + + function error(message, title, optionsOverride) { + var type = _getOptions().iconClasses.error; + return _buildNotification(type, message, title, optionsOverride); + } + + function info(message, title, optionsOverride) { + var type = _getOptions().iconClasses.info; + return _buildNotification(type, message, title, optionsOverride); + } + + function success(message, title, optionsOverride) { + var type = _getOptions().iconClasses.success; + return _buildNotification(type, message, title, optionsOverride); + } + + function warning(message, title, optionsOverride) { + var type = _getOptions().iconClasses.warning; + return _buildNotification(type, message, title, optionsOverride); + } + + function refreshTimer(toast, newTime) { + if (toast && toast.isOpened && toasts.indexOf(toast) >= 0) { + toast.scope.refreshTimer(newTime); + } + } + + function remove(toastId, wasClicked) { + var toast = findToast(toastId); + + if (toast && ! toast.deleting) { // Avoid clicking when fading out + toast.deleting = true; + toast.isOpened = false; + $animate.leave(toast.el).then(function() { + if (toast.scope.options.onHidden) { + toast.scope.options.onHidden(!!wasClicked, toast); + } + toast.scope.$destroy(); + var index = toasts.indexOf(toast); + delete openToasts[toast.scope.message]; + toasts.splice(index, 1); + var maxOpened = toastrConfig.maxOpened; + if (maxOpened && toasts.length >= maxOpened) { + toasts[maxOpened - 1].open.resolve(); + } + if (lastToast()) { + container.remove(); + container = null; + containerDefer = $q.defer(); + } + }); + } + + function findToast(toastId) { + for (var i = 0; i < toasts.length; i++) { + if (toasts[i].toastId === toastId) { + return toasts[i]; + } + } + } + + function lastToast() { + return !toasts.length; + } + } + + /* Internal functions */ + function _buildNotification(type, message, title, optionsOverride) { + if (angular.isObject(title)) { + optionsOverride = title; + title = null; + } + + return _notify({ + iconClass: type, + message: message, + optionsOverride: optionsOverride, + title: title + }); + } + + function _getOptions() { + return angular.extend({}, toastrConfig); + } + + function _createOrGetContainer(options) { + if(container) { return containerDefer.promise; } + + container = angular.element('
'); + container.attr('id', options.containerId); + container.addClass(options.positionClass); + container.css({'pointer-events': 'auto'}); + + var target = angular.element(document.querySelector(options.target)); + + if ( ! target || ! target.length) { + throw 'Target for toasts doesn\'t exist'; + } + + $animate.enter(container, target).then(function() { + containerDefer.resolve(); + }); + + return containerDefer.promise; + } + + function _notify(map) { + var options = _getOptions(); + + if (shouldExit()) { return; } + + var newToast = createToast(); + + toasts.push(newToast); + + if (ifMaxOpenedAndAutoDismiss()) { + var oldToasts = toasts.slice(0, (toasts.length - options.maxOpened)); + for (var i = 0, len = oldToasts.length; i < len; i++) { + remove(oldToasts[i].toastId); + } + } + + if (maxOpenedNotReached()) { + newToast.open.resolve(); + } + + newToast.open.promise.then(function() { + _createOrGetContainer(options).then(function() { + newToast.isOpened = true; + if (options.newestOnTop) { + $animate.enter(newToast.el, container).then(function() { + newToast.scope.init(); + }); + } else { + var sibling = container[0].lastChild ? angular.element(container[0].lastChild) : null; + $animate.enter(newToast.el, container, sibling).then(function() { + newToast.scope.init(); + }); + } + }); + }); + + return newToast; + + function ifMaxOpenedAndAutoDismiss() { + return options.autoDismiss && options.maxOpened && toasts.length > options.maxOpened; + } + + function createScope(toast, map, options) { + if (options.allowHtml) { + toast.scope.allowHtml = true; + toast.scope.title = $sce.trustAsHtml(map.title); + toast.scope.message = $sce.trustAsHtml(map.message); + } else { + toast.scope.title = map.title; + toast.scope.message = map.message; + } + + toast.scope.toastType = toast.iconClass; + toast.scope.toastId = toast.toastId; + toast.scope.extraData = options.extraData; + + toast.scope.options = { + extendedTimeOut: options.extendedTimeOut, + messageClass: options.messageClass, + onHidden: options.onHidden, + onShown: generateEvent('onShown'), + onTap: generateEvent('onTap'), + progressBar: options.progressBar, + tapToDismiss: options.tapToDismiss, + timeOut: options.timeOut, + titleClass: options.titleClass, + toastClass: options.toastClass + }; + + if (options.closeButton) { + toast.scope.options.closeHtml = options.closeHtml; + } + + function generateEvent(event) { + if (options[event]) { + return function() { + options[event](toast); + }; + } + } + } + + function createToast() { + var newToast = { + toastId: index++, + isOpened: false, + scope: $rootScope.$new(), + open: $q.defer() + }; + newToast.iconClass = map.iconClass; + if (map.optionsOverride) { + angular.extend(options, cleanOptionsOverride(map.optionsOverride)); + newToast.iconClass = map.optionsOverride.iconClass || newToast.iconClass; + } + + createScope(newToast, map, options); + + newToast.el = createToastEl(newToast.scope); + + return newToast; + + function cleanOptionsOverride(options) { + var badOptions = ['containerId', 'iconClasses', 'maxOpened', 'newestOnTop', + 'positionClass', 'preventDuplicates', 'preventOpenDuplicates', 'templates']; + for (var i = 0, l = badOptions.length; i < l; i++) { + delete options[badOptions[i]]; + } + + return options; + } + } + + function createToastEl(scope) { + var angularDomEl = angular.element('
'), + $compile = $injector.get('$compile'); + return $compile(angularDomEl)(scope); + } + + function maxOpenedNotReached() { + return options.maxOpened && toasts.length <= options.maxOpened || !options.maxOpened; + } + + function shouldExit() { + var isDuplicateOfLast = options.preventDuplicates && map.message === previousToastMessage; + var isDuplicateOpen = options.preventOpenDuplicates && openToasts[map.message]; + + if (isDuplicateOfLast || isDuplicateOpen) { + return true; + } + + previousToastMessage = map.message; + openToasts[map.message] = true; + + return false; + } + } + } +}()); + +(function() { + 'use strict'; + + angular.module('toastr') + .constant('toastrConfig', { + allowHtml: false, + autoDismiss: false, + closeButton: false, + closeHtml: '', + containerId: 'toast-container', + extendedTimeOut: 1000, + iconClasses: { + error: 'toast-error', + info: 'toast-info', + success: 'toast-success', + warning: 'toast-warning' + }, + maxOpened: 0, + messageClass: 'toast-message', + newestOnTop: true, + onHidden: null, + onShown: null, + onTap: null, + positionClass: 'toast-top-right', + preventDuplicates: false, + preventOpenDuplicates: false, + progressBar: false, + tapToDismiss: true, + target: 'body', + templates: { + toast: 'directives/toast/toast.html', + progressbar: 'directives/progressbar/progressbar.html' + }, + timeOut: 5000, + titleClass: 'toast-title', + toastClass: 'toast' + }); +}()); + +(function() { + 'use strict'; + + angular.module('toastr') + .directive('progressBar', progressBar); + + progressBar.$inject = ['toastrConfig']; + + function progressBar(toastrConfig) { + return { + require: '^toast', + templateUrl: function() { + return toastrConfig.templates.progressbar; + }, + link: linkFunction + }; + + function linkFunction(scope, element, attrs, toastCtrl) { + var intervalId, currentTimeOut, hideTime; + + toastCtrl.progressBar = scope; + + scope.start = function(duration) { + if (intervalId) { + clearInterval(intervalId); + } + + currentTimeOut = parseFloat(duration); + hideTime = new Date().getTime() + currentTimeOut; + intervalId = setInterval(updateProgress, 10); + }; + + scope.stop = function() { + if (intervalId) { + clearInterval(intervalId); + } + }; + + function updateProgress() { + var percentage = ((hideTime - (new Date().getTime())) / currentTimeOut) * 100; + element.css('width', percentage + '%'); + } + + scope.$on('$destroy', function() { + // Failsafe stop + clearInterval(intervalId); + }); + } + } +}()); + +(function() { + 'use strict'; + + angular.module('toastr') + .controller('ToastController', ToastController); + + function ToastController() { + this.progressBar = null; + + this.startProgressBar = function(duration) { + if (this.progressBar) { + this.progressBar.start(duration); + } + }; + + this.stopProgressBar = function() { + if (this.progressBar) { + this.progressBar.stop(); + } + }; + } +}()); + +(function() { + 'use strict'; + + angular.module('toastr') + .directive('toast', toast); + + toast.$inject = ['$injector', '$interval', 'toastrConfig', 'toastr']; + + function toast($injector, $interval, toastrConfig, toastr) { + return { + templateUrl: function() { + return toastrConfig.templates.toast; + }, + controller: 'ToastController', + link: toastLinkFunction + }; + + function toastLinkFunction(scope, element, attrs, toastCtrl) { + var timeout; + + scope.toastClass = scope.options.toastClass; + scope.titleClass = scope.options.titleClass; + scope.messageClass = scope.options.messageClass; + scope.progressBar = scope.options.progressBar; + + if (wantsCloseButton()) { + var button = angular.element(scope.options.closeHtml), + $compile = $injector.get('$compile'); + button.addClass('toast-close-button'); + button.attr('ng-click', 'close(true, $event)'); + $compile(button)(scope); + element.children().prepend(button); + } + + scope.init = function() { + if (scope.options.timeOut) { + timeout = createTimeout(scope.options.timeOut); + } + if (scope.options.onShown) { + scope.options.onShown(); + } + }; + + element.on('mouseenter', function() { + hideAndStopProgressBar(); + if (timeout) { + $interval.cancel(timeout); + } + }); + + scope.tapToast = function () { + if (angular.isFunction(scope.options.onTap)) { + scope.options.onTap(); + } + if (scope.options.tapToDismiss) { + scope.close(true); + } + }; + + scope.close = function (wasClicked, $event) { + if ($event && angular.isFunction($event.stopPropagation)) { + $event.stopPropagation(); + } + toastr.remove(scope.toastId, wasClicked); + }; + + scope.refreshTimer = function(newTime) { + if (timeout) { + $interval.cancel(timeout); + timeout = createTimeout(newTime || scope.options.timeOut); + } + }; + + element.on('mouseleave', function() { + if (scope.options.timeOut === 0 && scope.options.extendedTimeOut === 0) { return; } + scope.$apply(function() { + scope.progressBar = scope.options.progressBar; + }); + timeout = createTimeout(scope.options.extendedTimeOut); + }); + + function createTimeout(time) { + toastCtrl.startProgressBar(time); + return $interval(function() { + toastCtrl.stopProgressBar(); + toastr.remove(scope.toastId); + }, time, 1); + } + + function hideAndStopProgressBar() { + scope.progressBar = false; + toastCtrl.stopProgressBar(); + } + + function wantsCloseButton() { + return scope.options.closeHtml; + } + } + } +}()); + +(function() { + 'use strict'; + + angular.module('toastr', []) + .factory('toastr', toastr); + + toastr.$inject = ['$animate', '$injector', '$document', '$rootScope', '$sce', 'toastrConfig', '$q']; + + function toastr($animate, $injector, $document, $rootScope, $sce, toastrConfig, $q) { + var container; + var index = 0; + var toasts = []; + + var previousToastMessage = ''; + var openToasts = {}; + + var containerDefer = $q.defer(); + + var toast = { + active: active, + clear: clear, + error: error, + info: info, + remove: remove, + success: success, + warning: warning, + refreshTimer: refreshTimer + }; + + return toast; + + /* Public API */ + function active() { + return toasts.length; + } + + function clear(toast) { + // Bit of a hack, I will remove this soon with a BC + if (arguments.length === 1 && !toast) { return; } + + if (toast) { + remove(toast.toastId); + } else { + for (var i = 0; i < toasts.length; i++) { + remove(toasts[i].toastId); + } + } + } + + function error(message, title, optionsOverride) { + var type = _getOptions().iconClasses.error; + return _buildNotification(type, message, title, optionsOverride); + } + + function info(message, title, optionsOverride) { + var type = _getOptions().iconClasses.info; + return _buildNotification(type, message, title, optionsOverride); + } + + function success(message, title, optionsOverride) { + var type = _getOptions().iconClasses.success; + return _buildNotification(type, message, title, optionsOverride); + } + + function warning(message, title, optionsOverride) { + var type = _getOptions().iconClasses.warning; + return _buildNotification(type, message, title, optionsOverride); + } + + function refreshTimer(toast, newTime) { + if (toast && toast.isOpened && toasts.indexOf(toast) >= 0) { + toast.scope.refreshTimer(newTime); + } + } + + function remove(toastId, wasClicked) { + var toast = findToast(toastId); + + if (toast && ! toast.deleting) { // Avoid clicking when fading out + toast.deleting = true; + toast.isOpened = false; + $animate.leave(toast.el).then(function() { + if (toast.scope.options.onHidden) { + toast.scope.options.onHidden(!!wasClicked, toast); + } + toast.scope.$destroy(); + var index = toasts.indexOf(toast); + delete openToasts[toast.scope.message]; + toasts.splice(index, 1); + var maxOpened = toastrConfig.maxOpened; + if (maxOpened && toasts.length >= maxOpened) { + toasts[maxOpened - 1].open.resolve(); + } + if (lastToast()) { + container.remove(); + container = null; + containerDefer = $q.defer(); + } + }); + } + + function findToast(toastId) { + for (var i = 0; i < toasts.length; i++) { + if (toasts[i].toastId === toastId) { + return toasts[i]; + } + } + } + + function lastToast() { + return !toasts.length; + } + } + + /* Internal functions */ + function _buildNotification(type, message, title, optionsOverride) { + if (angular.isObject(title)) { + optionsOverride = title; + title = null; + } + + return _notify({ + iconClass: type, + message: message, + optionsOverride: optionsOverride, + title: title + }); + } + + function _getOptions() { + return angular.extend({}, toastrConfig); + } + + function _createOrGetContainer(options) { + if(container) { return containerDefer.promise; } + + container = angular.element('
'); + container.attr('id', options.containerId); + container.addClass(options.positionClass); + container.css({'pointer-events': 'auto'}); + + var target = angular.element(document.querySelector(options.target)); + + if ( ! target || ! target.length) { + throw 'Target for toasts doesn\'t exist'; + } + + $animate.enter(container, target).then(function() { + containerDefer.resolve(); + }); + + return containerDefer.promise; + } + + function _notify(map) { + var options = _getOptions(); + + if (shouldExit()) { return; } + + var newToast = createToast(); + + toasts.push(newToast); + + if (ifMaxOpenedAndAutoDismiss()) { + var oldToasts = toasts.slice(0, (toasts.length - options.maxOpened)); + for (var i = 0, len = oldToasts.length; i < len; i++) { + remove(oldToasts[i].toastId); + } + } + + if (maxOpenedNotReached()) { + newToast.open.resolve(); + } + + newToast.open.promise.then(function() { + _createOrGetContainer(options).then(function() { + newToast.isOpened = true; + if (options.newestOnTop) { + $animate.enter(newToast.el, container).then(function() { + newToast.scope.init(); + }); + } else { + var sibling = container[0].lastChild ? angular.element(container[0].lastChild) : null; + $animate.enter(newToast.el, container, sibling).then(function() { + newToast.scope.init(); + }); + } + }); + }); + + return newToast; + + function ifMaxOpenedAndAutoDismiss() { + return options.autoDismiss && options.maxOpened && toasts.length > options.maxOpened; + } + + function createScope(toast, map, options) { + if (options.allowHtml) { + toast.scope.allowHtml = true; + toast.scope.title = $sce.trustAsHtml(map.title); + toast.scope.message = $sce.trustAsHtml(map.message); + } else { + toast.scope.title = map.title; + toast.scope.message = map.message; + } + + toast.scope.toastType = toast.iconClass; + toast.scope.toastId = toast.toastId; + toast.scope.extraData = options.extraData; + + toast.scope.options = { + extendedTimeOut: options.extendedTimeOut, + messageClass: options.messageClass, + onHidden: options.onHidden, + onShown: generateEvent('onShown'), + onTap: generateEvent('onTap'), + progressBar: options.progressBar, + tapToDismiss: options.tapToDismiss, + timeOut: options.timeOut, + titleClass: options.titleClass, + toastClass: options.toastClass + }; + + if (options.closeButton) { + toast.scope.options.closeHtml = options.closeHtml; + } + + function generateEvent(event) { + if (options[event]) { + return function() { + options[event](toast); + }; + } + } + } + + function createToast() { + var newToast = { + toastId: index++, + isOpened: false, + scope: $rootScope.$new(), + open: $q.defer() + }; + newToast.iconClass = map.iconClass; + if (map.optionsOverride) { + angular.extend(options, cleanOptionsOverride(map.optionsOverride)); + newToast.iconClass = map.optionsOverride.iconClass || newToast.iconClass; + } + + createScope(newToast, map, options); + + newToast.el = createToastEl(newToast.scope); + + return newToast; + + function cleanOptionsOverride(options) { + var badOptions = ['containerId', 'iconClasses', 'maxOpened', 'newestOnTop', + 'positionClass', 'preventDuplicates', 'preventOpenDuplicates', 'templates']; + for (var i = 0, l = badOptions.length; i < l; i++) { + delete options[badOptions[i]]; + } + + return options; + } + } + + function createToastEl(scope) { + var angularDomEl = angular.element('
'), + $compile = $injector.get('$compile'); + return $compile(angularDomEl)(scope); + } + + function maxOpenedNotReached() { + return options.maxOpened && toasts.length <= options.maxOpened || !options.maxOpened; + } + + function shouldExit() { + var isDuplicateOfLast = options.preventDuplicates && map.message === previousToastMessage; + var isDuplicateOpen = options.preventOpenDuplicates && openToasts[map.message]; + + if (isDuplicateOfLast || isDuplicateOpen) { + return true; + } + + previousToastMessage = map.message; + openToasts[map.message] = true; + + return false; + } + } + } +}()); + +(function() { + 'use strict'; + + angular.module('toastr') + .constant('toastrConfig', { + allowHtml: false, + autoDismiss: false, + closeButton: false, + closeHtml: '', + containerId: 'toast-container', + extendedTimeOut: 1000, + iconClasses: { + error: 'toast-error', + info: 'toast-info', + success: 'toast-success', + warning: 'toast-warning' + }, + maxOpened: 0, + messageClass: 'toast-message', + newestOnTop: true, + onHidden: null, + onShown: null, + onTap: null, + positionClass: 'toast-top-right', + preventDuplicates: false, + preventOpenDuplicates: false, + progressBar: false, + tapToDismiss: true, + target: 'body', + templates: { + toast: 'directives/toast/toast.html', + progressbar: 'directives/progressbar/progressbar.html' + }, + timeOut: 5000, + titleClass: 'toast-title', + toastClass: 'toast' + }); +}()); + +(function() { + 'use strict'; + + angular.module('toastr') + .directive('progressBar', progressBar); + + progressBar.$inject = ['toastrConfig']; + + function progressBar(toastrConfig) { + return { + require: '^toast', + templateUrl: function() { + return toastrConfig.templates.progressbar; + }, + link: linkFunction + }; + + function linkFunction(scope, element, attrs, toastCtrl) { + var intervalId, currentTimeOut, hideTime; + + toastCtrl.progressBar = scope; + + scope.start = function(duration) { + if (intervalId) { + clearInterval(intervalId); + } + + currentTimeOut = parseFloat(duration); + hideTime = new Date().getTime() + currentTimeOut; + intervalId = setInterval(updateProgress, 10); + }; + + scope.stop = function() { + if (intervalId) { + clearInterval(intervalId); + } + }; + + function updateProgress() { + var percentage = ((hideTime - (new Date().getTime())) / currentTimeOut) * 100; + element.css('width', percentage + '%'); + } + + scope.$on('$destroy', function() { + // Failsafe stop + clearInterval(intervalId); + }); + } + } +}()); + +(function() { + 'use strict'; + + angular.module('toastr') + .controller('ToastController', ToastController); + + function ToastController() { + this.progressBar = null; + + this.startProgressBar = function(duration) { + if (this.progressBar) { + this.progressBar.start(duration); + } + }; + + this.stopProgressBar = function() { + if (this.progressBar) { + this.progressBar.stop(); + } + }; + } +}()); + +(function() { + 'use strict'; + + angular.module('toastr') + .directive('toast', toast); + + toast.$inject = ['$injector', '$interval', 'toastrConfig', 'toastr']; + + function toast($injector, $interval, toastrConfig, toastr) { + return { + templateUrl: function() { + return toastrConfig.templates.toast; + }, + controller: 'ToastController', + link: toastLinkFunction + }; + + function toastLinkFunction(scope, element, attrs, toastCtrl) { + var timeout; + + scope.toastClass = scope.options.toastClass; + scope.titleClass = scope.options.titleClass; + scope.messageClass = scope.options.messageClass; + scope.progressBar = scope.options.progressBar; + + if (wantsCloseButton()) { + var button = angular.element(scope.options.closeHtml), + $compile = $injector.get('$compile'); + button.addClass('toast-close-button'); + button.attr('ng-click', 'close(true, $event)'); + $compile(button)(scope); + element.children().prepend(button); + } + + scope.init = function() { + if (scope.options.timeOut) { + timeout = createTimeout(scope.options.timeOut); + } + if (scope.options.onShown) { + scope.options.onShown(); + } + }; + + element.on('mouseenter', function() { + hideAndStopProgressBar(); + if (timeout) { + $interval.cancel(timeout); + } + }); + + scope.tapToast = function () { + if (angular.isFunction(scope.options.onTap)) { + scope.options.onTap(); + } + if (scope.options.tapToDismiss) { + scope.close(true); + } + }; + + scope.close = function (wasClicked, $event) { + if ($event && angular.isFunction($event.stopPropagation)) { + $event.stopPropagation(); + } + toastr.remove(scope.toastId, wasClicked); + }; + + scope.refreshTimer = function(newTime) { + if (timeout) { + $interval.cancel(timeout); + timeout = createTimeout(newTime || scope.options.timeOut); + } + }; + + element.on('mouseleave', function() { + if (scope.options.timeOut === 0 && scope.options.extendedTimeOut === 0) { return; } + scope.$apply(function() { + scope.progressBar = scope.options.progressBar; + }); + timeout = createTimeout(scope.options.extendedTimeOut); + }); + + function createTimeout(time) { + toastCtrl.startProgressBar(time); + return $interval(function() { + toastCtrl.stopProgressBar(); + toastr.remove(scope.toastId); + }, time, 1); + } + + function hideAndStopProgressBar() { + scope.progressBar = false; + toastCtrl.stopProgressBar(); + } + + function wantsCloseButton() { + return scope.options.closeHtml; + } + } + } +}()); + +angular.module("toastr").run(["$templateCache", function($templateCache) {$templateCache.put("directives/progressbar/progressbar.html","
\n"); +$templateCache.put("directives/toast/toast.html","
\n
\n
{{title}}
\n
{{message}}
\n
\n
\n
\n \n
\n");}]); +/*! + Papa Parse + v4.3.6 + https://github.com/mholt/PapaParse + License: MIT +*/ +(function(root, factory) +{ + if (typeof define === 'function' && define.amd) + { + // AMD. Register as an anonymous module. + define([], factory); + } + else if (typeof module === 'object' && typeof exports !== 'undefined') + { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } + else + { + // Browser globals (root is window) + root.Papa = factory(); + } +}(this, function() +{ + 'use strict'; + + var global = (function () { + // alternative method, similar to `Function('return this')()` + // but without using `eval` (which is disabled when + // using Content Security Policy). + + if (typeof self !== 'undefined') { return self; } + if (typeof window !== 'undefined') { return window; } + if (typeof global !== 'undefined') { return global; } + + // When running tests none of the above have been defined + return {}; + })(); + + + var IS_WORKER = !global.document && !!global.postMessage, + IS_PAPA_WORKER = IS_WORKER && /(\?|&)papaworker(=|&|$)/.test(global.location.search), + LOADED_SYNC = false, AUTO_SCRIPT_PATH; + var workers = {}, workerIdCounter = 0; + + var Papa = {}; + + Papa.parse = CsvToJson; + Papa.unparse = JsonToCsv; + + Papa.RECORD_SEP = String.fromCharCode(30); + Papa.UNIT_SEP = String.fromCharCode(31); + Papa.BYTE_ORDER_MARK = '\ufeff'; + Papa.BAD_DELIMITERS = ['\r', '\n', '"', Papa.BYTE_ORDER_MARK]; + Papa.WORKERS_SUPPORTED = !IS_WORKER && !!global.Worker; + Papa.SCRIPT_PATH = null; // Must be set by your code if you use workers and this lib is loaded asynchronously + + // Configurable chunk sizes for local and remote files, respectively + Papa.LocalChunkSize = 1024 * 1024 * 10; // 10 MB + Papa.RemoteChunkSize = 1024 * 1024 * 5; // 5 MB + Papa.DefaultDelimiter = ','; // Used if not specified and detection fails + + // Exposed for testing and development only + Papa.Parser = Parser; + Papa.ParserHandle = ParserHandle; + Papa.NetworkStreamer = NetworkStreamer; + Papa.FileStreamer = FileStreamer; + Papa.StringStreamer = StringStreamer; + Papa.ReadableStreamStreamer = ReadableStreamStreamer; + + if (global.jQuery) + { + var $ = global.jQuery; + $.fn.parse = function(options) + { + var config = options.config || {}; + var queue = []; + + this.each(function(idx) + { + var supported = $(this).prop('tagName').toUpperCase() === 'INPUT' + && $(this).attr('type').toLowerCase() === 'file' + && global.FileReader; + + if (!supported || !this.files || this.files.length === 0) + return true; // continue to next input element + + for (var i = 0; i < this.files.length; i++) + { + queue.push({ + file: this.files[i], + inputElem: this, + instanceConfig: $.extend({}, config) + }); + } + }); + + parseNextFile(); // begin parsing + return this; // maintains chainability + + + function parseNextFile() + { + if (queue.length === 0) + { + if (isFunction(options.complete)) + options.complete(); + return; + } + + var f = queue[0]; + + if (isFunction(options.before)) + { + var returned = options.before(f.file, f.inputElem); + + if (typeof returned === 'object') + { + if (returned.action === 'abort') + { + error('AbortError', f.file, f.inputElem, returned.reason); + return; // Aborts all queued files immediately + } + else if (returned.action === 'skip') + { + fileComplete(); // parse the next file in the queue, if any + return; + } + else if (typeof returned.config === 'object') + f.instanceConfig = $.extend(f.instanceConfig, returned.config); + } + else if (returned === 'skip') + { + fileComplete(); // parse the next file in the queue, if any + return; + } + } + + // Wrap up the user's complete callback, if any, so that ours also gets executed + var userCompleteFunc = f.instanceConfig.complete; + f.instanceConfig.complete = function(results) + { + if (isFunction(userCompleteFunc)) + userCompleteFunc(results, f.file, f.inputElem); + fileComplete(); + }; + + Papa.parse(f.file, f.instanceConfig); + } + + function error(name, file, elem, reason) + { + if (isFunction(options.error)) + options.error({name: name}, file, elem, reason); + } + + function fileComplete() + { + queue.splice(0, 1); + parseNextFile(); + } + } + } + + + if (IS_PAPA_WORKER) + { + global.onmessage = workerThreadReceivedMessage; + } + else if (Papa.WORKERS_SUPPORTED) + { + AUTO_SCRIPT_PATH = getScriptPath(); + + // Check if the script was loaded synchronously + if (!document.body) + { + // Body doesn't exist yet, must be synchronous + LOADED_SYNC = true; + } + else + { + document.addEventListener('DOMContentLoaded', function () { + LOADED_SYNC = true; + }, true); + } + } + + + + + function CsvToJson(_input, _config) + { + _config = _config || {}; + var dynamicTyping = _config.dynamicTyping || false; + if (isFunction(dynamicTyping)) { + _config.dynamicTypingFunction = dynamicTyping; + // Will be filled on first row call + dynamicTyping = {}; + } + _config.dynamicTyping = dynamicTyping; + + if (_config.worker && Papa.WORKERS_SUPPORTED) + { + var w = newWorker(); + + w.userStep = _config.step; + w.userChunk = _config.chunk; + w.userComplete = _config.complete; + w.userError = _config.error; + + _config.step = isFunction(_config.step); + _config.chunk = isFunction(_config.chunk); + _config.complete = isFunction(_config.complete); + _config.error = isFunction(_config.error); + delete _config.worker; // prevent infinite loop + + w.postMessage({ + input: _input, + config: _config, + workerId: w.id + }); + + return; + } + + var streamer = null; + if (typeof _input === 'string') + { + if (_config.download) + streamer = new NetworkStreamer(_config); + else + streamer = new StringStreamer(_config); + } + else if (_input.readable === true && isFunction(_input.read) && isFunction(_input.on)) + { + streamer = new ReadableStreamStreamer(_config); + } + else if ((global.File && _input instanceof File) || _input instanceof Object) // ...Safari. (see issue #106) + streamer = new FileStreamer(_config); + + return streamer.stream(_input); + } + + + + + + + function JsonToCsv(_input, _config) + { + var _output = ''; + var _fields = []; + + // Default configuration + + /** whether to surround every datum with quotes */ + var _quotes = false; + + /** whether to write headers */ + var _writeHeader = true; + + /** delimiting character */ + var _delimiter = ','; + + /** newline character(s) */ + var _newline = '\r\n'; + + /** quote character */ + var _quoteChar = '"'; + + unpackConfig(); + + var quoteCharRegex = new RegExp(_quoteChar, 'g'); + + if (typeof _input === 'string') + _input = JSON.parse(_input); + + if (_input instanceof Array) + { + if (!_input.length || _input[0] instanceof Array) + return serialize(null, _input); + else if (typeof _input[0] === 'object') + return serialize(objectKeys(_input[0]), _input); + } + else if (typeof _input === 'object') + { + if (typeof _input.data === 'string') + _input.data = JSON.parse(_input.data); + + if (_input.data instanceof Array) + { + if (!_input.fields) + _input.fields = _input.meta && _input.meta.fields; + + if (!_input.fields) + _input.fields = _input.data[0] instanceof Array + ? _input.fields + : objectKeys(_input.data[0]); + + if (!(_input.data[0] instanceof Array) && typeof _input.data[0] !== 'object') + _input.data = [_input.data]; // handles input like [1,2,3] or ['asdf'] + } + + return serialize(_input.fields || [], _input.data || []); + } + + // Default (any valid paths should return before this) + throw 'exception: Unable to serialize unrecognized input'; + + + function unpackConfig() + { + if (typeof _config !== 'object') + return; + + if (typeof _config.delimiter === 'string' + && _config.delimiter.length === 1 + && Papa.BAD_DELIMITERS.indexOf(_config.delimiter) === -1) + { + _delimiter = _config.delimiter; + } + + if (typeof _config.quotes === 'boolean' + || _config.quotes instanceof Array) + _quotes = _config.quotes; + + if (typeof _config.newline === 'string') + _newline = _config.newline; + + if (typeof _config.quoteChar === 'string') + _quoteChar = _config.quoteChar; + + if (typeof _config.header === 'boolean') + _writeHeader = _config.header; + } + + + /** Turns an object's keys into an array */ + function objectKeys(obj) + { + if (typeof obj !== 'object') + return []; + var keys = []; + for (var key in obj) + keys.push(key); + return keys; + } + + /** The double for loop that iterates the data and writes out a CSV string including header row */ + function serialize(fields, data) + { + var csv = ''; + + if (typeof fields === 'string') + fields = JSON.parse(fields); + if (typeof data === 'string') + data = JSON.parse(data); + + var hasHeader = fields instanceof Array && fields.length > 0; + var dataKeyedByField = !(data[0] instanceof Array); + + // If there a header row, write it first + if (hasHeader && _writeHeader) + { + for (var i = 0; i < fields.length; i++) + { + if (i > 0) + csv += _delimiter; + csv += safe(fields[i], i); + } + if (data.length > 0) + csv += _newline; + } + + // Then write out the data + for (var row = 0; row < data.length; row++) + { + var maxCol = hasHeader ? fields.length : data[row].length; + + for (var col = 0; col < maxCol; col++) + { + if (col > 0) + csv += _delimiter; + var colIdx = hasHeader && dataKeyedByField ? fields[col] : col; + csv += safe(data[row][colIdx], col); + } + + if (row < data.length - 1) + csv += _newline; + } + + return csv; + } + + /** Encloses a value around quotes if needed (makes a value safe for CSV insertion) */ + function safe(str, col) + { + if (typeof str === 'undefined' || str === null) + return ''; + + str = str.toString().replace(quoteCharRegex, _quoteChar+_quoteChar); + + var needsQuotes = (typeof _quotes === 'boolean' && _quotes) + || (_quotes instanceof Array && _quotes[col]) + || hasAny(str, Papa.BAD_DELIMITERS) + || str.indexOf(_delimiter) > -1 + || str.charAt(0) === ' ' + || str.charAt(str.length - 1) === ' '; + + return needsQuotes ? _quoteChar + str + _quoteChar : str; + } + + function hasAny(str, substrings) + { + for (var i = 0; i < substrings.length; i++) + if (str.indexOf(substrings[i]) > -1) + return true; + return false; + } + } + + /** ChunkStreamer is the base prototype for various streamer implementations. */ + function ChunkStreamer(config) + { + this._handle = null; + this._paused = false; + this._finished = false; + this._input = null; + this._baseIndex = 0; + this._partialLine = ''; + this._rowCount = 0; + this._start = 0; + this._nextChunk = null; + this.isFirstChunk = true; + this._completeResults = { + data: [], + errors: [], + meta: {} + }; + replaceConfig.call(this, config); + + this.parseChunk = function(chunk) + { + // First chunk pre-processing + if (this.isFirstChunk && isFunction(this._config.beforeFirstChunk)) + { + var modifiedChunk = this._config.beforeFirstChunk(chunk); + if (modifiedChunk !== undefined) + chunk = modifiedChunk; + } + this.isFirstChunk = false; + + // Rejoin the line we likely just split in two by chunking the file + var aggregate = this._partialLine + chunk; + this._partialLine = ''; + + var results = this._handle.parse(aggregate, this._baseIndex, !this._finished); + + if (this._handle.paused() || this._handle.aborted()) + return; + + var lastIndex = results.meta.cursor; + + if (!this._finished) + { + this._partialLine = aggregate.substring(lastIndex - this._baseIndex); + this._baseIndex = lastIndex; + } + + if (results && results.data) + this._rowCount += results.data.length; + + var finishedIncludingPreview = this._finished || (this._config.preview && this._rowCount >= this._config.preview); + + if (IS_PAPA_WORKER) + { + global.postMessage({ + results: results, + workerId: Papa.WORKER_ID, + finished: finishedIncludingPreview + }); + } + else if (isFunction(this._config.chunk)) + { + this._config.chunk(results, this._handle); + if (this._paused) + return; + results = undefined; + this._completeResults = undefined; + } + + if (!this._config.step && !this._config.chunk) { + this._completeResults.data = this._completeResults.data.concat(results.data); + this._completeResults.errors = this._completeResults.errors.concat(results.errors); + this._completeResults.meta = results.meta; + } + + if (finishedIncludingPreview && isFunction(this._config.complete) && (!results || !results.meta.aborted)) + this._config.complete(this._completeResults, this._input); + + if (!finishedIncludingPreview && (!results || !results.meta.paused)) + this._nextChunk(); + + return results; + }; + + this._sendError = function(error) + { + if (isFunction(this._config.error)) + this._config.error(error); + else if (IS_PAPA_WORKER && this._config.error) + { + global.postMessage({ + workerId: Papa.WORKER_ID, + error: error, + finished: false + }); + } + }; + + function replaceConfig(config) + { + // Deep-copy the config so we can edit it + var configCopy = copy(config); + configCopy.chunkSize = parseInt(configCopy.chunkSize); // parseInt VERY important so we don't concatenate strings! + if (!config.step && !config.chunk) + configCopy.chunkSize = null; // disable Range header if not streaming; bad values break IIS - see issue #196 + this._handle = new ParserHandle(configCopy); + this._handle.streamer = this; + this._config = configCopy; // persist the copy to the caller + } + } + + + function NetworkStreamer(config) + { + config = config || {}; + if (!config.chunkSize) + config.chunkSize = Papa.RemoteChunkSize; + ChunkStreamer.call(this, config); + + var xhr; + + if (IS_WORKER) + { + this._nextChunk = function() + { + this._readChunk(); + this._chunkLoaded(); + }; + } + else + { + this._nextChunk = function() + { + this._readChunk(); + }; + } + + this.stream = function(url) + { + this._input = url; + this._nextChunk(); // Starts streaming + }; + + this._readChunk = function() + { + if (this._finished) + { + this._chunkLoaded(); + return; + } + + xhr = new XMLHttpRequest(); + + if (this._config.withCredentials) + { + xhr.withCredentials = this._config.withCredentials; + } + + if (!IS_WORKER) + { + xhr.onload = bindFunction(this._chunkLoaded, this); + xhr.onerror = bindFunction(this._chunkError, this); + } + + xhr.open('GET', this._input, !IS_WORKER); + // Headers can only be set when once the request state is OPENED + if (this._config.downloadRequestHeaders) + { + var headers = this._config.downloadRequestHeaders; + + for (var headerName in headers) + { + xhr.setRequestHeader(headerName, headers[headerName]); + } + } + + if (this._config.chunkSize) + { + var end = this._start + this._config.chunkSize - 1; // minus one because byte range is inclusive + xhr.setRequestHeader('Range', 'bytes='+this._start+'-'+end); + xhr.setRequestHeader('If-None-Match', 'webkit-no-cache'); // https://bugs.webkit.org/show_bug.cgi?id=82672 + } + + try { + xhr.send(); + } + catch (err) { + this._chunkError(err.message); + } + + if (IS_WORKER && xhr.status === 0) + this._chunkError(); + else + this._start += this._config.chunkSize; + } + + this._chunkLoaded = function() + { + if (xhr.readyState != 4) + return; + + if (xhr.status < 200 || xhr.status >= 400) + { + this._chunkError(); + return; + } + + this._finished = !this._config.chunkSize || this._start > getFileSize(xhr); + this.parseChunk(xhr.responseText); + } + + this._chunkError = function(errorMessage) + { + var errorText = xhr.statusText || errorMessage; + this._sendError(errorText); + } + + function getFileSize(xhr) + { + var contentRange = xhr.getResponseHeader('Content-Range'); + if (contentRange === null) { // no content range, then finish! + return -1; + } + return parseInt(contentRange.substr(contentRange.lastIndexOf('/') + 1)); + } + } + NetworkStreamer.prototype = Object.create(ChunkStreamer.prototype); + NetworkStreamer.prototype.constructor = NetworkStreamer; + + + function FileStreamer(config) + { + config = config || {}; + if (!config.chunkSize) + config.chunkSize = Papa.LocalChunkSize; + ChunkStreamer.call(this, config); + + var reader, slice; + + // FileReader is better than FileReaderSync (even in worker) - see http://stackoverflow.com/q/24708649/1048862 + // But Firefox is a pill, too - see issue #76: https://github.com/mholt/PapaParse/issues/76 + var usingAsyncReader = typeof FileReader !== 'undefined'; // Safari doesn't consider it a function - see issue #105 + + this.stream = function(file) + { + this._input = file; + slice = file.slice || file.webkitSlice || file.mozSlice; + + if (usingAsyncReader) + { + reader = new FileReader(); // Preferred method of reading files, even in workers + reader.onload = bindFunction(this._chunkLoaded, this); + reader.onerror = bindFunction(this._chunkError, this); + } + else + reader = new FileReaderSync(); // Hack for running in a web worker in Firefox + + this._nextChunk(); // Starts streaming + }; + + this._nextChunk = function() + { + if (!this._finished && (!this._config.preview || this._rowCount < this._config.preview)) + this._readChunk(); + } + + this._readChunk = function() + { + var input = this._input; + if (this._config.chunkSize) + { + var end = Math.min(this._start + this._config.chunkSize, this._input.size); + input = slice.call(input, this._start, end); + } + var txt = reader.readAsText(input, this._config.encoding); + if (!usingAsyncReader) + this._chunkLoaded({ target: { result: txt } }); // mimic the async signature + } + + this._chunkLoaded = function(event) + { + // Very important to increment start each time before handling results + this._start += this._config.chunkSize; + this._finished = !this._config.chunkSize || this._start >= this._input.size; + this.parseChunk(event.target.result); + } + + this._chunkError = function() + { + this._sendError(reader.error); + } + + } + FileStreamer.prototype = Object.create(ChunkStreamer.prototype); + FileStreamer.prototype.constructor = FileStreamer; + + + function StringStreamer(config) + { + config = config || {}; + ChunkStreamer.call(this, config); + + var string; + var remaining; + this.stream = function(s) + { + string = s; + remaining = s; + return this._nextChunk(); + } + this._nextChunk = function() + { + if (this._finished) return; + var size = this._config.chunkSize; + var chunk = size ? remaining.substr(0, size) : remaining; + remaining = size ? remaining.substr(size) : ''; + this._finished = !remaining; + return this.parseChunk(chunk); + } + } + StringStreamer.prototype = Object.create(StringStreamer.prototype); + StringStreamer.prototype.constructor = StringStreamer; + + + function ReadableStreamStreamer(config) + { + config = config || {}; + + ChunkStreamer.call(this, config); + + var queue = []; + var parseOnData = true; + + this.stream = function(stream) + { + this._input = stream; + + this._input.on('data', this._streamData); + this._input.on('end', this._streamEnd); + this._input.on('error', this._streamError); + } + + this._nextChunk = function() + { + if (queue.length) + { + this.parseChunk(queue.shift()); + } + else + { + parseOnData = true; + } + } + + this._streamData = bindFunction(function(chunk) + { + try + { + queue.push(typeof chunk === 'string' ? chunk : chunk.toString(this._config.encoding)); + + if (parseOnData) + { + parseOnData = false; + this.parseChunk(queue.shift()); + } + } + catch (error) + { + this._streamError(error); + } + }, this); + + this._streamError = bindFunction(function(error) + { + this._streamCleanUp(); + this._sendError(error.message); + }, this); + + this._streamEnd = bindFunction(function() + { + this._streamCleanUp(); + this._finished = true; + this._streamData(''); + }, this); + + this._streamCleanUp = bindFunction(function() + { + this._input.removeListener('data', this._streamData); + this._input.removeListener('end', this._streamEnd); + this._input.removeListener('error', this._streamError); + }, this); + } + ReadableStreamStreamer.prototype = Object.create(ChunkStreamer.prototype); + ReadableStreamStreamer.prototype.constructor = ReadableStreamStreamer; + + + // Use one ParserHandle per entire CSV file or string + function ParserHandle(_config) + { + // One goal is to minimize the use of regular expressions... + var FLOAT = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i; + + var self = this; + var _stepCounter = 0; // Number of times step was called (number of rows parsed) + var _input; // The input being parsed + var _parser; // The core parser being used + var _paused = false; // Whether we are paused or not + var _aborted = false; // Whether the parser has aborted or not + var _delimiterError; // Temporary state between delimiter detection and processing results + var _fields = []; // Fields are from the header row of the input, if there is one + var _results = { // The last results returned from the parser + data: [], + errors: [], + meta: {} + }; + + if (isFunction(_config.step)) + { + var userStep = _config.step; + _config.step = function(results) + { + _results = results; + + if (needsHeaderRow()) + processResults(); + else // only call user's step function after header row + { + processResults(); + + // It's possbile that this line was empty and there's no row here after all + if (_results.data.length === 0) + return; + + _stepCounter += results.data.length; + if (_config.preview && _stepCounter > _config.preview) + _parser.abort(); + else + userStep(_results, self); + } + }; + } + + /** + * Parses input. Most users won't need, and shouldn't mess with, the baseIndex + * and ignoreLastRow parameters. They are used by streamers (wrapper functions) + * when an input comes in multiple chunks, like from a file. + */ + this.parse = function(input, baseIndex, ignoreLastRow) + { + if (!_config.newline) + _config.newline = guessLineEndings(input); + + _delimiterError = false; + if (!_config.delimiter) + { + var delimGuess = guessDelimiter(input, _config.newline, _config.skipEmptyLines); + if (delimGuess.successful) + _config.delimiter = delimGuess.bestDelimiter; + else + { + _delimiterError = true; // add error after parsing (otherwise it would be overwritten) + _config.delimiter = Papa.DefaultDelimiter; + } + _results.meta.delimiter = _config.delimiter; + } + else if(isFunction(_config.delimiter)) + { + _config.delimiter = _config.delimiter(input); + _results.meta.delimiter = _config.delimiter; + } + + var parserConfig = copy(_config); + if (_config.preview && _config.header) + parserConfig.preview++; // to compensate for header row + + _input = input; + _parser = new Parser(parserConfig); + _results = _parser.parse(_input, baseIndex, ignoreLastRow); + processResults(); + return _paused ? { meta: { paused: true } } : (_results || { meta: { paused: false } }); + }; + + this.paused = function() + { + return _paused; + }; + + this.pause = function() + { + _paused = true; + _parser.abort(); + _input = _input.substr(_parser.getCharIndex()); + }; + + this.resume = function() + { + _paused = false; + self.streamer.parseChunk(_input); + }; + + this.aborted = function () + { + return _aborted; + }; + + this.abort = function() + { + _aborted = true; + _parser.abort(); + _results.meta.aborted = true; + if (isFunction(_config.complete)) + _config.complete(_results); + _input = ''; + }; + + function processResults() + { + if (_results && _delimiterError) + { + addError('Delimiter', 'UndetectableDelimiter', 'Unable to auto-detect delimiting character; defaulted to \''+Papa.DefaultDelimiter+'\''); + _delimiterError = false; + } + + if (_config.skipEmptyLines) + { + for (var i = 0; i < _results.data.length; i++) + if (_results.data[i].length === 1 && _results.data[i][0] === '') + _results.data.splice(i--, 1); + } + + if (needsHeaderRow()) + fillHeaderFields(); + + return applyHeaderAndDynamicTyping(); + } + + function needsHeaderRow() + { + return _config.header && _fields.length === 0; + } + + function fillHeaderFields() + { + if (!_results) + return; + for (var i = 0; needsHeaderRow() && i < _results.data.length; i++) + for (var j = 0; j < _results.data[i].length; j++) + _fields.push(_results.data[i][j]); + _results.data.splice(0, 1); + } + + function shouldApplyDynamicTyping(field) { + // Cache function values to avoid calling it for each row + if (_config.dynamicTypingFunction && _config.dynamicTyping[field] === undefined) { + _config.dynamicTyping[field] = _config.dynamicTypingFunction(field); + } + return (_config.dynamicTyping[field] || _config.dynamicTyping) === true + } + + function parseDynamic(field, value) + { + if (shouldApplyDynamicTyping(field)) + { + if (value === 'true' || value === 'TRUE') + return true; + else if (value === 'false' || value === 'FALSE') + return false; + else + return tryParseFloat(value); + } + return value; + } + + function applyHeaderAndDynamicTyping() + { + if (!_results || (!_config.header && !_config.dynamicTyping)) + return _results; + + for (var i = 0; i < _results.data.length; i++) + { + var row = _config.header ? {} : []; + + for (var j = 0; j < _results.data[i].length; j++) + { + var field = j; + var value = _results.data[i][j]; + + if (_config.header) + field = j >= _fields.length ? '__parsed_extra' : _fields[j]; + + value = parseDynamic(field, value); + + if (field === '__parsed_extra') + { + row[field] = row[field] || []; + row[field].push(value); + } + else + row[field] = value; + } + + _results.data[i] = row; + + if (_config.header) + { + if (j > _fields.length) + addError('FieldMismatch', 'TooManyFields', 'Too many fields: expected ' + _fields.length + ' fields but parsed ' + j, i); + else if (j < _fields.length) + addError('FieldMismatch', 'TooFewFields', 'Too few fields: expected ' + _fields.length + ' fields but parsed ' + j, i); + } + } + + if (_config.header && _results.meta) + _results.meta.fields = _fields; + return _results; + } + + function guessDelimiter(input, newline, skipEmptyLines) + { + var delimChoices = [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP]; + var bestDelim, bestDelta, fieldCountPrevRow; + + for (var i = 0; i < delimChoices.length; i++) + { + var delim = delimChoices[i]; + var delta = 0, avgFieldCount = 0, emptyLinesCount = 0; + fieldCountPrevRow = undefined; + + var preview = new Parser({ + delimiter: delim, + newline: newline, + preview: 10 + }).parse(input); + + for (var j = 0; j < preview.data.length; j++) + { + if (skipEmptyLines && preview.data[j].length === 1 && preview.data[j][0].length === 0) { + emptyLinesCount++ + continue + } + var fieldCount = preview.data[j].length; + avgFieldCount += fieldCount; + + if (typeof fieldCountPrevRow === 'undefined') + { + fieldCountPrevRow = fieldCount; + continue; + } + else if (fieldCount > 1) + { + delta += Math.abs(fieldCount - fieldCountPrevRow); + fieldCountPrevRow = fieldCount; + } + } + + if (preview.data.length > 0) + avgFieldCount /= (preview.data.length - emptyLinesCount); + + if ((typeof bestDelta === 'undefined' || delta < bestDelta) + && avgFieldCount > 1.99) + { + bestDelta = delta; + bestDelim = delim; + } + } + + _config.delimiter = bestDelim; + + return { + successful: !!bestDelim, + bestDelimiter: bestDelim + } + } + + function guessLineEndings(input) + { + input = input.substr(0, 1024*1024); // max length 1 MB + + var r = input.split('\r'); + + var n = input.split('\n'); + + var nAppearsFirst = (n.length > 1 && n[0].length < r[0].length); + + if (r.length === 1 || nAppearsFirst) + return '\n'; + + var numWithN = 0; + for (var i = 0; i < r.length; i++) + { + if (r[i][0] === '\n') + numWithN++; + } + + return numWithN >= r.length / 2 ? '\r\n' : '\r'; + } + + function tryParseFloat(val) + { + var isNumber = FLOAT.test(val); + return isNumber ? parseFloat(val) : val; + } + + function addError(type, code, msg, row) + { + _results.errors.push({ + type: type, + code: code, + message: msg, + row: row + }); + } + } + + + + + + /** The core parser implements speedy and correct CSV parsing */ + function Parser(config) + { + // Unpack the config object + config = config || {}; + var delim = config.delimiter; + var newline = config.newline; + var comments = config.comments; + var step = config.step; + var preview = config.preview; + var fastMode = config.fastMode; + var quoteChar = config.quoteChar || '"'; + + // Delimiter must be valid + if (typeof delim !== 'string' + || Papa.BAD_DELIMITERS.indexOf(delim) > -1) + delim = ','; + + // Comment character must be valid + if (comments === delim) + throw 'Comment character same as delimiter'; + else if (comments === true) + comments = '#'; + else if (typeof comments !== 'string' + || Papa.BAD_DELIMITERS.indexOf(comments) > -1) + comments = false; + + // Newline must be valid: \r, \n, or \r\n + if (newline != '\n' && newline != '\r' && newline != '\r\n') + newline = '\n'; + + // We're gonna need these at the Parser scope + var cursor = 0; + var aborted = false; + + this.parse = function(input, baseIndex, ignoreLastRow) + { + // For some reason, in Chrome, this speeds things up (!?) + if (typeof input !== 'string') + throw 'Input must be a string'; + + // We don't need to compute some of these every time parse() is called, + // but having them in a more local scope seems to perform better + var inputLen = input.length, + delimLen = delim.length, + newlineLen = newline.length, + commentsLen = comments.length; + var stepIsFunction = isFunction(step); + + // Establish starting state + cursor = 0; + var data = [], errors = [], row = [], lastCursor = 0; + + if (!input) + return returnable(); + + if (fastMode || (fastMode !== false && input.indexOf(quoteChar) === -1)) + { + var rows = input.split(newline); + for (var i = 0; i < rows.length; i++) + { + var row = rows[i]; + cursor += row.length; + if (i !== rows.length - 1) + cursor += newline.length; + else if (ignoreLastRow) + return returnable(); + if (comments && row.substr(0, commentsLen) === comments) + continue; + if (stepIsFunction) + { + data = []; + pushRow(row.split(delim)); + doStep(); + if (aborted) + return returnable(); + } + else + pushRow(row.split(delim)); + if (preview && i >= preview) + { + data = data.slice(0, preview); + return returnable(true); + } + } + return returnable(); + } + + var nextDelim = input.indexOf(delim, cursor); + var nextNewline = input.indexOf(newline, cursor); + var quoteCharRegex = new RegExp(quoteChar+quoteChar, 'g'); + + // Parser loop + for (;;) + { + // Field has opening quote + if (input[cursor] === quoteChar) + { + // Start our search for the closing quote where the cursor is + var quoteSearch = cursor; + + // Skip the opening quote + cursor++; + + for (;;) + { + // Find closing quote + var quoteSearch = input.indexOf(quoteChar, quoteSearch+1); + + //No other quotes are found - no other delimiters + if (quoteSearch === -1) + { + if (!ignoreLastRow) { + // No closing quote... what a pity + errors.push({ + type: 'Quotes', + code: 'MissingQuotes', + message: 'Quoted field unterminated', + row: data.length, // row has yet to be inserted + index: cursor + }); + } + return finish(); + } + + // Closing quote at EOF + if (quoteSearch === inputLen-1) + { + var value = input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar); + return finish(value); + } + + // If this quote is escaped, it's part of the data; skip it + if (input[quoteSearch+1] === quoteChar) + { + quoteSearch++; + continue; + } + + // Closing quote followed by delimiter + if (input[quoteSearch+1] === delim) + { + row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar)); + cursor = quoteSearch + 1 + delimLen; + nextDelim = input.indexOf(delim, cursor); + nextNewline = input.indexOf(newline, cursor); + break; + } + + // Closing quote followed by newline + if (input.substr(quoteSearch+1, newlineLen) === newline) + { + row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar)); + saveRow(quoteSearch + 1 + newlineLen); + nextDelim = input.indexOf(delim, cursor); // because we may have skipped the nextDelim in the quoted field + + if (stepIsFunction) + { + doStep(); + if (aborted) + return returnable(); + } + + if (preview && data.length >= preview) + return returnable(true); + + break; + } + + + // Checks for valid closing quotes are complete (escaped quotes or quote followed by EOF/delimiter/newline) -- assume these quotes are part of an invalid text string + errors.push({ + type: 'Quotes', + code: 'InvalidQuotes', + message: 'Trailing quote on quoted field is malformed', + row: data.length, // row has yet to be inserted + index: cursor + }); + + quoteSearch++; + continue; + + } + + continue; + } + + // Comment found at start of new line + if (comments && row.length === 0 && input.substr(cursor, commentsLen) === comments) + { + if (nextNewline === -1) // Comment ends at EOF + return returnable(); + cursor = nextNewline + newlineLen; + nextNewline = input.indexOf(newline, cursor); + nextDelim = input.indexOf(delim, cursor); + continue; + } + + // Next delimiter comes before next newline, so we've reached end of field + if (nextDelim !== -1 && (nextDelim < nextNewline || nextNewline === -1)) + { + row.push(input.substring(cursor, nextDelim)); + cursor = nextDelim + delimLen; + nextDelim = input.indexOf(delim, cursor); + continue; + } + + // End of row + if (nextNewline !== -1) + { + row.push(input.substring(cursor, nextNewline)); + saveRow(nextNewline + newlineLen); + + if (stepIsFunction) + { + doStep(); + if (aborted) + return returnable(); + } + + if (preview && data.length >= preview) + return returnable(true); + + continue; + } + + break; + } + + + return finish(); + + + function pushRow(row) + { + data.push(row); + lastCursor = cursor; + } + + /** + * Appends the remaining input from cursor to the end into + * row, saves the row, calls step, and returns the results. + */ + function finish(value) + { + if (ignoreLastRow) + return returnable(); + if (typeof value === 'undefined') + value = input.substr(cursor); + row.push(value); + cursor = inputLen; // important in case parsing is paused + pushRow(row); + if (stepIsFunction) + doStep(); + return returnable(); + } + + /** + * Appends the current row to the results. It sets the cursor + * to newCursor and finds the nextNewline. The caller should + * take care to execute user's step function and check for + * preview and end parsing if necessary. + */ + function saveRow(newCursor) + { + cursor = newCursor; + pushRow(row); + row = []; + nextNewline = input.indexOf(newline, cursor); + } + + /** Returns an object with the results, errors, and meta. */ + function returnable(stopped) + { + return { + data: data, + errors: errors, + meta: { + delimiter: delim, + linebreak: newline, + aborted: aborted, + truncated: !!stopped, + cursor: lastCursor + (baseIndex || 0) + } + }; + } + + /** Executes the user's step function and resets data & errors. */ + function doStep() + { + step(returnable()); + data = [], errors = []; + } + }; + + /** Sets the abort flag */ + this.abort = function() + { + aborted = true; + }; + + /** Gets the cursor position */ + this.getCharIndex = function() + { + return cursor; + }; + } + + + // If you need to load Papa Parse asynchronously and you also need worker threads, hard-code + // the script path here. See: https://github.com/mholt/PapaParse/issues/87#issuecomment-57885358 + function getScriptPath() + { + var scripts = document.getElementsByTagName('script'); + return scripts.length ? scripts[scripts.length - 1].src : ''; + } + + function newWorker() + { + if (!Papa.WORKERS_SUPPORTED) + return false; + if (!LOADED_SYNC && Papa.SCRIPT_PATH === null) + throw new Error( + 'Script path cannot be determined automatically when Papa Parse is loaded asynchronously. ' + + 'You need to set Papa.SCRIPT_PATH manually.' + ); + var workerUrl = Papa.SCRIPT_PATH || AUTO_SCRIPT_PATH; + // Append 'papaworker' to the search string to tell papaparse that this is our worker. + workerUrl += (workerUrl.indexOf('?') !== -1 ? '&' : '?') + 'papaworker'; + var w = new global.Worker(workerUrl); + w.onmessage = mainThreadReceivedMessage; + w.id = workerIdCounter++; + workers[w.id] = w; + return w; + } + + /** Callback when main thread receives a message */ + function mainThreadReceivedMessage(e) + { + var msg = e.data; + var worker = workers[msg.workerId]; + var aborted = false; + + if (msg.error) + worker.userError(msg.error, msg.file); + else if (msg.results && msg.results.data) + { + var abort = function() { + aborted = true; + completeWorker(msg.workerId, { data: [], errors: [], meta: { aborted: true } }); + }; + + var handle = { + abort: abort, + pause: notImplemented, + resume: notImplemented + }; + + if (isFunction(worker.userStep)) + { + for (var i = 0; i < msg.results.data.length; i++) + { + worker.userStep({ + data: [msg.results.data[i]], + errors: msg.results.errors, + meta: msg.results.meta + }, handle); + if (aborted) + break; + } + delete msg.results; // free memory ASAP + } + else if (isFunction(worker.userChunk)) + { + worker.userChunk(msg.results, handle, msg.file); + delete msg.results; + } + } + + if (msg.finished && !aborted) + completeWorker(msg.workerId, msg.results); + } + + function completeWorker(workerId, results) { + var worker = workers[workerId]; + if (isFunction(worker.userComplete)) + worker.userComplete(results); + worker.terminate(); + delete workers[workerId]; + } + + function notImplemented() { + throw 'Not implemented.'; + } + + /** Callback when worker thread receives a message */ + function workerThreadReceivedMessage(e) + { + var msg = e.data; + + if (typeof Papa.WORKER_ID === 'undefined' && msg) + Papa.WORKER_ID = msg.workerId; + + if (typeof msg.input === 'string') + { + global.postMessage({ + workerId: Papa.WORKER_ID, + results: Papa.parse(msg.input, msg.config), + finished: true + }); + } + else if ((global.File && msg.input instanceof File) || msg.input instanceof Object) // thank you, Safari (see issue #106) + { + var results = Papa.parse(msg.input, msg.config); + if (results) + global.postMessage({ + workerId: Papa.WORKER_ID, + results: results, + finished: true + }); + } + } + + /** Makes a deep copy of an array or object (mostly) */ + function copy(obj) + { + if (typeof obj !== 'object') + return obj; + var cpy = obj instanceof Array ? [] : {}; + for (var key in obj) + cpy[key] = copy(obj[key]); + return cpy; + } + + function bindFunction(f, self) + { + return function() { f.apply(self, arguments); }; + } + + function isFunction(func) + { + return typeof func === 'function'; + } + + return Papa; +})); + +(function (root, factory) { + 'use strict'; + + if (typeof define === 'function' && define.amd) { + define(['angular'], factory); + } else if (root.hasOwnProperty('angular')) { + // Browser globals (root is window), we don't register it. + factory(root.angular); + } else if (typeof exports === 'object') { + module.exports = factory(require('angular')); + } +}(this , function (angular) { + 'use strict'; + + // In cases where Angular does not get passed or angular is a truthy value + // but misses .module we can fall back to using window. + angular = (angular && angular.module ) ? angular : window.angular; + + + function isStorageSupported($window, storageType) { + + // Some installations of IE, for an unknown reason, throw "SCRIPT5: Error: Access is denied" + // when accessing window.localStorage. This happens before you try to do anything with it. Catch + // that error and allow execution to continue. + + // fix 'SecurityError: DOM Exception 18' exception in Desktop Safari, Mobile Safari + // when "Block cookies": "Always block" is turned on + var supported; + try { + supported = $window[storageType]; + } + catch(err) { + supported = false; + } + + // When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage and sessionStorage + // is available, but trying to call .setItem throws an exception below: + // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota." + if(supported) { + var key = '__' + Math.round(Math.random() * 1e7); + try { + $window[storageType].setItem(key, key); + $window[storageType].removeItem(key, key); + } + catch(err) { + supported = false; + } + } + + return supported; + } + + /** + * @ngdoc overview + * @name ngStorage + */ + + return angular.module('ngStorage', []) + + /** + * @ngdoc object + * @name ngStorage.$localStorage + * @requires $rootScope + * @requires $window + */ + + .provider('$localStorage', _storageProvider('localStorage')) + + /** + * @ngdoc object + * @name ngStorage.$sessionStorage + * @requires $rootScope + * @requires $window + */ + + .provider('$sessionStorage', _storageProvider('sessionStorage')); + + function _storageProvider(storageType) { + var providerWebStorage = isStorageSupported(window, storageType); + + return function () { + var storageKeyPrefix = 'ngStorage-'; + + this.setKeyPrefix = function (prefix) { + if (typeof prefix !== 'string') { + throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setKeyPrefix() expects a String.'); + } + storageKeyPrefix = prefix; + }; + + var serializer = angular.toJson; + var deserializer = angular.fromJson; + + this.setSerializer = function (s) { + if (typeof s !== 'function') { + throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setSerializer expects a function.'); + } + + serializer = s; + }; + + this.setDeserializer = function (d) { + if (typeof d !== 'function') { + throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setDeserializer expects a function.'); + } + + deserializer = d; + }; + + this.supported = function() { + return !!providerWebStorage; + }; + + // Note: This is not very elegant at all. + this.get = function (key) { + return providerWebStorage && deserializer(providerWebStorage.getItem(storageKeyPrefix + key)); + }; + + // Note: This is not very elegant at all. + this.set = function (key, value) { + return providerWebStorage && providerWebStorage.setItem(storageKeyPrefix + key, serializer(value)); + }; + + this.remove = function (key) { + providerWebStorage && providerWebStorage.removeItem(storageKeyPrefix + key); + } + + this.$get = [ + '$rootScope', + '$window', + '$log', + '$timeout', + '$document', + + function( + $rootScope, + $window, + $log, + $timeout, + $document + ){ + + // The magic number 10 is used which only works for some keyPrefixes... + // See https://github.com/gsklee/ngStorage/issues/137 + var prefixLength = storageKeyPrefix.length; + + // #9: Assign a placeholder object if Web Storage is unavailable to prevent breaking the entire AngularJS app + // Note: recheck mainly for testing (so we can use $window[storageType] rather than window[storageType]) + var isSupported = isStorageSupported($window, storageType), + webStorage = isSupported || ($log.warn('This browser does not support Web Storage!'), {setItem: angular.noop, getItem: angular.noop, removeItem: angular.noop}), + $storage = { + $default: function(items) { + for (var k in items) { + angular.isDefined($storage[k]) || ($storage[k] = angular.copy(items[k]) ); + } + + $storage.$sync(); + return $storage; + }, + $reset: function(items) { + for (var k in $storage) { + '$' === k[0] || (delete $storage[k] && webStorage.removeItem(storageKeyPrefix + k)); + } + + return $storage.$default(items); + }, + $sync: function () { + for (var i = 0, l = webStorage.length, k; i < l; i++) { + // #8, #10: `webStorage.key(i)` may be an empty string (or throw an exception in IE9 if `webStorage` is empty) + (k = webStorage.key(i)) && storageKeyPrefix === k.slice(0, prefixLength) && ($storage[k.slice(prefixLength)] = deserializer(webStorage.getItem(k))); + } + }, + $apply: function() { + var temp$storage; + + _debounce = null; + + if (!angular.equals($storage, _last$storage)) { + temp$storage = angular.copy(_last$storage); + angular.forEach($storage, function(v, k) { + if (angular.isDefined(v) && '$' !== k[0]) { + webStorage.setItem(storageKeyPrefix + k, serializer(v)); + delete temp$storage[k]; + } + }); + + for (var k in temp$storage) { + webStorage.removeItem(storageKeyPrefix + k); + } + + _last$storage = angular.copy($storage); + } + }, + $supported: function() { + return !!isSupported; + } + }, + _last$storage, + _debounce; + + $storage.$sync(); + + _last$storage = angular.copy($storage); + + $rootScope.$watch(function() { + _debounce || (_debounce = $timeout($storage.$apply, 100, false)); + }); + + // #6: Use `$window.addEventListener` instead of `angular.element` to avoid the jQuery-specific `event.originalEvent` + $window.addEventListener && $window.addEventListener('storage', function(event) { + if (!event.key) { + return; + } + + // Reference doc. + var doc = $document[0]; + + if ( (!doc.hasFocus || !doc.hasFocus()) && storageKeyPrefix === event.key.slice(0, prefixLength) ) { + event.newValue ? $storage[event.key.slice(prefixLength)] = deserializer(event.newValue) : delete $storage[event.key.slice(prefixLength)]; + + _last$storage = angular.copy($storage); + + $rootScope.$apply(); + } + }); + + $window.addEventListener && $window.addEventListener('beforeunload', function() { + $storage.$apply(); + }); + + return $storage; + } + ]; + }; + } + +})); + /*! AdminLTE app.js * ================ * Main JS application file for AdminLTE v2. This file diff --git a/u2f-connector.html b/u2f-connector.html index 4931cc2a..f48483cd 100644 --- a/u2f-connector.html +++ b/u2f-connector.html @@ -5,6 +5,6 @@ U2F Connector - + diff --git a/version.json b/version.json index a5ec78e6..9b35aea7 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "1.26.0" + "version": "1.27.0" } \ No newline at end of file