').html(template.trim()).contents();\n var linkFn = $compile(element);\n return {\n locals: locals,\n element: element,\n link: function link(scope) {\n locals.$scope = scope;\n if (controller) {\n var invokeCtrl = $controller(controller, locals, true);\n if (bindToController) {\n angular.extend(invokeCtrl.instance, locals);\n }\n var ctrl = angular.isObject(invokeCtrl) ? invokeCtrl : invokeCtrl();\n element.data('$ngControllerController', ctrl);\n element.children().data('$ngControllerController', ctrl);\n if (controllerAs) {\n scope[controllerAs] = ctrl;\n }\n }\n return linkFn.apply(null, arguments);\n }\n };\n });\n };\n function findElement(query, element) {\n return angular.element((element || document).querySelectorAll(query));\n }\n var fetchPromises = {};\n function fetchTemplate(template) {\n if (fetchPromises[template]) return fetchPromises[template];\n return fetchPromises[template] = $http.get(template, {\n cache: $templateCache\n }).then(function(res) {\n return res.data;\n });\n }\n }\n angular.module('mgcrea.ngStrap.dropdown', [ 'mgcrea.ngStrap.tooltip' ]).provider('$dropdown', function() {\n var defaults = this.defaults = {\n animation: 'am-fade',\n prefixClass: 'dropdown',\n prefixEvent: 'dropdown',\n placement: 'bottom-left',\n templateUrl: 'dropdown/dropdown.tpl.html',\n trigger: 'click',\n container: false,\n keyboard: true,\n html: false,\n delay: 0\n };\n this.$get = [ '$window', '$rootScope', '$tooltip', '$timeout', function($window, $rootScope, $tooltip, $timeout) {\n var bodyEl = angular.element($window.document.body);\n var matchesSelector = Element.prototype.matchesSelector || Element.prototype.webkitMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector;\n function DropdownFactory(element, config) {\n var $dropdown = {};\n var options = angular.extend({}, defaults, config);\n $dropdown.$scope = options.scope && options.scope.$new() || $rootScope.$new();\n $dropdown = $tooltip(element, options);\n var parentEl = element.parent();\n $dropdown.$onKeyDown = function(evt) {\n if (!/(38|40)/.test(evt.keyCode)) return;\n evt.preventDefault();\n evt.stopPropagation();\n var items = angular.element($dropdown.$element[0].querySelectorAll('li:not(.divider) a'));\n if (!items.length) return;\n var index;\n angular.forEach(items, function(el, i) {\n if (matchesSelector && matchesSelector.call(el, ':focus')) index = i;\n });\n if (evt.keyCode === 38 && index > 0) index--; else if (evt.keyCode === 40 && index < items.length - 1) index++; else if (angular.isUndefined(index)) index = 0;\n items.eq(index)[0].focus();\n };\n var show = $dropdown.show;\n $dropdown.show = function() {\n show();\n $timeout(function() {\n if (options.keyboard && $dropdown.$element) $dropdown.$element.on('keydown', $dropdown.$onKeyDown);\n bodyEl.on('click', onBodyClick);\n }, 0, false);\n if (parentEl.hasClass('dropdown')) parentEl.addClass('open');\n };\n var hide = $dropdown.hide;\n $dropdown.hide = function() {\n if (!$dropdown.$isShown) return;\n if (options.keyboard && $dropdown.$element) $dropdown.$element.off('keydown', $dropdown.$onKeyDown);\n bodyEl.off('click', onBodyClick);\n if (parentEl.hasClass('dropdown')) parentEl.removeClass('open');\n hide();\n };\n var destroy = $dropdown.destroy;\n $dropdown.destroy = function() {\n bodyEl.off('click', onBodyClick);\n destroy();\n };\n function onBodyClick(evt) {\n if (evt.target === element[0]) return;\n return evt.target !== element[0] && $dropdown.hide();\n }\n return $dropdown;\n }\n return DropdownFactory;\n } ];\n }).directive('bsDropdown', [ '$window', '$sce', '$dropdown', function($window, $sce, $dropdown) {\n return {\n restrict: 'EAC',\n scope: true,\n compile: function(tElement, tAttrs) {\n if (!tAttrs.bsDropdown) {\n var nextSibling = tElement[0].nextSibling;\n while (nextSibling && nextSibling.nodeType !== 1) {\n nextSibling = nextSibling.nextSibling;\n }\n if (nextSibling && nextSibling.className.split(' ').indexOf('dropdown-menu') >= 0) {\n tAttrs.template = nextSibling.outerHTML;\n tAttrs.templateUrl = undefined;\n nextSibling.parentNode.removeChild(nextSibling);\n }\n }\n return function postLink(scope, element, attr) {\n var options = {\n scope: scope\n };\n angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'id', 'autoClose' ], function(key) {\n if (angular.isDefined(tAttrs[key])) options[key] = tAttrs[key];\n });\n var falseValueRegExp = /^(false|0|)$/i;\n angular.forEach([ 'html', 'container' ], function(key) {\n if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;\n });\n angular.forEach([ 'onBeforeShow', 'onShow', 'onBeforeHide', 'onHide' ], function(key) {\n var bsKey = 'bs' + key.charAt(0).toUpperCase() + key.slice(1);\n if (angular.isDefined(attr[bsKey])) {\n options[key] = scope.$eval(attr[bsKey]);\n }\n });\n if (attr.bsDropdown) {\n scope.$watch(attr.bsDropdown, function(newValue, oldValue) {\n scope.content = newValue;\n }, true);\n }\n var dropdown = $dropdown(element, options);\n if (attr.bsShow) {\n scope.$watch(attr.bsShow, function(newValue, oldValue) {\n if (!dropdown || !angular.isDefined(newValue)) return;\n if (angular.isString(newValue)) newValue = !!newValue.match(/true|,?(dropdown),?/i);\n if (newValue === true) {\n dropdown.show();\n } else {\n dropdown.hide();\n }\n });\n }\n scope.$on('$destroy', function() {\n if (dropdown) dropdown.destroy();\n options = null;\n dropdown = null;\n });\n };\n }\n };\n } ]);\n angular.module('mgcrea.ngStrap.button', []).provider('$button', function() {\n var defaults = this.defaults = {\n activeClass: 'active',\n toggleEvent: 'click'\n };\n this.$get = function() {\n return {\n defaults: defaults\n };\n };\n }).directive('bsCheckboxGroup', function() {\n return {\n restrict: 'A',\n require: 'ngModel',\n compile: function postLink(element, attr) {\n element.attr('data-toggle', 'buttons');\n element.removeAttr('ng-model');\n var children = element[0].querySelectorAll('input[type=\"checkbox\"]');\n angular.forEach(children, function(child) {\n var childEl = angular.element(child);\n childEl.attr('bs-checkbox', '');\n childEl.attr('ng-model', attr.ngModel + '.' + childEl.attr('value'));\n });\n }\n };\n }).directive('bsCheckbox', [ '$button', '$$rAF', function($button, $$rAF) {\n var defaults = $button.defaults;\n var constantValueRegExp = /^(true|false|\\d+)$/;\n return {\n restrict: 'A',\n require: 'ngModel',\n link: function postLink(scope, element, attr, controller) {\n var options = defaults;\n var isInput = element[0].nodeName === 'INPUT';\n var activeElement = isInput ? element.parent() : element;\n var trueValue = angular.isDefined(attr.trueValue) ? attr.trueValue : true;\n if (constantValueRegExp.test(attr.trueValue)) {\n trueValue = scope.$eval(attr.trueValue);\n }\n var falseValue = angular.isDefined(attr.falseValue) ? attr.falseValue : false;\n if (constantValueRegExp.test(attr.falseValue)) {\n falseValue = scope.$eval(attr.falseValue);\n }\n var hasExoticValues = typeof trueValue !== 'boolean' || typeof falseValue !== 'boolean';\n if (hasExoticValues) {\n controller.$parsers.push(function(viewValue) {\n return viewValue ? trueValue : falseValue;\n });\n controller.$formatters.push(function(modelValue) {\n return angular.equals(modelValue, trueValue);\n });\n }\n controller.$render = function() {\n var isActive = !!controller.$viewValue;\n $$rAF(function() {\n if (isInput) element[0].checked = isActive;\n activeElement.toggleClass(options.activeClass, isActive);\n });\n };\n element.bind(options.toggleEvent, function() {\n scope.$apply(function() {\n if (!isInput) {\n controller.$setViewValue(!activeElement.hasClass('active'));\n }\n controller.$render();\n });\n });\n }\n };\n } ]).directive('bsRadioGroup', function() {\n return {\n restrict: 'A',\n require: 'ngModel',\n compile: function postLink(element, attr) {\n element.attr('data-toggle', 'buttons');\n element.removeAttr('ng-model');\n var children = element[0].querySelectorAll('input[type=\"radio\"]');\n angular.forEach(children, function(child) {\n angular.element(child).attr('bs-radio', '');\n angular.element(child).attr('ng-model', attr.ngModel);\n });\n }\n };\n }).directive('bsRadio', [ '$button', '$$rAF', function($button, $$rAF) {\n var defaults = $button.defaults;\n var constantValueRegExp = /^(true|false|\\d+)$/;\n return {\n restrict: 'A',\n require: 'ngModel',\n link: function postLink(scope, element, attr, controller) {\n var options = defaults;\n var isInput = element[0].nodeName === 'INPUT';\n var activeElement = isInput ? element.parent() : element;\n var value;\n attr.$observe('value', function(v) {\n if (typeof v !== 'boolean' && constantValueRegExp.test(v)) {\n value = scope.$eval(v);\n } else {\n value = v;\n }\n controller.$render();\n });\n controller.$render = function() {\n var isActive = angular.equals(controller.$viewValue, value);\n $$rAF(function() {\n if (isInput) element[0].checked = isActive;\n activeElement.toggleClass(options.activeClass, isActive);\n });\n };\n element.bind(options.toggleEvent, function() {\n scope.$apply(function() {\n controller.$setViewValue(value);\n controller.$render();\n });\n });\n }\n };\n } ]);\n angular.module('mgcrea.ngStrap.datepicker', [ 'mgcrea.ngStrap.helpers.dateParser', 'mgcrea.ngStrap.helpers.dateFormatter', 'mgcrea.ngStrap.tooltip' ]).provider('$datepicker', function() {\n var defaults = this.defaults = {\n animation: 'am-fade',\n prefixClass: 'datepicker',\n placement: 'bottom-left',\n templateUrl: 'datepicker/datepicker.tpl.html',\n trigger: 'focus',\n container: false,\n keyboard: true,\n html: false,\n delay: 0,\n useNative: false,\n dateType: 'date',\n dateFormat: 'shortDate',\n timezone: null,\n modelDateFormat: null,\n dayFormat: 'dd',\n monthFormat: 'MMM',\n yearFormat: 'yyyy',\n monthTitleFormat: 'MMMM yyyy',\n yearTitleFormat: 'yyyy',\n strictFormat: false,\n autoclose: false,\n minDate: -Infinity,\n maxDate: +Infinity,\n startView: 0,\n minView: 0,\n startWeek: 0,\n daysOfWeekDisabled: '',\n hasToday: false,\n hasClear: false,\n iconLeft: 'glyphicon glyphicon-chevron-left',\n iconRight: 'glyphicon glyphicon-chevron-right'\n };\n this.$get = [ '$window', '$document', '$rootScope', '$sce', '$dateFormatter', 'datepickerViews', '$tooltip', '$timeout', function($window, $document, $rootScope, $sce, $dateFormatter, datepickerViews, $tooltip, $timeout) {\n var isNative = /(ip[ao]d|iphone|android)/gi.test($window.navigator.userAgent);\n var isTouch = 'createTouch' in $window.document && isNative;\n if (!defaults.lang) defaults.lang = $dateFormatter.getDefaultLocale();\n function DatepickerFactory(element, controller, config) {\n var $datepicker = $tooltip(element, angular.extend({}, defaults, config));\n var parentScope = config.scope;\n var options = $datepicker.$options;\n var scope = $datepicker.$scope;\n if (options.startView) options.startView -= options.minView;\n var pickerViews = datepickerViews($datepicker);\n $datepicker.$views = pickerViews.views;\n var viewDate = pickerViews.viewDate;\n scope.$mode = options.startView;\n scope.$iconLeft = options.iconLeft;\n scope.$iconRight = options.iconRight;\n scope.$hasToday = options.hasToday;\n scope.$hasClear = options.hasClear;\n var $picker = $datepicker.$views[scope.$mode];\n scope.$select = function(date, disabled) {\n if (disabled) return;\n $datepicker.select(date);\n };\n scope.$selectPane = function(value) {\n $datepicker.$selectPane(value);\n };\n scope.$toggleMode = function() {\n $datepicker.setMode((scope.$mode + 1) % $datepicker.$views.length);\n };\n scope.$setToday = function() {\n if (options.autoclose) {\n $datepicker.setMode(0);\n $datepicker.select(new Date());\n } else {\n $datepicker.select(new Date(), true);\n }\n };\n scope.$clear = function() {\n if (options.autoclose) {\n $datepicker.setMode(0);\n $datepicker.select(null);\n } else {\n $datepicker.select(null, true);\n }\n };\n $datepicker.update = function(date) {\n if (angular.isDate(date) && !isNaN(date.getTime())) {\n $datepicker.$date = date;\n $picker.update.call($picker, date);\n }\n $datepicker.$build(true);\n };\n $datepicker.updateDisabledDates = function(dateRanges) {\n options.disabledDateRanges = dateRanges;\n for (var i = 0, l = scope.rows.length; i < l; i++) {\n angular.forEach(scope.rows[i], $datepicker.$setDisabledEl);\n }\n };\n $datepicker.select = function(date, keep) {\n if (angular.isDate(date)) {\n if (!angular.isDate(controller.$dateValue) || isNaN(controller.$dateValue.getTime())) {\n controller.$dateValue = new Date(date);\n }\n } else {\n controller.$dateValue = null;\n }\n if (!scope.$mode || keep) {\n controller.$setViewValue(angular.copy(date));\n controller.$render();\n if (options.autoclose && !keep) {\n $timeout(function() {\n $datepicker.hide(true);\n });\n }\n } else {\n angular.extend(viewDate, {\n year: date.getFullYear(),\n month: date.getMonth(),\n date: date.getDate()\n });\n $datepicker.setMode(scope.$mode - 1);\n $datepicker.$build();\n }\n };\n $datepicker.setMode = function(mode) {\n scope.$mode = mode;\n $picker = $datepicker.$views[scope.$mode];\n $datepicker.$build();\n };\n $datepicker.$build = function(pristine) {\n if (pristine === true && $picker.built) return;\n if (pristine === false && !$picker.built) return;\n $picker.build.call($picker);\n };\n $datepicker.$updateSelected = function() {\n for (var i = 0, l = scope.rows.length; i < l; i++) {\n angular.forEach(scope.rows[i], updateSelected);\n }\n };\n $datepicker.$isSelected = function(date) {\n return $picker.isSelected(date);\n };\n $datepicker.$setDisabledEl = function(el) {\n el.disabled = $picker.isDisabled(el.date);\n };\n $datepicker.$selectPane = function(value) {\n var steps = $picker.steps;\n var targetDate = new Date(Date.UTC(viewDate.year + (steps.year || 0) * value, viewDate.month + (steps.month || 0) * value, 1));\n angular.extend(viewDate, {\n year: targetDate.getUTCFullYear(),\n month: targetDate.getUTCMonth(),\n date: targetDate.getUTCDate()\n });\n $datepicker.$build();\n };\n $datepicker.$onMouseDown = function(evt) {\n evt.preventDefault();\n evt.stopPropagation();\n if (isTouch) {\n var targetEl = angular.element(evt.target);\n if (targetEl[0].nodeName.toLowerCase() !== 'button') {\n targetEl = targetEl.parent();\n }\n targetEl.triggerHandler('click');\n }\n };\n $datepicker.$onKeyDown = function(evt) {\n if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey) return;\n evt.preventDefault();\n evt.stopPropagation();\n if (evt.keyCode === 13) {\n if (!scope.$mode) {\n $datepicker.hide(true);\n } else {\n scope.$apply(function() {\n $datepicker.setMode(scope.$mode - 1);\n });\n }\n return;\n }\n $picker.onKeyDown(evt);\n parentScope.$digest();\n };\n function updateSelected(el) {\n el.selected = $datepicker.$isSelected(el.date);\n }\n function focusElement() {\n element[0].focus();\n }\n var _init = $datepicker.init;\n $datepicker.init = function() {\n if (isNative && options.useNative) {\n element.prop('type', 'date');\n element.css('-webkit-appearance', 'textfield');\n return;\n } else if (isTouch) {\n element.prop('type', 'text');\n element.attr('readonly', 'true');\n element.on('click', focusElement);\n }\n _init();\n };\n var _destroy = $datepicker.destroy;\n $datepicker.destroy = function() {\n if (isNative && options.useNative) {\n element.off('click', focusElement);\n }\n _destroy();\n };\n var _show = $datepicker.show;\n $datepicker.show = function() {\n if (!isTouch && element.attr('readonly') || element.attr('disabled')) return;\n _show();\n $timeout(function() {\n if (!$datepicker.$isShown) return;\n $datepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $datepicker.$onMouseDown);\n if (options.keyboard) {\n element.on('keydown', $datepicker.$onKeyDown);\n }\n }, 0, false);\n };\n var _hide = $datepicker.hide;\n $datepicker.hide = function(blur) {\n if (!$datepicker.$isShown) return;\n $datepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $datepicker.$onMouseDown);\n if (options.keyboard) {\n element.off('keydown', $datepicker.$onKeyDown);\n }\n _hide(blur);\n };\n return $datepicker;\n }\n DatepickerFactory.defaults = defaults;\n return DatepickerFactory;\n } ];\n }).directive('bsDatepicker', [ '$window', '$parse', '$q', '$dateFormatter', '$dateParser', '$datepicker', function($window, $parse, $q, $dateFormatter, $dateParser, $datepicker) {\n var isNative = /(ip[ao]d|iphone|android)/gi.test($window.navigator.userAgent);\n return {\n restrict: 'EAC',\n require: 'ngModel',\n link: function postLink(scope, element, attr, controller) {\n var options = {\n scope: scope\n };\n angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'placement', 'container', 'delay', 'trigger', 'html', 'animation', 'autoclose', 'dateType', 'dateFormat', 'timezone', 'modelDateFormat', 'dayFormat', 'strictFormat', 'startWeek', 'startDate', 'useNative', 'lang', 'startView', 'minView', 'iconLeft', 'iconRight', 'daysOfWeekDisabled', 'id', 'prefixClass', 'prefixEvent', 'hasToday', 'hasClear' ], function(key) {\n if (angular.isDefined(attr[key])) options[key] = attr[key];\n });\n var falseValueRegExp = /^(false|0|)$/i;\n angular.forEach([ 'html', 'container', 'autoclose', 'useNative', 'hasToday', 'hasClear' ], function(key) {\n if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) {\n options[key] = false;\n }\n });\n angular.forEach([ 'onBeforeShow', 'onShow', 'onBeforeHide', 'onHide' ], function(key) {\n var bsKey = 'bs' + key.charAt(0).toUpperCase() + key.slice(1);\n if (angular.isDefined(attr[bsKey])) {\n options[key] = scope.$eval(attr[bsKey]);\n }\n });\n var datepicker = $datepicker(element, controller, options);\n options = datepicker.$options;\n if (isNative && options.useNative) options.dateFormat = 'yyyy-MM-dd';\n var lang = options.lang;\n var formatDate = function(date, format) {\n return $dateFormatter.formatDate(date, format, lang);\n };\n var dateParser = $dateParser({\n format: options.dateFormat,\n lang: lang,\n strict: options.strictFormat\n });\n if (attr.bsShow) {\n scope.$watch(attr.bsShow, function(newValue, oldValue) {\n if (!datepicker || !angular.isDefined(newValue)) return;\n if (angular.isString(newValue)) newValue = !!newValue.match(/true|,?(datepicker),?/i);\n if (newValue === true) {\n datepicker.show();\n } else {\n datepicker.hide();\n }\n });\n }\n angular.forEach([ 'minDate', 'maxDate' ], function(key) {\n if (angular.isDefined(attr[key])) {\n attr.$observe(key, function(newValue) {\n datepicker.$options[key] = dateParser.getDateForAttribute(key, newValue);\n if (!isNaN(datepicker.$options[key])) datepicker.$build(false);\n validateAgainstMinMaxDate(controller.$dateValue);\n });\n }\n });\n if (angular.isDefined(attr.dateFormat)) {\n attr.$observe('dateFormat', function(newValue) {\n datepicker.$options.dateFormat = newValue;\n });\n }\n scope.$watch(attr.ngModel, function(newValue, oldValue) {\n datepicker.update(controller.$dateValue);\n }, true);\n function normalizeDateRanges(ranges) {\n if (!ranges || !ranges.length) return null;\n return ranges;\n }\n if (angular.isDefined(attr.disabledDates)) {\n scope.$watch(attr.disabledDates, function(disabledRanges, previousValue) {\n disabledRanges = normalizeDateRanges(disabledRanges);\n previousValue = normalizeDateRanges(previousValue);\n if (disabledRanges) {\n datepicker.updateDisabledDates(disabledRanges);\n }\n });\n }\n function validateAgainstMinMaxDate(parsedDate) {\n if (!angular.isDate(parsedDate)) return;\n var isMinValid = isNaN(datepicker.$options.minDate) || parsedDate.getTime() >= datepicker.$options.minDate;\n var isMaxValid = isNaN(datepicker.$options.maxDate) || parsedDate.getTime() <= datepicker.$options.maxDate;\n var isValid = isMinValid && isMaxValid;\n controller.$setValidity('date', isValid);\n controller.$setValidity('min', isMinValid);\n controller.$setValidity('max', isMaxValid);\n if (isValid) controller.$dateValue = parsedDate;\n }\n controller.$parsers.unshift(function(viewValue) {\n var date;\n if (!viewValue) {\n controller.$setValidity('date', true);\n return null;\n }\n var parsedDate = dateParser.parse(viewValue, controller.$dateValue);\n if (!parsedDate || isNaN(parsedDate.getTime())) {\n controller.$setValidity('date', false);\n return;\n }\n validateAgainstMinMaxDate(parsedDate);\n if (options.dateType === 'string') {\n date = dateParser.timezoneOffsetAdjust(parsedDate, options.timezone, true);\n return formatDate(date, options.modelDateFormat || options.dateFormat);\n }\n date = dateParser.timezoneOffsetAdjust(controller.$dateValue, options.timezone, true);\n if (options.dateType === 'number') {\n return date.getTime();\n } else if (options.dateType === 'unix') {\n return date.getTime() / 1e3;\n } else if (options.dateType === 'iso') {\n return date.toISOString();\n }\n return new Date(date);\n });\n controller.$formatters.push(function(modelValue) {\n var date;\n if (angular.isUndefined(modelValue) || modelValue === null) {\n date = NaN;\n } else if (angular.isDate(modelValue)) {\n date = modelValue;\n } else if (options.dateType === 'string') {\n date = dateParser.parse(modelValue, null, options.modelDateFormat);\n } else if (options.dateType === 'unix') {\n date = new Date(modelValue * 1e3);\n } else {\n date = new Date(modelValue);\n }\n controller.$dateValue = dateParser.timezoneOffsetAdjust(date, options.timezone);\n return getDateFormattedString();\n });\n controller.$render = function() {\n element.val(getDateFormattedString());\n };\n function getDateFormattedString() {\n return !controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : formatDate(controller.$dateValue, options.dateFormat);\n }\n scope.$on('$destroy', function() {\n if (datepicker) datepicker.destroy();\n options = null;\n datepicker = null;\n });\n }\n };\n } ]).provider('datepickerViews', function() {\n function split(arr, size) {\n var arrays = [];\n while (arr.length > 0) {\n arrays.push(arr.splice(0, size));\n }\n return arrays;\n }\n function mod(n, m) {\n return (n % m + m) % m;\n }\n this.$get = [ '$dateFormatter', '$dateParser', '$sce', function($dateFormatter, $dateParser, $sce) {\n return function(picker) {\n var scope = picker.$scope;\n var options = picker.$options;\n var lang = options.lang;\n var formatDate = function(date, format) {\n return $dateFormatter.formatDate(date, format, lang);\n };\n var dateParser = $dateParser({\n format: options.dateFormat,\n lang: lang,\n strict: options.strictFormat\n });\n var weekDaysMin = $dateFormatter.weekdaysShort(lang);\n var weekDaysLabels = weekDaysMin.slice(options.startWeek).concat(weekDaysMin.slice(0, options.startWeek));\n var weekDaysLabelsHtml = $sce.trustAsHtml('
' + weekDaysLabels.join(' | ') + ' | ');\n var startDate = picker.$date || (options.startDate ? dateParser.getDateForAttribute('startDate', options.startDate) : new Date());\n var viewDate = {\n year: startDate.getFullYear(),\n month: startDate.getMonth(),\n date: startDate.getDate()\n };\n var views = [ {\n format: options.dayFormat,\n split: 7,\n steps: {\n month: 1\n },\n update: function(date, force) {\n if (!this.built || force || date.getFullYear() !== viewDate.year || date.getMonth() !== viewDate.month) {\n angular.extend(viewDate, {\n year: picker.$date.getFullYear(),\n month: picker.$date.getMonth(),\n date: picker.$date.getDate()\n });\n picker.$build();\n } else if (date.getDate() !== viewDate.date || date.getDate() === 1) {\n viewDate.date = picker.$date.getDate();\n picker.$updateSelected();\n }\n },\n build: function() {\n var firstDayOfMonth = new Date(viewDate.year, viewDate.month, 1);\n var firstDayOfMonthOffset = firstDayOfMonth.getTimezoneOffset();\n var firstDate = new Date(+firstDayOfMonth - mod(firstDayOfMonth.getDay() - options.startWeek, 7) * 864e5);\n var firstDateOffset = firstDate.getTimezoneOffset();\n var today = dateParser.timezoneOffsetAdjust(new Date(), options.timezone).toDateString();\n if (firstDateOffset !== firstDayOfMonthOffset) firstDate = new Date(+firstDate + (firstDateOffset - firstDayOfMonthOffset) * 6e4);\n var days = [];\n var day;\n for (var i = 0; i < 42; i++) {\n day = dateParser.daylightSavingAdjust(new Date(firstDate.getFullYear(), firstDate.getMonth(), firstDate.getDate() + i));\n days.push({\n date: day,\n isToday: day.toDateString() === today,\n label: formatDate(day, this.format),\n selected: picker.$date && this.isSelected(day),\n muted: day.getMonth() !== viewDate.month,\n disabled: this.isDisabled(day)\n });\n }\n scope.title = formatDate(firstDayOfMonth, options.monthTitleFormat);\n scope.showLabels = true;\n scope.labels = weekDaysLabelsHtml;\n scope.rows = split(days, this.split);\n scope.isTodayDisabled = this.isDisabled(new Date());\n this.built = true;\n },\n isSelected: function(date) {\n return picker.$date && date.getFullYear() === picker.$date.getFullYear() && date.getMonth() === picker.$date.getMonth() && date.getDate() === picker.$date.getDate();\n },\n isDisabled: function(date) {\n var time = date.getTime();\n if (time < options.minDate || time > options.maxDate) return true;\n if (options.daysOfWeekDisabled.indexOf(date.getDay()) !== -1) return true;\n if (options.disabledDateRanges) {\n for (var i = 0; i < options.disabledDateRanges.length; i++) {\n if (time >= options.disabledDateRanges[i].start && time <= options.disabledDateRanges[i].end) {\n return true;\n }\n }\n }\n return false;\n },\n onKeyDown: function(evt) {\n if (!picker.$date) {\n return;\n }\n var actualTime = picker.$date.getTime();\n var newDate;\n if (evt.keyCode === 37) newDate = new Date(actualTime - 1 * 864e5); else if (evt.keyCode === 38) newDate = new Date(actualTime - 7 * 864e5); else if (evt.keyCode === 39) newDate = new Date(actualTime + 1 * 864e5); else if (evt.keyCode === 40) newDate = new Date(actualTime + 7 * 864e5);\n if (!this.isDisabled(newDate)) picker.select(newDate, true);\n }\n }, {\n name: 'month',\n format: options.monthFormat,\n split: 4,\n steps: {\n year: 1\n },\n update: function(date, force) {\n if (!this.built || date.getFullYear() !== viewDate.year) {\n angular.extend(viewDate, {\n year: picker.$date.getFullYear(),\n month: picker.$date.getMonth(),\n date: picker.$date.getDate()\n });\n picker.$build();\n } else if (date.getMonth() !== viewDate.month) {\n angular.extend(viewDate, {\n month: picker.$date.getMonth(),\n date: picker.$date.getDate()\n });\n picker.$updateSelected();\n }\n },\n build: function() {\n var months = [];\n var month;\n for (var i = 0; i < 12; i++) {\n month = new Date(viewDate.year, i, 1);\n months.push({\n date: month,\n label: formatDate(month, this.format),\n selected: picker.$isSelected(month),\n disabled: this.isDisabled(month)\n });\n }\n scope.title = formatDate(month, options.yearTitleFormat);\n scope.showLabels = false;\n scope.rows = split(months, this.split);\n this.built = true;\n },\n isSelected: function(date) {\n return picker.$date && date.getFullYear() === picker.$date.getFullYear() && date.getMonth() === picker.$date.getMonth();\n },\n isDisabled: function(date) {\n var lastDate = +new Date(date.getFullYear(), date.getMonth() + 1, 0);\n return lastDate < options.minDate || date.getTime() > options.maxDate;\n },\n onKeyDown: function(evt) {\n if (!picker.$date) {\n return;\n }\n var actualMonth = picker.$date.getMonth();\n var newDate = new Date(picker.$date);\n if (evt.keyCode === 37) newDate.setMonth(actualMonth - 1); else if (evt.keyCode === 38) newDate.setMonth(actualMonth - 4); else if (evt.keyCode === 39) newDate.setMonth(actualMonth + 1); else if (evt.keyCode === 40) newDate.setMonth(actualMonth + 4);\n if (!this.isDisabled(newDate)) picker.select(newDate, true);\n }\n }, {\n name: 'year',\n format: options.yearFormat,\n split: 4,\n steps: {\n year: 12\n },\n update: function(date, force) {\n if (!this.built || force || parseInt(date.getFullYear() / 20, 10) !== parseInt(viewDate.year / 20, 10)) {\n angular.extend(viewDate, {\n year: picker.$date.getFullYear(),\n month: picker.$date.getMonth(),\n date: picker.$date.getDate()\n });\n picker.$build();\n } else if (date.getFullYear() !== viewDate.year) {\n angular.extend(viewDate, {\n year: picker.$date.getFullYear(),\n month: picker.$date.getMonth(),\n date: picker.$date.getDate()\n });\n picker.$updateSelected();\n }\n },\n build: function() {\n var firstYear = viewDate.year - viewDate.year % (this.split * 3);\n var years = [];\n var year;\n for (var i = 0; i < 12; i++) {\n year = new Date(firstYear + i, 0, 1);\n years.push({\n date: year,\n label: formatDate(year, this.format),\n selected: picker.$isSelected(year),\n disabled: this.isDisabled(year)\n });\n }\n scope.title = years[0].label + '-' + years[years.length - 1].label;\n scope.showLabels = false;\n scope.rows = split(years, this.split);\n this.built = true;\n },\n isSelected: function(date) {\n return picker.$date && date.getFullYear() === picker.$date.getFullYear();\n },\n isDisabled: function(date) {\n var lastDate = +new Date(date.getFullYear() + 1, 0, 0);\n return lastDate < options.minDate || date.getTime() > options.maxDate;\n },\n onKeyDown: function(evt) {\n if (!picker.$date) {\n return;\n }\n var actualYear = picker.$date.getFullYear();\n var newDate = new Date(picker.$date);\n if (evt.keyCode === 37) newDate.setYear(actualYear - 1); else if (evt.keyCode === 38) newDate.setYear(actualYear - 4); else if (evt.keyCode === 39) newDate.setYear(actualYear + 1); else if (evt.keyCode === 40) newDate.setYear(actualYear + 4);\n if (!this.isDisabled(newDate)) picker.select(newDate, true);\n }\n } ];\n return {\n views: options.minView ? Array.prototype.slice.call(views, options.minView) : views,\n viewDate: viewDate\n };\n };\n } ];\n });\n angular.module('mgcrea.ngStrap.collapse', []).provider('$collapse', function() {\n var defaults = this.defaults = {\n animation: 'am-collapse',\n disallowToggle: false,\n activeClass: 'in',\n startCollapsed: false,\n allowMultiple: false\n };\n var controller = this.controller = function($scope, $element, $attrs) {\n var self = this;\n self.$options = angular.copy(defaults);\n angular.forEach([ 'animation', 'disallowToggle', 'activeClass', 'startCollapsed', 'allowMultiple' ], function(key) {\n if (angular.isDefined($attrs[key])) self.$options[key] = $attrs[key];\n });\n var falseValueRegExp = /^(false|0|)$/i;\n angular.forEach([ 'disallowToggle', 'startCollapsed', 'allowMultiple' ], function(key) {\n if (angular.isDefined($attrs[key]) && falseValueRegExp.test($attrs[key])) {\n self.$options[key] = false;\n }\n });\n self.$toggles = [];\n self.$targets = [];\n self.$viewChangeListeners = [];\n self.$registerToggle = function(element) {\n self.$toggles.push(element);\n };\n self.$registerTarget = function(element) {\n self.$targets.push(element);\n };\n self.$unregisterToggle = function(element) {\n var index = self.$toggles.indexOf(element);\n self.$toggles.splice(index, 1);\n };\n self.$unregisterTarget = function(element) {\n var index = self.$targets.indexOf(element);\n self.$targets.splice(index, 1);\n if (self.$options.allowMultiple) {\n deactivateItem(element);\n }\n fixActiveItemIndexes(index);\n self.$viewChangeListeners.forEach(function(fn) {\n fn();\n });\n };\n self.$targets.$active = !self.$options.startCollapsed ? [ 0 ] : [];\n self.$setActive = $scope.$setActive = function(value) {\n if (angular.isArray(value)) {\n self.$targets.$active = value;\n } else if (!self.$options.disallowToggle && isActive(value)) {\n deactivateItem(value);\n } else {\n activateItem(value);\n }\n self.$viewChangeListeners.forEach(function(fn) {\n fn();\n });\n };\n self.$activeIndexes = function() {\n if (self.$options.allowMultiple) {\n return self.$targets.$active;\n }\n return self.$targets.$active.length === 1 ? self.$targets.$active[0] : -1;\n };\n function fixActiveItemIndexes(index) {\n var activeIndexes = self.$targets.$active;\n for (var i = 0; i < activeIndexes.length; i++) {\n if (index < activeIndexes[i]) {\n activeIndexes[i] = activeIndexes[i] - 1;\n }\n if (activeIndexes[i] === self.$targets.length) {\n activeIndexes[i] = self.$targets.length - 1;\n }\n }\n }\n function isActive(value) {\n var activeItems = self.$targets.$active;\n return activeItems.indexOf(value) !== -1;\n }\n function deactivateItem(value) {\n var index = self.$targets.$active.indexOf(value);\n if (index !== -1) {\n self.$targets.$active.splice(index, 1);\n }\n }\n function activateItem(value) {\n if (!self.$options.allowMultiple) {\n self.$targets.$active.splice(0, 1);\n }\n if (self.$targets.$active.indexOf(value) === -1) {\n self.$targets.$active.push(value);\n }\n }\n };\n this.$get = function() {\n var $collapse = {};\n $collapse.defaults = defaults;\n $collapse.controller = controller;\n return $collapse;\n };\n }).directive('bsCollapse', [ '$window', '$animate', '$collapse', function($window, $animate, $collapse) {\n return {\n require: [ '?ngModel', 'bsCollapse' ],\n controller: [ '$scope', '$element', '$attrs', $collapse.controller ],\n link: function postLink(scope, element, attrs, controllers) {\n var ngModelCtrl = controllers[0];\n var bsCollapseCtrl = controllers[1];\n if (ngModelCtrl) {\n bsCollapseCtrl.$viewChangeListeners.push(function() {\n ngModelCtrl.$setViewValue(bsCollapseCtrl.$activeIndexes());\n });\n ngModelCtrl.$formatters.push(function(modelValue) {\n if (angular.isArray(modelValue)) {\n bsCollapseCtrl.$setActive(modelValue);\n } else {\n var activeIndexes = bsCollapseCtrl.$activeIndexes();\n if (angular.isArray(activeIndexes)) {\n if (activeIndexes.indexOf(modelValue * 1) === -1) {\n bsCollapseCtrl.$setActive(modelValue * 1);\n }\n } else if (activeIndexes !== modelValue * 1) {\n bsCollapseCtrl.$setActive(modelValue * 1);\n }\n }\n return modelValue;\n });\n }\n }\n };\n } ]).directive('bsCollapseToggle', function() {\n return {\n require: [ '^?ngModel', '^bsCollapse' ],\n link: function postLink(scope, element, attrs, controllers) {\n var bsCollapseCtrl = controllers[1];\n element.attr('data-toggle', 'collapse');\n bsCollapseCtrl.$registerToggle(element);\n scope.$on('$destroy', function() {\n bsCollapseCtrl.$unregisterToggle(element);\n });\n element.on('click', function() {\n if (!attrs.disabled) {\n var index = attrs.bsCollapseToggle && attrs.bsCollapseToggle !== 'bs-collapse-toggle' ? attrs.bsCollapseToggle : bsCollapseCtrl.$toggles.indexOf(element);\n bsCollapseCtrl.$setActive(index * 1);\n scope.$apply();\n }\n });\n }\n };\n }).directive('bsCollapseTarget', [ '$animate', function($animate) {\n return {\n require: [ '^?ngModel', '^bsCollapse' ],\n link: function postLink(scope, element, attrs, controllers) {\n var bsCollapseCtrl = controllers[1];\n element.addClass('collapse');\n if (bsCollapseCtrl.$options.animation) {\n element.addClass(bsCollapseCtrl.$options.animation);\n }\n bsCollapseCtrl.$registerTarget(element);\n scope.$on('$destroy', function() {\n bsCollapseCtrl.$unregisterTarget(element);\n });\n function render() {\n var index = bsCollapseCtrl.$targets.indexOf(element);\n var active = bsCollapseCtrl.$activeIndexes();\n var action = 'removeClass';\n if (angular.isArray(active)) {\n if (active.indexOf(index) !== -1) {\n action = 'addClass';\n }\n } else if (index === active) {\n action = 'addClass';\n }\n $animate[action](element, bsCollapseCtrl.$options.activeClass);\n }\n bsCollapseCtrl.$viewChangeListeners.push(function() {\n render();\n });\n render();\n }\n };\n } ]);\n angular.module('mgcrea.ngStrap.aside', [ 'mgcrea.ngStrap.modal' ]).provider('$aside', function() {\n var defaults = this.defaults = {\n animation: 'am-fade-and-slide-right',\n prefixClass: 'aside',\n prefixEvent: 'aside',\n placement: 'right',\n templateUrl: 'aside/aside.tpl.html',\n contentTemplate: false,\n container: false,\n element: null,\n backdrop: true,\n keyboard: true,\n html: false,\n show: true\n };\n this.$get = [ '$modal', function($modal) {\n function AsideFactory(config) {\n var $aside = {};\n var options = angular.extend({}, defaults, config);\n $aside = $modal(options);\n return $aside;\n }\n return AsideFactory;\n } ];\n }).directive('bsAside', [ '$window', '$sce', '$aside', function($window, $sce, $aside) {\n return {\n restrict: 'EAC',\n scope: true,\n link: function postLink(scope, element, attr, transclusion) {\n var options = {\n scope: scope,\n element: element,\n show: false\n };\n angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'contentTemplate', 'placement', 'backdrop', 'keyboard', 'html', 'container', 'animation' ], function(key) {\n if (angular.isDefined(attr[key])) options[key] = attr[key];\n });\n var falseValueRegExp = /^(false|0|)$/i;\n angular.forEach([ 'backdrop', 'keyboard', 'html', 'container' ], function(key) {\n if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;\n });\n angular.forEach([ 'onBeforeShow', 'onShow', 'onBeforeHide', 'onHide' ], function(key) {\n var bsKey = 'bs' + key.charAt(0).toUpperCase() + key.slice(1);\n if (angular.isDefined(attr[bsKey])) {\n options[key] = scope.$eval(attr[bsKey]);\n }\n });\n angular.forEach([ 'title', 'content' ], function(key) {\n if (attr[key]) {\n attr.$observe(key, function(newValue, oldValue) {\n scope[key] = $sce.trustAsHtml(newValue);\n });\n }\n });\n if (attr.bsAside) {\n scope.$watch(attr.bsAside, function(newValue, oldValue) {\n if (angular.isObject(newValue)) {\n angular.extend(scope, newValue);\n } else {\n scope.content = newValue;\n }\n }, true);\n }\n var aside = $aside(options);\n element.on(attr.trigger || 'click', aside.toggle);\n scope.$on('$destroy', function() {\n if (aside) aside.destroy();\n options = null;\n aside = null;\n });\n }\n };\n } ]);\n angular.module('mgcrea.ngStrap.alert', [ 'mgcrea.ngStrap.modal' ]).provider('$alert', function() {\n var defaults = this.defaults = {\n animation: 'am-fade',\n prefixClass: 'alert',\n prefixEvent: 'alert',\n placement: null,\n templateUrl: 'alert/alert.tpl.html',\n container: false,\n element: null,\n backdrop: false,\n keyboard: true,\n show: true,\n duration: false,\n type: false,\n dismissable: true\n };\n this.$get = [ '$modal', '$timeout', function($modal, $timeout) {\n function AlertFactory(config) {\n var $alert = {};\n var options = angular.extend({}, defaults, config);\n $alert = $modal(options);\n $alert.$scope.dismissable = !!options.dismissable;\n if (options.type) {\n $alert.$scope.type = options.type;\n }\n var show = $alert.show;\n if (options.duration) {\n $alert.show = function() {\n show();\n $timeout(function() {\n $alert.hide();\n }, options.duration * 1e3);\n };\n }\n return $alert;\n }\n return AlertFactory;\n } ];\n }).directive('bsAlert', [ '$window', '$sce', '$alert', function($window, $sce, $alert) {\n return {\n restrict: 'EAC',\n scope: true,\n link: function postLink(scope, element, attr, transclusion) {\n var options = {\n scope: scope,\n element: element,\n show: false\n };\n angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'placement', 'keyboard', 'html', 'container', 'animation', 'duration', 'dismissable' ], function(key) {\n if (angular.isDefined(attr[key])) options[key] = attr[key];\n });\n var falseValueRegExp = /^(false|0|)$/i;\n angular.forEach([ 'keyboard', 'html', 'container', 'dismissable' ], function(key) {\n if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;\n });\n angular.forEach([ 'onBeforeShow', 'onShow', 'onBeforeHide', 'onHide' ], function(key) {\n var bsKey = 'bs' + key.charAt(0).toUpperCase() + key.slice(1);\n if (angular.isDefined(attr[bsKey])) {\n options[key] = scope.$eval(attr[bsKey]);\n }\n });\n if (!scope.hasOwnProperty('title')) {\n scope.title = '';\n }\n angular.forEach([ 'title', 'content', 'type' ], function(key) {\n if (attr[key]) {\n attr.$observe(key, function(newValue, oldValue) {\n scope[key] = $sce.trustAsHtml(newValue);\n });\n }\n });\n if (attr.bsAlert) {\n scope.$watch(attr.bsAlert, function(newValue, oldValue) {\n if (angular.isObject(newValue)) {\n angular.extend(scope, newValue);\n } else {\n scope.content = newValue;\n }\n }, true);\n }\n var alert = $alert(options);\n element.on(attr.trigger || 'click', alert.toggle);\n scope.$on('$destroy', function() {\n if (alert) alert.destroy();\n options = null;\n alert = null;\n });\n }\n };\n } ]);\n angular.module('mgcrea.ngStrap.affix', [ 'mgcrea.ngStrap.helpers.dimensions', 'mgcrea.ngStrap.helpers.debounce' ]).provider('$affix', function() {\n var defaults = this.defaults = {\n offsetTop: 'auto',\n inlineStyles: true,\n setWidth: true\n };\n this.$get = [ '$window', 'debounce', 'dimensions', function($window, debounce, dimensions) {\n var bodyEl = angular.element($window.document.body);\n var windowEl = angular.element($window);\n function AffixFactory(element, config) {\n var $affix = {};\n var options = angular.extend({}, defaults, config);\n var targetEl = options.target;\n var reset = 'affix affix-top affix-bottom';\n var setWidth = false;\n var initialAffixTop = 0;\n var initialOffsetTop = 0;\n var offsetTop = 0;\n var offsetBottom = 0;\n var affixed = null;\n var unpin = null;\n var parent = element.parent();\n if (options.offsetParent) {\n if (options.offsetParent.match(/^\\d+$/)) {\n for (var i = 0; i < options.offsetParent * 1 - 1; i++) {\n parent = parent.parent();\n }\n } else {\n parent = angular.element(options.offsetParent);\n }\n }\n $affix.init = function() {\n this.$parseOffsets();\n initialOffsetTop = dimensions.offset(element[0]).top + initialAffixTop;\n setWidth = options.setWidth && !element[0].style.width;\n targetEl.on('scroll', this.checkPosition);\n targetEl.on('click', this.checkPositionWithEventLoop);\n windowEl.on('resize', this.$debouncedOnResize);\n this.checkPosition();\n this.checkPositionWithEventLoop();\n };\n $affix.destroy = function() {\n targetEl.off('scroll', this.checkPosition);\n targetEl.off('click', this.checkPositionWithEventLoop);\n windowEl.off('resize', this.$debouncedOnResize);\n };\n $affix.checkPositionWithEventLoop = function() {\n setTimeout($affix.checkPosition, 1);\n };\n $affix.checkPosition = function() {\n var scrollTop = getScrollTop();\n var position = dimensions.offset(element[0]);\n var elementHeight = dimensions.height(element[0]);\n var affix = getRequiredAffixClass(unpin, position, elementHeight);\n if (affixed === affix) return;\n affixed = affix;\n if (affix === 'top') {\n unpin = null;\n if (setWidth) {\n element.css('width', '');\n }\n if (options.inlineStyles) {\n element.css('position', options.offsetParent ? '' : 'relative');\n element.css('top', '');\n }\n } else if (affix === 'bottom') {\n if (options.offsetUnpin) {\n unpin = -(options.offsetUnpin * 1);\n } else {\n unpin = position.top - scrollTop;\n }\n if (setWidth) {\n element.css('width', '');\n }\n if (options.inlineStyles) {\n element.css('position', options.offsetParent ? '' : 'relative');\n element.css('top', options.offsetParent ? '' : bodyEl[0].offsetHeight - offsetBottom - elementHeight - initialOffsetTop + 'px');\n }\n } else {\n unpin = null;\n if (setWidth) {\n element.css('width', element[0].offsetWidth + 'px');\n }\n if (options.inlineStyles) {\n element.css('position', 'fixed');\n element.css('top', initialAffixTop + 'px');\n }\n }\n element.removeClass(reset).addClass('affix' + (affix !== 'middle' ? '-' + affix : ''));\n };\n $affix.$onResize = function() {\n $affix.$parseOffsets();\n $affix.checkPosition();\n };\n $affix.$debouncedOnResize = debounce($affix.$onResize, 50);\n $affix.$parseOffsets = function() {\n var initialPosition = element[0].style.position;\n var initialTop = element[0].style.top;\n if (options.inlineStyles) {\n element.css('position', options.offsetParent ? '' : 'relative');\n element.css('top', '');\n }\n if (options.offsetTop) {\n if (options.offsetTop === 'auto') {\n options.offsetTop = '+0';\n }\n if (options.offsetTop.match(/^[-+]\\d+$/)) {\n initialAffixTop = -options.offsetTop * 1;\n if (options.offsetParent) {\n offsetTop = dimensions.offset(parent[0]).top + options.offsetTop * 1;\n } else {\n offsetTop = dimensions.offset(element[0]).top - dimensions.css(element[0], 'marginTop', true) + options.offsetTop * 1;\n }\n } else {\n offsetTop = options.offsetTop * 1;\n }\n }\n if (options.offsetBottom) {\n if (options.offsetParent && options.offsetBottom.match(/^[-+]\\d+$/)) {\n offsetBottom = getScrollHeight() - (dimensions.offset(parent[0]).top + dimensions.height(parent[0])) + options.offsetBottom * 1 + 1;\n } else {\n offsetBottom = options.offsetBottom * 1;\n }\n }\n if (options.inlineStyles) {\n element.css('position', initialPosition);\n element.css('top', initialTop);\n }\n };\n function getRequiredAffixClass(_unpin, position, elementHeight) {\n var scrollTop = getScrollTop();\n var scrollHeight = getScrollHeight();\n if (scrollTop <= offsetTop) {\n return 'top';\n } else if (_unpin !== null) {\n return scrollTop + _unpin <= position.top ? 'middle' : 'bottom';\n } else if (offsetBottom !== null && position.top + elementHeight + initialAffixTop >= scrollHeight - offsetBottom) {\n return 'bottom';\n }\n return 'middle';\n }\n function getScrollTop() {\n return targetEl[0] === $window ? $window.pageYOffset : targetEl[0].scrollTop;\n }\n function getScrollHeight() {\n return targetEl[0] === $window ? $window.document.body.scrollHeight : targetEl[0].scrollHeight;\n }\n $affix.init();\n return $affix;\n }\n return AffixFactory;\n } ];\n }).directive('bsAffix', [ '$affix', '$window', '$timeout', function($affix, $window, $timeout) {\n return {\n restrict: 'EAC',\n require: '^?bsAffixTarget',\n link: function postLink(scope, element, attr, affixTarget) {\n var options = {\n scope: scope,\n target: affixTarget ? affixTarget.$element : angular.element($window)\n };\n angular.forEach([ 'offsetTop', 'offsetBottom', 'offsetParent', 'offsetUnpin', 'inlineStyles', 'setWidth' ], function(key) {\n if (angular.isDefined(attr[key])) {\n var option = attr[key];\n if (/true/i.test(option)) option = true;\n if (/false/i.test(option)) option = false;\n options[key] = option;\n }\n });\n var affix;\n $timeout(function() {\n affix = $affix(element, options);\n });\n scope.$on('$destroy', function() {\n if (affix) affix.destroy();\n options = null;\n affix = null;\n });\n }\n };\n } ]).directive('bsAffixTarget', function() {\n return {\n controller: [ '$element', function($element) {\n this.$element = $element;\n } ]\n };\n });\n angular.module('mgcrea.ngStrap', [ 'mgcrea.ngStrap.modal', 'mgcrea.ngStrap.aside', 'mgcrea.ngStrap.alert', 'mgcrea.ngStrap.button', 'mgcrea.ngStrap.select', 'mgcrea.ngStrap.datepicker', 'mgcrea.ngStrap.timepicker', 'mgcrea.ngStrap.navbar', 'mgcrea.ngStrap.tooltip', 'mgcrea.ngStrap.popover', 'mgcrea.ngStrap.dropdown', 'mgcrea.ngStrap.typeahead', 'mgcrea.ngStrap.scrollspy', 'mgcrea.ngStrap.affix', 'mgcrea.ngStrap.tab', 'mgcrea.ngStrap.collapse' ]);\n})(window, document);\n\n/***/ }),\n/* 163 */\n/***/ (function(module, exports) {\n\n/**\n * angular-strap\n * @version v2.3.12 - 2017-01-26\n * @link http://mgcrea.github.io/angular-strap\n * @author Olivier Louvignes
(https://github.com/mgcrea)\n * @license MIT License, http://www.opensource.org/licenses/MIT\n */\n!function(t,e,n){'use strict';angular.module('mgcrea.ngStrap.alert').run(['$templateCache',function(t){t.put('alert/alert.tpl.html','
')}]),angular.module('mgcrea.ngStrap.aside').run(['$templateCache',function(t){t.put('aside/aside.tpl.html','')}]),angular.module('mgcrea.ngStrap.datepicker').run(['$templateCache',function(t){t.put('datepicker/datepicker.tpl.html','')}]),angular.module('mgcrea.ngStrap.dropdown').run(['$templateCache',function(t){t.put('dropdown/dropdown.tpl.html','')}]),angular.module('mgcrea.ngStrap.modal').run(['$templateCache',function(t){t.put('modal/modal.tpl.html','')}]),angular.module('mgcrea.ngStrap.popover').run(['$templateCache',function(t){t.put('popover/popover.tpl.html','')}]),angular.module('mgcrea.ngStrap.select').run(['$templateCache',function(t){t.put('select/select.tpl.html','')}]),angular.module('mgcrea.ngStrap.tab').run(['$templateCache',function(t){t.put('tab/tab.tpl.html','')}]),angular.module('mgcrea.ngStrap.timepicker').run(['$templateCache',function(t){t.put('timepicker/timepicker.tpl.html','')}]),angular.module('mgcrea.ngStrap.tooltip').run(['$templateCache',function(t){t.put('tooltip/tooltip.tpl.html','')}]),angular.module('mgcrea.ngStrap.typeahead').run(['$templateCache',function(t){t.put('typeahead/typeahead.tpl.html','')}])}(window,document);\n\n/***/ }),\n/* 164 */\n/***/ (function(module, exports, __webpack_require__) {\n\n__webpack_require__(162);\n__webpack_require__(163);\nmodule.exports = 'mgcrea.ngStrap';\n\n\n/***/ }),\n/* 165 */\n/***/ (function(module, exports) {\n\n/**\n * State-based routing for AngularJS\n * @version v0.4.2\n * @link http://angular-ui.github.com/\n * @license MIT License, http://www.opensource.org/licenses/MIT\n */\n\n/* commonjs package manager support (eg componentjs) */\nif (typeof module !== \"undefined\" && typeof exports !== \"undefined\" && module.exports === exports){\n module.exports = 'ui.router';\n}\n\n(function (window, angular, undefined) {\n/*jshint globalstrict:true*/\n/*global angular:false*/\n'use strict';\n\nvar isDefined = angular.isDefined,\n isFunction = angular.isFunction,\n isString = angular.isString,\n isObject = angular.isObject,\n isArray = angular.isArray,\n forEach = angular.forEach,\n extend = angular.extend,\n copy = angular.copy,\n toJson = angular.toJson;\n\nfunction inherit(parent, extra) {\n return extend(new (extend(function() {}, { prototype: parent }))(), extra);\n}\n\nfunction merge(dst) {\n forEach(arguments, function(obj) {\n if (obj !== dst) {\n forEach(obj, function(value, key) {\n if (!dst.hasOwnProperty(key)) dst[key] = value;\n });\n }\n });\n return dst;\n}\n\n/**\n * Finds the common ancestor path between two states.\n *\n * @param {Object} first The first state.\n * @param {Object} second The second state.\n * @return {Array} Returns an array of state names in descending order, not including the root.\n */\nfunction ancestors(first, second) {\n var path = [];\n\n for (var n in first.path) {\n if (first.path[n] !== second.path[n]) break;\n path.push(first.path[n]);\n }\n return path;\n}\n\n/**\n * IE8-safe wrapper for `Object.keys()`.\n *\n * @param {Object} object A JavaScript object.\n * @return {Array} Returns the keys of the object as an array.\n */\nfunction objectKeys(object) {\n if (Object.keys) {\n return Object.keys(object);\n }\n var result = [];\n\n forEach(object, function(val, key) {\n result.push(key);\n });\n return result;\n}\n\n/**\n * IE8-safe wrapper for `Array.prototype.indexOf()`.\n *\n * @param {Array} array A JavaScript array.\n * @param {*} value A value to search the array for.\n * @return {Number} Returns the array index value of `value`, or `-1` if not present.\n */\nfunction indexOf(array, value) {\n if (Array.prototype.indexOf) {\n return array.indexOf(value, Number(arguments[2]) || 0);\n }\n var len = array.length >>> 0, from = Number(arguments[2]) || 0;\n from = (from < 0) ? Math.ceil(from) : Math.floor(from);\n\n if (from < 0) from += len;\n\n for (; from < len; from++) {\n if (from in array && array[from] === value) return from;\n }\n return -1;\n}\n\n/**\n * Merges a set of parameters with all parameters inherited between the common parents of the\n * current state and a given destination state.\n *\n * @param {Object} currentParams The value of the current state parameters ($stateParams).\n * @param {Object} newParams The set of parameters which will be composited with inherited params.\n * @param {Object} $current Internal definition of object representing the current state.\n * @param {Object} $to Internal definition of object representing state to transition to.\n */\nfunction inheritParams(currentParams, newParams, $current, $to) {\n var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = [];\n\n for (var i in parents) {\n if (!parents[i] || !parents[i].params) continue;\n parentParams = objectKeys(parents[i].params);\n if (!parentParams.length) continue;\n\n for (var j in parentParams) {\n if (indexOf(inheritList, parentParams[j]) >= 0) continue;\n inheritList.push(parentParams[j]);\n inherited[parentParams[j]] = currentParams[parentParams[j]];\n }\n }\n return extend({}, inherited, newParams);\n}\n\n/**\n * Performs a non-strict comparison of the subset of two objects, defined by a list of keys.\n *\n * @param {Object} a The first object.\n * @param {Object} b The second object.\n * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified,\n * it defaults to the list of keys in `a`.\n * @return {Boolean} Returns `true` if the keys match, otherwise `false`.\n */\nfunction equalForKeys(a, b, keys) {\n if (!keys) {\n keys = [];\n for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility\n }\n\n for (var i=0; i\n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n * \n */\nangular.module('ui.router', ['ui.router.state']);\n\nangular.module('ui.router.compat', ['ui.router']);\n\n/**\n * @ngdoc object\n * @name ui.router.util.$resolve\n *\n * @requires $q\n * @requires $injector\n *\n * @description\n * Manages resolution of (acyclic) graphs of promises.\n */\n$Resolve.$inject = ['$q', '$injector'];\nfunction $Resolve( $q, $injector) {\n \n var VISIT_IN_PROGRESS = 1,\n VISIT_DONE = 2,\n NOTHING = {},\n NO_DEPENDENCIES = [],\n NO_LOCALS = NOTHING,\n NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING });\n \n\n /**\n * @ngdoc function\n * @name ui.router.util.$resolve#study\n * @methodOf ui.router.util.$resolve\n *\n * @description\n * Studies a set of invocables that are likely to be used multiple times.\n * \n * $resolve.study(invocables)(locals, parent, self)\n *
\n * is equivalent to\n * \n * $resolve.resolve(invocables, locals, parent, self)\n *
\n * but the former is more efficient (in fact `resolve` just calls `study` \n * internally).\n *\n * @param {object} invocables Invocable objects\n * @return {function} a function to pass in locals, parent and self\n */\n this.study = function (invocables) {\n if (!isObject(invocables)) throw new Error(\"'invocables' must be an object\");\n var invocableKeys = objectKeys(invocables || {});\n \n // Perform a topological sort of invocables to build an ordered plan\n var plan = [], cycle = [], visited = {};\n function visit(value, key) {\n if (visited[key] === VISIT_DONE) return;\n \n cycle.push(key);\n if (visited[key] === VISIT_IN_PROGRESS) {\n cycle.splice(0, indexOf(cycle, key));\n throw new Error(\"Cyclic dependency: \" + cycle.join(\" -> \"));\n }\n visited[key] = VISIT_IN_PROGRESS;\n \n if (isString(value)) {\n plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES);\n } else {\n var params = $injector.annotate(value);\n forEach(params, function (param) {\n if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);\n });\n plan.push(key, value, params);\n }\n \n cycle.pop();\n visited[key] = VISIT_DONE;\n }\n forEach(invocables, visit);\n invocables = cycle = visited = null; // plan is all that's required\n \n function isResolve(value) {\n return isObject(value) && value.then && value.$$promises;\n }\n \n return function (locals, parent, self) {\n if (isResolve(locals) && self === undefined) {\n self = parent; parent = locals; locals = null;\n }\n if (!locals) locals = NO_LOCALS;\n else if (!isObject(locals)) {\n throw new Error(\"'locals' must be an object\");\n } \n if (!parent) parent = NO_PARENT;\n else if (!isResolve(parent)) {\n throw new Error(\"'parent' must be a promise returned by $resolve.resolve()\");\n }\n \n // To complete the overall resolution, we have to wait for the parent\n // promise and for the promise for each invokable in our plan.\n var resolution = $q.defer(),\n result = silenceUncaughtInPromise(resolution.promise),\n promises = result.$$promises = {},\n values = extend({}, locals),\n wait = 1 + plan.length/3,\n merged = false;\n\n silenceUncaughtInPromise(result);\n \n function done() {\n // Merge parent values we haven't got yet and publish our own $$values\n if (!--wait) {\n if (!merged) merge(values, parent.$$values); \n result.$$values = values;\n result.$$promises = result.$$promises || true; // keep for isResolve()\n delete result.$$inheritedValues;\n resolution.resolve(values);\n }\n }\n \n function fail(reason) {\n result.$$failure = reason;\n resolution.reject(reason);\n }\n\n // Short-circuit if parent has already failed\n if (isDefined(parent.$$failure)) {\n fail(parent.$$failure);\n return result;\n }\n \n if (parent.$$inheritedValues) {\n merge(values, omit(parent.$$inheritedValues, invocableKeys));\n }\n\n // Merge parent values if the parent has already resolved, or merge\n // parent promises and wait if the parent resolve is still in progress.\n extend(promises, parent.$$promises);\n if (parent.$$values) {\n merged = merge(values, omit(parent.$$values, invocableKeys));\n result.$$inheritedValues = omit(parent.$$values, invocableKeys);\n done();\n } else {\n if (parent.$$inheritedValues) {\n result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);\n } \n parent.then(done, fail);\n }\n \n // Process each invocable in the plan, but ignore any where a local of the same name exists.\n for (var i=0, ii=plan.length; i\n * Impact on loading templates for more details about this mechanism.\n *\n * @param {boolean} value\n */\n this.shouldUnsafelyUseHttp = function(value) {\n shouldUnsafelyUseHttp = !!value;\n };\n\n /**\n * @ngdoc object\n * @name ui.router.util.$templateFactory\n *\n * @requires $http\n * @requires $templateCache\n * @requires $injector\n *\n * @description\n * Service. Manages loading of templates.\n */\n this.$get = ['$http', '$templateCache', '$injector', function($http, $templateCache, $injector){\n return new TemplateFactory($http, $templateCache, $injector, shouldUnsafelyUseHttp);}];\n}\n\n\n/**\n * @ngdoc object\n * @name ui.router.util.$templateFactory\n *\n * @requires $http\n * @requires $templateCache\n * @requires $injector\n *\n * @description\n * Service. Manages loading of templates.\n */\nfunction TemplateFactory($http, $templateCache, $injector, shouldUnsafelyUseHttp) {\n\n /**\n * @ngdoc function\n * @name ui.router.util.$templateFactory#fromConfig\n * @methodOf ui.router.util.$templateFactory\n *\n * @description\n * Creates a template from a configuration object. \n *\n * @param {object} config Configuration object for which to load a template. \n * The following properties are search in the specified order, and the first one \n * that is defined is used to create the template:\n *\n * @param {string|object} config.template html string template or function to \n * load via {@link ui.router.util.$templateFactory#fromString fromString}.\n * @param {string|object} config.templateUrl url to load or a function returning \n * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}.\n * @param {Function} config.templateProvider function to invoke via \n * {@link ui.router.util.$templateFactory#fromProvider fromProvider}.\n * @param {object} params Parameters to pass to the template function.\n * @param {object} locals Locals to pass to `invoke` if the template is loaded \n * via a `templateProvider`. Defaults to `{ params: params }`.\n *\n * @return {string|object} The template html as a string, or a promise for \n * that string,or `null` if no template is configured.\n */\n this.fromConfig = function (config, params, locals) {\n return (\n isDefined(config.template) ? this.fromString(config.template, params) :\n isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) :\n isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) :\n null\n );\n };\n\n /**\n * @ngdoc function\n * @name ui.router.util.$templateFactory#fromString\n * @methodOf ui.router.util.$templateFactory\n *\n * @description\n * Creates a template from a string or a function returning a string.\n *\n * @param {string|object} template html template as a string or function that \n * returns an html template as a string.\n * @param {object} params Parameters to pass to the template function.\n *\n * @return {string|object} The template html as a string, or a promise for that \n * string.\n */\n this.fromString = function (template, params) {\n return isFunction(template) ? template(params) : template;\n };\n\n /**\n * @ngdoc function\n * @name ui.router.util.$templateFactory#fromUrl\n * @methodOf ui.router.util.$templateFactory\n * \n * @description\n * Loads a template from the a URL via `$http` and `$templateCache`.\n *\n * @param {string|Function} url url of the template to load, or a function \n * that returns a url.\n * @param {Object} params Parameters to pass to the url function.\n * @return {string|Promise.} The template html as a string, or a promise \n * for that string.\n */\n this.fromUrl = function (url, params) {\n if (isFunction(url)) url = url(params);\n if (url == null) return null;\n else {\n if(!shouldUnsafelyUseHttp) {\n return $injector.get('$templateRequest')(url);\n } else {\n return $http\n .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }})\n .then(function(response) { return response.data; });\n }\n }\n };\n\n /**\n * @ngdoc function\n * @name ui.router.util.$templateFactory#fromProvider\n * @methodOf ui.router.util.$templateFactory\n *\n * @description\n * Creates a template by invoking an injectable provider function.\n *\n * @param {Function} provider Function to invoke via `$injector.invoke`\n * @param {Object} params Parameters for the template.\n * @param {Object} locals Locals to pass to `invoke`. Defaults to \n * `{ params: params }`.\n * @return {string|Promise.} The template html as a string, or a promise \n * for that string.\n */\n this.fromProvider = function (provider, params, locals) {\n return $injector.invoke(provider, null, locals || { params: params });\n };\n}\n\nangular.module('ui.router.util').provider('$templateFactory', TemplateFactoryProvider);\n\nvar $$UMFP; // reference to $UrlMatcherFactoryProvider\n\n/**\n * @ngdoc object\n * @name ui.router.util.type:UrlMatcher\n *\n * @description\n * Matches URLs against patterns and extracts named parameters from the path or the search\n * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list\n * of search parameters. Multiple search parameter names are separated by '&'. Search parameters\n * do not influence whether or not a URL is matched, but their values are passed through into\n * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.\n *\n * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace\n * syntax, which optionally allows a regular expression for the parameter to be specified:\n *\n * * `':'` name - colon placeholder\n * * `'*'` name - catch-all placeholder\n * * `'{' name '}'` - curly placeholder\n * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the\n * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.\n *\n * Parameter names may contain only word characters (latin letters, digits, and underscore) and\n * must be unique within the pattern (across both path and search parameters). For colon\n * placeholders or curly placeholders without an explicit regexp, a path parameter matches any\n * number of characters other than '/'. For catch-all placeholders the path parameter matches\n * any number of characters.\n *\n * Examples:\n *\n * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for\n * trailing slashes, and patterns have to match the entire path, not just a prefix.\n * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or\n * '/user/bob/details'. The second path segment will be captured as the parameter 'id'.\n * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax.\n * * `'/user/{id:[^/]*}'` - Same as the previous example.\n * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id\n * parameter consists of 1 to 8 hex digits.\n * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the\n * path into the parameter 'path'.\n * * `'/files/*path'` - ditto.\n * * `'/calendar/{start:date}'` - Matches \"/calendar/2014-11-12\" (because the pattern defined\n * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start\n *\n * @param {string} pattern The pattern to compile into a matcher.\n * @param {Object} config A configuration object hash:\n * @param {Object=} parentMatcher Used to concatenate the pattern/config onto\n * an existing UrlMatcher\n *\n * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.\n * * `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`.\n *\n * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any\n * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns\n * non-null) will start with this prefix.\n *\n * @property {string} source The pattern that was passed into the constructor\n *\n * @property {string} sourcePath The path portion of the source property\n *\n * @property {string} sourceSearch The search portion of the source property\n *\n * @property {string} regex The constructed regex that will be used to match against the url when\n * it is time to determine which url will match.\n *\n * @returns {Object} New `UrlMatcher` object\n */\nfunction UrlMatcher(pattern, config, parentMatcher) {\n config = extend({ params: {} }, isObject(config) ? config : {});\n\n // Find all placeholders and create a compiled pattern, using either classic or curly syntax:\n // '*' name\n // ':' name\n // '{' name '}'\n // '{' name ':' regexp '}'\n // The regular expression is somewhat complicated due to the need to allow curly braces\n // inside the regular expression. The placeholder regexp breaks down as follows:\n // ([:*])([\\w\\[\\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case)\n // \\{([\\w\\[\\]]+)(?:\\:\\s*( ... ))?\\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case\n // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either\n // [^{}\\\\]+ - anything other than curly braces or backslash\n // \\\\. - a backslash escape\n // \\{(?:[^{}\\\\]+|\\\\.)*\\} - a matched set of curly braces containing other atoms\n var placeholder = /([:*])([\\w\\[\\]]+)|\\{([\\w\\[\\]]+)(?:\\:\\s*((?:[^{}\\\\]+|\\\\.|\\{(?:[^{}\\\\]+|\\\\.)*\\})+))?\\}/g,\n searchPlaceholder = /([:]?)([\\w\\[\\].-]+)|\\{([\\w\\[\\].-]+)(?:\\:\\s*((?:[^{}\\\\]+|\\\\.|\\{(?:[^{}\\\\]+|\\\\.)*\\})+))?\\}/g,\n compiled = '^', last = 0, m,\n segments = this.segments = [],\n parentParams = parentMatcher ? parentMatcher.params : {},\n params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),\n paramNames = [];\n\n function addParameter(id, type, config, location) {\n paramNames.push(id);\n if (parentParams[id]) return parentParams[id];\n if (!/^\\w+([-.]+\\w+)*(?:\\[\\])?$/.test(id)) throw new Error(\"Invalid parameter name '\" + id + \"' in pattern '\" + pattern + \"'\");\n if (params[id]) throw new Error(\"Duplicate parameter name '\" + id + \"' in pattern '\" + pattern + \"'\");\n params[id] = new $$UMFP.Param(id, type, config, location);\n return params[id];\n }\n\n function quoteRegExp(string, pattern, squash, optional) {\n var surroundPattern = ['',''], result = string.replace(/[\\\\\\[\\]\\^$*+?.()|{}]/g, \"\\\\$&\");\n if (!pattern) return result;\n switch(squash) {\n case false: surroundPattern = ['(', ')' + (optional ? \"?\" : \"\")]; break;\n case true:\n result = result.replace(/\\/$/, '');\n surroundPattern = ['(?:\\/(', ')|\\/)?'];\n break;\n default: surroundPattern = ['(' + squash + \"|\", ')?']; break;\n }\n return result + surroundPattern[0] + pattern + surroundPattern[1];\n }\n\n this.source = pattern;\n\n // Split into static segments separated by path parameter placeholders.\n // The number of segments is always 1 more than the number of parameters.\n function matchDetails(m, isSearch) {\n var id, regexp, segment, type, cfg, arrayMode;\n id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null\n cfg = config.params[id];\n segment = pattern.substring(last, m.index);\n regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);\n\n if (regexp) {\n type = $$UMFP.type(regexp) || inherit($$UMFP.type(\"string\"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });\n }\n\n return {\n id: id, regexp: regexp, segment: segment, type: type, cfg: cfg\n };\n }\n\n var p, param, segment;\n while ((m = placeholder.exec(pattern))) {\n p = matchDetails(m, false);\n if (p.segment.indexOf('?') >= 0) break; // we're into the search part\n\n param = addParameter(p.id, p.type, p.cfg, \"path\");\n compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional);\n segments.push(p.segment);\n last = placeholder.lastIndex;\n }\n segment = pattern.substring(last);\n\n // Find any search parameter names and remove them from the last segment\n var i = segment.indexOf('?');\n\n if (i >= 0) {\n var search = this.sourceSearch = segment.substring(i);\n segment = segment.substring(0, i);\n this.sourcePath = pattern.substring(0, last + i);\n\n if (search.length > 0) {\n last = 0;\n while ((m = searchPlaceholder.exec(search))) {\n p = matchDetails(m, true);\n param = addParameter(p.id, p.type, p.cfg, \"search\");\n last = placeholder.lastIndex;\n // check if ?&\n }\n }\n } else {\n this.sourcePath = pattern;\n this.sourceSearch = '';\n }\n\n compiled += quoteRegExp(segment) + (config.strict === false ? '\\/?' : '') + '$';\n segments.push(segment);\n\n this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);\n this.prefix = segments[0];\n this.$$paramNames = paramNames;\n}\n\n/**\n * @ngdoc function\n * @name ui.router.util.type:UrlMatcher#concat\n * @methodOf ui.router.util.type:UrlMatcher\n *\n * @description\n * Returns a new matcher for a pattern constructed by appending the path part and adding the\n * search parameters of the specified pattern to this pattern. The current pattern is not\n * modified. This can be understood as creating a pattern for URLs that are relative to (or\n * suffixes of) the current pattern.\n *\n * @example\n * The following two matchers are equivalent:\n * \n * new UrlMatcher('/user/{id}?q').concat('/details?date');\n * new UrlMatcher('/user/{id}/details?q&date');\n *
\n *\n * @param {string} pattern The pattern to append.\n * @param {Object} config An object hash of the configuration for the matcher.\n * @returns {UrlMatcher} A matcher for the concatenated pattern.\n */\nUrlMatcher.prototype.concat = function (pattern, config) {\n // Because order of search parameters is irrelevant, we can add our own search\n // parameters to the end of the new pattern. Parse the new pattern by itself\n // and then join the bits together, but it's much easier to do this on a string level.\n var defaultConfig = {\n caseInsensitive: $$UMFP.caseInsensitive(),\n strict: $$UMFP.strictMode(),\n squash: $$UMFP.defaultSquashPolicy()\n };\n return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this);\n};\n\nUrlMatcher.prototype.toString = function () {\n return this.source;\n};\n\n/**\n * @ngdoc function\n * @name ui.router.util.type:UrlMatcher#exec\n * @methodOf ui.router.util.type:UrlMatcher\n *\n * @description\n * Tests the specified path against this matcher, and returns an object containing the captured\n * parameter values, or null if the path does not match. The returned object contains the values\n * of any search parameters that are mentioned in the pattern, but their value may be null if\n * they are not present in `searchParams`. This means that search parameters are always treated\n * as optional.\n *\n * @example\n * \n * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {\n * x: '1', q: 'hello'\n * });\n * // returns { id: 'bob', q: 'hello', r: null }\n *
\n *\n * @param {string} path The URL path to match, e.g. `$location.path()`.\n * @param {Object} searchParams URL search parameters, e.g. `$location.search()`.\n * @returns {Object} The captured parameter values.\n */\nUrlMatcher.prototype.exec = function (path, searchParams) {\n var m = this.regexp.exec(path);\n if (!m) return null;\n searchParams = searchParams || {};\n\n var paramNames = this.parameters(), nTotal = paramNames.length,\n nPath = this.segments.length - 1,\n values = {}, i, j, cfg, paramName;\n\n if (nPath !== m.length - 1) throw new Error(\"Unbalanced capture group in route '\" + this.source + \"'\");\n\n function decodePathArray(string) {\n function reverseString(str) { return str.split(\"\").reverse().join(\"\"); }\n function unquoteDashes(str) { return str.replace(/\\\\-/g, \"-\"); }\n\n var split = reverseString(string).split(/-(?!\\\\)/);\n var allReversed = map(split, reverseString);\n return map(allReversed, unquoteDashes).reverse();\n }\n\n var param, paramVal;\n for (i = 0; i < nPath; i++) {\n paramName = paramNames[i];\n param = this.params[paramName];\n paramVal = m[i+1];\n // if the param value matches a pre-replace pair, replace the value before decoding.\n for (j = 0; j < param.replace.length; j++) {\n if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;\n }\n if (paramVal && param.array === true) paramVal = decodePathArray(paramVal);\n if (isDefined(paramVal)) paramVal = param.type.decode(paramVal);\n values[paramName] = param.value(paramVal);\n }\n for (/**/; i < nTotal; i++) {\n paramName = paramNames[i];\n values[paramName] = this.params[paramName].value(searchParams[paramName]);\n param = this.params[paramName];\n paramVal = searchParams[paramName];\n for (j = 0; j < param.replace.length; j++) {\n if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;\n }\n if (isDefined(paramVal)) paramVal = param.type.decode(paramVal);\n values[paramName] = param.value(paramVal);\n }\n\n return values;\n};\n\n/**\n * @ngdoc function\n * @name ui.router.util.type:UrlMatcher#parameters\n * @methodOf ui.router.util.type:UrlMatcher\n *\n * @description\n * Returns the names of all path and search parameters of this pattern in an unspecified order.\n *\n * @returns {Array.} An array of parameter names. Must be treated as read-only. If the\n * pattern has no parameters, an empty array is returned.\n */\nUrlMatcher.prototype.parameters = function (param) {\n if (!isDefined(param)) return this.$$paramNames;\n return this.params[param] || null;\n};\n\n/**\n * @ngdoc function\n * @name ui.router.util.type:UrlMatcher#validates\n * @methodOf ui.router.util.type:UrlMatcher\n *\n * @description\n * Checks an object hash of parameters to validate their correctness according to the parameter\n * types of this `UrlMatcher`.\n *\n * @param {Object} params The object hash of parameters to validate.\n * @returns {boolean} Returns `true` if `params` validates, otherwise `false`.\n */\nUrlMatcher.prototype.validates = function (params) {\n return this.params.$$validates(params);\n};\n\n/**\n * @ngdoc function\n * @name ui.router.util.type:UrlMatcher#format\n * @methodOf ui.router.util.type:UrlMatcher\n *\n * @description\n * Creates a URL that matches this pattern by substituting the specified values\n * for the path and search parameters. Null values for path parameters are\n * treated as empty strings.\n *\n * @example\n * \n * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });\n * // returns '/user/bob?q=yes'\n *
\n *\n * @param {Object} values the values to substitute for the parameters in this pattern.\n * @returns {string} the formatted URL (path and optionally search part).\n */\nUrlMatcher.prototype.format = function (values) {\n values = values || {};\n var segments = this.segments, params = this.parameters(), paramset = this.params;\n if (!this.validates(values)) return null;\n\n var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0];\n\n function encodeDashes(str) { // Replace dashes with encoded \"\\-\"\n return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); });\n }\n\n for (i = 0; i < nTotal; i++) {\n var isPathParam = i < nPath;\n var name = params[i], param = paramset[name], value = param.value(values[name]);\n var isDefaultValue = param.isOptional && param.type.equals(param.value(), value);\n var squash = isDefaultValue ? param.squash : false;\n var encoded = param.type.encode(value);\n\n if (isPathParam) {\n var nextSegment = segments[i + 1];\n var isFinalPathParam = i + 1 === nPath;\n\n if (squash === false) {\n if (encoded != null) {\n if (isArray(encoded)) {\n result += map(encoded, encodeDashes).join(\"-\");\n } else {\n result += encodeURIComponent(encoded);\n }\n }\n result += nextSegment;\n } else if (squash === true) {\n var capture = result.match(/\\/$/) ? /\\/?(.*)/ : /(.*)/;\n result += nextSegment.match(capture)[1];\n } else if (isString(squash)) {\n result += squash + nextSegment;\n }\n\n if (isFinalPathParam && param.squash === true && result.slice(-1) === '/') result = result.slice(0, -1);\n } else {\n if (encoded == null || (isDefaultValue && squash !== false)) continue;\n if (!isArray(encoded)) encoded = [ encoded ];\n if (encoded.length === 0) continue;\n encoded = map(encoded, encodeURIComponent).join('&' + name + '=');\n result += (search ? '&' : '?') + (name + '=' + encoded);\n search = true;\n }\n }\n\n return result;\n};\n\n/**\n * @ngdoc object\n * @name ui.router.util.type:Type\n *\n * @description\n * Implements an interface to define custom parameter types that can be decoded from and encoded to\n * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`}\n * objects when matching or formatting URLs, or comparing or validating parameter values.\n *\n * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more\n * information on registering custom types.\n *\n * @param {Object} config A configuration object which contains the custom type definition. The object's\n * properties will override the default methods and/or pattern in `Type`'s public interface.\n * @example\n * \n * {\n * decode: function(val) { return parseInt(val, 10); },\n * encode: function(val) { return val && val.toString(); },\n * equals: function(a, b) { return this.is(a) && a === b; },\n * is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },\n * pattern: /\\d+/\n * }\n *
\n *\n * @property {RegExp} pattern The regular expression pattern used to match values of this type when\n * coming from a substring of a URL.\n *\n * @returns {Object} Returns a new `Type` object.\n */\nfunction Type(config) {\n extend(this, config);\n}\n\n/**\n * @ngdoc function\n * @name ui.router.util.type:Type#is\n * @methodOf ui.router.util.type:Type\n *\n * @description\n * Detects whether a value is of a particular type. Accepts a native (decoded) value\n * and determines whether it matches the current `Type` object.\n *\n * @param {*} val The value to check.\n * @param {string} key Optional. If the type check is happening in the context of a specific\n * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the\n * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.\n * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`.\n */\nType.prototype.is = function(val, key) {\n return true;\n};\n\n/**\n * @ngdoc function\n * @name ui.router.util.type:Type#encode\n * @methodOf ui.router.util.type:Type\n *\n * @description\n * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the\n * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it\n * only needs to be a representation of `val` that has been coerced to a string.\n *\n * @param {*} val The value to encode.\n * @param {string} key The name of the parameter in which `val` is stored. Can be used for\n * meta-programming of `Type` objects.\n * @returns {string} Returns a string representation of `val` that can be encoded in a URL.\n */\nType.prototype.encode = function(val, key) {\n return val;\n};\n\n/**\n * @ngdoc function\n * @name ui.router.util.type:Type#decode\n * @methodOf ui.router.util.type:Type\n *\n * @description\n * Converts a parameter value (from URL string or transition param) to a custom/native value.\n *\n * @param {string} val The URL parameter value to decode.\n * @param {string} key The name of the parameter in which `val` is stored. Can be used for\n * meta-programming of `Type` objects.\n * @returns {*} Returns a custom representation of the URL parameter value.\n */\nType.prototype.decode = function(val, key) {\n return val;\n};\n\n/**\n * @ngdoc function\n * @name ui.router.util.type:Type#equals\n * @methodOf ui.router.util.type:Type\n *\n * @description\n * Determines whether two decoded values are equivalent.\n *\n * @param {*} a A value to compare against.\n * @param {*} b A value to compare against.\n * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`.\n */\nType.prototype.equals = function(a, b) {\n return a == b;\n};\n\nType.prototype.$subPattern = function() {\n var sub = this.pattern.toString();\n return sub.substr(1, sub.length - 2);\n};\n\nType.prototype.pattern = /.*/;\n\nType.prototype.toString = function() { return \"{Type:\" + this.name + \"}\"; };\n\n/** Given an encoded string, or a decoded object, returns a decoded object */\nType.prototype.$normalize = function(val) {\n return this.is(val) ? val : this.decode(val);\n};\n\n/*\n * Wraps an existing custom Type as an array of Type, depending on 'mode'.\n * e.g.:\n * - urlmatcher pattern \"/path?{queryParam[]:int}\"\n * - url: \"/path?queryParam=1&queryParam=2\n * - $stateParams.queryParam will be [1, 2]\n * if `mode` is \"auto\", then\n * - url: \"/path?queryParam=1 will create $stateParams.queryParam: 1\n * - url: \"/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2]\n */\nType.prototype.$asArray = function(mode, isSearch) {\n if (!mode) return this;\n if (mode === \"auto\" && !isSearch) throw new Error(\"'auto' array mode is for query parameters only\");\n\n function ArrayType(type, mode) {\n function bindTo(type, callbackName) {\n return function() {\n return type[callbackName].apply(type, arguments);\n };\n }\n\n // Wrap non-array value as array\n function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }\n // Unwrap array value for \"auto\" mode. Return undefined for empty array.\n function arrayUnwrap(val) {\n switch(val.length) {\n case 0: return undefined;\n case 1: return mode === \"auto\" ? val[0] : val;\n default: return val;\n }\n }\n function falsey(val) { return !val; }\n\n // Wraps type (.is/.encode/.decode) functions to operate on each value of an array\n function arrayHandler(callback, allTruthyMode) {\n return function handleArray(val) {\n if (isArray(val) && val.length === 0) return val;\n val = arrayWrap(val);\n var result = map(val, callback);\n if (allTruthyMode === true)\n return filter(result, falsey).length === 0;\n return arrayUnwrap(result);\n };\n }\n\n // Wraps type (.equals) functions to operate on each value of an array\n function arrayEqualsHandler(callback) {\n return function handleArray(val1, val2) {\n var left = arrayWrap(val1), right = arrayWrap(val2);\n if (left.length !== right.length) return false;\n for (var i = 0; i < left.length; i++) {\n if (!callback(left[i], right[i])) return false;\n }\n return true;\n };\n }\n\n this.encode = arrayHandler(bindTo(type, 'encode'));\n this.decode = arrayHandler(bindTo(type, 'decode'));\n this.is = arrayHandler(bindTo(type, 'is'), true);\n this.equals = arrayEqualsHandler(bindTo(type, 'equals'));\n this.pattern = type.pattern;\n this.$normalize = arrayHandler(bindTo(type, '$normalize'));\n this.name = type.name;\n this.$arrayMode = mode;\n }\n\n return new ArrayType(this, mode);\n};\n\n\n\n/**\n * @ngdoc object\n * @name ui.router.util.$urlMatcherFactory\n *\n * @description\n * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory\n * is also available to providers under the name `$urlMatcherFactoryProvider`.\n */\nfunction $UrlMatcherFactory() {\n $$UMFP = this;\n\n var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;\n\n // Use tildes to pre-encode slashes.\n // If the slashes are simply URLEncoded, the browser can choose to pre-decode them,\n // and bidirectional encoding/decoding fails.\n // Tilde was chosen because it's not a RFC 3986 section 2.2 Reserved Character\n function valToString(val) { return val != null ? val.toString().replace(/(~|\\/)/g, function (m) { return {'~':'~~', '/':'~2F'}[m]; }) : val; }\n function valFromString(val) { return val != null ? val.toString().replace(/(~~|~2F)/g, function (m) { return {'~~':'~', '~2F':'/'}[m]; }) : val; }\n\n var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = {\n \"string\": {\n encode: valToString,\n decode: valFromString,\n // TODO: in 1.0, make string .is() return false if value is undefined/null by default.\n // In 0.2.x, string params are optional by default for backwards compat\n is: function(val) { return val == null || !isDefined(val) || typeof val === \"string\"; },\n pattern: /[^/]*/\n },\n \"int\": {\n encode: valToString,\n decode: function(val) { return parseInt(val, 10); },\n is: function(val) { return val !== undefined && val !== null && this.decode(val.toString()) === val; },\n pattern: /\\d+/\n },\n \"bool\": {\n encode: function(val) { return val ? 1 : 0; },\n decode: function(val) { return parseInt(val, 10) !== 0; },\n is: function(val) { return val === true || val === false; },\n pattern: /0|1/\n },\n \"date\": {\n encode: function (val) {\n if (!this.is(val))\n return undefined;\n return [ val.getFullYear(),\n ('0' + (val.getMonth() + 1)).slice(-2),\n ('0' + val.getDate()).slice(-2)\n ].join(\"-\");\n },\n decode: function (val) {\n if (this.is(val)) return val;\n var match = this.capture.exec(val);\n return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;\n },\n is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); },\n equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },\n pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,\n capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/\n },\n \"json\": {\n encode: angular.toJson,\n decode: angular.fromJson,\n is: angular.isObject,\n equals: angular.equals,\n pattern: /[^/]*/\n },\n \"any\": { // does not encode/decode\n encode: angular.identity,\n decode: angular.identity,\n equals: angular.equals,\n pattern: /.*/\n }\n };\n\n function getDefaultConfig() {\n return {\n strict: isStrictMode,\n caseInsensitive: isCaseInsensitive\n };\n }\n\n function isInjectable(value) {\n return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));\n }\n\n /**\n * [Internal] Get the default value of a parameter, which may be an injectable function.\n */\n $UrlMatcherFactory.$$getDefaultValue = function(config) {\n if (!isInjectable(config.value)) return config.value;\n if (!injector) throw new Error(\"Injectable functions cannot be called at configuration time\");\n return injector.invoke(config.value);\n };\n\n /**\n * @ngdoc function\n * @name ui.router.util.$urlMatcherFactory#caseInsensitive\n * @methodOf ui.router.util.$urlMatcherFactory\n *\n * @description\n * Defines whether URL matching should be case sensitive (the default behavior), or not.\n *\n * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;\n * @returns {boolean} the current value of caseInsensitive\n */\n this.caseInsensitive = function(value) {\n if (isDefined(value))\n isCaseInsensitive = value;\n return isCaseInsensitive;\n };\n\n /**\n * @ngdoc function\n * @name ui.router.util.$urlMatcherFactory#strictMode\n * @methodOf ui.router.util.$urlMatcherFactory\n *\n * @description\n * Defines whether URLs should match trailing slashes, or not (the default behavior).\n *\n * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`.\n * @returns {boolean} the current value of strictMode\n */\n this.strictMode = function(value) {\n if (isDefined(value))\n isStrictMode = value;\n return isStrictMode;\n };\n\n /**\n * @ngdoc function\n * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy\n * @methodOf ui.router.util.$urlMatcherFactory\n *\n * @description\n * Sets the default behavior when generating or matching URLs with default parameter values.\n *\n * @param {string} value A string that defines the default parameter URL squashing behavior.\n * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL\n * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the\n * parameter is surrounded by slashes, squash (remove) one slash from the URL\n * any other string, e.g. \"~\": When generating an href with a default parameter value, squash (remove)\n * the parameter value from the URL and replace it with this string.\n */\n this.defaultSquashPolicy = function(value) {\n if (!isDefined(value)) return defaultSquashPolicy;\n if (value !== true && value !== false && !isString(value))\n throw new Error(\"Invalid squash policy: \" + value + \". Valid policies: false, true, arbitrary-string\");\n defaultSquashPolicy = value;\n return value;\n };\n\n /**\n * @ngdoc function\n * @name ui.router.util.$urlMatcherFactory#compile\n * @methodOf ui.router.util.$urlMatcherFactory\n *\n * @description\n * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.\n *\n * @param {string} pattern The URL pattern.\n * @param {Object} config The config object hash.\n * @returns {UrlMatcher} The UrlMatcher.\n */\n this.compile = function (pattern, config) {\n return new UrlMatcher(pattern, extend(getDefaultConfig(), config));\n };\n\n /**\n * @ngdoc function\n * @name ui.router.util.$urlMatcherFactory#isMatcher\n * @methodOf ui.router.util.$urlMatcherFactory\n *\n * @description\n * Returns true if the specified object is a `UrlMatcher`, or false otherwise.\n *\n * @param {Object} object The object to perform the type check against.\n * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by\n * implementing all the same methods.\n */\n this.isMatcher = function (o) {\n if (!isObject(o)) return false;\n var result = true;\n\n forEach(UrlMatcher.prototype, function(val, name) {\n if (isFunction(val)) {\n result = result && (isDefined(o[name]) && isFunction(o[name]));\n }\n });\n return result;\n };\n\n /**\n * @ngdoc function\n * @name ui.router.util.$urlMatcherFactory#type\n * @methodOf ui.router.util.$urlMatcherFactory\n *\n * @description\n * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to\n * generate URLs with typed parameters.\n *\n * @param {string} name The type name.\n * @param {Object|Function} definition The type definition. See\n * {@link ui.router.util.type:Type `Type`} for information on the values accepted.\n * @param {Object|Function} definitionFn (optional) A function that is injected before the app\n * runtime starts. The result of this function is merged into the existing `definition`.\n * See {@link ui.router.util.type:Type `Type`} for information on the values accepted.\n *\n * @returns {Object} Returns `$urlMatcherFactoryProvider`.\n *\n * @example\n * This is a simple example of a custom type that encodes and decodes items from an\n * array, using the array index as the URL-encoded value:\n *\n * \n * var list = ['John', 'Paul', 'George', 'Ringo'];\n *\n * $urlMatcherFactoryProvider.type('listItem', {\n * encode: function(item) {\n * // Represent the list item in the URL using its corresponding index\n * return list.indexOf(item);\n * },\n * decode: function(item) {\n * // Look up the list item by index\n * return list[parseInt(item, 10)];\n * },\n * is: function(item) {\n * // Ensure the item is valid by checking to see that it appears\n * // in the list\n * return list.indexOf(item) > -1;\n * }\n * });\n *\n * $stateProvider.state('list', {\n * url: \"/list/{item:listItem}\",\n * controller: function($scope, $stateParams) {\n * console.log($stateParams.item);\n * }\n * });\n *\n * // ...\n *\n * // Changes URL to '/list/3', logs \"Ringo\" to the console\n * $state.go('list', { item: \"Ringo\" });\n *
\n *\n * This is a more complex example of a type that relies on dependency injection to\n * interact with services, and uses the parameter name from the URL to infer how to\n * handle encoding and decoding parameter values:\n *\n * \n * // Defines a custom type that gets a value from a service,\n * // where each service gets different types of values from\n * // a backend API:\n * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {\n *\n * // Matches up services to URL parameter names\n * var services = {\n * user: Users,\n * post: Posts\n * };\n *\n * return {\n * encode: function(object) {\n * // Represent the object in the URL using its unique ID\n * return object.id;\n * },\n * decode: function(value, key) {\n * // Look up the object by ID, using the parameter\n * // name (key) to call the correct service\n * return services[key].findById(value);\n * },\n * is: function(object, key) {\n * // Check that object is a valid dbObject\n * return angular.isObject(object) && object.id && services[key];\n * }\n * equals: function(a, b) {\n * // Check the equality of decoded objects by comparing\n * // their unique IDs\n * return a.id === b.id;\n * }\n * };\n * });\n *\n * // In a config() block, you can then attach URLs with\n * // type-annotated parameters:\n * $stateProvider.state('users', {\n * url: \"/users\",\n * // ...\n * }).state('users.item', {\n * url: \"/{user:dbObject}\",\n * controller: function($scope, $stateParams) {\n * // $stateParams.user will now be an object returned from\n * // the Users service\n * },\n * // ...\n * });\n *
\n */\n this.type = function (name, definition, definitionFn) {\n if (!isDefined(definition)) return $types[name];\n if ($types.hasOwnProperty(name)) throw new Error(\"A type named '\" + name + \"' has already been defined.\");\n\n $types[name] = new Type(extend({ name: name }, definition));\n if (definitionFn) {\n typeQueue.push({ name: name, def: definitionFn });\n if (!enqueue) flushTypeQueue();\n }\n return this;\n };\n\n // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s\n function flushTypeQueue() {\n while(typeQueue.length) {\n var type = typeQueue.shift();\n if (type.pattern) throw new Error(\"You cannot override a type's .pattern at runtime.\");\n angular.extend($types[type.name], injector.invoke(type.def));\n }\n }\n\n // Register default types. Store them in the prototype of $types.\n forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); });\n $types = inherit($types, {});\n\n /* No need to document $get, since it returns this */\n this.$get = ['$injector', function ($injector) {\n injector = $injector;\n enqueue = false;\n flushTypeQueue();\n\n forEach(defaultTypes, function(type, name) {\n if (!$types[name]) $types[name] = new Type(type);\n });\n return this;\n }];\n\n this.Param = function Param(id, type, config, location) {\n var self = this;\n config = unwrapShorthand(config);\n type = getType(config, type, location);\n var arrayMode = getArrayMode();\n type = arrayMode ? type.$asArray(arrayMode, location === \"search\") : type;\n if (type.name === \"string\" && !arrayMode && location === \"path\" && config.value === undefined)\n config.value = \"\"; // for 0.2.x; in 0.3.0+ do not automatically default to \"\"\n var isOptional = config.value !== undefined;\n var squash = getSquashPolicy(config, isOptional);\n var replace = getReplace(config, arrayMode, isOptional, squash);\n\n function unwrapShorthand(config) {\n var keys = isObject(config) ? objectKeys(config) : [];\n var isShorthand = indexOf(keys, \"value\") === -1 && indexOf(keys, \"type\") === -1 &&\n indexOf(keys, \"squash\") === -1 && indexOf(keys, \"array\") === -1;\n if (isShorthand) config = { value: config };\n config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };\n return config;\n }\n\n function getType(config, urlType, location) {\n if (config.type && urlType) throw new Error(\"Param '\"+id+\"' has two type configurations.\");\n if (urlType) return urlType;\n if (!config.type) return (location === \"config\" ? $types.any : $types.string);\n\n if (angular.isString(config.type))\n return $types[config.type];\n if (config.type instanceof Type)\n return config.type;\n return new Type(config.type);\n }\n\n // array config: param name (param[]) overrides default settings. explicit config overrides param name.\n function getArrayMode() {\n var arrayDefaults = { array: (location === \"search\" ? \"auto\" : false) };\n var arrayParamNomenclature = id.match(/\\[\\]$/) ? { array: true } : {};\n return extend(arrayDefaults, arrayParamNomenclature, config).array;\n }\n\n /**\n * returns false, true, or the squash value to indicate the \"default parameter url squash policy\".\n */\n function getSquashPolicy(config, isOptional) {\n var squash = config.squash;\n if (!isOptional || squash === false) return false;\n if (!isDefined(squash) || squash == null) return defaultSquashPolicy;\n if (squash === true || isString(squash)) return squash;\n throw new Error(\"Invalid squash policy: '\" + squash + \"'. Valid policies: false, true, or arbitrary string\");\n }\n\n function getReplace(config, arrayMode, isOptional, squash) {\n var replace, configuredKeys, defaultPolicy = [\n { from: \"\", to: (isOptional || arrayMode ? undefined : \"\") },\n { from: null, to: (isOptional || arrayMode ? undefined : \"\") }\n ];\n replace = isArray(config.replace) ? config.replace : [];\n if (isString(squash))\n replace.push({ from: squash, to: undefined });\n configuredKeys = map(replace, function(item) { return item.from; } );\n return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);\n }\n\n /**\n * [Internal] Get the default value of a parameter, which may be an injectable function.\n */\n function $$getDefaultValue() {\n if (!injector) throw new Error(\"Injectable functions cannot be called at configuration time\");\n var defaultValue = injector.invoke(config.$$fn);\n if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))\n throw new Error(\"Default value (\" + defaultValue + \") for parameter '\" + self.id + \"' is not an instance of Type (\" + self.type.name + \")\");\n return defaultValue;\n }\n\n /**\n * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the\n * default value, which may be the result of an injectable function.\n */\n function $value(value) {\n function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; }\n function $replace(value) {\n var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; });\n return replacement.length ? replacement[0] : value;\n }\n value = $replace(value);\n return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);\n }\n\n function toString() { return \"{Param:\" + id + \" \" + type + \" squash: '\" + squash + \"' optional: \" + isOptional + \"}\"; }\n\n extend(this, {\n id: id,\n type: type,\n location: location,\n array: arrayMode,\n squash: squash,\n replace: replace,\n isOptional: isOptional,\n value: $value,\n dynamic: undefined,\n config: config,\n toString: toString\n });\n };\n\n function ParamSet(params) {\n extend(this, params || {});\n }\n\n ParamSet.prototype = {\n $$new: function() {\n return inherit(this, extend(new ParamSet(), { $$parent: this}));\n },\n $$keys: function () {\n var keys = [], chain = [], parent = this,\n ignore = objectKeys(ParamSet.prototype);\n while (parent) { chain.push(parent); parent = parent.$$parent; }\n chain.reverse();\n forEach(chain, function(paramset) {\n forEach(objectKeys(paramset), function(key) {\n if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key);\n });\n });\n return keys;\n },\n $$values: function(paramValues) {\n var values = {}, self = this;\n forEach(self.$$keys(), function(key) {\n values[key] = self[key].value(paramValues && paramValues[key]);\n });\n return values;\n },\n $$equals: function(paramValues1, paramValues2) {\n var equal = true, self = this;\n forEach(self.$$keys(), function(key) {\n var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key];\n if (!self[key].type.equals(left, right)) equal = false;\n });\n return equal;\n },\n $$validates: function $$validate(paramValues) {\n var keys = this.$$keys(), i, param, rawVal, normalized, encoded;\n for (i = 0; i < keys.length; i++) {\n param = this[keys[i]];\n rawVal = paramValues[keys[i]];\n if ((rawVal === undefined || rawVal === null) && param.isOptional)\n break; // There was no parameter value, but the param is optional\n normalized = param.type.$normalize(rawVal);\n if (!param.type.is(normalized))\n return false; // The value was not of the correct Type, and could not be decoded to the correct Type\n encoded = param.type.encode(normalized);\n if (angular.isString(encoded) && !param.type.pattern.exec(encoded))\n return false; // The value was of the correct type, but when encoded, did not match the Type's regexp\n }\n return true;\n },\n $$parent: undefined\n };\n\n this.ParamSet = ParamSet;\n}\n\n// Register as a provider so it's available to other providers\nangular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);\nangular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);\n\n/**\n * @ngdoc object\n * @name ui.router.router.$urlRouterProvider\n *\n * @requires ui.router.util.$urlMatcherFactoryProvider\n * @requires $locationProvider\n *\n * @description\n * `$urlRouterProvider` has the responsibility of watching `$location`. \n * When `$location` changes it runs through a list of rules one by one until a \n * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify \n * a url in a state configuration. All urls are compiled into a UrlMatcher object.\n *\n * There are several methods on `$urlRouterProvider` that make it useful to use directly\n * in your module config.\n */\n$UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];\nfunction $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {\n var rules = [], otherwise = null, interceptDeferred = false, listener;\n\n // Returns a string that is a prefix of all strings matching the RegExp\n function regExpPrefix(re) {\n var prefix = /^\\^((?:\\\\[^a-zA-Z0-9]|[^\\\\\\[\\]\\^$*+?.()|{}]+)*)/.exec(re.source);\n return (prefix != null) ? prefix[1].replace(/\\\\(.)/g, \"$1\") : '';\n }\n\n // Interpolates matched values into a String.replace()-style pattern\n function interpolate(pattern, match) {\n return pattern.replace(/\\$(\\$|\\d{1,2})/, function (m, what) {\n return match[what === '$' ? 0 : Number(what)];\n });\n }\n\n /**\n * @ngdoc function\n * @name ui.router.router.$urlRouterProvider#rule\n * @methodOf ui.router.router.$urlRouterProvider\n *\n * @description\n * Defines rules that are used by `$urlRouterProvider` to find matches for\n * specific URLs.\n *\n * @example\n * \n * var app = angular.module('app', ['ui.router.router']);\n *\n * app.config(function ($urlRouterProvider) {\n * // Here's an example of how you might allow case insensitive urls\n * $urlRouterProvider.rule(function ($injector, $location) {\n * var path = $location.path(),\n * normalized = path.toLowerCase();\n *\n * if (path !== normalized) {\n * return normalized;\n * }\n * });\n * });\n *
\n *\n * @param {function} rule Handler function that takes `$injector` and `$location`\n * services as arguments. You can use them to return a valid path as a string.\n *\n * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance\n */\n this.rule = function (rule) {\n if (!isFunction(rule)) throw new Error(\"'rule' must be a function\");\n rules.push(rule);\n return this;\n };\n\n /**\n * @ngdoc object\n * @name ui.router.router.$urlRouterProvider#otherwise\n * @methodOf ui.router.router.$urlRouterProvider\n *\n * @description\n * Defines a path that is used when an invalid route is requested.\n *\n * @example\n * \n * var app = angular.module('app', ['ui.router.router']);\n *\n * app.config(function ($urlRouterProvider) {\n * // if the path doesn't match any of the urls you configured\n * // otherwise will take care of routing the user to the\n * // specified url\n * $urlRouterProvider.otherwise('/index');\n *\n * // Example of using function rule as param\n * $urlRouterProvider.otherwise(function ($injector, $location) {\n * return '/a/valid/url';\n * });\n * });\n *
\n *\n * @param {string|function} rule The url path you want to redirect to or a function \n * rule that returns the url path. The function version is passed two params: \n * `$injector` and `$location` services, and must return a url string.\n *\n * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance\n */\n this.otherwise = function (rule) {\n if (isString(rule)) {\n var redirect = rule;\n rule = function () { return redirect; };\n }\n else if (!isFunction(rule)) throw new Error(\"'rule' must be a function\");\n otherwise = rule;\n return this;\n };\n\n\n function handleIfMatch($injector, handler, match) {\n if (!match) return false;\n var result = $injector.invoke(handler, handler, { $match: match });\n return isDefined(result) ? result : true;\n }\n\n /**\n * @ngdoc function\n * @name ui.router.router.$urlRouterProvider#when\n * @methodOf ui.router.router.$urlRouterProvider\n *\n * @description\n * Registers a handler for a given url matching. \n * \n * If the handler is a string, it is\n * treated as a redirect, and is interpolated according to the syntax of match\n * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).\n *\n * If the handler is a function, it is injectable. It gets invoked if `$location`\n * matches. You have the option of inject the match object as `$match`.\n *\n * The handler can return\n *\n * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`\n * will continue trying to find another one that matches.\n * - **string** which is treated as a redirect and passed to `$location.url()`\n * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.\n *\n * @example\n * \n * var app = angular.module('app', ['ui.router.router']);\n *\n * app.config(function ($urlRouterProvider) {\n * $urlRouterProvider.when($state.url, function ($match, $stateParams) {\n * if ($state.$current.navigable !== state ||\n * !equalForKeys($match, $stateParams) {\n * $state.transitionTo(state, $match, false);\n * }\n * });\n * });\n *
\n *\n * @param {string|object} what The incoming path that you want to redirect.\n * @param {string|function} handler The path you want to redirect your user to.\n */\n this.when = function (what, handler) {\n var redirect, handlerIsString = isString(handler);\n if (isString(what)) what = $urlMatcherFactory.compile(what);\n\n if (!handlerIsString && !isFunction(handler) && !isArray(handler))\n throw new Error(\"invalid 'handler' in when()\");\n\n var strategies = {\n matcher: function (what, handler) {\n if (handlerIsString) {\n redirect = $urlMatcherFactory.compile(handler);\n handler = ['$match', function ($match) { return redirect.format($match); }];\n }\n return extend(function ($injector, $location) {\n return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));\n }, {\n prefix: isString(what.prefix) ? what.prefix : ''\n });\n },\n regex: function (what, handler) {\n if (what.global || what.sticky) throw new Error(\"when() RegExp must not be global or sticky\");\n\n if (handlerIsString) {\n redirect = handler;\n handler = ['$match', function ($match) { return interpolate(redirect, $match); }];\n }\n return extend(function ($injector, $location) {\n return handleIfMatch($injector, handler, what.exec($location.path()));\n }, {\n prefix: regExpPrefix(what)\n });\n }\n };\n\n var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };\n\n for (var n in check) {\n if (check[n]) return this.rule(strategies[n](what, handler));\n }\n\n throw new Error(\"invalid 'what' in when()\");\n };\n\n /**\n * @ngdoc function\n * @name ui.router.router.$urlRouterProvider#deferIntercept\n * @methodOf ui.router.router.$urlRouterProvider\n *\n * @description\n * Disables (or enables) deferring location change interception.\n *\n * If you wish to customize the behavior of syncing the URL (for example, if you wish to\n * defer a transition but maintain the current URL), call this method at configuration time.\n * Then, at run time, call `$urlRouter.listen()` after you have configured your own\n * `$locationChangeSuccess` event handler.\n *\n * @example\n * \n * var app = angular.module('app', ['ui.router.router']);\n *\n * app.config(function ($urlRouterProvider) {\n *\n * // Prevent $urlRouter from automatically intercepting URL changes;\n * // this allows you to configure custom behavior in between\n * // location changes and route synchronization:\n * $urlRouterProvider.deferIntercept();\n *\n * }).run(function ($rootScope, $urlRouter, UserService) {\n *\n * $rootScope.$on('$locationChangeSuccess', function(e) {\n * // UserService is an example service for managing user state\n * if (UserService.isLoggedIn()) return;\n *\n * // Prevent $urlRouter's default handler from firing\n * e.preventDefault();\n *\n * UserService.handleLogin().then(function() {\n * // Once the user has logged in, sync the current URL\n * // to the router:\n * $urlRouter.sync();\n * });\n * });\n *\n * // Configures $urlRouter's listener *after* your custom listener\n * $urlRouter.listen();\n * });\n *
\n *\n * @param {boolean} defer Indicates whether to defer location change interception. Passing\n no parameter is equivalent to `true`.\n */\n this.deferIntercept = function (defer) {\n if (defer === undefined) defer = true;\n interceptDeferred = defer;\n };\n\n /**\n * @ngdoc object\n * @name ui.router.router.$urlRouter\n *\n * @requires $location\n * @requires $rootScope\n * @requires $injector\n * @requires $browser\n *\n * @description\n *\n */\n this.$get = $get;\n $get.$inject = ['$location', '$rootScope', '$injector', '$browser', '$sniffer'];\n function $get( $location, $rootScope, $injector, $browser, $sniffer) {\n\n var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;\n\n function appendBasePath(url, isHtml5, absolute) {\n if (baseHref === '/') return url;\n if (isHtml5) return baseHref.slice(0, -1) + url;\n if (absolute) return baseHref.slice(1) + url;\n return url;\n }\n\n // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree\n function update(evt) {\n if (evt && evt.defaultPrevented) return;\n var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;\n lastPushedUrl = undefined;\n // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573\n //if (ignoreUpdate) return true;\n\n function check(rule) {\n var handled = rule($injector, $location);\n\n if (!handled) return false;\n if (isString(handled)) $location.replace().url(handled);\n return true;\n }\n var n = rules.length, i;\n\n for (i = 0; i < n; i++) {\n if (check(rules[i])) return;\n }\n // always check otherwise last to allow dynamic updates to the set of rules\n if (otherwise) check(otherwise);\n }\n\n function listen() {\n listener = listener || $rootScope.$on('$locationChangeSuccess', update);\n return listener;\n }\n\n if (!interceptDeferred) listen();\n\n return {\n /**\n * @ngdoc function\n * @name ui.router.router.$urlRouter#sync\n * @methodOf ui.router.router.$urlRouter\n *\n * @description\n * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.\n * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,\n * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed\n * with the transition by calling `$urlRouter.sync()`.\n *\n * @example\n * \n * angular.module('app', ['ui.router'])\n * .run(function($rootScope, $urlRouter) {\n * $rootScope.$on('$locationChangeSuccess', function(evt) {\n * // Halt state change from even starting\n * evt.preventDefault();\n * // Perform custom logic\n * var meetsRequirement = ...\n * // Continue with the update and state transition if logic allows\n * if (meetsRequirement) $urlRouter.sync();\n * });\n * });\n *
\n */\n sync: function() {\n update();\n },\n\n listen: function() {\n return listen();\n },\n\n update: function(read) {\n if (read) {\n location = $location.url();\n return;\n }\n if ($location.url() === location) return;\n\n $location.url(location);\n $location.replace();\n },\n\n push: function(urlMatcher, params, options) {\n var url = urlMatcher.format(params || {});\n\n // Handle the special hash param, if needed\n if (url !== null && params && params['#']) {\n url += '#' + params['#'];\n }\n\n $location.url(url);\n lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;\n if (options && options.replace) $location.replace();\n },\n\n /**\n * @ngdoc function\n * @name ui.router.router.$urlRouter#href\n * @methodOf ui.router.router.$urlRouter\n *\n * @description\n * A URL generation method that returns the compiled URL for a given\n * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.\n *\n * @example\n * \n * $bob = $urlRouter.href(new UrlMatcher(\"/about/:person\"), {\n * person: \"bob\"\n * });\n * // $bob == \"/about/bob\";\n *
\n *\n * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.\n * @param {object=} params An object of parameter values to fill the matcher's required parameters.\n * @param {object=} options Options object. The options are:\n *\n * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. \"http://www.example.com/fullurl\".\n *\n * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`\n */\n href: function(urlMatcher, params, options) {\n if (!urlMatcher.validates(params)) return null;\n\n var isHtml5 = $locationProvider.html5Mode();\n if (angular.isObject(isHtml5)) {\n isHtml5 = isHtml5.enabled;\n }\n\n isHtml5 = isHtml5 && $sniffer.history;\n \n var url = urlMatcher.format(params);\n options = options || {};\n\n if (!isHtml5 && url !== null) {\n url = \"#\" + $locationProvider.hashPrefix() + url;\n }\n\n // Handle special hash param, if needed\n if (url !== null && params && params['#']) {\n url += '#' + params['#'];\n }\n\n url = appendBasePath(url, isHtml5, options.absolute);\n\n if (!options.absolute || !url) {\n return url;\n }\n\n var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();\n port = (port === 80 || port === 443 ? '' : ':' + port);\n\n return [$location.protocol(), '://', $location.host(), port, slash, url].join('');\n }\n };\n }\n}\n\nangular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);\n\n/**\n * @ngdoc object\n * @name ui.router.state.$stateProvider\n *\n * @requires ui.router.router.$urlRouterProvider\n * @requires ui.router.util.$urlMatcherFactoryProvider\n *\n * @description\n * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely\n * on state.\n *\n * A state corresponds to a \"place\" in the application in terms of the overall UI and\n * navigation. A state describes (via the controller / template / view properties) what\n * the UI looks like and does at that place.\n *\n * States often have things in common, and the primary way of factoring out these\n * commonalities in this model is via the state hierarchy, i.e. parent/child states aka\n * nested states.\n *\n * The `$stateProvider` provides interfaces to declare these states for your app.\n */\n$StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];\nfunction $StateProvider( $urlRouterProvider, $urlMatcherFactory) {\n\n var root, states = {}, $state, queue = {}, abstractKey = 'abstract';\n\n // Builds state properties from definition passed to registerState()\n var stateBuilder = {\n\n // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.\n // state.children = [];\n // if (parent) parent.children.push(state);\n parent: function(state) {\n if (isDefined(state.parent) && state.parent) return findState(state.parent);\n // regex matches any valid composite state name\n // would match \"contact.list\" but not \"contacts\"\n var compositeName = /^(.+)\\.[^.]+$/.exec(state.name);\n return compositeName ? findState(compositeName[1]) : root;\n },\n\n // inherit 'data' from parent and override by own values (if any)\n data: function(state) {\n if (state.parent && state.parent.data) {\n state.data = state.self.data = inherit(state.parent.data, state.data);\n }\n return state.data;\n },\n\n // Build a URLMatcher if necessary, either via a relative or absolute URL\n url: function(state) {\n var url = state.url, config = { params: state.params || {} };\n\n if (isString(url)) {\n if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);\n return (state.parent.navigable || root).url.concat(url, config);\n }\n\n if (!url || $urlMatcherFactory.isMatcher(url)) return url;\n throw new Error(\"Invalid url '\" + url + \"' in state '\" + state + \"'\");\n },\n\n // Keep track of the closest ancestor state that has a URL (i.e. is navigable)\n navigable: function(state) {\n return state.url ? state : (state.parent ? state.parent.navigable : null);\n },\n\n // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params\n ownParams: function(state) {\n var params = state.url && state.url.params || new $$UMFP.ParamSet();\n forEach(state.params || {}, function(config, id) {\n if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, \"config\");\n });\n return params;\n },\n\n // Derive parameters for this state and ensure they're a super-set of parent's parameters\n params: function(state) {\n var ownParams = pick(state.ownParams, state.ownParams.$$keys());\n return state.parent && state.parent.params ? extend(state.parent.params.$$new(), ownParams) : new $$UMFP.ParamSet();\n },\n\n // If there is no explicit multi-view configuration, make one up so we don't have\n // to handle both cases in the view directive later. Note that having an explicit\n // 'views' property will mean the default unnamed view properties are ignored. This\n // is also a good time to resolve view names to absolute names, so everything is a\n // straight lookup at link time.\n views: function(state) {\n var views = {};\n\n forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) {\n if (name.indexOf('@') < 0) name += '@' + state.parent.name;\n view.resolveAs = view.resolveAs || state.resolveAs || '$resolve';\n views[name] = view;\n });\n return views;\n },\n\n // Keep a full path from the root down to this state as this is needed for state activation.\n path: function(state) {\n return state.parent ? state.parent.path.concat(state) : []; // exclude root from path\n },\n\n // Speed up $state.contains() as it's used a lot\n includes: function(state) {\n var includes = state.parent ? extend({}, state.parent.includes) : {};\n includes[state.name] = true;\n return includes;\n },\n\n $delegates: {}\n };\n\n function isRelative(stateName) {\n return stateName.indexOf(\".\") === 0 || stateName.indexOf(\"^\") === 0;\n }\n\n function findState(stateOrName, base) {\n if (!stateOrName) return undefined;\n\n var isStr = isString(stateOrName),\n name = isStr ? stateOrName : stateOrName.name,\n path = isRelative(name);\n\n if (path) {\n if (!base) throw new Error(\"No reference point given for path '\" + name + \"'\");\n base = findState(base);\n \n var rel = name.split(\".\"), i = 0, pathLength = rel.length, current = base;\n\n for (; i < pathLength; i++) {\n if (rel[i] === \"\" && i === 0) {\n current = base;\n continue;\n }\n if (rel[i] === \"^\") {\n if (!current.parent) throw new Error(\"Path '\" + name + \"' not valid for state '\" + base.name + \"'\");\n current = current.parent;\n continue;\n }\n break;\n }\n rel = rel.slice(i).join(\".\");\n name = current.name + (current.name && rel ? \".\" : \"\") + rel;\n }\n var state = states[name];\n\n if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) {\n return state;\n }\n return undefined;\n }\n\n function queueState(parentName, state) {\n if (!queue[parentName]) {\n queue[parentName] = [];\n }\n queue[parentName].push(state);\n }\n\n function flushQueuedChildren(parentName) {\n var queued = queue[parentName] || [];\n while(queued.length) {\n registerState(queued.shift());\n }\n }\n\n function registerState(state) {\n // Wrap a new object around the state so we can store our private details easily.\n state = inherit(state, {\n self: state,\n resolve: state.resolve || {},\n toString: function() { return this.name; }\n });\n\n var name = state.name;\n if (!isString(name) || name.indexOf('@') >= 0) throw new Error(\"State must have a valid name\");\n if (states.hasOwnProperty(name)) throw new Error(\"State '\" + name + \"' is already defined\");\n\n // Get parent name\n var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))\n : (isString(state.parent)) ? state.parent\n : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name\n : '';\n\n // If parent is not registered yet, add state to queue and register later\n if (parentName && !states[parentName]) {\n return queueState(parentName, state.self);\n }\n\n for (var key in stateBuilder) {\n if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]);\n }\n states[name] = state;\n\n // Register the state in the global state list and with $urlRouter if necessary.\n if (!state[abstractKey] && state.url) {\n $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {\n if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {\n $state.transitionTo(state, $match, { inherit: true, location: false });\n }\n }]);\n }\n\n // Register any queued children\n flushQueuedChildren(name);\n\n return state;\n }\n\n // Checks text to see if it looks like a glob.\n function isGlob (text) {\n return text.indexOf('*') > -1;\n }\n\n // Returns true if glob matches current $state name.\n function doesStateMatchGlob (glob) {\n var globSegments = glob.split('.'),\n segments = $state.$current.name.split('.');\n\n //match single stars\n for (var i = 0, l = globSegments.length; i < l; i++) {\n if (globSegments[i] === '*') {\n segments[i] = '*';\n }\n }\n\n //match greedy starts\n if (globSegments[0] === '**') {\n segments = segments.slice(indexOf(segments, globSegments[1]));\n segments.unshift('**');\n }\n //match greedy ends\n if (globSegments[globSegments.length - 1] === '**') {\n segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);\n segments.push('**');\n }\n\n if (globSegments.length != segments.length) {\n return false;\n }\n\n return segments.join('') === globSegments.join('');\n }\n\n\n // Implicit root state that is always active\n root = registerState({\n name: '',\n url: '^',\n views: null,\n 'abstract': true\n });\n root.navigable = null;\n\n\n /**\n * @ngdoc function\n * @name ui.router.state.$stateProvider#decorator\n * @methodOf ui.router.state.$stateProvider\n *\n * @description\n * Allows you to extend (carefully) or override (at your own peril) the \n * `stateBuilder` object used internally by `$stateProvider`. This can be used \n * to add custom functionality to ui-router, for example inferring templateUrl \n * based on the state name.\n *\n * When passing only a name, it returns the current (original or decorated) builder\n * function that matches `name`.\n *\n * The builder functions that can be decorated are listed below. Though not all\n * necessarily have a good use case for decoration, that is up to you to decide.\n *\n * In addition, users can attach custom decorators, which will generate new \n * properties within the state's internal definition. There is currently no clear \n * use-case for this beyond accessing internal states (i.e. $state.$current), \n * however, expect this to become increasingly relevant as we introduce additional \n * meta-programming features.\n *\n * **Warning**: Decorators should not be interdependent because the order of \n * execution of the builder functions in non-deterministic. Builder functions \n * should only be dependent on the state definition object and super function.\n *\n *\n * Existing builder functions and current return values:\n *\n * - **parent** `{object}` - returns the parent state object.\n * - **data** `{object}` - returns state data, including any inherited data that is not\n * overridden by own values (if any).\n * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}\n * or `null`.\n * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is \n * navigable).\n * - **params** `{object}` - returns an array of state params that are ensured to \n * be a super-set of parent's params.\n * - **views** `{object}` - returns a views object where each key is an absolute view \n * name (i.e. \"viewName@stateName\") and each value is the config object \n * (template, controller) for the view. Even when you don't use the views object \n * explicitly on a state config, one is still created for you internally.\n * So by decorating this builder function you have access to decorating template \n * and controller properties.\n * - **ownParams** `{object}` - returns an array of params that belong to the state, \n * not including any params defined by ancestor states.\n * - **path** `{string}` - returns the full path from the root down to this state. \n * Needed for state activation.\n * - **includes** `{object}` - returns an object that includes every state that \n * would pass a `$state.includes()` test.\n *\n * @example\n * \n * // Override the internal 'views' builder with a function that takes the state\n * // definition, and a reference to the internal function being overridden:\n * $stateProvider.decorator('views', function (state, parent) {\n * var result = {},\n * views = parent(state);\n *\n * angular.forEach(views, function (config, name) {\n * var autoName = (state.name + '.' + name).replace('.', '/');\n * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';\n * result[name] = config;\n * });\n * return result;\n * });\n *\n * $stateProvider.state('home', {\n * views: {\n * 'contact.list': { controller: 'ListController' },\n * 'contact.item': { controller: 'ItemController' }\n * }\n * });\n *\n * // ...\n *\n * $state.go('home');\n * // Auto-populates list and item views with /partials/home/contact/list.html,\n * // and /partials/home/contact/item.html, respectively.\n *
\n *\n * @param {string} name The name of the builder function to decorate. \n * @param {object} func A function that is responsible for decorating the original \n * builder function. The function receives two parameters:\n *\n * - `{object}` - state - The state config object.\n * - `{object}` - super - The original builder function.\n *\n * @return {object} $stateProvider - $stateProvider instance\n */\n this.decorator = decorator;\n function decorator(name, func) {\n /*jshint validthis: true */\n if (isString(name) && !isDefined(func)) {\n return stateBuilder[name];\n }\n if (!isFunction(func) || !isString(name)) {\n return this;\n }\n if (stateBuilder[name] && !stateBuilder.$delegates[name]) {\n stateBuilder.$delegates[name] = stateBuilder[name];\n }\n stateBuilder[name] = func;\n return this;\n }\n\n /**\n * @ngdoc function\n * @name ui.router.state.$stateProvider#state\n * @methodOf ui.router.state.$stateProvider\n *\n * @description\n * Registers a state configuration under a given state name. The stateConfig object\n * has the following acceptable properties.\n *\n * @param {string} name A unique state name, e.g. \"home\", \"about\", \"contacts\".\n * To create a parent/child state use a dot, e.g. \"about.sales\", \"home.newest\".\n * @param {object} stateConfig State configuration object.\n * @param {string|function=} stateConfig.template\n * \n * html template as a string or a function that returns\n * an html template as a string which should be used by the uiView directives. This property \n * takes precedence over templateUrl.\n * \n * If `template` is a function, it will be called with the following parameters:\n *\n * - {array.<object>} - state parameters extracted from the current $location.path() by\n * applying the current state\n *\n * template:\n * \"inline template definition
\" +\n * \"\"
\n * template: function(params) {\n * return \"generated template
\"; }
\n * \n *\n * @param {string|function=} stateConfig.templateUrl\n *