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
JavaScript
/*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();