/*global angular */ /** @module Action Bar @icon bolt @summary The action bar provides a Sky UX-themed container for buttons that can collapse when the screen is in extra-small mode. @description The action bar creates a Sky UX-themed container for buttons. It includes the option to collapse groups of buttons into dropdowns when the screen is in extra-small mode. ### Action Bar Settings ### - `bb-action-bar` Wraps the content in the action bar. - `bb-action-bar-item` Wraps the content in an action button. Any `ng-click` applied to this directive is applied to the action button. - `bb-action-bar-item-group` Wraps `bb-action-bar-item` directives to collapse the buttons into a dropdown in extra-small mode. You can also pass an optional `bb-action-bar-item-group-title` to edit the default **Actions** label for the dropdown. If it is necessary to apply action bar stylying to more complicated scenarios (e.g. hiding and showing buttons at different breakpoints other than xs, collapsing dropdowns into submenus), then you can place any content in a `div` that has the `bb-action-bar` class. Some Bootstrap convenience classes for showing/hiding arbitrary content are the `hidden-xs`, `hidden-sm`, `hidden-md`, and `hidden-lg` classes. You can get more information on these in the [Bootstrap](http://getbootstrap.com/css/#responsive-utilities-classes) documentation. */ (function () { 'use strict'; function bbActionBar() { return { transclude: true, restrict: 'E', templateUrl: 'sky/templates/actionbar/actionbar.html' }; } function bbActionBarItemGroup(bbResources, bbMediaBreakpoints) { return { replace: true, transclude: true, controller: function () { }, restrict: 'E', scope: { title: '=?bbActionBarItemGroupTitle' }, link: function ($scope, el) { if ($scope.title === null || angular.isUndefined($scope.title)) { $scope.title = bbResources.action_bar_actions; } function mediaBreakpointHandler(breakpoints) { if (breakpoints.xs) { el.find('.bb-action-bar-buttons > ng-transclude').appendTo(el.find('.bb-action-bar-dropdown > .dropdown > ul')); } else { el.find('.bb-action-bar-dropdown .dropdown > ul > ng-transclude').appendTo(el.find('.bb-action-bar-buttons')); } } bbMediaBreakpoints.register(mediaBreakpointHandler); $scope.$on('$destroy', function () { bbMediaBreakpoints.unregister(mediaBreakpointHandler); }); }, templateUrl: 'sky/templates/actionbar/actionbaritemgroup.html' }; } bbActionBarItemGroup.$inject = ['bbResources', 'bbMediaBreakpoints']; function bbActionBarItem(bbMediaBreakpoints) { return { replace: true, require: '?^bbActionBarItemGroup', transclude: true, restrict: 'E', link: function ($scope, el, attrs, groupCtrl) { function mediaBreakpointHandler(breakpoints) { if (breakpoints.xs) { if (!el.parent().is('li')) { el.wrap('
'); } } else { if (el.parent().is('li')) { el.unwrap(); } } } if (groupCtrl !== null) { bbMediaBreakpoints.register(mediaBreakpointHandler); $scope.$on('$destroy', function () { bbMediaBreakpoints.unregister(mediaBreakpointHandler); //get rid of wrapper on destroy if (el.parent().is('li')) { el.unwrap(); } }); } }, templateUrl: 'sky/templates/actionbar/actionbaritem.html' }; } bbActionBarItem.$inject = ['bbMediaBreakpoints']; angular.module('sky.actionbar', ['sky.resources', 'sky.mediabreakpoints']) .directive('bbActionBar', bbActionBar) .directive('bbActionBarItemGroup', bbActionBarItemGroup) .directive('bbActionBarItem', bbActionBarItem); }()); /*jshint browser: true */ /*global angular */ /** @module Autofocus @icon camera @summary The autofocus component specifies the input item on a form that should get focus when the form loads. @description The `bb-autofocus` directive specifies the item on a form that should get focus when the form renders. You can use this directive when items such as Angular dynamically loaded templates do not play nicely with the HTML autofocus property. The **Open Modal** button below demonstrates a modal form where the `bb-autofocus` directive places the focus on an input control on the form. */ (function () { 'use strict'; angular.module('sky.autofocus', []) .directive('bbAutofocus', ['$timeout', function ($timeout) { return { restrict: 'A', link: function ($scope, $element) { /*jslint unparam: true */ $timeout(function () { $element.focus(); }, 500); } }; }]); }()); /*jslint browser: true, plusplus: true */ /*global angular, jQuery */ /** @module Autonumeric @icon calculator @summary The autonumeric component wraps up the autoNumeric jQuery plugin to format any type of number, including currency. @description The `bb-autonumeric` directive wraps up the autoNumeric jQuery plugin to format any type of number, including currency. You must use this directive in conjunction with the `ngModel` directive where the property bound to `ngModel` is the raw numeric value on your model. ### Dependencies ### - **[autoNumeric](http://www.decorplanit.com/plugin/) (1.9.27 or higher)** Used to format money values --- ### Autonumeric Settings ### - `bb-autonumeric` This can optionally be assigned the name of a property from the `bbAutonumericConfig` object. If none is specified, it defaults to `number`. - `bb-autonumeric-settings` This can be assigned a value that represents a settings object that can be passed to autoNumeric. These options will override any default options specified in the `bb-autonumeric` attribute. A complete list of options is available [here](http://www.decorplanit.com/plugin/). ### Autonumeric Filter ### In addition to the directive, there is also a filter that can be used to format numbers. The filter has the added feature of optionally abbreviating a number according to Sky patterns. For instance, numbers over 10,000 will be displayed as 10k, over 1,000,000 as 1m, and 1,000,000,000 as 1b. The filter takes three arguments: - `input` The value to format. - `configType` The name of the configuration (`number` or `money`) to apply to the value. - `abbreviate` A Boolean value indicating whether to abbreviate large numbers. */ (function ($) { 'use strict'; function getBaseSettings(bbAutoNumericConfig, configType) { var baseSettings, configSettings; baseSettings = angular.extend( {}, $.fn.autoNumeric.defaults, bbAutoNumericConfig.number ); if (configType) { configSettings = bbAutoNumericConfig[configType]; } if (configSettings) { angular.extend(baseSettings, configSettings); } return baseSettings; } angular.module('sky.autonumeric', ['sky.resources', 'sky.window']) .constant('bbAutonumericConfig', { number: { aSep: ',', dGroup: 3, aDec: '.', pSign: 'p', mDec: 2 }, money: { aSign: '$' } }) .directive('bbAutonumeric', ['$timeout', 'bbAutonumericConfig', 'bbWindow', function ($timeout, bbAutoNumericConfig, bbWindow) { return { require: 'ngModel', restrict: 'A', link: function ($scope, el, attrs, ngModel) { var customSettings = {}, isIosUserAgent = bbWindow.isIosUserAgent(); function applySettings() { el.autoNumeric('update', angular.extend({}, getBaseSettings(bbAutoNumericConfig, attrs.bbAutonumeric), customSettings)); } function applyCssSettings(el) { if (attrs.bbAutonumeric) { el.addClass('bb-autonumeric-' + attrs.bbAutonumeric); } } if (attrs.bbAutonumericSettings) { $scope.$watch(attrs.bbAutonumericSettings, function (newValue) { customSettings = newValue || {}; applySettings(); }, true); } el.autoNumeric(getBaseSettings(bbAutoNumericConfig, attrs.bbAutonumeric)); applyCssSettings(el); $scope.$watch(attrs.ngModel, function (newValue) { if (newValue !== undefined && newValue !== null && !isNaN(newValue)) { el.autoNumeric('set', newValue); } else if (isNaN(newValue)) { return; } else { el.val(null); } }); function autonumericChange() { return $scope.$apply(function () { var value = parseFloat(el.autoNumeric('get')); if (isNaN(value)) { value = null; } return ngModel.$setViewValue(value); }); } //Setup on change handler to update scope value el.on('change', function () { autonumericChange(); }); el.on('keydown', function (event) { if (event.which === 13) { autonumericChange(); } }); // When focusing in textbox, select all. This is to workaround not having placeholder text for autonumeric. /* istanbul ignore next: the test for this code isn't passing on IE 10 on BrowserStack in automated mode. This isn't mission-critical so I'm just ignoring it for now. */ el.on('focusin.bbAutonumeric', function () { $timeout(function () { if (!isIosUserAgent) { el.select(); } else { //use setSelectionRange instead of select because select in a timeout does not work with iOS el[0].setSelectionRange(0, 9999); } }); }); } }; }]) .filter('bbAutonumeric', ['bbAutonumericConfig', 'bbResources', function (bbAutonumericConfig, bbResources) { return function (input, configType, abbreviate) { var aSign, dividend, mDec, formatted, settings, suffix, tempEl; if (input === null || angular.isUndefined(input)) { return ''; } if (isNaN(input)) { return input; } tempEl = $(''); settings = getBaseSettings(bbAutonumericConfig, configType); if (abbreviate) { if (settings.pSign === 's') { // The suffix needs to go between the number and the currency symbol, so the currency // symbol has to be left off and appended after the number is formatted. aSign = settings.aSign; settings.aSign = ''; } input = Math.round(input); if (input >= 1000000000) { dividend = 100000000; suffix = bbResources.autonumeric_abbr_billions; } else if (input >= 1000000) { dividend = 100000; suffix = bbResources.autonumeric_abbr_millions; } else if (input >= 10000) { dividend = 100; suffix = bbResources.autonumeric_abbr_thousands; } if (suffix) { input = Math.floor(input / dividend) / 10; mDec = Math.floor(input) === input ? 0 : 1; } else { mDec = 0; } settings.mDec = mDec; } tempEl.autoNumeric(settings); tempEl.autoNumeric('set', input); formatted = tempEl.text(); if (suffix) { formatted += suffix; } if (abbreviate && settings.pSign === 's' && aSign) { formatted += aSign; } return formatted; }; }]); }(jQuery)); /*jshint browser: true */ /*global angular */ /** @module Check @icon check-square @summary The check applies a commonly styled selector to a checkbox or radio button. @description The check directive allows you to change an input element of type checkbox or radio into a commonly-styled selector. The value that is selected is driven through the `ng-model` attribute specified on the input element and for radio input types the value to set on the `ng-model` can be specified by the value attribute. --- */ (function () { 'use strict'; angular.module('sky.check', []) .directive('bbCheck', [function () { return { link: function (scope, el, attr) { var labelEl = el.parent('label'), typeClass; if (labelEl.length < 1) { el.wrap(''); } else { labelEl.addClass('bb-check-wrapper'); } if (attr.type === 'radio') { typeClass = 'bb-check-radio'; } else { typeClass = 'bb-check-checkbox'; } el.after(''); } }; }]); }()); /*jslint browser: true */ /*global angular */ /** @module Checklist @icon list-ul @summary The checklist builds a filterable checkbox list that can display multiple columns of data. @description The checklist directive allows you to easily build a filterable checkbox list. Multiple columns of data can be provided for the checkbox rows using the `bb-checklist-column` element. Items can also be displayed in a list view with each row displaying a title and description. The list view is preferable when building a responsive application. ### Checklist Settings ### - `bb-checklist` - `bb-checklist-items` An array of objects representing the rows that will be shown in the list. - `bb-checklist-selected-items` An array representing the selected items in the list. - `bb-checklist-include-search` A Boolean to optionally include a search textbox for filtering the items. The search text will be highlighted in the columns of the list. A callback function can be used to filter the items based on the search text. - `bb-checklist-search-placeholder` Placeholder text for the search textbox. - `bb-checklist-filter-callback` A function to be called when the search text is modified. Used by the consumer to update the `bb-checklist-items` array as desired based on the search text. The function will be passed a single object as a parameter containing `searchText` and `category` properties. Useful when loading items remotely or using custom logic other than simple case-insensitive string matching to filter items. - `bb-checklist-filter-local` When specified, items are filtered by the checklist directive by examining the properties of each item to match the specified category or search text. - `bb-checklist-search-debounce` Number of milliseconds to debounce changes to the search text. Useful if making a web request in the `bb-checklist-filter-callback` to avoid making the request after every character typed. - `bb-checklist-no-items-message` *(Default: `'No items found'`)* Message to display when no items are in the list. - `bb-checklist-mode` *(Optional. Default: 'grid')* one of two possible values: - `list` Displays items in a list with a title and description. Items are expected to have `title`, `description` and `category` properties. This is the preferred method of displaying a checklist. - `grid` Displays items in a grid with any number of columns. Columns are specified using mulitple `bb-checklist-column` elements. For backwards compatibility reasons this is the default mode, but `list` is the preferred mode since it is mobile-responsive. - `bb-checklist-categories` An array of category names used to build category filter buttons at the top of the list. ### Checklist Column Settings ### - `bb-checklist-column-caption` Caption text for the column header. - `bb-checklist-column-field` The name of the property on the checklist items that contains the text to display in this column. - `bb-checklist-column-class` A CSS class to apply to this column's header and cells. - `bb-checklist-column-width` Set the width to be used by the column. */ (function () { 'use strict'; var PROP_CATEGORY = 'category'; function bbChecklist(bbChecklistUtility) { return { replace: true, restrict: 'E', transclude: true, templateUrl: 'sky/templates/checklist/checklist.html', scope: { bbChecklistItems: '=', bbChecklistSelectedItems: '=', bbChecklistFilterCallback: '=', bbChecklistIncludeSearch: '=', bbChecklistSearchDebounce: '=', bbChecklistSearchPlaceholder: '@', bbChecklistNoItemsMessage: '@', bbChecklistAutomationField: '=', bbChecklistCategories: '=', bbChecklistMode: '@' }, controller: ['$scope', function ($scope) { var locals = $scope.locals = {}; this.setColumns = function (columns) { locals.columns = columns; }; }], link: function ($scope, el, attrs) { var filterLocal = angular.isDefined(attrs.bbChecklistFilterLocal), locals = $scope.locals; function itemMatchesCategory(item, category) { return !category || item.category === category; } function itemMatchesFilter(item, category, searchTextUpper) { var p, val; if (itemMatchesCategory(item, category)) { if (!searchTextUpper) { return true; } for (p in item) { if (item.hasOwnProperty(p) && p !== PROP_CATEGORY) { val = item[p]; if (angular.isString(val) && val.toUpperCase().indexOf(searchTextUpper) >= 0) { return true; } } } } return false; } function invokeFilterLocal() { var filteredItems, i, item, items = $scope.bbChecklistItems, n, searchTextUpper = (locals.searchText || '').toUpperCase(), selectedCategory = locals.selectedCategory; if (!searchTextUpper && !selectedCategory) { filteredItems = items.slice(0); } else { filteredItems = []; for (i = 0, n = items.length; i < n; i++) { item = items[i]; if (itemMatchesFilter(item, selectedCategory, searchTextUpper)) { filteredItems.push(item); } } } locals.filteredItems = filteredItems; } function invokeFilter() { if (filterLocal) { invokeFilterLocal(); } else if ($scope.bbChecklistFilterCallback) { $scope.bbChecklistFilterCallback({ searchText: locals.searchText, category: locals.selectedCategory }); } } $scope.bbChecklistSelectedItems = $scope.bbChecklistSelectedItems || []; locals.selectAll = function () { var i, item, items = locals.filteredItems, selected = $scope.bbChecklistSelectedItems; for (i = 0; i < items.length; i += 1) { item = items[i]; if (!bbChecklistUtility.contains(selected, item)) { bbChecklistUtility.add(selected, item); } } }; locals.clear = function () { var i, item, items = locals.filteredItems, selected = $scope.bbChecklistSelectedItems; for (i = 0; i < items.length; i += 1) { item = items[i]; bbChecklistUtility.remove(selected, item); } }; locals.rowClicked = function (item) { var selected = $scope.bbChecklistSelectedItems; if (!bbChecklistUtility.contains(selected, item)) { bbChecklistUtility.add(selected, item); } else { bbChecklistUtility.remove(selected, item); } }; locals.filterByCategory = function (selectedCategory) { locals.selectedCategory = selectedCategory; invokeFilter(); }; $scope.$watch('bbChecklistItems', function () { locals.filteredItems = $scope.bbChecklistItems; locals.highlightRefresh = new Date().getTime(); }); $scope.$watch('locals.searchText', function (newValue, oldValue) { if (newValue !== oldValue) { invokeFilter(); } }); } }; } bbChecklist.$inject = ['bbChecklistUtility']; angular.module('sky.checklist', ['sky.check', 'sky.checklist.column', 'sky.checklist.columns', 'sky.checklist.model', 'sky.checklist.utility', 'sky.resources']) .directive('bbChecklist', bbChecklist); }()); /*global angular */ (function () { 'use strict'; function bbChecklistColumn() { return { require: '^bbChecklistColumns', restrict: 'E', scope: { bbChecklistColumnCaption: "=", bbChecklistColumnField: "=", bbChecklistColumnClass: "=", bbChecklistColumnWidth: "=", bbChecklistColumnAutomationId: "=" }, link: function ($scope, element, attrs, bbChecklistColumns) { /*jslint unparam: true */ var column = { caption: $scope.bbChecklistColumnCaption, field: $scope.bbChecklistColumnField, 'class': $scope.bbChecklistColumnClass, width: $scope.bbChecklistColumnWidth, automationId: $scope.bbChecklistColumnAutomationId }; bbChecklistColumns.addColumn(column); } }; } angular.module('sky.checklist.column', ['sky.checklist.columns']) .directive('bbChecklistColumn', bbChecklistColumn); }()); /*global angular */ (function () { 'use strict'; function bbChecklistColumns() { return { require: '^bbChecklist', restrict: 'E', scope: { }, controller: ['$scope', function ($scope) { $scope.columns = []; this.addColumn = function (column) { $scope.columns.push(column); }; }], link: function ($scope, element, attrs, bbChecklist) { /*jslint unparam: true */ bbChecklist.setColumns($scope.columns); } }; } angular.module('sky.checklist.columns', []) .directive('bbChecklistColumns', bbChecklistColumns); }()); /*global angular */ (function () { 'use strict'; function checklistModel($compile, $parse, bbChecklistUtility) { // http://stackoverflow.com/a/19228302/1458162 function postLinkFn(scope, elem, attrs) { var getter, setter, value; // compile with `ng-model` pointing to `checked` $compile(elem)(scope); // getter / setter for original model getter = $parse(attrs.checklistModel); setter = getter.assign; // value added to list value = $parse(attrs.checklistValue)(scope.$parent); // watch UI checked change scope.$watch('checked', function (newValue, oldValue) { var current; if (newValue === oldValue) { return; } current = getter(scope.$parent); if (newValue === true) { setter(scope.$parent, bbChecklistUtility.add(current, value)); } else { setter(scope.$parent, bbChecklistUtility.remove(current, value)); } }); // watch original model change scope.$parent.$watch(attrs.checklistModel, function (newArr) { scope.checked = bbChecklistUtility.contains(newArr, value); }, true); } return { restrict: 'A', priority: 1000, terminal: true, scope: true, compile: function (tElement, tAttrs) { if (tElement[0].tagName !== 'INPUT' || !tElement.attr('type', 'checkbox')) { throw 'checklist-model should be applied to `input[type="checkbox"]`.'; } if (!tAttrs.checklistValue) { throw 'You should provide `checklist-value`.'; } // exclude recursion tElement.removeAttr('checklist-model'); // local scope var storing individual checkbox model tElement.attr('ng-model', 'checked'); return postLinkFn; } }; } checklistModel.$inject = ['$compile', '$parse', 'bbChecklistUtility']; angular.module('sky.checklist.model', ['sky.checklist.utility']) .directive('checklistModel', checklistModel); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.checklist.utility', []) .factory('bbChecklistUtility', function () { return { contains: function (arr, item) { var i; if (angular.isArray(arr)) { for (i = 0; i < arr.length; i += 1) { if (angular.equals(arr[i], item)) { return true; } } } return false; }, // add add: function (arr, item) { var i; arr = angular.isArray(arr) ? arr : []; for (i = 0; i < arr.length; i += 1) { if (angular.equals(arr[i], item)) { return arr; } } arr.push(item); return arr; }, // remove remove: function (arr, item) { var i; if (angular.isArray(arr)) { for (i = 0; i < arr.length; i += 1) { if (angular.equals(arr[i], item)) { arr.splice(i, 1); break; } } } return arr; } }; }); }()); /* global angular */ /** @module Context Menu @icon ellipsis-h @summary The context menu creates simple or complicated dropdown menus that you can incorporate into buttons. @description The context menu directives allow you to easily create Sky-styled [dropdown](https://angular-ui.github.io/bootstrap/#/dropdown) menus. There are 3 directives in the context menu module: - `bb-context-menu` creates a dropdown with the context menu button. - `bb-context-menu-item` creates dropdown menu items within a dropdown that execute `bb-context-menu-action` on click. - `bb-context-menu-button` creates a button with the Sky context menu styles. - `bb-submenu` creates an accordion style submenu in a dropdown, you can place it in a dropdown list element. - `bb-submenu-heading` Can be either an attribute on `bb-submenu` that can be set equal to static header text, or can be used as a directive inside of `bb-submenu` to place arbitrary content in an accordion heading. */ (function () { 'use strict'; function bbContextMenu() { return { replace: true, restrict: 'E', transclude: true, templateUrl: 'sky/templates/contextmenu/contextmenu.html', link: function ($scope) { $scope.contextButtonStopPropagation = function ($event) { $event.stopPropagation(); }; } }; } function bbContextMenuItem() { return { restrict: 'E', transclude: true, replace: true, scope: { clickItem: '&bbContextMenuAction' }, template: '