UNPKG

blackbaud-skyux

Version:

Sky UX provides an HTML, CSS and JavaScript framework to implement Blackbaud's design patterns.

1,672 lines (1,326 loc) 701 kB
/*global angular */ (function () { 'use strict'; angular.module('sky.accordion', ['sky.accordion.uibaccordiongroup']); })(); /*global angular */ (function () { 'use strict'; angular.module('sky.alert', ['sky.alert.component']); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.avatar', ['sky.avatar.config', 'sky.avatar.component']); }()); /*global angular */ (function () { 'use strict'; angular.module( 'sky.card', [ 'sky.card.directive' ] ); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.carousel', ['sky.carousel.component', 'sky.carousel.item.component']); }()); /*global angular */ (function () { 'use strict'; angular.module( 'sky.checklist', [ 'sky.checklist.directive', 'sky.checklist.column.directive', 'sky.checklist.columns.directive', 'sky.checklist.model.directive' ] ); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.chevron', ['sky.chevron.component']); }()); /*global angular */ (function () { 'use strict'; angular.module( 'sky.contextmenu', [ 'sky.contextmenu.directive', 'sky.contextmenu.button.directive', 'sky.contextmenu.item.directive', 'sky.submenu' ] ); }()); /* global angular */ (function () { 'use strict'; angular.module('sky.submenu', ['sky.submenu.directive']); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.datepicker', ['sky.datepicker.directive']); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.definitionlist', ['sky.definitionlist.component', 'sky.definitionlistcontent.component']); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.error', ['sky.error.directive', 'sky.errormodal.service']); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.filter', [ 'sky.filter.modal.footer.component', 'sky.filter.button.component', 'sky.filter.summary.component', 'sky.filter.summary.item.component' ]); })(); /*global angular */ (function () { 'use strict'; angular.module('sky.infinitescroll', ['sky.infinitescroll.component']); })(); /*global angular */ (function () { 'use strict'; angular.module('sky.keyinfo', ['sky.keyinfo.component']); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.listbuilder', [ 'sky.listbuilder.component', 'sky.listbuilder.toolbar.component', 'sky.listbuilder.footer.component', 'sky.listbuilder.content.component', 'sky.listbuilder.content.custom.component', 'sky.listbuilder.content.custom.item.directive', 'sky.listbuilder.card.component', 'sky.listbuilder.cards.component', 'sky.listbuilder.grid.component', 'sky.listbuilder.switcher.component', 'sky.listbuilder.repeater.component', 'sky.listbuilder.repeater.item.directive' ]); }()); /*jshint browser: true */ /*global angular */ (function () { 'use strict'; angular.module( 'sky.modal', [ 'sky.modal.directive', 'sky.modal.body.directive', 'sky.modal.header.directive', 'sky.modal.footer.directive', 'sky.modal.footer.button.directive', 'sky.modal.footer.button.primary.directive', 'sky.modal.footer.button.cancel.directive', 'sky.modal.factory' ] ); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.pagesummary', []); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.phonefield', ['sky.phonefield.directive']); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.reorder', ['sky.reorder.component']); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.reordertable', ['sky.reordertable.component']); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.repeater', ['sky.repeater.component', 'sky.repeater.item.directive']); }()); /*global angular */ (function () { 'use strict'; angular.module('sky.search', [ 'sky.search.input.component', 'sky.search.container.directive' ]); })(); /*global angular */ (function () { 'use strict'; angular.module('sky.sectionedform', ['sky.sectionedform.component']); }()); /*global angular */ (function () { 'use strict'; angular.module( 'sky.selectfield', [ 'sky.selectfield.directive', 'sky.selectfieldpicker.directive', 'sky.selectfield.item.animation' ] ); })(); /*global angular */ (function () { 'use strict'; angular.module( 'sky.sort', [ 'sky.sort.component', 'sky.sort.item.component' ] ); })(); /*global angular */ (function () { 'use strict'; angular.module('sky.summary.actionbar', [ 'sky.summary.actionbar.component', 'sky.summary.actionbar.primary.component', 'sky.summary.actionbar.secondary.component', 'sky.summary.actionbar.secondary.actions.component', 'sky.summary.actionbar.cancel.component' ]); })(); /*jslint browser: true */ /*global angular */ (function () { 'use strict'; angular.module('sky.wait', ['sky.wait.directive', 'sky.wait.factory']); }()); /* global angular */ /* From https://github.com/angular-ui/bootstrap/blob/1.2.5/src/accordion/accordion.js so that we can have graceful deprecation of accordion group element directive */ (function () { 'use strict'; function uibAccoridonGroup($log) { return { require: '^uibAccordion', // We need this directive to be inside an accordion transclude: true, // It transcludes the contents of the directive into the template replace: true, restrict: 'E', templateUrl: function (element, attrs) { return attrs.templateUrl || 'sky/templates/accordion/uib.accordiongroup.directive.html'; }, scope: { heading: '@', // Interpolate the heading attribute onto this scope panelClass: '@?', // Ditto with panelClass isOpen: '=?', isDisabled: '=?' }, controller: function () { this.setHeading = function (element) { this.heading = element; }; }, link: function (scope, element, attrs, accordionCtrl) { var id; $log.warn('uibAccordionGroup should not be used as an element directive, instead use as an attribute directive'); accordionCtrl.addGroup(scope); scope.openClass = attrs.openClass || 'panel-open'; scope.panelClass = attrs.panelClass || 'panel-default'; scope.$watch('isOpen', function (value) { element.toggleClass(scope.openClass, !!value); if (value) { accordionCtrl.closeOthers(scope); } }); scope.toggleOpen = function ($event) { if (!scope.isDisabled) { if (!$event || $event.which === 32) { scope.isOpen = !scope.isOpen; } } }; id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000); scope.headingId = id + '-tab'; scope.panelId = id + '-panel'; } }; } uibAccoridonGroup.$inject = ['$log']; angular.module('sky.accordion.uibaccordiongroup', [ 'ui.bootstrap.accordion' ]) .directive('uibAccordionGroup', uibAccoridonGroup); })(); /*global angular */ (function () { 'use strict'; function bbActionBar() { return { controller: angular.noop, controllerAs: 'bbActionBar', bindToController: true, scope: {}, transclude: true, restrict: 'E', templateUrl: 'sky/templates/actionbar/actionbar.html' }; } function bbActionBarItemGroup(bbResources, bbMediaBreakpoints) { return { transclude: true, controller: function () { var vm = this; function onInit() { if (vm.title === null || angular.isUndefined(vm.title)) { vm.title = bbResources.action_bar_actions; } } vm.$onInit = onInit; }, controllerAs: 'bbActionBarItemGroup', bindToController: { title: '=?bbActionBarItemGroupTitle' }, restrict: 'E', require: 'bbActionBarItemGroup', scope: {}, link: function ($scope, el, attr, vm) { 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); }); vm.toggleId = 'bb-action-bar-item-group-' + $scope.$id; }, templateUrl: 'sky/templates/actionbar/actionbaritemgroup.html' }; } bbActionBarItemGroup.$inject = ['bbResources', 'bbMediaBreakpoints']; function bbActionBarItem(bbMediaBreakpoints) { return { replace: true, controller: angular.noop, controllerAs: 'bbActionBarItem', bindToController: { bbActionBarItemLabel: '@' }, scope: {}, 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('<li role="menuitem"></li>'); } } 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', 'ui.bootstrap.dropdown']) .directive('bbActionBar', bbActionBar) .directive('bbActionBarItemGroup', bbActionBarItemGroup) .directive('bbActionBarItem', bbActionBarItem); }()); /*global angular */ (function () { 'use strict'; function Controller() { var vm = this; vm.close = function () { vm.bbAlertClosed = true; }; } angular.module('sky.alert.component', ['sky.resources']) .component('bbAlert', { bindings: { bbAlertType: '@', bbAlertCloseable: '@', bbAlertClosed: '=?' }, templateUrl: 'sky/templates/alert/alert.html', transclude: true, controller: Controller }); }()); /*jslint browser: true, plusplus: true */ /*global angular, jQuery */ (function ($) { 'use strict'; function getBaseSettings(bbAutoNumericConfig, configType) { var baseSettings, configSettings; baseSettings = angular.extend( {}, $.fn.autoNumeric.defaults, bbAutoNumericConfig.number ); if (configType) { configSettings = angular.isObject(configType) ? configType : bbAutoNumericConfig[configType]; /* istanbul ignore else */ /* sanity check */ 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: '$' }, percent: { aSign: '%', pSign: 's', mDec: 0 } }) .directive('bbAutonumeric', ['$timeout', 'bbAutonumericConfig', 'bbWindow', '$document', function ($timeout, bbAutoNumericConfig, bbWindow, $document) { 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); } } function autonumericChange() { return $scope.$apply(function () { var value = parseFloat(el.autoNumeric('get')); if (isNaN(value)) { value = null; } return ngModel.$setViewValue(value); }); } if (attrs.bbAutonumericSettings) { $scope.$watch(attrs.bbAutonumericSettings, function (newValue) { customSettings = newValue || {}; applySettings(); }, true); } el.autoNumeric(getBaseSettings(bbAutoNumericConfig, attrs.bbAutonumeric)); applyCssSettings(el); // If a valid number, update the autoNumeric value. // Also handles the model being updated, but being in correct (usually a paste). // In that case, updates the model to what the autoNumeric plugin's value. $scope.$watch(attrs.ngModel, function (newValue, oldValue) { var getValue, selectionStart; if (newValue !== undefined && newValue !== null && !isNaN(newValue)) { if (parseFloat(newValue) !== parseFloat(oldValue)) { selectionStart = el[0].selectionStart; } el.autoNumeric('set', newValue); getValue = el.autoNumeric('get'); if (parseFloat(getValue) !== parseFloat(newValue)) { $timeout(autonumericChange); } else if (el[0] && angular.isFunction(el[0].setSelectionRange) && angular.isDefined(selectionStart)) { $timeout(function () { if ($document[0] && $document[0].activeElement === el[0]) { el[0].setSelectionRange(selectionStart, selectionStart); } }); } } else if (newValue === null) { el.val(null); } }); el.on('keydown', function (event) { if (event.which === 13) { autonumericChange(); } }); el.on('change paste onpaste', function () { 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 () { // Check to ensure the field still has focus once the $timeout callback is executed. // https://github.com/blackbaud/skyux/issues/64 if (el.is(':focus')) { 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 = $('<span></span>'); 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)); /*global angular, jQuery */ (function ($) { 'use strict'; function Controller($filter, $templateCache, $window, bbAvatarConfig, bbErrorModal, bbFormat, bbPalette, bbResources, $element, $scope) { var vm = this, blobUrl, templateLoaded; function setImageUrl(url) { $element.find('.bb-avatar-image').css('background-image', 'url(' + url + ')'); } function getInitial(name) { return name.charAt(0).toUpperCase(); } function getInitials(name) { var initials, nameSplit; if (name) { nameSplit = name.split(' '); initials = getInitial(nameSplit[0]); /* istanbul ignore else */ /* this is tested through a visual regression test */ if (nameSplit.length > 1) { initials += getInitial(nameSplit[nameSplit.length - 1]); } } return initials; } function getPlaceholderColor(name) { var colorIndex, colors = bbPalette.getColorSequence(6), seed; if (name) { // Generate a unique-ish color based on the record name. This is deterministic // so that a given name will always generate the same color. seed = name.charCodeAt(0) + name.charCodeAt(name.length - 1) + name.length; colorIndex = Math.abs(seed % colors.length); } else { colorIndex = 0; } return colors[colorIndex]; } function drawPlaceolderImage() { var canvas, context, devicePixelRatio, fontSize = "46px", initials, name, size = 100; name = vm.bbAvatarName; initials = getInitials(name); canvas = $element.find('.bb-avatar-initials')[0]; context = canvas.getContext('2d'); devicePixelRatio = $window.devicePixelRatio; /* istanbul ignore else */ if (devicePixelRatio) { $(canvas) .attr('width', size * devicePixelRatio) .attr('height', size * devicePixelRatio); context.scale(devicePixelRatio, devicePixelRatio); } context.fillStyle = getPlaceholderColor(name); context.fillRect(0, 0, canvas.width, canvas.height); if (initials) { context.font = fontSize + ' Arial'; context.textAlign = 'center'; context.fillStyle = '#FFF'; context.fillText(initials, size * 0.5, size * (2 / 3)); } } function revokeBlobUrl() { if (blobUrl) { $window.URL.revokeObjectURL(blobUrl); blobUrl = null; } } function loadPhoto() { var src, url; revokeBlobUrl(); if (templateLoaded) { src = vm.bbAvatarSrc; if (src) { if (src instanceof $window.File) { url = $window.URL.createObjectURL(src); // Keep the last blob URL around so we can revoke it later. // https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL blobUrl = url; } else { url = src; } setImageUrl(url); } else { drawPlaceolderImage(); } } } function handleInvalidFileDrop(rejectedFile) { var errorDescription, errorTitle, maxFileSizeFormatted; if (rejectedFile.type.toUpperCase().indexOf('IMAGE/') !== 0) { errorDescription = bbResources.avatar_error_not_image_description; errorTitle = bbResources.avatar_error_not_image_title; } else { maxFileSizeFormatted = $filter('bbFileSize')(bbAvatarConfig.maxFileSize); errorDescription = bbFormat.formatText(bbResources.avatar_error_too_large_description, maxFileSizeFormatted); errorTitle = bbResources.avatar_error_too_large_title; } bbErrorModal.open({ errorDescription: errorDescription, errorTitle: errorTitle }); } vm.onTemplateLoad = function () { templateLoaded = true; }; vm.photoDrop = function (files, rejectedFiles) { if (angular.isArray(rejectedFiles) && rejectedFiles.length > 0) { handleInvalidFileDrop(rejectedFiles[0]); } else { vm.bbAvatarChange({ file: files[0] }); } }; vm.showInitials = function () { return !!(vm.bbAvatarName && !vm.bbAvatarSrc); }; if ($element.attr('bb-avatar-change')) { vm.canChange = true; } $scope.$watch(function () { return templateLoaded; }, loadPhoto); $scope.$watch(function () { return vm.bbAvatarSrc; }, loadPhoto); $scope.$watch(function () { return vm.bbAvatarName; }, loadPhoto); $scope.$on('$destroy', function () { revokeBlobUrl(); }); vm.maxFileSize = bbAvatarConfig.maxFileSize; } Controller.$inject = ['$filter', '$templateCache', '$window', 'bbAvatarConfig', 'bbErrorModal', 'bbFormat', 'bbPalette', 'bbResources', '$element', '$scope']; function template($element, $templateCache, bbAvatarConfig) { var dropEl; $element.html($templateCache.get('sky/templates/avatar/avatar.component.html')); dropEl = $element.find('.bb-avatar-file-drop'); dropEl.attr('bb-file-drop-max-size', bbAvatarConfig.maxFileSize); } template.$inject = ['$element', '$templateCache', 'bbAvatarConfig']; angular.module('sky.avatar.component', ['sky.avatar.config', 'sky.error', 'sky.format', 'sky.palette', 'sky.resources']) .component('bbAvatar', { bindings: { bbAvatarSrc: '=', bbAvatarName: '=', bbAvatarChange: '&' }, controller: Controller, template: template }); }(jQuery)); /*global angular */ (function () { 'use strict'; var bbAvatarConfig = { maxFileSize: 500000 }; angular.module('sky.avatar.config', []) .constant('bbAvatarConfig', bbAvatarConfig); }()); /*global angular */ (function () { 'use strict'; var components = [{ name: 'Title', cls: 'title' }, { name: 'Content', cls: 'content' }, { name: 'Actions', cls: 'actions' }], cardModule = angular.module('sky.card.directive', ['sky.check']), nextId = 0; function makeCardComponent(component) { var controllerName, name = component.name; function Controller($scope) { var vm = this; $scope.$on('$destroy', function () { vm.onDestroy(); vm = null; }); } Controller.$inject = ['$scope']; function componentFn() { function link(scope, el, attrs, ctrls) { var vm = ctrls[0], bbCard = ctrls[1]; vm.el = el; bbCard['set' + name](vm); } return { restrict: 'E', require: ['bbCard' + name, '^bbCard'], controller: controllerName, controllerAs: 'bbCard' + name, bindToController: true, link: link, scope: {} }; } controllerName = 'BBCard' + name + 'Controller'; cardModule .controller(controllerName, Controller) .directive('bbCard' + name, componentFn); } function getCtrlPropName(component) { var name = component.name; return name.charAt(0).toLowerCase() + name.substr(1) + 'Ctrl'; } function BBCardController($timeout, $scope) { var vm = this; function addComponentSetter(component) { var name = component.name; vm['set' + name] = function (ctrl) { var propName = getCtrlPropName(component); vm[propName] = ctrl; ctrl.onDestroy = function () { vm[propName] = null; }; }; } function cardIsSelectable() { return vm.bbCardSelectable === 'true'; } function cardSelectionToggled(isSelected) { $timeout(function () { if (angular.isFunction(vm.bbCardSelectionToggled)) { vm.bbCardSelectionToggled({isSelected: isSelected}); } }); } vm.cardSelectionToggled = cardSelectionToggled; vm.cardIsSelectable = cardIsSelectable; components.forEach(addComponentSetter); vm.getClass = function () { var cls = []; switch (vm.bbCardSize) { case 'small': cls.push('bb-card-small'); break; } if (vm.bbCardSelectable) { cls.push('bb-card-selectable'); if (vm.bbCardSelected) { cls.push('bb-card-selected'); } } return cls; }; nextId++; vm.cardCheckId = 'bb-card-check-' + nextId; $scope.$emit('bbCardInitialized', { cardCtrl: vm }); } BBCardController.$inject = ['$timeout', '$scope']; function bbCard() { function link(scope, el, attrs, ctrls) { var vm = ctrls[0]; function watchForComponent(component) { scope.$watch(function () { return vm[getCtrlPropName(component)]; }, function (newValue) { if (newValue) { el.find('.bb-card-' + component.cls) .empty() .append(newValue.el); } }); } components.forEach(watchForComponent); } return { bindToController: { bbCardSelectable: '@?', bbCardSelected: '=?', bbCardSelectionToggled: '&?', bbCardSize: '@?' }, require: ['bbCard'], controller: 'BBCardController', controllerAs: 'bbCard', link: link, restrict: 'E', scope: {}, templateUrl: 'sky/templates/card/card.directive.html', transclude: true }; } cardModule .controller('BBCardController', BBCardController) .directive('bbCard', bbCard); components.forEach(makeCardComponent); }()); /*global angular */ (function () { 'use strict'; var MAX_DOTS = 10; function Controller($scope, $element, bbFormat, bbResources) { var currentItemIndex, currentItem, vm = this; function getItemEls() { return $element[0].querySelectorAll('.bb-carousel-item'); } function allowIndex(itemIndex) { return itemIndex >= 0 && itemIndex < getItemEls().length; } function createDots() { var i, itemCount = getItemEls().length, nextDot; vm.dots = []; if (itemCount <= MAX_DOTS) { for (i = 0; i < itemCount; i++) { vm.dots.push(i); } } else { for (i = 0; i < MAX_DOTS; i++) { nextDot = Math.floor((i * itemCount / (MAX_DOTS))); vm.dots.push(nextDot); } } } function createTransformCss(offset) { if (offset) { return 'translate3d(' + offset + '%, 0, 0) scale(0.9)'; } return 'none'; } function getElIndex(item) { var i, itemEls = getItemEls(), n; for (i = 0, n = itemEls.length; i < n; i++) { if (item.elIsItem(itemEls[i])) { return i; } } } function getElItem(index) { var el, i, items = vm.items, itemEls = getItemEls(), n; /* istanbul ignore else */ /* sanity check */ if (index < itemEls.length) { el = itemEls[index]; for (i = 0, n = items.length; i < n; i++) { if (items[i].elIsItem(el)) { return items[i]; } } } } currentItemIndex = 0; vm.items = []; vm.addItem = function (item) { vm.items.push(item); if ((angular.isUndefined(vm.bbCarouselSelectedIndex) && vm.items.length === 1) || vm.bbCarouselSelectedIndex === (vm.items.length - 1)) { vm.setSelectedItem(vm.bbCarouselSelectedIndex || 0, true); } }; vm.removeItem = function (item) { var elIndex, i, items = vm.items, index, n; //Remove the item from the item array; for (i = 0, n = items.length; i < n; i++) { if (items[i] === item) { index = i; break; } } items.splice(index, 1); //Update selected element index. if (currentItemIndex >= (items.length)) { //If the selected index is out of bounds after removal, select the last element vm.setSelectedItem(items.length - 1, true); } else { //If the current selected item is still in the array but at a different index, update the current selected index. This would happen //if an item before the current selected item is removed. In that case, the indexes would have shifted. elIndex = getElIndex(currentItem); if (elIndex >= 0 && elIndex !== currentItemIndex) { vm.setSelectedItem(elIndex, true); } } }; vm.setSelectedItem = function (item, skipChange) { var i, itemEl, itemEls, n, offset; if (typeof item !== 'number') { item = getElIndex(item); } /*istanbul ignore if */ if (!allowIndex(item)) { return; } offset = item * -100; itemEls = getItemEls(); /*istanbul ignore else */ if (itemEls) { for (i = 0, n = itemEls.length; i < n; i++) { itemEl = itemEls[i]; itemEl.style.transform = createTransformCss(offset); offset += 100; } } currentItemIndex = item; currentItem = getElItem(item); vm.allowPrevious = currentItemIndex > 0; vm.allowNext = currentItemIndex < itemEls.length - 1; if (!skipChange && angular.isFunction(vm.bbCarouselSelectedIndexChange)) { vm.bbCarouselSelectedIndexChange({ index: currentItemIndex }); } }; vm.nextCard = function () { vm.setSelectedItem(currentItemIndex + 1); }; vm.previousCard = function () { vm.setSelectedItem(currentItemIndex - 1); }; vm.dotIsSelected = function (dot) { var dotIndex, dots = vm.dots, itemIndex = currentItemIndex; if (dot === itemIndex) { return true; } dotIndex = dots.indexOf(dot); if (dotIndex === dots.length - 1) { if (currentItemIndex > dots[dotIndex]) { return true; } } else if (itemIndex > dots[dotIndex] && itemIndex < dots[dotIndex + 1]) { return true; } return false; }; vm.getDotLabel = function (dot) { return bbFormat.formatText(bbResources.carousel_dot_label, dot + 1); }; $scope.$watchCollection(function () { return vm.items; }, function () { createDots(); vm.setSelectedItem(currentItemIndex, true); }); function onChanges(changesObj) { /* istanbul ignore else */ /* sanity check */ if (changesObj.bbCarouselSelectedIndex && angular.isDefined(changesObj.bbCarouselSelectedIndex.currentValue)) { vm.setSelectedItem(changesObj.bbCarouselSelectedIndex.currentValue || 0, true); } } vm.$onChanges = onChanges; } Controller.$inject = ['$scope', '$element', 'bbFormat', 'bbResources']; angular.module('sky.carousel.component', ['ngTouch', 'sky.format', 'sky.resources']) .component('bbCarousel', { bindings: { bbCarouselSelectedIndex: '<?', bbCarouselSelectedIndexChange: '&?', bbCarouselStyle: '@' }, templateUrl: 'sky/templates/carousel/carousel.component.html', transclude: true, controller: Controller }); }()); /*global angular */ (function () { 'use strict'; function Controller($element, $scope) { var vm = this; vm.elIsItem = function (el) { return $element[0].contains(el); }; vm.itemClick = function () { vm.carouselCtrl.setSelectedItem(vm); }; vm.$onInit = function () { vm.carouselCtrl.addItem(vm); }; vm.$onDestroy = function () { $element.find('.bb-carousel-item').removeClass('bb-carousel-item'); vm.carouselCtrl.removeItem(vm); }; // There's no "ng-focusin" equivalent so we have to attach the handler // here instead. $element.on('focusin', function () { // Select this item when it or any of its child elements receive focus. // Otherwise if the user is tabbing through focusable elements inside // the item the browser will scroll the carousel container itself // and throw off the positioning of the selected item. vm.carouselCtrl.setSelectedItem(vm); $scope.$apply(); }); } Controller.$inject = ['$element', '$scope']; angular.module('sky.carousel.item.component', []) .component('bbCarouselItem', { bindings: {}, require: { carouselCtrl: '^bbCarousel' }, templateUrl: 'sky/templates/carousel/carousel.item.component.html', transclude: true, controller: Controller }); }()); /*jshint browser: true */ /*global angular */ (function () { 'use strict'; angular.module('sky.check', []) .directive('bbCheck', ['$templateCache', '$compile', function ($templateCache, $compile) { function createEl(name, scope) { var templateHtml; templateHtml = $templateCache.get('sky/templates/check/' + name + '.html'); if (scope) { templateHtml = $compile(templateHtml)(scope); } return angular.element(templateHtml); } return { link: function (scope, el, attr) { var labelEl = el.parent('label'), styledEl; if (labelEl.length < 1) { el.wrap(createEl('wrapper')); } else { labelEl.addClass('bb-check-wrapper'); /* wrap non-whitespace label text to set vertical-align middle properly */ labelEl.contents() .filter(function () { return this.nodeType === 3 && /\S/.test(this.textContent); }) .wrap(createEl('labeltext')); } styledEl = createEl(('styled' + attr.type), scope); el.after(styledEl); } }; }]); }()); /*global angular */ (function () { 'use strict'; var SEARCH_PROPS = ['title', 'description']; function BBChecklistController($scope, bbChecklistUtility, bbResources) { var vm = this; function itemMatchesCategory(item, category) { return !category || item.category === category; } function itemInSubset(item, subsetSelected) { if (!vm.bbChecklistSubsetLabel || angular.isUndefined(item[vm.bbChecklistSubsetProperty]) || item[vm.bbChecklistSubsetProperty] === false) { return true; } if (vm.subsetExclude) { return item[vm.bbChecklistSubsetProperty] !== subsetSelected; } else { return item[vm.bbChecklistSubsetProperty] === subsetSelected; } } function itemIsSelected(item) { return bbChecklistUtility.contains(vm.bbChecklistSelectedItems, item); } function itemMatchesFilter(item, category, searchTextUpper) { var i, p, len, val; if (itemMatchesCategory(item, category)) { if (itemInSubset(item, vm.subsetSelected)) { if (!searchTextUpper) { return true; } for (i = 0, len = SEARCH_PROPS.length; i < len; i++) { p = SEARCH_PROPS[i]; if (item.hasOwnProperty(p)) { val = item[p]; if (angular.isString(val) && val.toUpperCase().indexOf(searchTextUpper) >= 0) { return true; } } } } } return false; } function invokeFilterLocal() { var filteredItems, i, item, items = vm.bbChecklistItems, n, searchTextUpper = (vm.searchText || '').toUpperCase(), selectedCategory = vm.selectedCategory; if (!searchTextUpper && !selectedCategory && !vm.bbChecklistSubsetLabel && !vm.onlyShowSelected) { 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); } } } vm.filteredItems = filteredItems; } function invokeFilter() { /* When the show only selected items checkbox is checked, then no other filters should be applied */ if (vm.onlyShowSelected) { /*istanbul ignore next */ /* sanity check */ vm.filteredItems = vm.bbChecklistSelectedItems || []; } else if (vm.filterLocal) { invokeFilterLocal(); } else if (vm.bbChecklistFilterCallback) { vm.bbChecklistFilterCallback({ searchText: vm.searchText, category: vm.selectedCategory, subsetSelected: vm.subsetSelected }); } } function eachFilteredItem(callback) { var filteredItemsCopy = angular.copy(vm.filteredItems); filteredItemsCopy.forEach(callback); } function selectItem(item) { bbChecklistUtility.add(vm.bbChecklistSelectedItems, item); } function unselectItem(item) { bbChecklistUtility.remove(vm.bbChecklistSelectedItems, item); } vm.selectAll = function () { eachFilteredItem(selectItem); }; vm.clear = function () { eachFilteredItem(unselectItem); }; vm.rowClicked = function (item) { if (!itemIsSelected(item)) { selectItem(item); } else { unselectItem(item); } }; vm.isSingleSelect = function () { return vm.bbChecklistSelectStyle === 'single'; }; vm.getChecklistCls = function () { return { 'bb-checklist-single': vm.isSingleSelect() }; }; vm.getRowCls = function (item) { return { 'bb-checklist-row-selected': itemIsSelected(item) }; }; vm.singleSelectRowClick = function (item) { vm.bbChecklistSelectedItems = [item]; $scope.$emit('bbPickerSelected', { selectedItems: vm.bbChecklistSelectedItems }); }; /* Ensure that clicking input does not also cause the row click function to be called */ vm.inputClicked = function ($event) { $event.stopPropagation(); }; vm.setColumns = function (columns) { vm.columns = columns; }; function onInit() { vm.bbChecklistSelectedItems = vm.bbChecklistSelectedItems || []; vm.itemIsSelected = itemIsSelected; $scope.$watch(function () { return vm.bbChecklistItems; }, function () { vm.filteredItems = vm.bbChecklistItems; vm.highlightRefresh = new Date().getTime(); }); $scope.$watch(function () { return vm.searchText; }, function (newValue, oldValue) { if (newValue !== oldValue) { invokeFilter(); } }); if (angular.isDefined(vm.bbChecklistCategories)) { vm.allCategories = 'bbChecklistAllCategories'; vm.selectedOption = vm.allCategories; if (angular.isUndefined(vm.bbChecklistAllCategoriesLabel)) { vm.bbChecklistAllCategoriesLabel = bbResources.grid_column_picker_all_categories; } $scope.$watch(function () { return vm.selectedOption; }, function (newValue, oldValue) { if (newValue === vm.allCategories) { vm.selectedCategory = null; } else { vm.selectedCategory = newValue; } if (newValue !== oldValue) { invokeFilter(); } }); } if (angular.isDefined(vm.bbChecklistSubsetLabel)) { $scope.$watch(function () { return vm.subsetSelected; }, function () { invokeFilter(); }); } $scope.$watch(function () { return vm.onlyShowSelected; }, function () { invokeFilter();