1 /*! b2b-angular-library - v1.0.5 - Last updated: 2017-05-24. Copyright (c) 2016 AT&T Services, Inc. */
2 angular.module("b2b.att.tpls", ['b2bTemplate/audioPlayer/audioPlayer.html', 'b2bTemplate/audioRecorder/audioRecorder.html', 'b2bTemplate/backToTop/backToTop.html', 'b2bTemplate/boardstrip/b2bAddBoard.html', 'b2bTemplate/boardstrip/b2bBoard.html', 'b2bTemplate/boardstrip/b2bBoardstrip.html', 'b2bTemplate/calendar/datepicker-popup.html', 'b2bTemplate/calendar/datepicker.html', 'b2bTemplate/coachmark/coachmark.html', 'b2bTemplate/dropdowns/b2bDropdownDesktop.html', 'b2bTemplate/dropdowns/b2bDropdownGroupDesktop.html', 'b2bTemplate/dropdowns/b2bDropdownListDesktop.html', 'b2bTemplate/fileUpload/fileUpload.html', 'b2bTemplate/flyout/flyout.html', 'b2bTemplate/flyout/flyoutContent.html', 'b2bTemplate/footer/footer_column_switch_tpl.html', 'b2bTemplate/horizontalTable/horizontalTable.html', 'b2bTemplate/hourPicker/b2bHourpicker.html', 'b2bTemplate/hourPicker/b2bHourpickerPanel.html', 'b2bTemplate/hourPicker/b2bHourpickerValue.html', 'b2bTemplate/leftNavigation/leftNavigation.html', 'b2bTemplate/listbox/listbox.html', 'b2bTemplate/modalsAndAlerts/b2b-backdrop.html', 'b2bTemplate/modalsAndAlerts/b2b-window.html', 'b2bTemplate/monthSelector/monthSelector-popup.html', 'b2bTemplate/monthSelector/monthSelector.html', 'b2bTemplate/monthSelector/monthSelectorLink.html', 'b2bTemplate/pagination/b2b-pagination.html', 'b2bTemplate/paneSelector/paneSelector.html', 'b2bTemplate/paneSelector/paneSelectorPane.html', 'b2bTemplate/profileCard/profileCard-addUser.html', 'b2bTemplate/profileCard/profileCard.html', 'b2bTemplate/searchField/searchField.html', 'b2bTemplate/seekBar/seekBar.html', 'b2bTemplate/slider/slider.html', 'b2bTemplate/spinButton/spinButton.html', 'b2bTemplate/statusTracker/statusTracker.html', 'b2bTemplate/stepTracker/stepTracker.html', 'b2bTemplate/switches/switches-spanish.html', 'b2bTemplate/switches/switches.html', 'b2bTemplate/tableMessages/tableMessage.html', 'b2bTemplate/tables/b2bTable.html', 'b2bTemplate/tables/b2bTableBody.html', 'b2bTemplate/tables/b2bTableHeaderSortable.html', 'b2bTemplate/tables/b2bTableHeaderUnsortable.html', 'b2bTemplate/tableScrollbar/tableScrollbar.html', 'b2bTemplate/tabs/b2bTab.html', 'b2bTemplate/tabs/b2bTabset.html', 'b2bTemplate/treeNav/groupedTree.html', 'b2bTemplate/treeNav/treeMember.html', 'b2bTemplate/treeNav/ungroupedTree.html', 'b2bTemplate/treeNodeCheckbox/groupedTree.html', 'b2bTemplate/treeNodeCheckbox/treeMember.html', 'b2bTemplate/treeNodeCheckbox/ungroupedTree.html']);angular.module("b2b.att", ["b2b.att.tpls", 'b2b.att.addressInputTemplate','b2b.att.arrows','b2b.att.audioPlayer','b2b.att.audioRecorder','b2b.att.backToTop','b2b.att.badgesForAlerts','b2b.att.boardstrip','b2b.att.bootstrapGridTemplate','b2b.att.breadcrumbs','b2b.att.buttonGroups','b2b.att.buttons','b2b.att.calendar','b2b.att.checkboxes','b2b.att.coachmark','b2b.att.configurationSection','b2b.att.directoryListingTemplate','b2b.att.dropdowns','b2b.att.fileUpload','b2b.att.filters','b2b.att.flyout','b2b.att.footer','b2b.att.header','b2b.att.headingsAndCopy','b2b.att.horizontalTable','b2b.att.hourPicker','b2b.att.inputTemplate','b2b.att.leftNavigation','b2b.att.links','b2b.att.listbox','b2b.att.loaderAnimation','b2b.att.messageWrapper','b2b.att.modalsAndAlerts','b2b.att.monthSelector','b2b.att.multiLevelNavigation','b2b.att.multipurposeExpander','b2b.att.notesMessagesAndErrors','b2b.att.notificationCardTemplate','b2b.att.orderConfirmationTemplate','b2b.att.pagination','b2b.att.paneSelector','b2b.att.phoneNumberInput','b2b.att.profileBlockTemplate','b2b.att.profileCard','b2b.att.radios','b2b.att.searchField','b2b.att.seekBar','b2b.att.separators','b2b.att.slider','b2b.att.spinButton','b2b.att.staticRouteTemplate','b2b.att.statusTracker','b2b.att.stepTracker','b2b.att.switches','b2b.att.tableDragAndDrop','b2b.att.tableMessages','b2b.att.tables','b2b.att.tableScrollbar','b2b.att.tabs','b2b.att.tagBadges','b2b.att.textArea','b2b.att.timeInputField','b2b.att.tooltipsForForms','b2b.att.treeNav','b2b.att.treeNodeCheckbox','b2b.att.utilities']);/**
4 * @name Template.att:Address Input
7 * <file src="src/addressInputTemplate/docs/readme.md" />
14 <example module="b2b.att">
15 <file src="src/addressInputTemplate/docs/demo.html" />
16 <file src="src/addressInputTemplate/docs/demo.js" />
21 angular.module('b2b.att.addressInputTemplate', ['ngMessages']);
24 * @name Buttons, links & UI controls.att:arrows
27 * <file src="src/arrows/docs/readme.md" />
30 * Please refer demo.html tab in Example section below.
34 <example module="b2b.att">
35 <file src="src/arrows/docs/demo.html" />
36 <file src="src/arrows/docs/demo.js" />
41 angular.module('b2b.att.arrows', []);
44 * @name Videos, audio & animation.att:Audio Player
46 * @param {string} audioSrcUrl - MP3 audio source URL or Blob URL
48 * <file src="src/audioPlayer/docs/readme.md" />
52 <div b2b-audio audio-src-url='audioSrcUrl'></div>
56 <example module="b2b.att">
57 <file src="src/audioPlayer/docs/demo.html" />
58 <file src="src/audioPlayer/docs/demo.js" />
64 angular.module('b2b.att.audioPlayer', ['b2b.att.utilities', 'b2b.att.seekBar'])
65 .constant('AudioPlayerConfig', {
67 'timeShiftInSeconds': 5
69 .filter('trustedAudioUrl', ['$sce', function ($sce) {
70 return function (audioFileFullPath) {
71 return audioFileFullPath ? $sce.trustAsResourceUrl(audioFileFullPath) : 'undefined';
74 .directive('b2bAudio', ['$log', '$timeout', 'AudioPlayerConfig', '$compile', 'events', function ($log, $timeout, AudioPlayerConfig, $compile, events) {
81 templateUrl: 'b2bTemplate/audioPlayer/audioPlayer.html',
82 controller: function ($scope) {
86 if (!angular.isDefined($scope.audioSrcUrl)) {
87 $log.warn('b2b-audio : audio-src-url undefined');
88 $scope.audioSrcUrl = undefined;
89 $scope.audio.mp3 = undefined;
93 link: function (scope, element) {
94 var audioElement = angular.element(element[0].querySelector('audio'))[0];
95 scope.audio.audioElement = audioElement;
97 function setAttributes(element, attributes) {
98 Object.keys(attributes).forEach(function (name) {
99 element.setAttribute(name, attributes[name]);
103 $timeout(function () {
104 // TODO: Replace with DDA Tooltip
105 var seekBarKnob = element[0].querySelector('.b2b-seek-bar-knob');
106 var tooltipObject = {
107 'tooltip': '{{timeFormatter(audio.currentTime)}}',
108 'tooltip-placement': 'above',
109 'tooltip-style': 'blue',
110 'tooltip-trigger': 'mousedown',
111 'tooltip-append-to-body': 'false',
112 'tooltip-offset': '-10',
113 'refer-by': 'seek-bar-tooltip'
115 setAttributes(seekBarKnob, tooltipObject);
116 $compile(seekBarKnob)(scope);
119 if (angular.isDefined(scope.audioSrcUrl)) {
120 scope.audio.mp3 = scope.audioSrcUrl;
123 scope.audio.currentTime = 0;
124 scope.audio.currentVolume = AudioPlayerConfig.defaultVolume;
125 scope.audio.timeShiftInSeconds = AudioPlayerConfig.timeShiftInSeconds;
126 scope.isPlayInProgress = false;
127 scope.isReady = false;
128 scope.isAudioDragging = false;
130 $timeout(function () {
132 audioElement.volume = scope.audio.currentVolume / 100;
135 scope.$watch('audioSrcUrl', function (newVal, oldVal) {
136 if (newVal !== oldVal) {
138 $log.warn('b2b-audio : audio-src-url undefined. Please provide a valid URL');
141 scope.audio.mp3 = newVal;
142 $timeout(function () {
148 scope.playAudio = function () {
154 audioElement.onplay = function () {
155 scope.isPlayInProgress = true;
159 scope.pauseAudio = function () {
160 audioElement.pause();
163 audioElement.onpause = function () {
164 scope.isPlayInProgress = false;
168 scope.toggleAudio = function () {
169 if (audioElement.paused) {
176 scope.volumeUp = function (delta) {
182 audioElement.muted = false;
183 if (audioElement.volume < 1) {
184 audioElement.volume = Math.min((Math.round((audioElement.volume + delta) * 100) / 100), 1);
186 scope.audio.currentVolume = audioElement.volume * 100;
187 return audioElement.volume;
190 scope.volumeDown = function (delta) {
196 audioElement.muted = false;
197 if (audioElement.volume > 0) {
198 audioElement.volume = Math.max((Math.round((audioElement.volume - delta) * 100) / 100), 0);
200 scope.audio.currentVolume = audioElement.volume * 100;
201 return audioElement.volume;
204 var volumeHandler = function (e) {
205 events.preventDefault(e);
206 if ((e.wheelDelta && e.wheelDelta > 0) || (e.detail && e.detail < 0)) {
216 scope.$watch('audio.currentVolume', function (newVal, oldVal) {
217 if (newVal !== oldVal) {
218 audioElement.volume = newVal / 100;
222 scope.setCurrentTime = function (timeInSec) {
223 audioElement.currentTime = timeInSec;
226 scope.setAudioPosition = function (val) {
228 scope.setCurrentTime(val);
229 scope.isAudioDragging = false;
233 function getTimestampArray(timestamp) {
234 var d = Math.abs(timestamp) / 1000; // delta
235 var r = {}; // result
236 var s = { // structure
243 Object.keys(s).forEach(function (key) {
244 r[key] = Math.floor(d / s[key]);
245 d -= r[key] * s[key];
251 scope.timeFormatter = function (timeInSec) {
252 var formattedTime = '00:00';
254 if (!timeInSec || timeInSec < 1) {
255 return formattedTime;
258 if (typeof timeInSec === 'string') {
262 var dateArray = getTimestampArray(timeInSec * 1000);
263 Object.keys(dateArray).forEach(function (key) {
264 if (dateArray[key] === 0) {
265 dateArray[key] = '00';
266 } else if (dateArray[key] < 10) {
267 dateArray[key] = '0' + dateArray[key];
271 formattedTime = dateArray['minute'] + ':' + dateArray['second'];
273 if (dateArray['hour'] !== '00') {
274 formattedTime = dateArray['hour'] + ':' + formattedTime;
277 if (dateArray['day'] !== '00') {
278 formattedTime = dateArray['day'] + ':' + formattedTime;
281 return formattedTime;
284 audioElement.onloadedmetadata = function () {
285 scope.audio.duration = audioElement.duration;
289 audioElement.ontimeupdate = function () {
290 if (!scope.isAudioDragging) {
291 scope.audio.currentTime = audioElement.currentTime;
296 audioElement.onended = function () {
297 scope.setCurrentTime(0);
298 scope.audio.currentTime = 0;
299 if (!audioElement.paused) {
305 audioElement.oncanplay = function () {
306 scope.isReady = true;
307 scope.isPlayInProgress = !audioElement.paused;
311 var onloadstart = function () {
312 scope.isReady = false;
313 scope.isPlayInProgress = !audioElement.paused;
314 scope.audio.currentTime = 0;
315 scope.audio.duration = 0;
318 audioElement.addEventListener("loadstart", onloadstart);
324 * @name Videos, audio & animation.att:Audio Recorder
326 * @param {function} callback - A callback to handle the WAV blob
327 * @param {object} config - A config object with properties startRecordingMessage & whileRecordingMessage
329 * <file src="src/audioRecorder/docs/readme.md" />
333 * <section id="code">
334 <example module="b2b.att">
335 <file src="src/audioRecorder/docs/demo.html" />
336 <file src="src/audioRecorder/docs/demo.js" />
341 angular.module('b2b.att.audioRecorder', ['b2b.att.utilities'])
342 .constant('AudioRecorderConfig', {
343 'startRecordingMessage': 'Click on REC icon to being recording',
344 'whileRecordingMessage': 'Recording...'
346 .directive('b2bAudioRecorder', ['$interval', 'AudioRecorderConfig', 'b2bUserAgent', 'b2bRecorder', function($interval, AudioRecorderConfig, b2bUserAgent, b2bRecorder) {
353 templateUrl: 'b2bTemplate/audioRecorder/audioRecorder.html',
354 controller: function($scope) {
356 function hasGetUserMedia() {
357 return !!(navigator.getUserMedia || navigator.webkitGetUserMedia ||
358 navigator.mozGetUserMedia || navigator.msGetUserMedia);
361 if (!hasGetUserMedia()) {
362 throw new Error('Your broswer does not support MediaRecorder API');
365 if (!(b2bUserAgent.isFF() || b2bUserAgent.isChrome())) {
366 throw new Error('b2bAudioRecorder does not support this browser!');
370 link: function(scope, element) {
371 scope.elapsedTime = 0;
372 scope.isRecording = false;
374 scope.config.startRecordingMessage = AudioRecorderConfig.startRecordingMessage;
375 scope.config.whileRecordingMessage = AudioRecorderConfig.whileRecordingMessage;
378 var timer = undefined; // Interval promise
379 navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
381 var audio = angular.element(element[0].querySelector('audio'))[0];
382 var recorder = undefined;
384 function startRecording() {
385 scope.isRecording = true;
386 navigator.mediaDevices.getUserMedia({
388 }).then(function(stream) {
389 //create the MediaStreamAudioSourceNode
390 context = new AudioContext();
391 source = context.createMediaStreamSource(stream);
392 recorder = new b2bRecorder(source);
395 timer = $interval(function() {
396 scope.elapsedTime += 1;
398 }).catch(function(err) {
404 function stopRecording() {
405 scope.isRecording = false;
408 recorder.exportWAV(function(s) {
409 audio.src = window.URL.createObjectURL(s);
410 context.close().then(function() {
412 $interval.cancel(timer);
414 scope.elapsedTime = 0;
417 recorder = undefined;
419 if (angular.isFunction(scope.callback)){
420 scope.callback({'data': audio});
427 scope.toggleRecording = function() {
428 if (scope.isRecording) {
437 //TODO: Move this into utilities
438 function getTimestampArray(timestamp) {
439 var d = Math.abs(timestamp) / 1000; // delta
440 var r = {}; // result
441 var s = { // structure
448 Object.keys(s).forEach(function(key) {
449 r[key] = Math.floor(d / s[key]);
450 d -= r[key] * s[key];
455 scope.timeFormatter = function(timeInSec) {
456 var formattedTime = '00:00';
458 if (!timeInSec || timeInSec < 1) {
459 return formattedTime;
462 if (typeof timeInSec === 'string') {
466 var dateArray = getTimestampArray(timeInSec * 1000);
467 Object.keys(dateArray).forEach(function(key) {
468 if (dateArray[key] === 0) {
469 dateArray[key] = '00';
470 } else if (dateArray[key] < 10) {
471 dateArray[key] = '0' + dateArray[key];
475 formattedTime = dateArray['minute'] + ':' + dateArray['second'];
477 if (dateArray['hour'] !== '00') {
478 formattedTime = dateArray['hour'] + ':' + formattedTime;
481 if (dateArray['day'] !== '00') {
482 formattedTime = dateArray['day'] + ':' + formattedTime;
485 return formattedTime;
488 scope.$on('$destroy', function() {
490 $interval.cancel(timer);
499 * @name Navigation.att:Back To Top
502 * <file src="src/backToTop/docs/readme.md" />
503 * @param {integer} scrollSpeed - Scroll speed in seconds, default is 1
507 <div ng-controller="backToTopController">
508 <div b2b-backtotop></div>
512 * <section id="code">
513 <example module="b2b.att">
514 <file src="src/backToTop/docs/demo.html" />
515 <file src="src/backToTop/docs/demo.js" />
521 angular.module('b2b.att.backToTop', ['b2b.att.utilities','b2b.att.position'])
522 .directive('b2bBacktotopButton', [function () {
526 templateUrl: 'b2bTemplate/backToTop/backToTop.html',
527 link: function (scope, elem, attr) {
528 elem.bind('click', function(evt) {
529 var scrollSpeed = parseInt(attr.scrollSpeed) || 1;
530 TweenLite.to(window, scrollSpeed, {scrollTo:{x: 0, y: 0}});
537 * @name Messages, modals & alerts.att:badgesForAlerts
540 * <file src="src/badgesForAlerts/docs/readme.md" />
542 * <section id="code">
543 <example module="b2b.att">
544 <file src="src/badgesForAlerts/docs/demo.html" />
545 <file src="src/badgesForAlerts/docs/demo.js" />
550 angular.module('b2b.att.badgesForAlerts', []);
553 * @name Misc.att:boardstrip
556 * <file src="src/boardstrip/docs/readme.md" />
562 <b>HTML + AngularJS</b>
563 <example module="b2b.att">
564 <file src="src/boardstrip/docs/demo.html" />
565 <file src="src/boardstrip/docs/demo.js" />
569 angular.module('b2b.att.boardstrip', ['b2b.att.utilities'])
570 .constant('BoardStripConfig', {
571 'maxVisibleBoards': 4,
573 /* These parameters are non-configurable and remain unaltered, until there is a change in corresponding CSS */
577 .directive('b2bBoard', [function () {
582 require: '^b2bBoardStrip',
587 templateUrl: 'b2bTemplate/boardstrip/b2bBoard.html',
588 link: function (scope, element, attrs, ctrls) {
590 var parentCtrl = ctrls;
592 scope.getCurrentIndex = function () {
593 return parentCtrl.getCurrentIndex();
595 scope.selectBoard = function (boardIndex) {
596 if (!isNaN(boardIndex)) {
597 parentCtrl.setCurrentIndex(boardIndex);
603 .directive('b2bBoardStrip', ['BoardStripConfig', '$timeout', function (BoardStripConfig, $timeout) {
608 require: ['?ngModel', 'b2bBoardStrip'],
610 boardsMasterArray: '=',
613 templateUrl: 'b2bTemplate/boardstrip/b2bBoardstrip.html',
614 controller: function ($scope) {
615 if (!angular.isDefined($scope.boardsMasterArray)) {
616 $scope.boardsMasterArray = [];
619 this.rectifyMaxVisibleBoards = function () {
620 if (this.maxVisibleIndex >= $scope.boardsMasterArray.length) {
621 this.maxVisibleIndex = $scope.boardsMasterArray.length - 1;
624 if (this.maxVisibleIndex < 0) {
625 this.maxVisibleIndex = 0;
629 this.resetBoardStrip = function () {
630 $scope.currentIndex = 0;
632 this.maxVisibleIndex = BoardStripConfig.maxVisibleBoards - 1;
633 this.minVisibleIndex = 0;
635 this.rectifyMaxVisibleBoards();
638 this.getCurrentIndex = function () {
639 return $scope.currentIndex;
641 this.setCurrentIndex = function (indx) {
642 $scope.currentIndex = indx;
645 this.getBoardsMasterArrayLength = function () {
646 return $scope.boardsMasterArray.length;
649 $scope.addBoardPressedFlag = false;
650 this.getAddBoardPressedFlag = function () {
651 return $scope.addBoardPressedFlag;
653 this.setAddBoardPressedFlag = function (booleanValue) {
654 $scope.addBoardPressedFlag = booleanValue;
658 link: function (scope, element, attrs, ctrls) {
660 var ngModelCtrl = ctrls[0];
664 var animationTimeout = 1000;
666 var getBoardViewportWidth = function (numberOfVisibleBoards) {
667 return numberOfVisibleBoards * (BoardStripConfig.boardLength + BoardStripConfig.boardMargin);
669 if (element[0].querySelector(".board-viewport")) {
670 angular.element(element[0].querySelector(".board-viewport")).css({
671 "width": getBoardViewportWidth(BoardStripConfig.maxVisibleBoards) + "px"
675 var getBoardstripContainerWidth = function (totalNumberOfBoards) {
676 return totalNumberOfBoards * (BoardStripConfig.boardLength + BoardStripConfig.boardMargin);
678 if (element[0].querySelector(".boardstrip-container")) {
679 angular.element(element[0].querySelector(".boardstrip-container")).css({
680 "width": getBoardstripContainerWidth(ctrl.getBoardsMasterArrayLength()) + "px"
682 angular.element(element[0].querySelector(".boardstrip-container")).css({
687 var calculateAndGetBoardstripContainerAdjustment = function () {
689 var calculatedAdjustmentValue;
691 if (ctrl.getBoardsMasterArrayLength() <= BoardStripConfig.maxVisibleBoards) {
692 calculatedAdjustmentValue = 0;
694 calculatedAdjustmentValue = (ctrl.minVisibleIndex * (BoardStripConfig.boardLength + BoardStripConfig.boardMargin)) * -1;
697 return calculatedAdjustmentValue;
700 var animateBoardstripContainerAdjustment = function (elementToFocusAfterAnimation) {
701 var oldContainerAdjustment = angular.element(element[0].querySelector(".boardstrip-container"))[0].style.left;
702 var containerAdjustment = calculateAndGetBoardstripContainerAdjustment();
703 if (oldContainerAdjustment !== containerAdjustment + 'px') {
704 angular.element(element[0].querySelector(".boardstrip-container")).css({
705 "left": containerAdjustment + "px"
708 $timeout.cancel(oldTimeout);
709 oldTimeout = $timeout(function () {
710 elementToFocusAfterAnimation.focus();
711 }, animationTimeout);
713 elementToFocusAfterAnimation.focus();
717 var updateBoardsTabIndex = function (boardArray, minViewIndex, maxViewIndex) {
718 for (var i = 0; i < boardArray.length; i++) {
719 angular.element(boardArray[i]).attr('tabindex', '-1');
721 for (var j = minViewIndex; j <= maxViewIndex; j++) {
722 angular.element(boardArray[j]).attr('tabindex', '0');
726 $timeout(function () {
727 var currentBoardArray = element[0].querySelectorAll('[b2b-board]');
728 updateBoardsTabIndex(currentBoardArray, ctrl.minVisibleIndex, ctrl.maxVisibleIndex);
731 scope.$watchCollection('boardsMasterArray', function (newVal, oldVal) {
732 if (newVal !== oldVal) {
733 /* When a board is removed */
734 if (newVal.length < oldVal.length) {
735 ctrl.resetBoardStrip();
736 $timeout(function () {
738 var currentBoardArray = element[0].querySelectorAll('[b2b-board]');
739 if (currentBoardArray.length !== 0) {
740 animateBoardstripContainerAdjustment(currentBoardArray[0]);
742 element[0].querySelector('div.boardstrip-item--add').focus();
745 angular.element(element[0].querySelector(".boardstrip-container")).css({
746 "width": getBoardstripContainerWidth(ctrl.getBoardsMasterArrayLength()) + "px"
748 /* Update tabindecies to ensure keyboard navigation behaves correctly */
749 updateBoardsTabIndex(currentBoardArray, ctrl.minVisibleIndex, ctrl.maxVisibleIndex);
752 /* When a board is added */
754 if (ctrl.getAddBoardPressedFlag()) {
755 ctrl.maxVisibleIndex = ctrl.getBoardsMasterArrayLength() - 1;
756 ctrl.minVisibleIndex = Math.max(ctrl.maxVisibleIndex - BoardStripConfig.maxVisibleBoards + 1, 0);
758 ctrl.setCurrentIndex(ctrl.maxVisibleIndex);
760 $timeout(function () {
761 angular.element(element[0].querySelector(".boardstrip-container")).css({
762 "width": getBoardstripContainerWidth(ctrl.getBoardsMasterArrayLength()) + "px"
765 var currentBoardArray = element[0].querySelectorAll('[b2b-board]');
766 animateBoardstripContainerAdjustment(currentBoardArray[currentBoardArray.length - 1]);
767 /* Update tabindecies to ensure keyboard navigation behaves correctly */
768 updateBoardsTabIndex(currentBoardArray, ctrl.minVisibleIndex, ctrl.maxVisibleIndex);
771 if (ctrl.minVisibleIndex === 0 && ctrl.getBoardsMasterArrayLength() < BoardStripConfig.maxVisibleBoards + 1) {
772 ctrl.maxVisibleIndex = ctrl.getBoardsMasterArrayLength() - 1;
773 ctrl.rectifyMaxVisibleBoards();
776 $timeout(function () {
777 angular.element(element[0].querySelector(".boardstrip-container")).css({
778 "width": getBoardstripContainerWidth(ctrl.getBoardsMasterArrayLength()) + "px"
781 var currentBoardArray = element[0].querySelectorAll('[b2b-board]');
782 /* Update tabindecies to ensure keyboard navigation behaves correctly */
783 updateBoardsTabIndex(currentBoardArray, ctrl.minVisibleIndex, ctrl.maxVisibleIndex);
787 ctrl.setAddBoardPressedFlag(false);
792 scope.nextBoard = function () {
793 ctrl.maxVisibleIndex += BoardStripConfig.boardsToScroll;
794 ctrl.rectifyMaxVisibleBoards();
795 ctrl.minVisibleIndex = ctrl.maxVisibleIndex - (BoardStripConfig.maxVisibleBoards - 1);
797 $timeout.cancel(oldTimeout);
798 angular.element(element[0].querySelector(".boardstrip-container")).css({
799 "left": calculateAndGetBoardstripContainerAdjustment() + "px"
802 $timeout(function () {
803 var currentBoardArray = element[0].querySelectorAll('[b2b-board]');
805 /* Remove tabindex from non-visible boards */
806 updateBoardsTabIndex(currentBoardArray, ctrl.minVisibleIndex, ctrl.maxVisibleIndex);
808 if (!(scope.isNextBoard())) {
810 currentBoardArray[currentBoardArray.length - 1].focus();
811 } catch (e) { /* IE8 may throw exception */ }
813 }, animationTimeout);
815 scope.prevBoard = function () {
817 ctrl.minVisibleIndex -= BoardStripConfig.boardsToScroll;
818 if (ctrl.minVisibleIndex < 0) {
819 ctrl.minVisibleIndex = 0;
822 ctrl.maxVisibleIndex = ctrl.minVisibleIndex + BoardStripConfig.maxVisibleBoards - 1;
823 ctrl.rectifyMaxVisibleBoards();
825 $timeout.cancel(oldTimeout);
826 angular.element(element[0].querySelector(".boardstrip-container")).css({
827 "left": calculateAndGetBoardstripContainerAdjustment() + "px"
830 $timeout(function () {
831 var currentBoardArray = element[0].querySelectorAll('[b2b-board]');
833 /* Remove tabindex from non-visible boards */
834 updateBoardsTabIndex(currentBoardArray, ctrl.minVisibleIndex, ctrl.maxVisibleIndex);
836 if (ctrl.minVisibleIndex === 0) {
838 element[0].querySelector('div.boardstrip-item--add').focus();
839 } catch (e) { /* IE8 may throw exception */ }
844 scope.isPrevBoard = function () {
845 return (ctrl.minVisibleIndex > 0);
847 scope.isNextBoard = function () {
848 return (ctrl.getBoardsMasterArrayLength() - 1 > ctrl.maxVisibleIndex);
851 ngModelCtrl.$render = function () {
852 if (ngModelCtrl.$viewValue || ngModelCtrl.$viewValue === 0) {
853 var newCurrentIndex = ngModelCtrl.$viewValue;
855 if (!(newCurrentIndex = parseInt(newCurrentIndex, 10))) {
859 if (newCurrentIndex <= 0) {
860 ctrl.resetBoardStrip();
863 var currentBoardArray = element[0].querySelectorAll('[b2b-board]');
864 if (currentBoardArray.length !== 0) {
865 animateBoardstripContainerAdjustment(currentBoardArray[0]);
867 element[0].querySelector('div.boardstrip-item--add').focus();
869 /* Update tabindecies to ensure keyboard navigation behaves correctly */
870 updateBoardsTabIndex(currentBoardArray, ctrl.minVisibleIndex, ctrl.maxVisibleIndex);
871 } else if (newCurrentIndex >= ctrl.getBoardsMasterArrayLength()) {
872 ctrl.maxVisibleIndex = ctrl.getBoardsMasterArrayLength() - 1;
873 ctrl.rectifyMaxVisibleBoards();
874 ctrl.minVisibleIndex = Math.max(ctrl.maxVisibleIndex - BoardStripConfig.maxVisibleBoards + 1, 0);
876 newCurrentIndex = ctrl.maxVisibleIndex;
878 $timeout(function () {
879 var currentBoardArray = element[0].querySelectorAll('[b2b-board]');
880 animateBoardstripContainerAdjustment(currentBoardArray[newCurrentIndex]);
881 /* Update tabindecies to ensure keyboard navigation behaves correctly */
882 updateBoardsTabIndex(currentBoardArray, ctrl.minVisibleIndex, ctrl.maxVisibleIndex);
886 if (!(newCurrentIndex >= ctrl.minVisibleIndex && newCurrentIndex <= ctrl.maxVisibleIndex)) {
887 ctrl.minVisibleIndex = newCurrentIndex;
888 ctrl.maxVisibleIndex = ctrl.minVisibleIndex + BoardStripConfig.maxVisibleBoards - 1;
889 ctrl.rectifyMaxVisibleBoards();
891 if (ctrl.getBoardsMasterArrayLength() < BoardStripConfig.maxVisibleBoards) {
892 ctrl.minVisibleIndex = 0;
894 ctrl.minVisibleIndex = Math.max(ctrl.maxVisibleIndex - BoardStripConfig.maxVisibleBoards + 1, 0);
897 $timeout(function () {
898 var currentBoardArray = element[0].querySelectorAll('[b2b-board]');
899 animateBoardstripContainerAdjustment(currentBoardArray[newCurrentIndex]);
900 /* Update tabindecies to ensure keyboard navigation behaves correctly */
901 updateBoardsTabIndex(currentBoardArray, ctrl.minVisibleIndex, ctrl.maxVisibleIndex);
905 scope.currentIndex = newCurrentIndex;
906 ngModelCtrl.$setViewValue(newCurrentIndex);
908 ctrl.resetBoardStrip();
909 ngModelCtrl.$setViewValue(0);
913 scope.$watch('currentIndex', function (newVal, oldVal) {
914 if (newVal !== oldVal && ngModelCtrl && ngModelCtrl.$viewValue !== newVal) {
915 ngModelCtrl.$setViewValue(newVal);
921 .directive('b2bAddBoard', ['BoardStripConfig', '$parse', function (BoardStripConfig, $parse) {
925 require: '^b2bBoardStrip',
929 templateUrl: 'b2bTemplate/boardstrip/b2bAddBoard.html',
930 link: function (scope, element, attrs, ctrl) {
931 scope.addBoard = function () {
932 if (attrs['onAddBoard']) {
933 scope.onAddBoard = $parse(scope.onAddBoard);
935 ctrl.setAddBoardPressedFlag(true);
941 .directive('b2bBoardNavigation', ['keymap', 'events', function (keymap, events) {
944 link: function (scope, elem) {
946 var prevElem = keymap.KEY.LEFT;
947 var nextElem = keymap.KEY.RIGHT;
949 elem.bind('keydown', function (ev) {
952 ev.keyCode = ev.which;
955 switch (ev.keyCode) {
957 events.preventDefault(ev);
958 events.stopPropagation(ev);
960 if (elem[0].nextElementSibling && parseInt(angular.element(elem[0].nextElementSibling).attr('tabindex')) >= 0) {
961 angular.element(elem[0])[0].nextElementSibling.focus();
964 var el = angular.element(elem[0])[0];
966 if (el.nextSibling) {
971 } while (el && el.tagName !== 'LI');
973 if (el.tagName && el.tagName === 'LI' && parseInt(angular.element(el).attr('tabindex')) >= 0) {
980 events.preventDefault(ev);
981 events.stopPropagation(ev);
983 if (elem[0].previousElementSibling && parseInt(angular.element(elem[0].previousElementSibling).attr('tabindex')) >= 0) {
984 angular.element(elem[0])[0].previousElementSibling.focus();
987 var el1 = angular.element(elem[0])[0];
989 if (el1.previousSibling) {
990 el1 = el1.previousSibling;
994 } while (el1 && el1.tagName !== 'LI');
996 if (el1.tagName && el1.tagName === 'LI' && parseInt(angular.element(el1).attr('tabindex')) >= 0) {
1010 * @name Template.att:Bootstrap Grid Template
1013 * <file src="src/bootstrapGridTemplate/docs/readme.md" />
1016 * <section id="code">
1017 <example module="b2b.att">
1018 <file src="src/bootstrapGridTemplate/docs/demo.html" />
1019 <file src="src/bootstrapGridTemplate/docs/demo.js" />
1024 angular.module('b2b.att.bootstrapGridTemplate', [])
1028 * @name Navigation.att:breadcrumbs
1031 * <file src="src/breadcrumbs/docs/readme.md" />
1033 <ul class="breadcrumb">
1034 <li ng-repeat="link in breadCrumbsLink"><a tabindex="{{(idx==$index)?-1:0}}" href='javascript:void(0)' ng-click="clickActive($index)" ng-class="{'active':idx==$index, '': idx!=$index}">{{link.title}}</a></li>
1037 <example module="b2b.att">
1038 <file src="src/breadcrumbs/docs/demo.html" />
1039 <file src="src/breadcrumbs/docs/demo.js" />
1042 angular.module('b2b.att.breadcrumbs',[])
1045 * @name Buttons, links & UI controls.att:buttonGroups
1048 * <file src="src/buttonGroups/docs/readme.md" />
1051 <h2>Radio Aproach</h2>
1052 <div class="btn-group" b2b-key prev="37,38" next="39,40" circular-traversal role="radiogroup">
1053 <button type="button" class="btn btn-secondary" b2b-key-item ng-focus="radioModel='Button 1'" ng-model="radioModel" b2b-btn-radio="'Button 1'" tabindex="{{(!radioModel || 'Button 1'===radioModel)?0:-1}}">Button 1</button>
1054 <button type="button" class="btn btn-secondary" b2b-key-item ng-focus="radioModel='Button 2'" ng-model="radioModel" b2b-btn-radio="'Button 2'" tabindex="{{(!radioModel || 'Button 2'===radioModel)?0:-1}}">Button 2</button>
1055 <button type="button" class="btn btn-secondary" b2b-key-item ng-focus="radioModel='Button 3'" ng-model="radioModel" b2b-btn-radio="'Button 3'" tabindex="{{(!radioModel || 'Button 3'===radioModel)?0:-1}}">Button 3</button>
1058 <h2>Checkbox Aproach</h2>
1059 <span b2b-button-group class="btn-group btn-fullwidth" role="group" max-select="3" ng-model="checkModel1">
1060 <button type="button" class="btn btn-secondary" ng-model="checkModel1.Button1" b2b-btn-checkbox>Button1</button>
1061 <button type="button" class="btn btn-secondary" ng-model="checkModel1.Button2" b2b-btn-checkbox>Button2</button>
1062 <button type="button" class="btn btn-secondary" ng-model="checkModel1.Button3" b2b-btn-checkbox>Button3</button>
1063 <button type="button" class="btn btn-secondary" ng-model="checkModel1.Button4" b2b-btn-checkbox>Button4</button>
1064 <button type="button" class="btn btn-secondary" ng-model="checkModel1.Button5" b2b-btn-checkbox>Button5</button>
1068 * <section id="code">
1069 <example module="b2b.att">
1070 <file src="src/buttonGroups/docs/demo.html" />
1071 <file src="src/buttonGroups/docs/demo.js" />
1076 angular.module('b2b.att.buttonGroups', ['b2b.att.utilities'])
1077 .constant('buttonConfig', {
1078 activeClass: 'active',
1079 toggleEvent: 'click'
1081 .directive('b2bBtnRadio', ['buttonConfig', function (buttonConfig) {
1082 var activeClass = buttonConfig.activeClass || 'active';
1083 var toggleEvent = buttonConfig.toggleEvent || 'click';
1087 link: function (scope, element, attrs, ngModelCtrl) {
1088 var notMobile = !/Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
1091 element.bind('focus', function () {
1092 scope.$apply(function () {
1093 ngModelCtrl.$setViewValue(scope.$eval(attrs.b2bBtnRadio));
1094 ngModelCtrl.$render();
1099 element.attr('role', 'radio');
1102 ngModelCtrl.$render = function () {
1103 element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.b2bBtnRadio)));
1104 if (angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.b2bBtnRadio))) {
1105 element.attr("aria-checked", true);
1107 element.attr("aria-checked", false);
1112 element.bind(toggleEvent, function () {
1113 if (!element.hasClass(activeClass)) {
1114 scope.$apply(function () {
1115 ngModelCtrl.$setViewValue(scope.$eval(attrs.b2bBtnRadio));
1116 ngModelCtrl.$render();
1123 .directive('b2bBtnCheckbox', ['buttonConfig', function (buttonConfig) {
1124 var activeClass = buttonConfig.activeClass || 'active';
1125 var toggleEvent = buttonConfig.toggleEvent || 'click';
1128 require: ['ngModel', '^^b2bButtonGroup'],
1129 link: function (scope, element, attrs, ctrls) {
1131 var ngModelCtrl = ctrls[0];
1132 var parentCtrl = ctrls[1];
1134 element.attr('role', 'checkbox');
1135 element.attr('aria-describedby', parentCtrl.getStateDescriptionElemId());
1137 function getTrueValue() {
1138 var trueValue = scope.$eval(attrs.b2bBtnCheckboxTrue);
1139 return angular.isDefined(trueValue) ? trueValue : true;
1142 function getFalseValue() {
1143 var falseValue = scope.$eval(attrs.b2bBtnCheckboxFalse);
1144 return angular.isDefined(falseValue) ? falseValue : false;
1148 ngModelCtrl.$render = function () {
1149 element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
1150 if ((angular.equals(ngModelCtrl.$modelValue, getTrueValue()))) {
1151 element.attr("aria-checked", true);
1153 element.attr("aria-checked", false);
1158 element.bind(toggleEvent, function () {
1159 scope.$apply(function () {
1160 ngModelCtrl.$setViewValue(element.hasClass(activeClass) ? getFalseValue() : getTrueValue());
1161 ngModelCtrl.$render();
1167 .directive('b2bButtonGroup', ['$timeout', '$compile', function ($timeout, $compile) {
1172 ngModelButtonState: '=ngModel'
1174 controller: ['$scope', '$element', function ($scope, $element) {
1177 var stateDescriptionElem = angular.element('<span id="b2b_button_group_' + $scope.$id + '" class="hide" aria-hidden="true">{{nSel}} of {{maxSelect}} options selected.</span>');
1178 $compile(stateDescriptionElem)($scope);
1179 $element.after(stateDescriptionElem);
1181 this.getStateDescriptionElemId = function () {
1182 return stateDescriptionElem.attr('id');
1185 link: function (scope, element) {
1188 var executeFxn = function () {
1190 angular.forEach(scope.ngModelButtonState, function (value, key) {
1191 if (value === true) {
1196 if (scope.nSel >= scope.maxSelect) {
1197 angular.forEach(element.children(), function (chd) {
1198 if (chd.className.indexOf('active') < 0) {
1199 chd.disabled = true;
1200 chd.setAttribute('aria-disabled', true);
1204 angular.forEach(element.children(), function (chd) {
1205 chd.disabled = false;
1206 chd.setAttribute('aria-disabled', false);
1212 $timeout(function () {
1215 element.bind('click', executeFxn);
1221 * @name Buttons, links & UI controls.att:buttons
1226 * <file src="src/buttons/docs/readme.md" />
1230 <button class="btn" type="button">Button</button> button.btn (button shape only)
1231 <button aria-label="Custom aria label" class="btn" type="button">Button</button> button.btn (button shape only) with custom aria label
1232 <button aria-label="Click on button/Press enter" class="btn" type="button" onclick="javascript:alert('It works!');">Click on button/Press enter</button> button.btn with click functionality
1233 <a b2b-keyup-click="32" href="javascript:void(0)" class="btn" role="button">Button</a> a.btn (button shape only)
1234 <button class="btn btn-primary">Button</button> .btn-primary
1235 <a b2b-keyup-click="32" href="javascript:void(0)" class="btn btn-primary" role="button">Button</a> a.btn-primary
1238 <button class="btn btn-secondary">Button</button> .btn-secondary
1239 <a b2b-keyup-click="32" href="javascript:void(0)" class="btn btn-secondary" role="button">Button</a> a.btn-secondary
1240 <button class="btn btn-alt">Button</button> .btn-alt
1241 <a b2b-keyup-click="32" href="javascript:void(0)" class="btn btn-alt" role="button">Button</a> a.btn-alt
1242 <button class="btn btn-specialty">Button</button> .btn-specialty
1243 <a b2b-keyup-click="32" href="javascript:void(0)" class="btn btn-specialty" role="button">Button</a> a.btn-specialty
1244 <button class="btn btn-specialty" disabled="">Button</button> disabled="disabled"
1245 <a b2b-keyup-click="32" aria-disabled="true" href="javascript:void(0)" class="btn btn-primary disabled" role="button">Button</a> a.disabled
1248 <button class="btn btn-secondary">Button</button> .btn is default and 46px height
1249 <button class="btn btn-secondary btn-medium">Button</button> .btn-medium is 42px
1250 <button class="btn btn-secondary btn-small">Button</button> .btn-small is 36px
1252 .row-nowrap 2 up buttons
1253 <div class="row-nowrap">
1254 <button class="btn btn-secondary btn-fullwidth" type="button">Cancel</button>
1255 <button class="btn btn-primary btn-fullwidth" type="button">Continue</button>
1258 .row 2 up buttons (desktop) stacked (mobile) (different order)
1259 <div class="row cta-button-group">
1260 <button class="span btn btn-secondary btn-fullwidth hidden-phone" type="button">Cancel</button>
1261 <button class="span btn btn-primary btn-fullwidth" type="button">Continue</button>
1262 <button class="span btn btn-secondary btn-fullwidth visible-phone" type="button">Cancel</button>
1266 * <section id="code">
1267 <b>HTML + AngularJS</b>
1268 * <example module="b2b.att">
1269 * <file src="src/buttons/docs/demo.html" />
1270 <file src="src/buttons/docs/demo.js" />
1275 angular.module('b2b.att.buttons', ['b2b.att.utilities']);
1278 * @name Forms.att:calendar
1281 * <file src="src/calendar/docs/readme.md" />
1283 * <input type="text" ng-model="dt" b2b-datepicker>
1287 <b>HTML + AngularJS</b>
1288 <example module="b2b.att">
1289 <file src="src/calendar/docs/demo.html" />
1290 <file src="src/calendar/docs/demo.js" />
1294 angular.module('b2b.att.calendar', ['b2b.att.position', 'b2b.att.utilities'])
1296 .constant('b2bDatepickerConfig', {
1297 dateFormat: 'MM/dd/yyyy',
1299 monthFormat: 'MMMM',
1301 dayHeaderFormat: 'EEEE',
1302 dayTitleFormat: 'MMMM yyyy',
1303 disableWeekend: false,
1304 disableSunday: false,
1306 onSelectClose: null,
1313 legendMessage: null,
1314 calendarDisabled: false,
1316 orientation: 'right',
1318 helperText: 'The date you selected is $date. In case of mobile double tap to open calendar. Select a date to close the calendar.',
1319 datepickerEvalAttributes: ['dateFormat', 'dayFormat', 'monthFormat', 'yearFormat', 'dayHeaderFormat', 'dayTitleFormat', 'disableWeekend', 'disableSunday', 'startingDay', 'collapseWait', 'orientation'],
1320 datepickerWatchAttributes: ['min', 'max', 'due', 'from', 'legendIcon', 'legendMessage', 'ngDisabled'],
1321 datepickerFunctionAttributes: ['disableDates', 'onSelectClose']
1324 .factory('b2bDatepickerService', ['b2bDatepickerConfig', 'dateFilter', function (b2bDatepickerConfig, dateFilter) {
1325 var setAttributes = function (attr, elem) {
1326 if (angular.isDefined(attr) && attr !== null && angular.isDefined(elem) && elem !== null) {
1327 var attributes = b2bDatepickerConfig.datepickerEvalAttributes.concat(b2bDatepickerConfig.datepickerWatchAttributes, b2bDatepickerConfig.datepickerFunctionAttributes);
1328 for (var key in attr) {
1329 var val = attr[key];
1330 if (attributes.indexOf(key) !== -1 && angular.isDefined(val)) {
1331 elem.attr(key.toSnakeCase(), key);
1337 var bindScope = function (attr, scope) {
1338 if (angular.isDefined(attr) && attr !== null && angular.isDefined(scope) && scope !== null) {
1339 var evalFunction = function (key, val) {
1340 scope[key] = scope.$parent.$eval(val);
1343 var watchFunction = function (key, val) {
1344 scope.$parent.$watch(val, function (value) {
1347 scope.$watch(key, function (value) {
1348 scope.$parent[val] = value;
1352 var evalAttributes = b2bDatepickerConfig.datepickerEvalAttributes;
1353 var watchAttributes = b2bDatepickerConfig.datepickerWatchAttributes;
1354 for (var key in attr) {
1355 var val = attr[key];
1356 if (evalAttributes.indexOf(key) !== -1 && angular.isDefined(val)) {
1357 evalFunction(key, val);
1358 } else if (watchAttributes.indexOf(key) !== -1 && angular.isDefined(val)) {
1359 watchFunction(key, val);
1366 setAttributes: setAttributes,
1367 bindScope: bindScope
1371 .controller('b2bDatepickerController', ['$scope', '$attrs', 'dateFilter', '$element', '$position', 'b2bDatepickerConfig', function ($scope, $attrs, dateFilter, $element, $position, dtConfig) {
1373 date: getValue($attrs.dateFormat, dtConfig.dateFormat),
1374 day: getValue($attrs.dayFormat, dtConfig.dayFormat),
1375 month: getValue($attrs.monthFormat, dtConfig.monthFormat),
1376 year: getValue($attrs.yearFormat, dtConfig.yearFormat),
1377 dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat),
1378 dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat),
1379 disableWeekend: getValue($attrs.disableWeekend, dtConfig.disableWeekend),
1380 disableSunday: getValue($attrs.disableSunday, dtConfig.disableSunday)
1382 startingDay = getValue($attrs.startingDay, dtConfig.startingDay);
1384 if($attrs.disableDates !== undefined) {
1385 format.disableDates = $attrs.disableDates;
1387 format.disableDates = dtConfig.disableDates;
1389 $scope.minDate = dtConfig.minDate ? $scope.resetTime(dtConfig.minDate) : null;
1390 $scope.maxDate = dtConfig.maxDate ? $scope.resetTime(dtConfig.maxDate) : null;
1391 $scope.dueDate = dtConfig.dueDate ? $scope.resetTime(dtConfig.dueDate) : null;
1392 $scope.fromDate = dtConfig.fromDate ? $scope.resetTime(dtConfig.fromDate) : null;
1393 $scope.legendIcon = dtConfig.legendIcon ? dtConfig.legendIcon : null;
1394 $scope.legendMessage = dtConfig.legendMessage ? dtConfig.legendMessage : null;
1395 $scope.ngDisabled = dtConfig.calendarDisabled ? dtConfig.calendarDisabled : null;
1396 $scope.collapseWait = getValue($attrs.collapseWait, dtConfig.collapseWait);
1397 $scope.orientation = getValue($attrs.orientation, dtConfig.orientation);
1398 $scope.onSelectClose = getValue($attrs.onSelectClose, dtConfig.onSelectClose);
1400 $scope.inline = $attrs.inline === 'true' ? true : dtConfig.inline;
1402 function getValue(value, defaultValue) {
1403 return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue;
1406 function getDaysInMonth(year, month) {
1407 return new Date(year, month, 0).getDate();
1410 function getDates(startDate, n) {
1411 var dates = new Array(n);
1412 var current = startDate,
1415 dates[i++] = new Date(current);
1416 current.setDate(current.getDate() + 1);
1421 this.updatePosition = function (b2bDatepickerPopupTemplate) {
1422 $scope.position = $position.offset($element);
1423 $scope.position.top = $scope.position.top + $element.prop('offsetHeight');
1424 $scope.position.left = $scope.position.left - (((b2bDatepickerPopupTemplate && b2bDatepickerPopupTemplate.prop('offsetWidth')) || 290) - $element.prop('offsetWidth'));
1427 this.isDateInRange = function(date) {
1428 if ((compare(date, $scope.minDate) >= 0) && (compare(date, $scope.maxDate) <= 0)) {
1436 this.isDisbaledDate = function(date) {
1437 if ($attrs.from && !angular.isDate($scope.fromDate)) {
1440 if (format.disableWeekend === true && (dateFilter(date, format.dayHeader) === "Saturday" || dateFilter(date, format.dayHeader) === "Sunday")) {
1443 if (format.disableSunday === true && (dateFilter(date, format.dayHeader) === "Sunday")) {
1447 return (($scope.minDate && compare(date, $scope.minDate) < 0) || ($scope.maxDate && compare(date, $scope.maxDate) > 0) || ($scope.datesCallBack({
1452 function isSelected(dt) {
1453 if (dt && angular.isDate($scope.currentDate) && compare(dt, $scope.currentDate) === 0) {
1459 function isFromDate(dt) {
1460 if (dt && angular.isDate($scope.fromDate) && compare(dt, $scope.fromDate) === 0) {
1466 function isDateRange(dt) {
1467 if (dt && $scope.fromDate && angular.isDate($scope.currentDate) && (compare(dt, $scope.fromDate) >= 0) && (compare(dt, $scope.currentDate) <= 0)) {
1469 } else if (dt && $scope.fromDate && compare(dt, $scope.fromDate) === 0) {
1475 function isOld(date, currentMonthDate) {
1476 if (date && currentMonthDate && (new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0).getTime() < new Date(currentMonthDate.getFullYear(), currentMonthDate.getMonth(), 1, 0, 0, 0).getTime())) {
1483 function isNew(date, currentMonthDate) {
1484 if (date && currentMonthDate && (new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0).getTime() > new Date(currentMonthDate.getFullYear(), currentMonthDate.getMonth(), 1, 0, 0, 0).getTime())) {
1491 function isPastDue(dt) {
1492 if ($scope.dueDate) {
1493 return (dt > $scope.dueDate);
1498 function isDueDate(dt) {
1499 if ($scope.dueDate) {
1500 return (dt.getTime() === $scope.dueDate.getTime());
1505 var isDisabled = function (date, currentMonthDate) {
1506 if ($attrs.from && !angular.isDate($scope.fromDate)) {
1509 if (format.disableWeekend === true && (dateFilter(date, format.dayHeader) === "Saturday" || dateFilter(date, format.dayHeader) === "Sunday")) {
1512 if (format.disableSunday === true && (dateFilter(date, format.dayHeader) === "Sunday")) {
1515 if (isOld(date, currentMonthDate) || isNew(date, currentMonthDate)) {
1518 return (($scope.minDate && compare(date, $scope.minDate) < 0) || ($scope.maxDate && compare(date, $scope.maxDate) > 0) || ($scope.datesCallBack({
1523 var compare = function (date1, date2) {
1524 return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()));
1527 function isMinDateAvailable(startDate, endDate) {
1528 if (($scope.minDate && $scope.minDate.getTime() >= startDate.getTime()) && ($scope.minDate.getTime() <= endDate.getTime())) {
1529 $scope.disablePrev = true;
1530 $scope.visibilityPrev = "hidden";
1532 $scope.disablePrev = false;
1533 $scope.visibilityPrev = "visible";
1537 function isMaxDateAvailable(startDate, endDate) {
1538 if (($scope.maxDate && $scope.maxDate.getTime() >= startDate.getTime()) && ($scope.maxDate.getTime() <= endDate.getTime())) {
1539 $scope.disableNext = true;
1540 $scope.visibilityNext = "hidden";
1542 $scope.disableNext = false;
1543 $scope.visibilityNext = "visible";
1547 function getLabel(label) {
1550 pre: label.substr(0, 1).toUpperCase(),
1558 function makeDate(date, dayFormat, dayHeaderFormat, isSelected, isFromDate, isDateRange, isOld, isNew, isDisabled, dueDate, pastDue) {
1561 label: dateFilter(date, dayFormat),
1562 header: dateFilter(date, dayHeaderFormat),
1563 selected: !!isSelected,
1564 fromDate: !!isFromDate,
1565 dateRange: !!isDateRange,
1568 disabled: !!isDisabled,
1571 focusable: !((isDisabled && !(isSelected || isDateRange)) || (isOld || isNew))
1578 getVisibleDates: function (date) {
1579 var year = date.getFullYear(),
1580 month = date.getMonth(),
1581 firstDayOfMonth = new Date(year, month, 1),
1582 lastDayOfMonth = new Date(year, month + 1, 0);
1583 var difference = startingDay - firstDayOfMonth.getDay(),
1584 numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : -difference,
1585 firstDate = new Date(firstDayOfMonth),
1588 if (numDisplayedFromPreviousMonth > 0) {
1589 firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
1590 numDates += numDisplayedFromPreviousMonth; // Previous
1592 numDates += getDaysInMonth(year, month + 1); // Current
1593 numDates += (7 - numDates % 7) % 7; // Next
1595 var days = getDates(firstDate, numDates),
1596 labels = new Array(7);
1597 for (var i = 0; i < numDates; i++) {
1598 var dt = new Date(days[i]);
1599 days[i] = makeDate(dt,
1607 isDisabled(dt, date),
1611 for (var j = 0; j < 7; j++) {
1612 labels[j] = getLabel(dateFilter(days[j].date, format.dayHeader));
1614 isMinDateAvailable(firstDayOfMonth, lastDayOfMonth);
1615 isMaxDateAvailable(firstDayOfMonth, lastDayOfMonth);
1618 title: dateFilter(date, format.dayTitle),
1630 .directive('b2bDatepicker', ['$parse', '$log', '$timeout', '$document', '$documentBind', '$isElement', '$templateCache', '$compile', 'trapFocusInElement', '$position', '$window', '$filter', 'b2bDatepickerConfig', function ($parse, $log, $timeout, $document, $documentBind, $isElement, $templateCache, $compile, trapFocusInElement, $position, $window, $filter, b2bDatepickerConfig) {
1635 datesCallBack: '&disableDates',
1637 disabledInput: '=?ngDisabled'
1639 require: ['b2bDatepicker', 'ngModel', '?^b2bDatepickerGroup'],
1640 controller: 'b2bDatepickerController',
1641 link: function (scope, element, attrs, ctrls) {
1642 var datepickerCtrl = ctrls[0],
1644 b2bDatepickerGroupCtrl = ctrls[2];
1645 var b2bDatepickerPopupTemplate;
1646 var isCalendarOpened = false;
1647 if(scope.disabledInput === undefined || scope.disabledInput === '') {
1648 scope.disabledInput = false;
1650 if(attrs.inline == 'true'){
1651 element.after($compile($templateCache.get('b2bTemplate/calendar/datepicker-popup.html'))(scope));
1652 var temp = element.after();
1656 var buttonTabIndex = scope.disabledInput===true ? -1 : 0;
1658 element.after($compile('<button class="btn-calendar-icon" ng-disabled='+scope.disabledInput+' ><i class="icon-primary-calendar b2b-calendar-icon" aria-haspopup="true" aria-expanded="false" ng-class=\"{\'disabled\': '+scope.disabledInput+'}\" ></i></button>')(scope));
1659 element.attr('placeholder', 'MM/dd/yyyy');
1660 element.attr('b2b-format-date', b2bDatepickerConfig.dateFormat);
1662 scope.$watch('model', function(val) {
1664 if(val !== undefined && val !== '') {
1665 var date_regex = /^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$/ ;
1667 if(!date_regex.test(element[0].value)) {
1668 ngModel.$setValidity('datePattern', false);
1670 ngModel.$setValidity('datePattern', true);
1674 ngModel.$setValidity('datePattern', true);
1680 $log.error("ng-model is required.");
1681 return; // do nothing if no ng-model
1684 if(scope.model !== undefined && scope.model !== '') {
1685 element[0].value = $filter('date')(scope.model, "MM/dd/yyyy");
1688 // Configuration parameters
1691 scope.isOpen = false;
1692 var isValidDate = false;
1697 if (b2bDatepickerGroupCtrl) {
1698 b2bDatepickerGroupCtrl.registerDatepickerScope(scope);
1701 var calendarButton = angular.element(element[0].nextElementSibling);
1703 calendarButton.bind('click',function(){
1704 openCalendarPopup = false;
1705 if (!scope.ngDisabled) {
1706 scope.isOpen = !scope.isOpen;
1707 toggleCalendar(scope.isOpen);
1709 datepickerCtrl.updatePosition(b2bDatepickerPopupTemplate);
1710 $timeout(function () {
1711 // angular.element(element[0].querySelector('.datepicker-input')).scrollTop=0;
1715 var openCalendarPopup = false;
1717 element.bind('blur', function() {
1718 if(scope.model !== undefined && scope.model !== '') {
1719 var dateEntered = scope.model;
1721 var date_regex = /^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$/ ;
1723 if(date_regex.test(dateEntered)) {
1724 var parts = dateEntered.split('/');
1725 var enteredDate = new Date(parts[2],parts[0]-1,parts[1]);
1727 if(datepickerCtrl.isDateInRange(enteredDate)) {
1729 ngModel.$setValidity('outOfRange', true);
1730 $timeout(function(){
1731 ngModel.$setValidity('outOfRange', true);
1734 if(!datepickerCtrl.isDisbaledDate(enteredDate)) {
1735 $timeout(function(){
1736 ngModel.$setValidity('disabledDate', true);
1738 scope.select(enteredDate);
1739 openCalendarPopup = true;
1741 $timeout(function(){
1742 ngModel.$setValidity('disabledDate', false);
1744 isValidDate = false;
1745 openCalendarPopup = false;
1750 $timeout(function(){
1751 ngModel.$setValidity('outOfRange', false);
1753 isValidDate = false;
1754 openCalendarPopup = false;
1761 var toggleCalendar = function (flag) {
1762 if (!scope.inline) {
1764 b2bDatepickerPopupTemplate = angular.element($templateCache.get('b2bTemplate/calendar/datepicker-popup.html'));
1765 b2bDatepickerPopupTemplate = $compile(b2bDatepickerPopupTemplate)(scope);
1766 $document.find('body').append(b2bDatepickerPopupTemplate);
1767 b2bDatepickerPopupTemplate.bind('keydown', keyPress);
1768 $timeout(function () {
1769 scope.getFocus = true;
1770 trapFocusInElement(flag, b2bDatepickerPopupTemplate);
1772 $timeout(function () {
1773 scope.getFocus = false;
1778 angular.element(document.querySelector('.b2b-calendar-icon')).attr('aria-expanded','true');
1780 if(!openCalendarPopup) {
1781 b2bDatepickerPopupTemplate.unbind('keydown', keyPress);
1782 b2bDatepickerPopupTemplate.remove();
1785 scope.getFocus = false;
1786 angular.element(document.querySelector('.b2b-calendar-icon')).attr('aria-expanded','false');
1787 trapFocusInElement(flag, b2bDatepickerPopupTemplate);
1792 var handleTabEvent = function(){
1793 b2bDatepickerPopupTemplate.find('td').on('keydown', function (e) {
1794 if (e.keyCode == '9') {
1796 if(b2bDatepickerPopupTemplate.find('tr')[0].querySelector('th.next')){
1797 b2bDatepickerPopupTemplate.find('tr')[0].querySelector('th.next').focus();
1799 b2bDatepickerPopupTemplate.find('tr')[0].querySelector('th.datepicker-switch').focus()
1802 if(b2bDatepickerPopupTemplate.find('tr')[0].querySelector('th.prev')){
1803 b2bDatepickerPopupTemplate.find('tr')[0].querySelector('th.prev').focus();
1805 b2bDatepickerPopupTemplate.find('tr')[0].querySelector('th.datepicker-switch').focus()
1810 e.stopPropagation();
1815 var outsideClick = function (e) {
1816 var isElement = $isElement(angular.element(e.target), element, $document);
1817 var isb2bDatepickerPopupTemplate = $isElement(angular.element(e.target), b2bDatepickerPopupTemplate, $document);
1818 if (!(isElement || isb2bDatepickerPopupTemplate)) {
1819 scope.isOpen = false;
1820 toggleCalendar(scope.isOpen);
1825 var keyPress = function (ev) {
1828 ev.keyCode = ev.which;
1829 } else if (ev.charCode) {
1830 ev.keyCode = ev.charCode;
1834 if (ev.keyCode === 27) {
1835 scope.isOpen = false;
1836 toggleCalendar(scope.isOpen);
1837 ev.preventDefault();
1838 ev.stopPropagation();
1839 } else if (ev.keyCode === 33) {
1840 !scope.disablePrev && scope.move(-1);
1841 $timeout(function () {
1842 scope.getFocus = true;
1844 $timeout(function () {
1845 scope.getFocus = false;
1849 ev.preventDefault();
1850 ev.stopPropagation();
1851 } else if (ev.keyCode === 34) {
1852 !scope.disableNext && scope.move(1);
1853 $timeout(function () {
1854 scope.getFocus = true;
1856 $timeout(function () {
1857 scope.getFocus = false;
1861 ev.preventDefault();
1862 ev.stopPropagation();
1868 $documentBind.click('isOpen', outsideClick, scope);
1870 var modalContainer = angular.element(document.querySelector('.modalwrapper'));
1871 var modalBodyContainer = angular.element(document.querySelector('.modal-body'));
1872 if (modalContainer) {
1873 modalContainer.bind('scroll', function () {
1874 if (b2bDatepickerPopupTemplate) {
1875 datepickerCtrl.updatePosition(b2bDatepickerPopupTemplate);
1880 if (modalBodyContainer) {
1881 modalBodyContainer.bind('scroll', function () {
1882 if (b2bDatepickerPopupTemplate) {
1883 datepickerCtrl.updatePosition(b2bDatepickerPopupTemplate);
1884 var datepickerTextfield = $position.offset(element.find('input'));
1885 var modalBodyPosition = $position.offset(modalBodyContainer);
1887 if (((datepickerTextfield.top + datepickerTextfield.height) < modalBodyPosition.top || datepickerTextfield.top > (modalBodyPosition.top + modalBodyPosition.height)) && scope.isOpen) {
1888 scope.isOpen = false;
1889 toggleCalendar(scope.isOpen);
1895 var window = angular.element($window);
1896 window.bind('resize', function () {
1897 if (b2bDatepickerPopupTemplate) {
1898 datepickerCtrl.updatePosition(b2bDatepickerPopupTemplate);
1903 scope.$on('$destroy', function () {
1905 scope.isOpen = false;
1906 toggleCalendar(scope.isOpen);
1910 scope.resetTime = function (date) {
1911 if (typeof date === 'string') {
1912 date = date + 'T12:00:00';
1915 if (!isNaN(new Date(date))) {
1916 dt = new Date(date);
1920 return new Date(dt.getFullYear(), dt.getMonth(), dt.getDate());
1924 scope.$parent.$watch($parse(attrs.min), function (value) {
1925 scope.minDate = value ? scope.resetTime(value) : null;
1930 scope.$parent.$watch($parse(attrs.max), function (value) {
1931 scope.maxDate = value ? scope.resetTime(value) : null;
1936 scope.$parent.$watch($parse(attrs.due), function (value) {
1937 scope.dueDate = value ? scope.resetTime(value) : null;
1942 scope.$parent.$watch($parse(attrs.from), function (value) {
1943 scope.fromDate = value ? scope.resetTime(value) : null;
1948 if (attrs.legendIcon) {
1949 scope.$parent.$watch(attrs.legendIcon, function (value) {
1950 scope.legendIcon = value ? value : null;
1954 if (attrs.legendMessage) {
1955 scope.$parent.$watch(attrs.legendMessage, function (value) {
1956 scope.legendMessage = value ? value : null;
1960 if (attrs.ngDisabled) {
1961 scope.$parent.$watch(attrs.ngDisabled, function (value) {
1962 scope.ngDisabled = value ? value : null;
1966 // Split array into smaller arrays
1967 function split(arr, size) {
1969 while (arr.length > 0) {
1970 arrays.push(arr.splice(0, size));
1975 function refill(date) {
1976 if (angular.isDate(date) && !isNaN(date)) {
1977 selected = new Date(date);
1980 selected = new Date();
1985 var currentMode = datepickerCtrl.modes[mode],
1986 data = currentMode.getVisibleDates(selected);
1987 scope.rows = split(data.objects, currentMode.split);
1989 var startFlag = false;
1990 var firstSelected = false;
1991 for (var i = 0; i < scope.rows.length; i++) {
1992 for (var j = 0; j < scope.rows[i].length; j++) {
1994 if (scope.rows[i][j].label === "1" && !firstSelected) {
1995 firstSelected = true;
1996 var firstDay = scope.rows[i][j];
1999 if (scope.rows[i][j].selected === true) {
2009 firstDay.firstFocus = true;
2012 scope.labels = data.labels || [];
2013 scope.title = data.title;
2015 datepickerCtrl.updatePosition(b2bDatepickerPopupTemplate);
2019 scope.select = function (date) {
2020 var dt = new Date(date.getFullYear(), date.getMonth(), date.getDate());
2021 if (!scope.onSelectClose || (scope.onSelectClose && scope.onSelectClose({
2024 scope.currentDate = dt;
2025 element[0].value = $filter('date')(dt, "MM/dd/yyyy");
2026 ngModel.$setValidity('outOfRange', true);
2027 if (angular.isNumber(scope.collapseWait)) {
2028 $timeout(function () {
2029 scope.isOpen = false;
2030 toggleCalendar(scope.isOpen);
2031 }, scope.collapseWait);
2033 scope.isOpen = false;
2034 toggleCalendar(scope.isOpen);
2039 scope.move = function (direction,$event) {
2040 var step = datepickerCtrl.modes[mode].step;
2041 selected.setDate(1);
2042 selected.setMonth(selected.getMonth() + direction * (step.months || 0));
2043 selected.setFullYear(selected.getFullYear() + direction * (step.years || 0));
2046 $timeout(function () {
2047 trapFocusInElement();
2051 $event.preventDefault();
2052 $event.stopPropagation();
2055 scope.trapFocus = function () {
2056 $timeout(function () {
2057 trapFocusInElement();
2061 scope.$watch('currentDate', function (value) {
2062 if (angular.isDefined(value) && value !== null) {
2067 ngModel.$setViewValue(value);
2070 ngModel.$render = function () {
2071 scope.currentDate = ngModel.$viewValue;
2074 var stringToDate = function (value) {
2075 if (!isNaN(new Date(value))) {
2076 value = new Date(value);
2080 ngModel.$formatters.unshift(stringToDate);
2086 .directive('b2bDatepickerGroup', [function () {
2089 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
2090 this.$$headers = [];
2091 this.$$footers = [];
2092 this.registerDatepickerScope = function (datepickerScope) {
2093 datepickerScope.headers = this.$$headers;
2094 datepickerScope.footers = this.$$footers;
2097 link: function (scope, elem, attr, ctrl) {}
2101 .directive('b2bFormatDate', ['dateFilter', function (dateFilter) {
2105 link: function (scope, elem, attr, ctrl) {
2106 var b2bFormatDate = "";
2107 attr.$observe('b2bFormatDate', function (value) {
2108 b2bFormatDate = value;
2110 var dateToString = function (value) {
2111 if (!isNaN(new Date(value))) {
2112 return dateFilter(new Date(value), b2bFormatDate);
2116 ctrl.$formatters.unshift(dateToString);
2121 .directive('b2bDatepickerHeader', [function () {
2124 require: '^b2bDatepickerGroup',
2128 compile: function (elem, attr, transclude) {
2129 return function link(scope, elem, attr, ctrl) {
2131 ctrl.$$headers.push(transclude(scope, function () {}));
2139 .directive('b2bDatepickerFooter', [function () {
2142 require: '^b2bDatepickerGroup',
2146 compile: function (elem, attr, transclude) {
2147 return function link(scope, elem, attr, ctrl) {
2149 ctrl.$$footers.push(transclude(scope, function () {}));
2158 * @name Forms.att:checkboxes
2161 * <file src="src/checkboxes/docs/readme.md" />
2165 <example module="b2b.att">
2166 <file src="src/checkboxes/docs/demo.html" />
2167 <file src="src/checkboxes/docs/demo.js" />
2170 angular.module('b2b.att.checkboxes', ['b2b.att.utilities'])
2171 .directive('b2bSelectGroup', [function (){
2178 link: function (scope, elem, attr, ctrl) {
2179 elem.bind('change', function () {
2180 var isChecked = elem.prop('checked');
2181 angular.forEach(scope.checkboxes, function (item) {
2182 item.isSelected = isChecked;
2186 scope.$watch('checkboxes', function () {
2188 if(scope.checkboxes === undefined) {
2191 angular.forEach(scope.checkboxes, function (item) {
2192 if (item.isSelected) {
2196 elem.prop('indeterminate', false);
2197 if ( scope.checkboxes !==undefined && setBoxes === scope.checkboxes.length && scope.checkboxes.length > 0) {
2198 ctrl.$setViewValue(true);
2199 elem.removeClass('indeterminate');
2200 } else if (setBoxes === 0) {
2201 ctrl.$setViewValue(false);
2202 elem.removeClass('indeterminate');
2204 ctrl.$setViewValue(false);
2205 elem.addClass('indeterminate');
2206 elem.prop('indeterminate', true);
2215 * @name Misc.att:coachmark
2218 * <file src="src/coachmark/docs/readme.md" />
2222 <button b2b-coachmark start-coachmark-callback="startCoachmark()" end-coachmark-callback="endCoachmark()" action-coachmark-callback="actionCoachmark(action)" coachmark-index="coachmarkIndex" coachmarks="coachmarkElements" id="coachmark0" class="btn btn-alt">Initiate tour</button>
2226 <b>HTML + AngularJS</b>
2227 <example module="b2b.att">
2228 <file src="src/coachmark/docs/demo.html" />
2229 <file src="src/coachmark/docs/demo.js" />
2234 angular.module('b2b.att.coachmark', ['b2b.att.utilities','b2b.att.position'])
2236 .directive('b2bCoachmark', ['$document', '$compile', '$position', '$timeout', 'b2bViewport', 'keymap', function($document, $compile, $position, $timeout, b2bViewport, keymap) {
2241 coachmarkIndex: '=',
2242 startCoachmarkCallback: '&',
2243 endCoachmarkCallback: '&',
2244 actionCoachmarkCallback: '&'
2246 link: function (scope, element, attrs, ctrl) {
2247 var coachmarkItems = scope.coachmarks;
2248 var body = $document.find('body').eq(0);
2249 var coackmarkJqContainer;
2250 var coackmarkContainer;
2251 var coachMarkElement;
2252 var backdropjqLiteEl;
2253 var coachmarkHighlight;
2254 var initaitedCoachmark = false;
2255 scope.coackmarkElPos ={
2260 scope.currentCoachmark = {};
2263 var coachmarkBackdrop = function(){
2264 backdropjqLiteEl = angular.element('<div class="b2b-modal-backdrop fade in hidden-by-modal"></div>');
2265 body.append(backdropjqLiteEl);
2267 backdropjqLiteEl.bind('click', function() {
2268 scope.closeCoachmark();
2274 scope.closeButtonFocus = function(){
2275 if(document.getElementsByClassName('b2b-coachmark-header').length >0){
2276 document.getElementsByClassName('b2b-coachmark-header')[0].scrollLeft = 0;
2277 document.getElementsByClassName('b2b-coachmark-header')[0].scrollTop = 0;
2281 scope.actionCoachmark = function(action){
2282 scope.actionCoachmarkCallback({
2287 scope.closeCoachmark = function(){
2288 initaitedCoachmark = false;
2289 backdropjqLiteEl.remove();
2290 coackmarkContainer.remove();
2291 coachmarkHighlight.remove();
2292 if(coachMarkElement !== undefined && coachMarkElement !==""){
2293 coachMarkElement.removeClass('b2b-coachmark-label')
2295 if (angular.isFunction(scope.endCoachmarkCallback)){
2296 scope.endCoachmarkCallback();
2301 var realStyle = function(_elem, _style) {
2303 if ( typeof _elem.currentStyle != 'undefined' ) {
2304 computedStyle = _elem.currentStyle;
2306 computedStyle = document.defaultView.getComputedStyle(_elem, null);
2309 return _style ? computedStyle[_style] : computedStyle;
2312 var copyComputedStyle = function(src, dest) {
2313 var s = realStyle(src);
2314 for ( var i in s ) {
2315 // Do not use `hasOwnProperty`, nothing will get copied
2316 if ( typeof i == "string" && i != "cssText" && !/\d/.test(i) && i.indexOf('webkit') !== 0 ) {
2317 // The try is for setter only properties
2319 dest.style[i] = s[i];
2320 // `fontSize` comes before `font` If `font` is empty, `fontSize` gets
2321 // overwritten. So make sure to reset this property. (hackyhackhack)
2322 // Other properties may need similar treatment
2323 if ( i == "font" ) {
2324 dest.style.fontSize = s.fontSize;
2331 function showCoachmark(targetElement) {
2333 scope.currentCoachmark = targetElement;
2334 if(coachMarkElement !== undefined && coachMarkElement !==""){
2335 coachMarkElement.removeClass('b2b-coachmark-label')
2336 coackmarkContainer.remove();
2337 coachmarkHighlight.remove();
2339 coachMarkElement = angular.element(document.querySelector(targetElement.elementId));
2341 var elementPosition = $position.offset(coachMarkElement);
2343 coachmarkHighlight = angular.element('<div class="b2b-coachmark-highlight"></div><div class="b2b-coachmark-highlight b2b-coachmark-highlight-mask"></div>');
2344 coachmarkHighlight.css({
2345 'width': (elementPosition.width + 25) +'px',
2346 'top': (elementPosition.top -10) + 'px',
2347 'left': (elementPosition.left - 10) + 'px',
2348 'height': (elementPosition.height + 20) +'px'
2350 if(targetElement.cloneHtml){
2351 var copy = coachMarkElement[0].cloneNode(true);
2352 copy.id = "b2b-unique-"+targetElement.elementId.slice(1);
2353 copyComputedStyle(coachMarkElement[0],copy);
2354 var copychildNodes = copy.childNodes;
2355 var coachmarkChildNodes = coachMarkElement[0].childNodes;
2356 for(i=0;i<copychildNodes.length;i++){
2357 if(copychildNodes[i].nodeType === '3'){
2358 copyComputedStyle(coachmarkChildNodes[i],copychildNodes[i])
2361 coachmarkHighlight[0].appendChild(copy); // IE11 only supports appendChild, not append
2363 coachMarkElement.addClass('b2b-coachmark-label');
2366 body.append(coachmarkHighlight);
2368 scope.coackmarkElPos.top = (elementPosition.top + elementPosition.height + 32) + 'px';
2369 scope.coackmarkElPos.left = (elementPosition.left - 158 + elementPosition.width / 2 ) + 'px';
2370 coackmarkJqContainer = angular.element('<div b2b-coachmark-container b2b-trap-focus-inside-element="true"></div>');
2371 coackmarkContainer = $compile(coackmarkJqContainer)(scope);
2372 body.append(coackmarkContainer);
2375 $timeout(function () {
2376 var currentCoachmarkContainer = document.getElementsByClassName('b2b-coachmark-container')[0];
2377 currentCoachmarkContainer.focus();
2379 newElem = angular.element(currentCoachmarkContainer);
2380 newElem.bind('keydown', function (e) {
2381 if(e.keyCode == keymap.KEY.TAB){
2383 if(e.target.className === 'b2b-coachmark-container'){
2385 e.stopPropagation();
2390 var coachmarkHeight = window.getComputedStyle(currentCoachmarkContainer).height.split('px')[0];
2391 var newOffsetHeight = Math.round(elementPosition.top) - elementPosition.height;
2393 // We need a slight offset to show the lightboxed item
2394 if(!targetElement.cloneHtml){
2395 TweenLite.to(window, 2, {scrollTo:{x: (scope.coackmarkElPos.left.split('px')[0] - 100), y: newOffsetHeight-200}});
2400 element.bind('click', function (e) {
2401 initaitedCoachmark = true;
2403 scope.$watch('coachmarkIndex', function () {
2404 if(initaitedCoachmark === true){
2405 if(scope.coachmarkIndex === -1){
2406 scope.closeCoachmark();
2408 findAvailableCoachmark(scope.coachmarkIndex);
2409 showCoachmark(scope.coachmarks[scope.coachmarkIndex]);
2413 coachmarkBackdrop();
2414 var findAvailableCoachmark = function(index){
2416 scope.coachmarkIndex = 0;
2417 } else if(!angular.isDefined(scope.coachmarks[index]) || angular.element(document.querySelector(scope.coachmarks[index].elementId)).length === 0){
2418 findAvailableCoachmark(index-1);
2420 scope.coachmarkIndex = index;
2423 if (angular.isFunction(scope.startCoachmarkCallback)){
2424 scope.startCoachmarkCallback();
2426 findAvailableCoachmark(scope.coachmarkIndex);
2427 showCoachmark(scope.coachmarks[scope.coachmarkIndex]);
2429 $document.bind('keydown', function (evt) {
2430 if (evt.which === 27 && initaitedCoachmark) {
2431 scope.closeCoachmark();
2436 //performance technique to ensure scroll event doesn't cause lag
2437 var throttle = function(type, name, obj) {
2438 obj = obj || window;
2439 var running = false;
2440 var func = function() {
2441 if (running) { return; }
2443 requestAnimationFrame(function() {
2444 obj.dispatchEvent(new CustomEvent(name));
2448 obj.addEventListener(type, func);
2451 scope.viewportWidth = b2bViewport.viewportWidth();
2452 /* init - you can init any event */
2453 throttle("resize", "optimizedResize");
2454 window.addEventListener("optimizedResize", function() {
2455 if(initaitedCoachmark){
2456 showCoachmark(scope.coachmarks[scope.coachmarkIndex]);
2457 scope.viewportWidth = b2bViewport.viewportWidth();
2464 .directive('b2bCoachmarkContainer', ['$document', '$position', function($document, $position) {
2469 templateUrl: 'b2bTemplate/coachmark/coachmark.html',
2470 link: function (scope, element, attrs, ctrl) {
2479 * @name Template.att:Configuration Section
2482 * <file src="src/configurationSection/docs/readme.md" />
2485 * <section id="code">
2486 <b>HTML + AngularJS</b>
2487 <example module="b2b.att">
2488 <file src="src/configurationSection/docs/demo.html" />
2489 <file src="src/configurationSection/docs/demo.js" />
2494 angular.module('b2b.att.configurationSection', [])
2498 * @name Template.att:Directory Listing
2501 * <file src="src/directoryListingTemplate/docs/readme.md" />
2504 * <section id="code">
2505 <b>HTML + AngularJS</b>
2506 <example module="b2b.att">
2507 <file src="src/directoryListingTemplate/docs/demo.html" />
2508 <file src="src/directoryListingTemplate/docs/demo.js" />
2513 angular.module('b2b.att.directoryListingTemplate', [])
2517 * @name Forms.att:dropdowns
2520 * <file src="src/dropdowns/docs/readme.md" />
2525 <example module="b2b.att">
2526 <file src="src/dropdowns/docs/demo.html" />
2527 <file src="src/dropdowns/docs/demo.js" />
2531 angular.module('b2b.att.dropdowns', ['b2b.att.utilities', 'b2b.att.position', 'ngSanitize'])
2533 .constant('b2bDropdownConfig', {
2536 menuKeyword: 'menu',
2537 linkMenuKeyword: 'link-menu',
2538 largeKeyword: 'large',
2539 smallKeyword: 'small'
2542 .directive("b2bDropdown", ['$timeout', '$compile', '$templateCache', 'b2bUserAgent', 'b2bDropdownConfig', '$position', function ($timeout, $compile, $templateCache, b2bUserAgent, b2bDropdownConfig, $position) {
2547 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
2548 scope.isInputDropdown = true;
2549 scope.placeHoldertext = attr.placeholderText;
2551 if (attr.type.indexOf(b2bDropdownConfig.menuKeyword) > -1 || attr.type.indexOf(b2bDropdownConfig.linkMenuKeyword) > -1) {
2552 scope.isInputDropdown = false;
2553 if (attr.type.indexOf(b2bDropdownConfig.linkMenuKeyword) > -1) {
2554 scope.dropdownType = b2bDropdownConfig.linkMenuKeyword;
2555 } else if (attr.type.indexOf(b2bDropdownConfig.menuKeyword) > -1) {
2556 scope.dropdownType = b2bDropdownConfig.menuKeyword;
2559 if (attr.type.indexOf(b2bDropdownConfig.largeKeyword) > -1) {
2560 scope.dropdownSize = b2bDropdownConfig.largeKeyword;
2561 } else if (attr.type.indexOf(b2bDropdownConfig.smallKeyword) > -1) {
2562 scope.dropdownSize = b2bDropdownConfig.smallKeyword;
2566 scope.labelText = attr.labelText;
2568 scope.setBlur = function () {
2572 if ((scope.isInputDropdown && b2bUserAgent.notMobile()) || (!scope.isInputDropdown)) {
2573 var formCtrl = elem.controller('form');
2574 scope.setNgModelController = function (name, ngModelCtrl) {
2575 if (name && formCtrl && ngModelCtrl) {
2576 formCtrl[name] = ngModelCtrl;
2579 scope.setOptionalCta = function (optionalCta) {
2580 scope.optionalCta = optionalCta;
2582 var innerHtml = angular.element('<div></div>').append(elem.html());
2583 innerHtml = ($compile(innerHtml)(scope)).html();
2584 var template = angular.element($templateCache.get('b2bTemplate/dropdowns/b2bDropdownDesktop.html'));
2585 template.find('ul').eq(0).append(innerHtml);
2586 template = $compile(template)(scope);
2587 elem.replaceWith(template);
2588 } else if (scope.isInputDropdown && b2bUserAgent.isMobile()) {
2591 'filter': 'alpha(opacity=0)'
2593 elem.addClass('awd-select isWrapped');
2594 elem.wrap('<span class="selectWrap"></span>');
2595 var cover = angular.element('<span aria-hidden="true"><i class="icon-primary-down" aria-hidden="true"></i></span>');
2596 elem.parent().append(cover);
2597 elem.parent().append('<i class="icon-primary-down" aria-hidden="true"></i>');
2598 var set = function () {
2599 var sel = elem[0] ? elem[0] : elem;
2600 var selectedText = "";
2601 var selIndex = sel.selectedIndex;
2602 if (typeof selIndex !== 'undefined') {
2603 selectedText = sel.options[selIndex].text;
2605 cover.text(selectedText).append('<i class="icon-primary-down" aria-hidden="true"></i>');
2607 var update = function (value) {
2612 scope.$watch(attr.ngModel, function (newVal, oldVal) {
2616 elem.bind('keyup', function (ev) {
2617 if (ev.keyCode === keymap.KEY.TAB || ev.keyCode === keymap.KEY.ESC) {
2625 link: function (scope, elem, attr, ctrl) {
2626 if ((scope.isInputDropdown && b2bUserAgent.notMobile()) || (!scope.isInputDropdown)) {
2627 scope.updateModel = function () {
2628 ctrl.$setViewValue(scope.currentSelected.value);
2629 if (scope.dropdownRequired && scope.currentSelected.value === '') {
2630 scope.setRequired(false);
2632 scope.setRequired(true);
2635 if (scope.dropdownType === b2bDropdownConfig.linkMenuKeyword) {
2636 $timeout(function () {
2637 scope.appendCaretPositionStyle();
2641 ctrl.$render = function () {
2643 $timeout(function () {
2645 if ((angular.isUndefined(ctrl.$viewValue) || ctrl.$viewValue == '') && (angular.isUndefined(scope.placeHoldertext) || scope.placeHoldertext == '')) {
2646 scope.dropdownLists[ctrl.$viewValue] && scope.dropdownLists[ctrl.$viewValue][0].updateDropdownValue();
2647 } else if ((angular.isUndefined(scope.placeHoldertext) || scope.placeHoldertext == '') && ctrl.$viewValue !== '' ) {
2648 scope.dropdownLists[ctrl.$viewValue] && scope.dropdownLists[ctrl.$viewValue][0].updateDropdownValue();
2649 } else if ((angular.isUndefined(ctrl.$viewValue) || ctrl.$viewValue == '') && scope.placeHoldertext !== '' ) {
2650 scope.currentSelected.text = scope.placeHoldertext;
2651 ctrl.$setViewValue(scope.placeHoldertext);
2653 scope.dropdownLists[ctrl.$viewValue] && scope.dropdownLists[ctrl.$viewValue][0].updateDropdownValue();
2659 scope.disabled = false;
2660 scope.dropdownName = attr.name;
2661 scope.dropdownId = attr.id;
2662 scope.labelId = attr.ariaLabelledby;
2663 scope.dropdownDescribedBy = attr.ariaDescribedby;
2664 if (attr.required) {
2665 scope.dropdownRequired = true;
2667 scope.dropdownRequired = false;
2669 elem.removeAttr('name');
2670 elem.removeAttr('id');
2671 scope.$parent.$watch(attr.ngDisabled, function (val) {
2672 scope.disabled = val;
2679 .directive("b2bDropdownToggle", ['$document', '$documentBind', '$isElement', 'b2bDropdownConfig', 'keymap', 'b2bUtilitiesConfig', '$timeout', '$position', function ($document, $documentBind, $isElement, b2bDropdownConfig, keymap, b2bUtilitiesConfig, $timeout, $position) {
2682 require: '?^b2bKey',
2683 link: function (scope, elem, attr, ctrl) {
2684 scope.appendCaretPositionStyle = function () {
2685 while (document.querySelector('style.b2bDropdownCaret')) {
2686 document.querySelector('style.b2bDropdownCaret').remove();
2688 var caretPosition = $position.position(elem).width - 26;
2689 if (scope.dropdownType === b2bDropdownConfig.linkMenuKeyword) {
2690 var template = angular.element('<style class="b2bDropdownCaret" type="text/css">.linkSelectorModule .active+.moduleWrapper:before {left: ' + caretPosition + 'px;}</style>');
2691 $document.find('head').append(template);
2695 if (scope.isInputDropdown && (scope.labelText !== undefined)) {
2696 elem.attr('aria-label', scope.labelText);
2699 scope.toggleFlag = false;
2700 scope.dropdownLists = {};
2701 scope.dropdownListValues = [];
2705 scope.currentSelected = {
2711 scope.dropdownTextList = [];
2712 var searchString = '';
2714 scope.removeItem = function(value){
2715 delete scope.dropdownLists[value];
2716 var index = scope.dropdownListValues.indexOf(value);
2717 scope.dropdownListValues.splice(index,1);
2718 scope.dropdownTextList=[];
2719 scope.dropdown.totalIndex = scope.dropdownListValues.length-1;
2721 var getDropdownText = function(){
2722 var dropdownItems = elem.parent().find('ul').children();
2723 var count = dropdownItems.length;
2724 for(var i=0;i<count;i++){
2725 scope.dropdownTextList.push(dropdownItems.eq(i).text());
2728 var searchElement = function (searchExp) {
2729 if(scope.dropdownTextList.length ==0){
2732 var regex = new RegExp("^" + searchExp, "i");
2733 var position = scope.dropdownTextList.regexIndexOf(regex, scope.currentSelected.index + 1, true);
2734 if (position > -1) {
2739 var startTimer = function (time) {
2740 if (searchString === '') {
2741 $timeout(function () {
2746 scope.toggleDropdown = function (toggleFlag) {
2747 if (!scope.disabled) {
2748 if (angular.isDefined(toggleFlag)) {
2749 scope.toggleFlag = toggleFlag;
2751 scope.toggleFlag = !scope.toggleFlag;
2753 if (!scope.toggleFlag) {
2754 if (scope.isInputDropdown) {
2755 elem.parent().find('input')[0].focus();
2757 elem.parent().find('button')[0].focus();
2760 scope.dropdown.highlightedValue = scope.currentSelected.value;
2761 if (ctrl && ctrl.enableSearch) {
2762 if (angular.isDefined(scope.dropdownLists[scope.currentSelected.value])) {
2763 ctrl.resetCounter(scope.dropdownLists[scope.currentSelected.value][2]);
2766 $timeout(function () {
2767 if(scope.dropdownLists[scope.currentSelected.value] !== undefined){
2768 (scope.dropdownLists[scope.currentSelected.value][1])[0].focus();
2770 if (scope.isInputDropdown) {
2771 elem.parent().find('input')[0].focus();
2773 elem.parent().find('button')[0].focus();
2777 if (scope.dropdownType === b2bDropdownConfig.linkMenuKeyword) {
2778 scope.appendCaretPositionStyle();
2784 elem.bind('keydown', function (ev) {
2787 ev.keyCode = ev.which;
2788 } else if (ev.charCode) {
2789 ev.keyCode = ev.charCode;
2792 if (!scope.toggleFlag) {
2794 var currentIndex = scope.currentSelected.index;
2795 if (ev.keyCode === keymap.KEY.DOWN) {
2796 scope.toggleDropdown(true);
2797 ev.preventDefault();
2798 ev.stopPropagation();
2799 } else if (b2bDropdownConfig.prev.split(',').indexOf(ev.keyCode.toString()) > -1) {
2800 angular.isDefined(scope.dropdownListValues[currentIndex - 1]) ? scope.dropdownLists[scope.dropdownListValues[currentIndex - 1]][0].updateDropdownValue() : angular.noop();
2801 ev.preventDefault();
2802 ev.stopPropagation();
2803 } else if (b2bDropdownConfig.next.split(',').indexOf(ev.keyCode.toString()) > -1) {
2804 angular.isDefined(scope.dropdownListValues[currentIndex + 1]) ? scope.dropdownLists[scope.dropdownListValues[currentIndex + 1]][0].updateDropdownValue() : angular.noop();
2805 ev.preventDefault();
2806 ev.stopPropagation();
2807 } else if (ev.keyCode >= 48 && ev.keyCode <= 105) {
2808 startTimer(b2bUtilitiesConfig.searchTimer);
2809 searchString = searchString + (keymap.MAP[ev.keyCode] || '');
2810 var position = searchElement(searchString);
2811 angular.isDefined(scope.dropdownListValues[position]) ? scope.dropdownLists[scope.dropdownListValues[position]][0].updateDropdownValue() : angular.noop();
2812 ev.preventDefault();
2813 ev.stopPropagation();
2817 if (ev.altKey === true && ev.keyCode === keymap.KEY.UP) {
2818 scope.toggleDropdown(false);
2819 ev.preventDefault();
2820 ev.stopPropagation();
2821 } else if (ev.keyCode === keymap.KEY.TAB || ev.keyCode === keymap.KEY.ESC) {
2822 scope.toggleDropdown(false);
2823 ev.preventDefault();
2824 ev.stopPropagation();
2827 scope.$apply(); // TODO: Move this into each block to avoid expensive digest cycles
2829 var outsideClick = function (e) {
2830 var isElement = $isElement(angular.element(e.target), elem.parent(), $document);
2832 scope.toggleDropdown(false);
2836 $documentBind.click('toggleFlag', outsideClick, scope);
2837 $documentBind.touch('toggleFlag', outsideClick, scope);
2842 .directive("b2bDropdownGroup", ['$compile', '$templateCache', 'b2bUserAgent', function ($compile, $templateCache, b2bUserAgent) {
2845 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
2846 if ((scope.isInputDropdown && b2bUserAgent.notMobile()) || (!scope.isInputDropdown)) {
2847 var innerHtml = angular.element('<div></div>').append(elem.html());
2848 innerHtml = ($compile(innerHtml)(scope)).html();
2849 var template = angular.element($templateCache.get('b2bTemplate/dropdowns/b2bDropdownGroupDesktop.html'));
2850 template.attr('ng-repeat', attr.optGroupRepeat);
2851 template.attr('label', elem.attr('label'));
2852 template.find('ul').append(innerHtml);
2853 elem.replaceWith(template);
2854 } else if (scope.isInputDropdown && b2bUserAgent.isMobile()) {
2855 var template = angular.element(elem.prop('outerHTML'));
2856 template.attr('ng-repeat', attr.optGroupRepeat);
2857 template.removeAttr('b2b-dropdown-group');
2858 template.removeAttr('opt-group-repeat');
2859 template = $compile(template)(scope);
2860 elem.replaceWith(template);
2866 .directive("b2bDropdownGroupDesktop", [function () {
2870 link: function (scope, elem, attr, ctrl) {
2871 scope.groupHeader = attr.label;
2876 .directive("b2bDropdownList", ['$compile', '$templateCache', 'b2bUserAgent', function ($compile, $templateCache, b2bUserAgent) {
2879 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
2880 if ((scope.isInputDropdown && b2bUserAgent.notMobile()) || (!scope.isInputDropdown)) {
2881 var innerHtml = angular.element('<div></div>').append(elem.html());
2882 innerHtml = ($compile(innerHtml)(scope)).html();
2883 var template = angular.element($templateCache.get('b2bTemplate/dropdowns/b2bDropdownListDesktop.html'));
2884 template.attr('ng-repeat', attr.optionRepeat);
2885 template.attr('value', elem.attr('value'));
2886 template.attr('search-key', elem.text());
2887 if (elem.attr('aria-describedby')){
2888 template.attr('aria-describedby', attr.ariaDescribedby);
2890 if (elem.attr('imgsrc')) {
2891 if (elem.attr('imgalt')) {
2892 template.append('<img role="presentation" ng-src="' + elem.attr('imgsrc') + '" alt="' + elem.attr('imgalt') + '"/>');
2894 template.append('<img role="presentation" ng-src="' + elem.attr('imgsrc') + '" alt=""/>');
2897 template.append(innerHtml);
2898 elem.replaceWith(template);
2899 } else if (scope.isInputDropdown && b2bUserAgent.isMobile()) {
2900 var template = angular.element(elem.prop('outerHTML'));
2901 template.attr('ng-repeat', attr.optionRepeat);
2902 if (elem.attr('aria-describedby')){
2903 template.attr('aria-describedby', attr.ariaDescribedby);
2905 template.removeAttr('b2b-dropdown-list');
2906 template.removeAttr('option-repeat');
2907 template = $compile(template)(scope);
2908 elem.replaceWith(template);
2914 .directive("b2bDropdownListDesktop", ['$sce', 'keymap', 'b2bDropdownConfig', function ($sce, keymap, b2bDropdownConfig) {
2919 link: function (scope, elem, attr, ctrl) {
2920 var dropdownListValue = scope.dropdownListValue = attr.value;
2921 scope.dropdown.totalIndex++;
2922 var dropdownListIndex = scope.dropdown.totalIndex;
2923 scope.dropdownListValues.push(dropdownListValue);
2924 scope.dropdownLists[dropdownListValue] = [];
2925 scope.dropdownLists[dropdownListValue][0] = scope;
2926 scope.dropdownLists[dropdownListValue][1] = elem;
2927 scope.dropdownLists[dropdownListValue][2] = dropdownListIndex;
2928 scope.$parent.$parent.dropdownTextList=[];
2929 scope.updateDropdownValue = function () {
2930 scope.currentSelected.value = dropdownListValue;
2931 if (scope.isInputDropdown) {
2932 scope.currentSelected.text = elem.text();
2933 scope.currentSelected.label = elem.text();
2934 } else if ((scope.dropdownType === b2bDropdownConfig.linkMenuKeyword) || (scope.dropdownType === b2bDropdownConfig.menuKeyword && scope.dropdownSize === b2bDropdownConfig.smallKeyword)) {
2935 scope.currentSelected.text = dropdownListValue;
2936 scope.currentSelected.label = dropdownListValue;
2937 } else if (scope.dropdownType === b2bDropdownConfig.menuKeyword) {
2938 scope.currentSelected.text = $sce.trustAsHtml(elem.html());
2939 scope.currentSelected.label = elem.text();
2941 scope.currentSelected.index = dropdownListIndex;
2942 scope.updateModel();
2944 scope.selectDropdownItem = function () {
2946 scope.updateDropdownValue();
2947 scope.toggleDropdown(false);
2949 scope.highlightDropdown = function () {
2950 scope.dropdown.highlightedValue = dropdownListValue;
2952 elem.bind('mouseover', function (ev) {
2956 elem.bind('keydown', function (ev) {
2959 ev.keyCode = ev.which;
2960 } else if (ev.charCode) {
2961 ev.keyCode = ev.charCode;
2964 if (ev.altKey === true && ev.keyCode === keymap.KEY.UP) {
2965 scope.toggleDropdown(false);
2966 ev.preventDefault();
2967 ev.stopPropagation();
2968 } else if (ev.keyCode === keymap.KEY.TAB || ev.keyCode === keymap.KEY.ESC) {
2969 scope.toggleDropdown(false);
2970 ev.preventDefault();
2971 ev.stopPropagation();
2975 scope.$on('$destroy',function(){
2976 scope.removeItem(dropdownListValue);
2982 .directive("b2bDropdownRepeat", ['$compile', 'b2bUserAgent', function ($compile, b2bUserAgent) {
2985 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
2986 if ((scope.isInputDropdown && b2bUserAgent.notMobile()) || (!scope.isInputDropdown)) {
2987 var innerHtml = angular.element('<div></div>').append(elem.html());
2988 innerHtml = ($compile(innerHtml)(scope)).html();
2989 var template = angular.element('<div></div>');
2990 template.attr('ng-repeat', attr.b2bDropdownRepeat);
2991 template.append(innerHtml);
2992 elem.replaceWith(template);
2993 } else if (scope.isInputDropdown && b2bUserAgent.isMobile()) {
3000 .directive("b2bDropdownValidation", ['$timeout', function ($timeout) {
3004 link: function (scope, elem, attr, ctrl) {
3005 $timeout(function () {
3006 scope.setNgModelController(attr.name, ctrl);
3008 scope.setDirty = function () {
3009 if (ctrl.$dirty === false) {
3011 ctrl.$pristine = false;
3014 scope.setTouched = function () {
3015 if (ctrl.$touched === false) {
3016 ctrl.$touched = true;
3017 ctrl.$pristine = false;
3020 scope.setRequired = function (flag) {
3021 ctrl.$setValidity('required', flag);
3027 .directive('b2bDropdownOptionalCta', [function () {
3033 compile: function (elem, attr, transclude) {
3034 return function link(scope, elem, attr, ctrl) {
3035 if (scope.setOptionalCta) {
3036 scope.setOptionalCta(transclude(scope, function () {}));
3045 * @name Forms.att:File Upload
3048 * <file src="src/fileUpload/docs/readme.md" />
3052 <form id="dragDropFile">
3053 <div b2b-file-drop file-model="fileModel" on-drop="triggerFileUpload()" align="center">
3055 <br>To upload a file, drag & drop it here or
3056 <span b2b-file-link file-model="fileModel" on-file-select="triggerFileUpload()" >
3057 click here to select from your computer.
3064 * <section id="code">
3065 <example module="b2b.att">
3066 <file src="src/fileUpload/docs/demo.html" />
3067 <file src="src/fileUpload/docs/demo.js" />
3072 angular.module('b2b.att.fileUpload', ['b2b.att.utilities'])
3073 .directive('b2bFileDrop', [function() {
3080 controller: ['$scope', '$attrs', function($scope, $attrs) {
3081 this.onDrop = $scope.onDrop;
3083 link: function(scope, element) {
3084 element.addClass('b2b-dragdrop');
3088 if (e.originalEvent) {
3089 e.dataTransfer = e.originalEvent.dataTransfer;
3091 e.dataTransfer.dropEffect = 'move';
3092 // allows us to drop
3093 if (e.preventDefault) {
3096 element.addClass('b2b-dragdrop-over');
3103 // allows us to drop
3104 if (e.preventDefault) {
3107 element.addClass('b2b-dragdrop-over');
3114 element.removeClass('b2b-dragdrop-over');
3121 // Stops some browsers from redirecting.
3122 if (e.preventDefault) {
3125 if (e.stopPropagation) {
3126 e.stopPropagation();
3128 if (e.originalEvent) {
3129 e.dataTransfer = e.originalEvent.dataTransfer;
3131 element.removeClass('b2b-dragdrop-over');
3132 if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
3133 scope.fileModel = e.dataTransfer.files[0];
3135 if (angular.isFunction(scope.onDrop)) {
3145 .directive('b2bFileLink', [function() {
3148 require: '^?b2bFileDrop',
3151 templateUrl: 'b2bTemplate/fileUpload/fileUpload.html',
3156 controller: ['$scope', function($scope) {
3157 this.setFileModel = function(fileModel) {
3158 if ($scope.takeFileModelFromParent) {
3159 $scope.$parent.fileModel = fileModel;
3160 $scope.$parent.$apply();
3162 $scope.fileModel = fileModel;
3166 this.callbackFunction = function() {
3167 if (angular.isFunction($scope.onFileSelect)) {
3168 $scope.onFileSelect();
3173 link: function(scope, element, attr, b2bFileDropCtrl) {
3174 scope.takeFileModelFromParent = false;
3175 if (!(attr.fileModel) && b2bFileDropCtrl) {
3176 scope.takeFileModelFromParent = true;
3178 if (!(attr.onFileSelect) && b2bFileDropCtrl) {
3179 scope.onFileSelect = b2bFileDropCtrl.onDrop;
3184 .directive('b2bFileChange', ['$log', '$rootScope', function($log, $rootScope) {
3187 require: '^b2bFileLink',
3188 link: function(scope, element, attr, b2bFileLinkCtrl) {
3189 element.bind('change', changeFileModel);
3191 function changeFileModel(e) {
3192 if (e.target.files && e.target.files.length > 0) {
3193 b2bFileLinkCtrl.setFileModel(e.target.files[0]);
3194 b2bFileLinkCtrl.callbackFunction();
3196 var strFileName = e.target.value;
3198 var objFSO = new ActiveXObject("Scripting.FileSystemObject");
3199 b2bFileLinkCtrl.setFileModel(objFSO.getFile(strFileName));
3200 b2bFileLinkCtrl.callbackFunction();
3202 var errMsg = "There was an issue uploading " + strFileName + ". Please try again.";
3204 $rootScope.$broadcast('b2b-file-link-failure', errMsg);
3213 * @name Navigation.att:filters
3216 * <file src="src/filters/docs/readme.md" />
3219 * <div b2b-filters></div>
3222 * <section id="code">
3223 <b>HTML + AngularJS</b>
3224 <example module="b2b.att">
3225 <file src="src/filters/docs/demo.html" />
3226 <file src="src/filters/docs/demo.js" />
3231 angular.module('b2b.att.filters', ['b2b.att.utilities', 'b2b.att.multipurposeExpander'])
3232 .filter('filtersSelectedItemsFilter', [function () {
3233 return function (listOfItemsArray) {
3235 if (!listOfItemsArray) {
3236 listOfItemsArray = [];
3239 var returnArray = [];
3241 for (var i = 0; i < listOfItemsArray.length; i++) {
3242 for (var j = 0; j < listOfItemsArray[i].filterTypeItems.length; j++) {
3243 if (listOfItemsArray[i].filterTypeItems[j].selected && !listOfItemsArray[i].filterTypeItems[j].inProgress) {
3244 returnArray.push(listOfItemsArray[i].filterTypeItems[j]);
3254 * @name Messages, modals & alerts.att:flyout
3257 * <file src="src/flyout/docs/readme.md" />
3259 * <section id="code">
3260 <example module="b2b.att">
3261 <file src="src/flyout/docs/demo.html" />
3262 <file src="src/flyout/docs/demo.js" />
3267 angular.module('b2b.att.flyout', ['b2b.att.utilities', 'b2b.att.position'])
3268 .directive('b2bFlyout', ['$timeout', 'b2bDOMHelper', 'keymap', 'events', function ($timeout, b2bDOMHelper, keymap, events) {
3272 templateUrl: 'b2bTemplate/flyout/flyout.html',
3273 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
3274 scope.flyoutOpened = false;
3275 var contentScope = '';
3276 var togglerScope = '';
3277 this.registerContentScope = function (scp) {
3280 this.registerTogglerScope = function (scp) {
3284 this.toggleFlyoutState = function () {
3286 contentScope.toggleFlyout();
3289 this.getTogglerDimensions = function () {
3290 return togglerScope.getTogglerDimensions();
3292 this.setTogglerFocus = function () {
3293 return togglerScope.setTogglerFocus();
3296 this.closeFlyout = function (e) {
3297 contentScope.closeFromChild(e);
3299 this.gotFocus = function () {
3300 contentScope.gotFocus();
3303 this.updateAriaModel = function (val) {
3304 scope.flyoutOpened = val;
3307 var firstTabableElement = undefined,
3308 lastTabableElement = undefined;
3310 var firstTabableElementKeyhandler = function (e) {
3312 e.keyCode = e.which;
3314 if (e.keyCode === keymap.KEY.TAB && e.shiftKey && scope.flyoutOpened) {
3315 contentScope.gotFocus();
3316 events.preventDefault(e);
3317 events.stopPropagation(e);
3321 var lastTabableElementKeyhandler = function (e) {
3323 e.keyCode = e.which;
3325 if (e.keyCode === keymap.KEY.TAB && !e.shiftKey) {
3326 contentScope.gotFocus();
3327 events.preventDefault(e);
3328 events.stopPropagation(e);
3331 this.associateTabEvent = function(){
3332 $timeout(function () {
3333 var element = elem[0].getElementsByClassName('b2b-flyout-container')[0];
3334 firstTabableElement = b2bDOMHelper.firstTabableElement(element);
3335 lastTabableElement = b2bDOMHelper.lastTabableElement(element);
3336 if(angular.isUndefined(firstTabableElement)){
3337 angular.element(element).css('display','block');
3338 firstTabableElement = b2bDOMHelper.firstTabableElement(element);
3339 lastTabableElement = b2bDOMHelper.lastTabableElement(element);
3340 angular.element(element).css('display','none');
3342 angular.element(firstTabableElement).bind('keydown', firstTabableElementKeyhandler);
3343 angular.element(lastTabableElement).bind('keydown', lastTabableElementKeyhandler);
3346 this.updateTabbableElements = function(){
3347 $timeout(function () {
3348 var element = elem[0].getElementsByClassName('b2b-flyout-container')[0];
3349 angular.element(element).css('display','block');
3350 firstTabableElement = b2bDOMHelper.firstTabableElement(element);
3351 lastTabableElement = b2bDOMHelper.lastTabableElement(element);
3352 angular.element(firstTabableElement).bind('keydown', firstTabableElementKeyhandler);
3353 angular.element(lastTabableElement).bind('keydown', lastTabableElementKeyhandler);
3354 angular.element(element).css('display','none');
3357 this.unbindTabbaleEvents = function(){
3358 if(angular.isDefined(firstTabableElement)){
3359 angular.element(firstTabableElement).unbind('keydown', firstTabableElementKeyhandler);
3362 if(angular.isDefined(lastTabableElement)){
3363 angular.element(lastTabableElement).unbind('keydown', lastTabableElementKeyhandler);
3367 link: function (scope, element, attrs, ctrl) {
3372 .directive('b2bFlyoutToggler', [function () {
3375 require: '^b2bFlyout',
3376 link: function (scope, element, attrs, ctrl) {
3377 element.bind('click', function (e) {
3378 ctrl.toggleFlyoutState();
3381 scope.getTogglerDimensions = function () {
3382 return element[0].getBoundingClientRect();
3385 scope.setTogglerFocus = function () {
3389 ctrl.registerTogglerScope(scope);
3393 .directive('b2bFlyoutContent', ['$position', '$timeout', '$documentBind', '$isElement', '$document', function ($position, $timeout, $documentBind, $isElement, $document) {
3398 require: '^b2bFlyout',
3400 horizontalPlacement: '@',
3401 verticalPlacement: '@',
3404 contentUpdated: "=?"
3406 templateUrl: 'b2bTemplate/flyout/flyoutContent.html',
3407 link: function (scope, element, attrs, ctrl) {
3408 var flyoutStyleArray, eachCssProperty, cssPropertyKey, cssPropertyVal, temp;
3409 scope.openFlyout = false;
3410 if (!scope.horizontalPlacement) {
3411 scope.horizontalPlacement = 'center';
3413 if (!scope.verticalPlacement) {
3414 scope.verticalPlacement = 'below';
3417 scope.toggleFlyout = function () {
3419 scope.openFlyout = !scope.openFlyout;
3421 if (scope.openFlyout) {
3423 if (angular.isDefined(scope.flyoutStyle) && scope.flyoutStyle != "") {
3424 flyoutStyleArray = scope.flyoutStyle.split(";");
3425 for (i = 0; i < flyoutStyleArray.length; i++) {
3426 eachCssProperty = flyoutStyleArray[i].split(":");
3427 if (eachCssProperty.length == 2) {
3428 cssPropertyKey = eachCssProperty[0].trim();
3429 cssPropertyVal = eachCssProperty[1].trim();
3430 angular.element(element[0])[0].style[cssPropertyKey] = cssPropertyVal;
3435 angular.element(element[0]).css({
3440 var flyoutIcons = angular.element(document.querySelectorAll(".b2b-flyout-icon"));
3441 angular.forEach(flyoutIcons, function (elm) {
3442 angular.element(elm)[0].blur();
3445 $timeout(function () {
3446 ctrl.setTogglerFocus();
3448 var togglerDimensions = ctrl.getTogglerDimensions();
3449 var flyoutDimensions = element[0].getBoundingClientRect();
3451 switch (scope.horizontalPlacement) {
3453 angular.element(element[0]).css({
3454 'left': ((togglerDimensions.width / 2) - 26) + 'px'
3458 angular.element(element[0]).css({
3459 'right': ((togglerDimensions.width / 2) - 23) + 'px'
3464 var marginLeft = 10-(flyoutDimensions.width)-20;
3465 angular.element(element[0]).css({
3466 'margin-left': marginLeft + 'px'
3470 angular.element(element[0]).css({
3471 'left': ((togglerDimensions.width + 9 )) + 'px'
3476 var marginLeft = (togglerDimensions.width / 2) - (flyoutDimensions.width / 2) - 8;
3477 angular.element(element[0]).css({
3478 'margin-left': marginLeft + 'px'
3482 switch (scope.verticalPlacement) {
3484 angular.element(element[0]).css({
3485 'top': -(flyoutDimensions.height + 13) + 'px'
3489 angular.element(element[0]).css({
3490 'top': -((togglerDimensions.height-13))+ 'px'
3494 angular.element(element[0]).css({
3495 'top': -(flyoutDimensions.height - 23)+ 'px'
3499 angular.element(element[0]).css({
3500 'top': (togglerDimensions.height + 13) + 'px'
3504 angular.element(element[0]).css({
3513 scope.gotFocus = function () {
3514 scope.openFlyout = false;
3516 ctrl.setTogglerFocus();
3520 scope.closeFromChild = function (e) {
3521 scope.openFlyout = false;
3523 ctrl.setTogglerFocus();
3527 scope.hideFlyout = function () {
3528 angular.element(element[0]).css({
3534 scope.closeFlyout = function (e) {
3535 var isElement = $isElement(angular.element(e.target), element, $document);
3536 if ((e.type === "keydown" && e.which === 27) || ((e.type === "click" || e.type==="touchend") && !isElement)) {
3537 scope.openFlyout = false;
3539 ctrl.setTogglerFocus();
3544 scope.$watch('openFlyout', function () {
3545 ctrl.updateAriaModel(scope.openFlyout);
3548 $documentBind.click('openFlyout', scope.closeFlyout, scope);
3549 $documentBind.event('keydown', 'openFlyout', scope.closeFlyout, scope);
3550 $documentBind.event('touchend', 'openFlyout', scope.closeFlyout, scope);
3551 ctrl.registerContentScope(scope);
3553 if (angular.isDefined(scope.contentUpdated) && scope.contentUpdated !== null) {
3554 scope.$watch('contentUpdated', function (newVal, oldVal) {
3556 if (newVal !== oldVal) {
3557 ctrl.unbindTabbaleEvents();
3558 ctrl.associateTabEvent();
3560 scope.contentUpdated = false;
3568 .directive('b2bCloseFlyout', [function () {
3571 require: '^b2bFlyout',
3575 link: function (scope, element, attrs, ctrl) {
3576 element.bind('click touchstart', function (e) {
3577 scope.closeFlyout(e);
3578 ctrl.closeFlyout(e);
3583 .directive('b2bFlyoutTrapFocusInside', [function () {
3587 require: '^b2bFlyout',
3588 link: function (scope, elem, attr, ctrl) {
3589 /* Before opening modal, find the focused element */
3590 ctrl.updateTabbableElements();
3596 * @name Layouts.att:footer
3599 * <file src="src/footer/docs/readme.md" />
3603 <footer class="b2b-footer-wrapper" role="contentinfo" aria-label="footer">
3604 <div class="b2b-footer-container" b2b-column-switch-footer footer-link-items='footerItems'>
3606 <div class="divider-bottom-footer">
3607 <div class="span2 dispalyInline"> </div>
3608 <div class="span6 dispalyInline">
3609 <ul class="footer-nav-content">
3610 <li><a href="Terms_of_use.html" title="Terms of use" id="foot0">Terms of use</a>|</li>
3611 <li><a href="Privacy_policy.html" title="Privacy policy" id="foot1" class="active">Privacy policy</a>|</li>
3612 <li><a href="Tollfree_directory_assistance.html" title="Tollfree directory assistance" id="foot2">Tollfree directory assistance</a>|</li>
3613 <li><a href="compliance.html" title="Accessibility" id="foot3">Accessibility</a></li>
3616 <p><a href="//www.att.com/gen/privacy-policy?pid=2587" target="_blank">© <span class="copyright">2016</span> AT&T Intellectual Property</a>. All rights reserved. AT&T,the AT&T Globe logo and all other AT&T marks contained herein are tardemarks of AT&T intellectual property and/or AT&T affiliated companines.
3620 <div class="span3 footerLogo dispalyInline">
3621 <a href="index.html" class="footer-logo">
3622 <i class="icon-primary-att-globe"><span class="hidden-spoken">A T & T</span></i>
3623 <h2 class="logo-title">AT&T</h2>
3632 * <section id="code">
3633 <example module="b2b.att">
3634 <file src="src/footer/docs/demo.html" />
3635 <file src="src/footer/docs/demo.js" />
3640 angular.module('b2b.att.footer', ['b2b.att.utilities']).
3641 directive('b2bColumnSwitchFooter', [function() {
3646 footerLinkItems: "="
3648 templateUrl: 'b2bTemplate/footer/footer_column_switch_tpl.html',
3649 link: function(scope) {
3650 var tempFooterColumns = scope.footerLinkItems.length;
3651 scope.footerColumns = 3;
3652 if ( (tempFooterColumns === 5) || (tempFooterColumns === 4) ) {
3653 scope.footerColumns = tempFooterColumns;
3664 * @name Layouts.att:header
3667 * <file src="src/header/docs/readme.md" />
3670 * <li b2b-header-menu class="header__item b2b-headermenu" ng-repeat="item in tabItems" role="presentation">
3671 <a href="#" class="menu__item" role="menuitem">{{item.title}}</a>
3672 <div class="header-secondary-wrapper">
3673 <ul class="header-secondary" role="menu">
3674 <li class="header-subitem" b2b-header-submenu ng-repeat="i in item.subitems" role="presentation">
3675 <a href="#" class="menu__item" aria-haspopup="true" role="menuitem">{{i.value}}</a>
3676 <div class="header-tertiary-wrapper" ng-if="i.links">
3677 <ul class="header-tertiary" role="menu">
3678 <li b2b-header-tertiarymenu ng-repeat="link in i.links" role="presentation">
3679 <label>{{link.title}}</label>
3680 <div b2b-tertiary-link ng-repeat="title in link.value">
3681 <a href="{{link.href}}" class="header-tertiaryitem" ng-if="!title.subitems" aria-haspopup="false" role="menuitem"><span class="b2b-label-hide">{{link.title}}</span>{{title.title}}</a>
3682 <a href="{{link.href}}" class="header-tertiaryitem" b2b-header-togglemenu ng-if="title.subitems" aria-haspopup="true" role="menuitem"><span class="b2b-label-hide">{{link.title}}</span>{{title.title}}</a>
3683 <ul class="header-quarternary" role="menu" ng-if="title.subitems">
3684 <li b2b-header-quarternarymenu role="presentation">
3685 <a href="{{nav.href}}" ng-repeat="nav in title.subitems" role="menuitem" aria-haspopup="true">{{nav.title}}</a>
3698 * <section id="code">
3699 <example module="b2b.att.header">
3700 <file src="src/header/docs/demo.html" />
3701 <file src="src/header/docs/demo.js" />
3706 angular.module('b2b.att.header', ['b2b.att.dropdowns','b2b.att.utilities'])
3707 .directive('b2bHeaderMenu', ['keymap', '$documentBind', '$timeout', '$isElement', '$document', function (keymap, $documentBind, $timeout, $isElement, $document) {
3710 controller:['$scope',function($scope){
3711 this.nextSiblingFocus = function (elObj,flag) {
3712 if (elObj.nextElementSibling) {
3714 var nextmenuItem = this.getFirstElement(elObj.nextElementSibling,'a');
3715 nextmenuItem.focus();
3717 elObj.nextElementSibling.focus();
3722 this.previousSiblingFocus = function (elObj,flag) {
3723 if (elObj.previousElementSibling) {
3725 var prevmenuItem = this.getFirstElement(elObj.previousElementSibling,'a');
3726 prevmenuItem.focus();
3728 elObj.previousElementSibling.focus();
3733 this.getFirstElement = function(elmObj,selector){
3734 return elmObj.querySelector(selector);
3737 link: function (scope, elem,attr,ctrl) {
3738 scope.showMenu = false;
3739 var activeElm, subMenu, tertiaryMenu, el= angular.element(elem)[0],
3740 menuItem = angular.element(elem[0].children[0]);
3741 menuItem.bind('click', function () {
3742 elem.parent().children().removeClass('active');
3743 elem.addClass('active');
3744 var elems= this.parentElement.parentElement.querySelectorAll('li[b2b-header-menu]>a');
3745 for (var i=0; i<elems.length; i++) {
3746 elems[i].setAttribute("aria-expanded",false);
3748 scope.showMenu = true;
3749 var elmTofocus = ctrl.getFirstElement(this.parentElement,'li[b2b-header-submenu]');
3750 elmTofocus.firstElementChild.focus();
3751 this.setAttribute('aria-expanded',true);
3755 elem.bind('keydown', function (evt) {
3756 activeElm = document.activeElement;
3757 subMenu = ctrl.getFirstElement(activeElm.parentElement,'li[b2b-header-submenu]');
3758 tertiaryMenu = ctrl.getFirstElement(activeElm.parentElement,'li[b2b-header-tertiarymenu]');
3759 switch (evt.keyCode) {
3760 case keymap.KEY.ENTER:
3761 case keymap.KEY.SPACE:
3765 evt.stopPropagation();
3766 evt.preventDefault();
3767 if (activeElm.parentElement.hasAttribute('b2b-header-submenu')) {
3768 menuItem[0].focus();
3771 case keymap.KEY.DOWN:
3772 evt.stopPropagation();
3773 evt.preventDefault();
3775 subMenu.firstElementChild.focus();
3776 } else if (tertiaryMenu) {
3777 var firstSubitem = ctrl.getFirstElement(tertiaryMenu,'a.header-tertiaryitem');
3778 firstSubitem.focus();
3781 case keymap.KEY.RIGHT:
3782 evt.stopPropagation();
3783 evt.preventDefault();
3784 if (activeElm.parentElement.hasAttribute('b2b-header-submenu')) {
3785 var elm = angular.element(activeElm.parentElement)[0];
3786 ctrl.nextSiblingFocus(elm,true);
3787 } else if (activeElm.parentElement.parentElement.hasAttribute('b2b-header-tertiarymenu')) {
3788 var tertiaryLI = angular.element(activeElm.parentElement.parentElement)[0];
3789 if (tertiaryLI.nextElementSibling) {
3790 var nextElm = ctrl.getFirstElement(tertiaryLI.nextElementSibling,"a.header-tertiaryitem");
3794 else if(activeElm.parentElement.hasAttribute('b2b-header-menu')){
3795 ctrl.nextSiblingFocus(el,true);
3798 case keymap.KEY.LEFT:
3799 evt.stopPropagation();
3800 evt.preventDefault();
3801 if (activeElm.parentElement.hasAttribute('b2b-header-submenu')) {
3802 var previousElm = angular.element(activeElm.parentElement)[0];
3803 ctrl.previousSiblingFocus(previousElm,true);
3804 } else if (activeElm.parentElement.parentElement.hasAttribute('b2b-header-tertiarymenu')) {
3805 var tertiaryLI = angular.element(activeElm.parentElement.parentElement)[0];
3806 if (tertiaryLI.previousElementSibling) {
3807 var prevElm = ctrl.getFirstElement(tertiaryLI.previousElementSibling,"a.header-tertiaryitem");
3811 else if(activeElm.parentElement.hasAttribute('b2b-header-menu')) {
3812 ctrl.previousSiblingFocus(el,true);
3815 case keymap.KEY.ESC:
3816 evt.stopPropagation();
3817 evt.preventDefault();
3818 scope.showMenu = false;
3819 elem.removeClass('active');
3820 menuItem.attr('aria-expanded',false);
3821 $timeout(function(){
3822 menuItem[0].focus();
3830 var outsideClick = function (e) {
3831 var isElement = $isElement(angular.element(e.target), elem, $document);
3833 scope.showMenu = false;
3834 elem.removeClass('active');
3838 $documentBind.click('showMenu', outsideClick, scope);
3841 }]).directive('b2bHeaderSubmenu', ['$timeout',function ($timeout) {
3844 link: function (scope, elem) {
3845 var caretSign = angular.element("<i class='menuCaret'></i>");
3846 $timeout(function(){
3847 var menuItem = angular.element(elem[0].children[0]);
3848 menuItem.bind('focus mouseenter', function () {
3849 elem.parent().children().removeClass('active');
3850 elem.addClass('active');
3851 if(elem[0].childElementCount > 1){ // > 1 has third level menu
3852 menuItem.attr('aria-expanded',true);
3853 menuItem.attr('aria-haspopup',true);
3855 var caretLeft = (elem[0].offsetLeft + elem[0].offsetWidth/2) - 10;
3856 caretSign.css({left: caretLeft + 'px'});
3857 angular.element(caretSign);
3858 var tertiaryItems = elem[0].querySelectorAll('[b2b-header-tertiarymenu]');
3859 if(tertiaryItems.length >=1){
3860 elem.append(caretSign);
3863 menuItem.bind('blur', function () {
3864 $timeout(function () {
3865 var parentElm = document.activeElement.parentElement.parentElement;
3867 if (!(parentElm.hasAttribute('b2b-header-tertiarymenu'))) {
3868 elem.removeClass('active');
3869 if(elem[0].childElementCount > 1){ // > 1 has third level menu
3870 menuItem.attr('aria-expanded',false);
3872 var caret = elem[0].querySelector('.menuCaret');
3883 }]).directive('b2bHeaderTertiarymenu', ['$timeout','keymap', function ($timeout,keymap){
3886 require:'^b2bHeaderMenu',
3887 link: function (scope, elem,attr,ctrl) {
3889 elem.bind('keydown', function (evt) {
3890 var activeElm = document.activeElement;
3891 var activeParentElm = activeElm.parentElement;
3892 var activeParentObj = angular.element(activeParentElm)[0];
3894 if(activeParentElm.hasAttribute('b2b-tertiary-link')){
3895 var quarterNav = angular.element(activeParentElm)[0].querySelector('li[b2b-header-quarternarymenu]');
3897 var links = ctrl.getFirstElement(angular.element(quarterNav)[0],'a');
3900 var tertiaryMenu = activeElm.parentElement.parentElement.parentElement;
3901 var tertiaryMenuFlag = tertiaryMenu.hasAttribute('b2b-tertiary-link');
3903 switch (evt.keyCode) {
3904 case keymap.KEY.DOWN:
3905 evt.stopPropagation();
3906 evt.preventDefault();
3907 if (activeParentElm.hasAttribute('b2b-tertiary-link')) {
3908 if(angular.element(quarterNav).hasClass('active')){
3910 }else if(activeParentObj.nextElementSibling){
3911 ctrl.nextSiblingFocus(activeParentObj,true);
3914 else if(angular.element(activeParentElm).hasClass('active')){
3915 ctrl.nextSiblingFocus(activeElm);
3919 evt.stopPropagation();
3920 evt.preventDefault();
3921 if(activeParentElm.hasAttribute('b2b-tertiary-link')){
3922 if(activeParentObj.previousElementSibling.hasAttribute('b2b-tertiary-link')){
3923 ctrl.previousSiblingFocus(activeParentObj,true);
3925 var elm = angular.element(activeElm.parentElement.parentElement.parentElement.parentElement.parentElement)[0];
3926 ctrl.getFirstElement(elm,"a").focus();
3928 }else if(angular.element(activeParentElm).hasClass('active')){
3929 if (activeElm.previousElementSibling) {
3930 ctrl.previousSiblingFocus(activeElm);
3931 }else if (tertiaryMenuFlag) {
3932 var elm = angular.element(tertiaryMenu)[0];
3933 ctrl.getFirstElement(elm,"a.header-tertiaryitem").focus();
3943 }]).directive('b2bHeaderTogglemenu', ['$timeout', 'keymap', function ($timeout, keymap) {
3946 require: '^b2bHeaderMenu',
3947 link: function (scope, elem, attrs, ctrl) {
3949 $timeout(function () {
3950 quarterNav = angular.element(elem.parent())[0].querySelector('li[b2b-header-quarternarymenu]');
3951 elem.bind('click', function () {
3952 angular.element(quarterNav).toggleClass('active');
3957 }]).directive('b2bHeaderResponsive', ['$timeout',function ($timeout) {
3960 controller: function($scope){
3961 this.applyMediaQueries = function(value){
3962 document.querySelector('style').textContent +=
3963 "@media screen and (max-width:950px) { \
3964 .header__item.profile { right: " + value + "px; } \
3967 this.arrangeResponsiveHeader = function(children){
3969 * clientWidth of 1090 === max-width of 1100px
3970 * clientWidth of 920 === max-width of 950px
3971 * see b2b-angular.css for rest of responsive header CSS
3973 if (document.documentElement.clientWidth <= 920) {
3976 this.applyMediaQueries(200);
3979 this.applyMediaQueries(200);
3981 default: // anthing above 3, however, should not have more than 3 to date
3982 this.applyMediaQueries(200);
3987 link: function (scope, elem, attrs, ctrl) {
3992 $timeout(function(){
3993 profile = document.querySelector('li.header__item.profile');
3994 children = angular.element(profile).children().length;
3996 ctrl.arrangeResponsiveHeader(children); // shift right-side icon flyovers
4000 window.addEventListener('resize', function(event){ // caret adjustmet
4001 var activeSubmenu = elem[0].querySelector('[b2b-header-menu] [b2b-header-submenu].active');
4002 var activeSubmenuEl = angular.element(activeSubmenu);
4004 var caretSign = activeSubmenu.querySelector('i.menuCaret');
4006 var caretSignEl = angular.element(caretSign);
4007 var caretLeft = (activeSubmenu.offsetLeft + activeSubmenu.offsetWidth/2) - 10;
4008 caretSignEl.css({left: caretLeft + 'px'});
4012 ctrl.arrangeResponsiveHeader(children); // shift right-side icon flyovers
4020 * @name Layouts.att:headings & copy
4023 * <file src="src/headingsAndCopy/docs/readme.md" />
4027 <b>HTML + AngularJS</b>
4028 <example module="b2b.att">
4029 <file src="src/headingsAndCopy/docs/demo.html" />
4034 var b2bLegalCopy = angular.module('b2b.att.headingsAndCopy', []);
4037 * @name Tabs, tables & accordions.att:horizontalTable
4040 * <file src="src/horizontalTable/docs/readme.md" />
4043 * @param {int} sticky - Number of sticky columns to have. Maximum of 3.
4044 * @param {boolean} refresh - A boolean that when set to true will force a re-render of table. Only use when using 'bulk mode'
4045 * @param {string} legendContent - A string of html to fill in the legend flyout. This should generally be a <ul> with <li> and should not rely on Angular for repeating.
4046 * @param {boolean} retainColumnSet - A boolean that on re-render of the table, determines if the columns visible should reset to 0 or not. Default is false.
4048 * <section id="code">
4049 <example module="b2b.att">
4050 <file src="src/horizontalTable/docs/demo.html" />
4051 <file src="src/horizontalTable/docs/demo.js" />
4056 angular.module('b2b.att.horizontalTable', [])
4057 .constant('b2bHorizontalTableConfig', {
4058 'maxStickyColumns': 3
4060 .directive('b2bHorizontalTable', ['$timeout', 'b2bHorizontalTableConfig', 'b2bDOMHelper', function ($timeout, b2bHorizontalTableConfig, b2bDOMHelper) {
4066 numOfStickyCols: '=?sticky',
4068 legendContent: '=?',
4069 retainColumnSet: '=?'
4072 templateUrl: 'b2bTemplate/horizontalTable/horizontalTable.html',
4073 link: function (scope, element, attrs, ctrl) {
4074 scope.numOfStickyCols = scope.numOfStickyCols || 1;
4075 scope.viewportIndex = scope.numOfStickyCols;
4076 scope.countDisplayText = "";
4077 var tableElement = element.find('table');
4078 var thElements = element.find('th');
4079 var innerContainer = angular.element(element[0].querySelector('.b2b-horizontal-table-inner-container'));
4080 var outerContainer = angular.element(element[0].querySelector('.b2b-horizontal-table'));
4082 var tableColumns = [];
4083 var tableRows = element.find('tr');
4087 var totalWidth = element.children()[0].offsetWidth;
4088 var lastVisibleColumn = 0;
4089 var collectiveColumnWidth = [];
4090 var collectiveRowHeight = [];
4091 var columnSets = [];
4093 var stickyPixels = 0;
4095 var displayNoneCSS = {'display': 'none'};
4096 var displayBlockCSS = {'display': 'table-cell'};
4098 var init = function() {
4099 // Reset this from a previous execution
4101 collectiveColumnWidth = [];
4102 collectiveRowHeight = [];
4105 lastVisibleColumn = 0;
4107 if ((!!scope.retainColumnSet)) {
4110 visibleColumns = [];
4113 tableElement = element.find('table');
4114 thElements = element.find('th');
4115 innerContainer = angular.element(element[0].querySelector('.b2b-horizontal-table-inner-container'));
4116 outerContainer = angular.element(element[0].querySelector('.b2b-horizontal-table'));
4117 totalWidth = element.children()[0].offsetWidth;
4118 tableRows = element.find('tr');
4120 scope.disableLeft = true;
4121 scope.disableRight = false;
4123 if (scope.numOfStickyCols > b2bHorizontalTableConfig.maxStickyColumns) {
4124 throw new Error('Table can only support ' + b2bHorizontalTableConfig.maxStickyColumns + ' sticky columns.');
4126 scope.countDisplayText = "";
4127 for(var count = 1; count <= scope.numOfStickyCols; count++) {
4128 scope.countDisplayText = scope.countDisplayText + count + ", "
4130 angular.forEach(tableRows, function(row, rowIndex) {
4131 for(var j = 0; j < row.children.length; j++) {
4132 if (tableColumns[j] === undefined) {
4133 tableColumns[j] = [];
4135 tableColumns[j].push(row.children[j]);
4139 // We need to reset all the displayNones from previous runs, if applicable
4140 if (attrs.refresh !== undefined && attrs.refresh !== '') {
4141 for (var i = scope.numOfStickyCols+1; i < tableColumns.length; i++) {
4142 angular.element(tableColumns[i]).css(displayBlockCSS);
4146 // We must calculate here as we need cells to be reset after re-render.
4147 angular.forEach(tableRows, function(row, rowIndex) {
4148 collectiveRowHeight.push(findMax(row.children, 'height')); // BUG: Keeping this here now causes row height bugs
4152 for (var i = 0; i < tableColumns.length; i++) {
4153 collectiveColumnWidth.push(findMax(tableColumns[i], 'width')); //offsetWidth doesn't take into account custom css inside
4155 for(var i = 0; i < scope.numOfStickyCols; i++) {
4156 maxWidth += collectiveColumnWidth[i];
4159 // BUG: The code I put in to fix the table not spanning 100% width is now preventing
4160 // table cells from laying out more than stickyPixels and thus we have weird wrapping
4161 stickyPixels = totalWidth-maxWidth;
4163 // At this point, for each tr, I need to set the properties (height) and each numOfStickyCols children
4164 // should be set with sticky properties (margin-left and width)
4165 var width = maxWidth;
4168 if (angular.element(document).find('html').hasClass('isIE')) {
4171 var thObject = undefined;
4172 for(var i = 0; i < scope.numOfStickyCols; i++) {
4173 for (var j = 0; j < tableRows.length; j++) {
4174 thObject = angular.element(tableRows[j].children[i]);
4175 angular.element(thObject).css({
4176 'margin-left': -(width + 2) + 'px',
4177 'width': (collectiveColumnWidth[i] + 3) + 'px', // instead of taking the max width, grab max width for that column
4178 'height': (collectiveRowHeight[j] + additive) + 'px',
4179 'position': 'absolute',
4180 'background-color': '#F2F2F2'
4183 width -= collectiveColumnWidth[i];
4185 angular.element(tableRows[0]).css('height', collectiveRowHeight[0] + 'px');
4186 for(var i = 0; i < tableRows.length; i++) {
4187 angular.element(tableRows[i]).css('height', (collectiveRowHeight[i] + additive) + 'px');
4190 innerContainer.css({
4191 'padding-left': (maxWidth + 2) + 'px'
4195 // Let's precompute all the (set) combinations beforehand
4197 for (var i = scope.numOfStickyCols; i < tableColumns.length;) {
4198 visibleColumns = calculateVisibleColumns(i);
4199 if(visibleColumns === tableColumns.length){
4200 columnSets.push([i, visibleColumns-1]);
4202 columnSets.push([i, visibleColumns]);
4204 i = visibleColumns + 1;
4207 //columnSets = [[1, 1], [2,7]];
4209 updateCellDisplay(columnSets[setIndex]);
4210 checkScrollArrows();
4212 scope.numOfCols = tableColumns.length;
4216 // JM520E: This is a temporary hack until I solve the ngRepeat issue
4218 if (element.find('th').length < scope.numOfStickyCols) {
4219 // DOM ngRepeat is not ready, let's check back in 10 ms
4220 $timeout(hack, 10, false);
4222 if (scope.refresh !== undefined) {
4223 scope.$watch('refresh', function(oldVal, newVal) { // this watch calls too many times
4224 if (!angular.equals(oldVal, newVal)) { //hackFinished && oldVal < newVal
4225 // From testing it takes about 30 ms before ngRepeat executes, so let's set initial timeout
4226 // NOTE: May need to expose timeout to developers. Application is known to have digest cycle of 3-5k watches.
4227 $timeout(init, 100, false);
4228 scope.refresh = false;
4240 // Let's get started with some math!
4243 function calculateVisibleColumns(startingPoint) {
4245 visibleColumns = startingPoint || scope.numOfStickyCols;
4247 while(usedWidth < stickyPixels && visibleColumns < collectiveColumnWidth.length) {
4248 if (usedWidth+collectiveColumnWidth[visibleColumns] > stickyPixels) {
4249 if (startingPoint === visibleColumns) {
4250 return visibleColumns; // The next cell is too large to fit, it should be only thing to fit
4253 return visibleColumns;
4255 usedWidth += collectiveColumnWidth[visibleColumns];
4259 if (usedWidth > stickyPixels) {
4260 return --visibleColumns;
4262 return visibleColumns;
4265 function updateCellDisplay(set) {
4266 for (var i = scope.numOfStickyCols; i < tableColumns.length; i++) {
4267 angular.element(tableColumns[i]).css(displayNoneCSS);
4270 for (var i = set[0]; i <= set[1]; i++) {
4271 angular.element(tableColumns[i]).css(displayBlockCSS);
4275 function findMax(arr, prop) {
4280 for (var i = 0; i < arr.length; i++) {
4282 prevDisplay = angular.element(item).css('display');
4284 if (scope.$$phase) {
4287 // Remove inline styles, they will mess up calculations from original run
4288 angular.element(item).css('height', '');
4289 angular.element(item).css('width', '');
4290 if (prop === 'width') {
4291 // If we do not undo previous run's inline styles, this will grow widths on each re-render.
4292 localVal = Math.ceil(parseInt(window.getComputedStyle(item).width.split('px')[0], 10)) + 30; // 30 px is padding
4293 } else if (prop === 'offsetWidth') {
4294 localVal = item.offsetWidth;
4295 } else if (prop === 'height') {
4296 //localVal = item.offsetHeight;
4297 localVal = Math.ceil(parseInt(window.getComputedStyle(item).height.split('px')[0], 10))
4300 if (localVal >= max) {
4308 function checkScrollArrows() {
4309 scope.disableLeft = (setIndex === 0);
4310 scope.disableRight = !(setIndex < columnSets.length-1);
4313 scope.moveViewportLeft = function () {
4315 updateCellDisplay(columnSets[setIndex]);
4316 checkScrollArrows();
4318 if (scope.disableLeft) {
4319 element[0].querySelector('.b2b-horizontal-table-column-info').focus();
4323 scope.moveViewportRight = function () {
4325 updateCellDisplay(columnSets[setIndex]);
4326 checkScrollArrows();
4328 if (scope.disableRight) {
4329 element[0].querySelector('.b2b-horizontal-table-column-info').focus();
4333 scope.getColumnSet = function () {
4334 return columnSets[setIndex];
4337 innerContainer.bind('scroll', function () {
4338 $timeout(function () {
4339 checkScrollArrows();
4348 * @name Forms.att:hourPicker
4351 * <file src="src/hourPicker/docs/readme.md" />
4354 * <div b2b-hourpicker ng-model="hourpickerValue.value"></div>
4357 * <section id="code">
4358 <example module="b2b.att">
4359 <file src="src/hourPicker/docs/demo.html" />
4360 <file src="src/hourPicker/docs/demo.js" />
4365 angular.module('b2b.att.hourPicker', ['b2b.att.utilities'])
4367 .constant('b2bHourpickerConfig', {
4404 startTimeOptions: ['1:00', '2:00', '3:00', '4:00', '5:00', '6:00', '7:00', '8:00', '9:00', '10:00', '11:00', '12:00'],
4405 startTimeDefaultOptionIndex: -1,
4406 startTimeDefaultMeridiem: "am",
4407 endTimeOptions: ['1:00', '2:00', '3:00', '4:00', '5:00', '6:00', '7:00', '8:00', '9:00', '10:00', '11:00', '12:00'],
4408 endTimeDefaultOptionIndex: -1,
4409 endTimeDefaultMeridiem: "pm",
4413 .factory('b2bNormalizeHourpickerValues', [function () {
4414 var _normalize = function (hourpickerValues) {
4415 if (angular.isDefined(hourpickerValues) && hourpickerValues != null) {
4416 var finalHourpickerValues = [];
4417 var hourpickerValue = {};
4419 for (var i = 0; i < hourpickerValues.length; i++) {
4420 days = hourpickerValues[i].days ? hourpickerValues[i].days : {};
4421 hourpickerValue.startTime = hourpickerValues[i].startTime ? hourpickerValues[i].startTime : '';
4422 hourpickerValue.startMeridiem = hourpickerValues[i].startMeridiem ? hourpickerValues[i].startMeridiem : '';
4423 hourpickerValue.endTime = hourpickerValues[i].endTime ? hourpickerValues[i].endTime : '';
4424 hourpickerValue.endMeridiem = hourpickerValues[i].endMeridiem ? hourpickerValues[i].endMeridiem : '';
4425 hourpickerValue.days = [];
4427 var retrieveDaysText = function (daysDetails) {
4432 for (var i in days) {
4433 if (days[i].value) {
4438 first = daysTexts[0];
4439 last = daysTexts[0];
4441 hourpickerValue.days[index] = days[first].caption;
4442 if (daysTexts.length > 1) {
4443 for (var i = 1; i < daysTexts.length; i++) {
4444 if (daysTexts[i] - last === 1) {
4445 last = daysTexts[i];
4446 hourpickerValue.days[index] = days[first].caption + ' - ' + days[last].caption;
4449 first = last = daysTexts[i];
4450 hourpickerValue.days[index] = days[first].caption;
4457 finalHourpickerValues.push(angular.copy(hourpickerValue));
4460 return angular.copy(finalHourpickerValues);
4465 normalize: _normalize
4469 .directive('b2bHourpicker', ['b2bHourpickerConfig', 'b2bNormalizeHourpickerValues', function (b2bHourpickerConfig, b2bNormalizeHourpickerValues) {
4475 templateUrl: 'b2bTemplate/hourPicker/b2bHourpicker.html',
4476 controller: ['$scope', function (scope) {
4479 link: function (scope, elem, attr, ctrl) {
4480 scope.hourpicker = {};
4481 scope.hourpicker.dayOptions = attr.dayOptions ? scope.$parent.$eval(attr.dayOptions) : b2bHourpickerConfig.dayOptions;
4482 scope.hourpicker.startTimeOptions = attr.startTimeOptions ? scope.$parent.$eval(attr.startTimeOptions) : b2bHourpickerConfig.startTimeOptions;
4483 scope.hourpicker.endTimeOptions = attr.endTimeOptions ? scope.$parent.$eval(attr.endTimeOptions) : b2bHourpickerConfig.endTimeOptions;
4484 scope.hourpicker.startTimeDefaultOptionIndex = attr.startTimeDefaultOptionIndex ? scope.$parent.$eval(attr.startTimeDefaultOptionIndex) : b2bHourpickerConfig.startTimeDefaultOptionIndex;
4485 scope.hourpicker.endTimeDefaultOptionIndex = attr.endTimeDefaultOptionIndex ? scope.$parent.$eval(attr.endTimeDefaultOptionIndex) : b2bHourpickerConfig.endTimeDefaultOptionIndex;
4486 scope.hourpicker.startTimeDefaultMeridiem = attr.startTimeDefaultMeridiem ? scope.$parent.$eval(attr.startTimeDefaultMeridiem) : b2bHourpickerConfig.startTimeDefaultMeridiem;
4487 scope.hourpicker.endTimeDefaultMeridiem = attr.endTimeDefaultMeridiem ? scope.$parent.$eval(attr.endTimeDefaultMeridiem) : b2bHourpickerConfig.endTimeDefaultMeridiem;
4488 scope.hourpicker.sameDayOption = attr.sameDayOption ? scope.$parent.$eval(attr.sameDayOption) : b2bHourpickerConfig.sameDayOption;
4489 scope.hourpicker.editMode = -1;
4491 scope.hourpickerValues = [];
4492 scope.finalHourpickerValues = [];
4493 scope.addHourpickerValue = function (hourpickerPanelValue) {
4494 if (hourpickerPanelValue) {
4495 if (scope.hourpicker.editMode > -1) {
4496 scope.hourpickerValues[scope.hourpicker.editMode] = hourpickerPanelValue;
4497 scope.hourpicker.editMode = -1;
4499 scope.hourpickerValues.push(hourpickerPanelValue);
4502 scope.finalHourpickerValues = b2bNormalizeHourpickerValues.normalize(angular.copy(scope.hourpickerValues));
4503 ctrl.$setViewValue(angular.copy(scope.hourpickerValues));
4505 ctrl.$render = function () {
4506 if (angular.isDefined(ctrl.$modelValue)) {
4507 scope.hourpickerValues = angular.copy(ctrl.$modelValue);
4508 scope.finalHourpickerValues = b2bNormalizeHourpickerValues.normalize(angular.copy(scope.hourpickerValues));
4511 scope.editHourpickerValue = function (index) {
4512 scope.hourpickerPanelValue = angular.copy(scope.hourpickerValues[index]);
4513 scope.hourpicker.editMode = index;
4515 scope.deleteHourpickerValue = function (index) {
4516 scope.hourpickerValues.splice(index, 1);
4517 scope.resetHourpickerPanelValue();
4518 scope.addHourpickerValue();
4521 scope.setValidity = function (errorType, errorValue) {
4522 ctrl.$setValidity(errorType, errorValue);
4528 .directive('b2bHourpickerPanel', [function () {
4532 templateUrl: 'b2bTemplate/hourPicker/b2bHourpickerPanel.html',
4533 controller: ['$scope', function (scope) {
4536 link: function (scope, elem, attr, ctrl) {
4537 var hourpickerPanelValueTemplate = {
4540 startMeridiem: 'am',
4544 for (var i = 0; i < scope.hourpicker.dayOptions.length; i++) {
4545 hourpickerPanelValueTemplate.days[i] = {
4547 title: scope.hourpicker.dayOptions[i].title,
4548 caption: scope.hourpicker.dayOptions[i].caption
4551 scope.hourpickerPanelValue = {};
4552 scope.disableAddBtn = true;
4554 scope.$watch('hourpickerPanelValue.days', function(){
4555 for(var i in scope.hourpickerPanelValue.days)
4557 if(scope.hourpickerPanelValue.days[i].value)
4559 scope.disableAddBtn = false;
4562 scope.disableAddBtn = true;
4566 scope.resetHourpickerPanelValue = function () {
4567 scope.hourpickerPanelValue = angular.copy(hourpickerPanelValueTemplate);
4568 if (scope.hourpicker.startTimeDefaultOptionIndex > -1) {
4569 scope.hourpickerPanelValue.startTime = scope.hourpicker.startTimeOptions[scope.hourpicker.startTimeDefaultOptionIndex];
4571 if (scope.hourpicker.endTimeDefaultOptionIndex > -1) {
4572 scope.hourpickerPanelValue.endTime = scope.hourpicker.endTimeOptions[scope.hourpicker.endTimeDefaultOptionIndex];
4574 scope.hourpickerPanelValue.startMeridiem = scope.hourpicker.startTimeDefaultMeridiem;
4575 scope.hourpickerPanelValue.endMeridiem = scope.hourpicker.endTimeDefaultMeridiem;
4576 scope.hourpicker.editMode = -1;
4577 scope.setValidity('invalidHourpickerData', true);
4578 scope.setValidity('invalidHourpickerTimeRange', true);
4580 scope.resetHourpickerPanelValue();
4581 scope.updateHourpickerValue = function () {
4582 if (scope.isFormValid() && !scope.isTimeOverlap()) {
4583 scope.addHourpickerValue(angular.copy(scope.hourpickerPanelValue));
4584 scope.resetHourpickerPanelValue();
4588 scope.isFormValid = function () {
4589 var isStartTimeAvailable = scope.hourpickerPanelValue.startTime ? true : false;
4590 var isStartMeridiemAvailable = scope.hourpickerPanelValue.startMeridiem ? true : false;
4591 var isEndTimeAvailable = scope.hourpickerPanelValue.endTime ? true : false;
4592 var isEndMeridiemAvailable = scope.hourpickerPanelValue.endMeridiem ? true : false;
4593 var currentStartTime = getTime(scope.hourpickerPanelValue.startTime, scope.hourpickerPanelValue.startMeridiem);
4594 var currentEndTime = getTime(scope.hourpickerPanelValue.endTime, scope.hourpickerPanelValue.endMeridiem);
4595 var isTimeInProperSequence = currentEndTime > currentStartTime;
4596 var isDayChecked = false;
4597 for (var i in scope.hourpickerPanelValue.days) {
4598 if (scope.hourpickerPanelValue.days[i].value) {
4599 isDayChecked = true;
4604 if (isStartTimeAvailable && isStartMeridiemAvailable && isEndTimeAvailable && isEndMeridiemAvailable && isTimeInProperSequence && isDayChecked) {
4605 scope.setValidity('invalidHourpickerData', true);
4608 scope.setValidity('invalidHourpickerData', false);
4612 scope.isTimeOverlap = function () {
4613 var selectedDays = [];
4614 for (var i in scope.hourpickerPanelValue.days) {
4615 if (scope.hourpickerPanelValue.days[i].value) {
4616 selectedDays.push(i);
4620 var currentStartTime, currentEndTime, existingStartTime, existingEndTime;
4621 currentStartTime = getTime(scope.hourpickerPanelValue.startTime, scope.hourpickerPanelValue.startMeridiem);
4622 currentEndTime = getTime(scope.hourpickerPanelValue.endTime, scope.hourpickerPanelValue.endMeridiem);
4623 for (var i = 0; i < scope.hourpickerValues.length; i++) {
4625 if (i === scope.hourpicker.editMode) {
4629 for (var j = 0; j < selectedDays.length; j++) {
4630 existingStartTime = getTime(scope.hourpickerValues[i].startTime, scope.hourpickerValues[i].startMeridiem);
4631 existingEndTime = getTime(scope.hourpickerValues[i].endTime, scope.hourpickerValues[i].endMeridiem);
4632 if (scope.hourpickerValues[i].days[selectedDays[j]].value) {
4633 if(!scope.hourpicker.sameDayOption){
4634 scope.setValidity('dayAlreadySelected', false);
4636 } else if ((currentStartTime > existingStartTime && currentStartTime < existingEndTime) || (currentEndTime > existingStartTime && currentEndTime < existingEndTime)) {
4637 scope.setValidity('invalidHourpickerTimeRange', false);
4639 } else if ((existingStartTime > currentStartTime && existingStartTime < currentEndTime) || (existingEndTime > currentStartTime && existingEndTime < currentEndTime)) {
4640 scope.setValidity('invalidHourpickerTimeRange', false);
4642 } else if ((currentStartTime === existingStartTime) && (currentEndTime === existingEndTime)) {
4643 scope.setValidity('invalidHourpickerTimeRange', false);
4650 scope.setValidity('dayAlreadySelected', true);
4651 scope.setValidity('invalidHourpickerTimeRange', true);
4654 var getTime = function (timeString, meridiem) {
4655 var tempDate = new Date();
4656 if (timeString && meridiem) {
4657 var timeSplit = timeString.split(':');
4658 var hour = ((meridiem === 'PM' || meridiem === 'pm') && timeSplit[0] !== '12') ? parseInt(timeSplit[0], 10) + 12 : parseInt(timeSplit[0], 10);
4659 tempDate.setHours(hour, parseInt(timeSplit[1], 10), 0, 0);
4662 return tempDate.getTime();
4668 .directive('b2bHourpickerValue', [function () {
4672 templateUrl: 'b2bTemplate/hourPicker/b2bHourpickerValue.html',
4673 controller: ['$scope', function (scope) {
4676 link: function (scope, elem, attr, ctrl) {
4677 scope.hourpickerValue = {};
4678 scope.hourpickerValue.startTime = attr.startTime ? scope.$eval(attr.startTime) : '';
4679 scope.hourpickerValue.startMeridiem = attr.startMeridiem ? scope.$eval(attr.startMeridiem) : '';
4680 scope.hourpickerValue.endTime = attr.endTime ? scope.$eval(attr.endTime) : '';
4681 scope.hourpickerValue.endMeridiem = attr.endMeridiem ? scope.$eval(attr.endMeridiem) : '';
4682 scope.hourpickerValue.days = attr.days ? scope.$eval(attr.days).join(', ') : '';
4683 scope.hourpickerValue.index = attr.b2bHourpickerValue ? scope.$eval(attr.b2bHourpickerValue) : -1;
4689 * @name Template.att:inputTemplate
4692 * <file src="src/inputTemplate/docs/readme.md" />
4695 * <input type="text" id="fieldId" placeholder="placholder text here" class="span12 input-enhanced" name="fieldName">
4699 <b>HTML + AngularJS</b>
4700 <example module="b2b.att">
4701 <file src="src/inputTemplate/docs/demo.html" />
4702 <file src="src/inputTemplate/docs/demo.js" />
4706 angular.module('b2b.att.inputTemplate', []);
4710 * @name Navigation.att:leftNavigation
4713 * <file src="src/leftNavigation/docs/readme.md" />
4716 * <b2b-left-navigation data-menu="menuData"></b2b-left-navigation>
4719 * <section id="code">
4720 <example module="b2b.att">
4721 <file src="src/leftNavigation/docs/demo.html" />
4722 <file src="src/leftNavigation/docs/demo.js" />
4727 angular.module('b2b.att.leftNavigation', [])
4728 .directive('b2bLeftNavigation', [function () {
4731 templateUrl: 'b2bTemplate/leftNavigation/leftNavigation.html',
4735 link: function (scope, element, attrs, ctrl) {
4739 scope.toggleNav = function (val) {
4740 if (val === scope.idx) {
4746 scope.liveLink = function (evt, val1, val2) {
4747 scope.itemIdx = val1;
4748 scope.navIdx = val2;
4749 evt.stopPropagation();
4756 * @name Buttons, links & UI controls.att:links
4759 * <file src="src/links/docs/readme.md" />
4761 * <!-- See below examples for link implementation -->
4765 <b>HTML + AngularJS</b>
4766 <example module="b2b.att">
4767 <file src="src/links/docs/demo.html" />
4768 <file src="src/links/docs/demo.js" />
4772 angular.module('b2b.att.links', []);
4775 * @name Misc.att:listbox
4778 * <file src="src/listbox/docs/readme.md" />
4780 * @param {int} currentIndex - Current index of selected listbox item. Is not supported on multiselect listbox
4781 * @param {Array} listboxData - Data of listbox items. Should include full data regardless if HTML will be filtered.
4784 * <section id="code">
4785 <example module="b2b.att">
4786 <file src="src/listbox/docs/demo.html" />
4787 <file src="src/listbox/docs/demo.js" />
4792 angular.module('b2b.att.listbox', ['b2b.att.utilities'])
4793 .directive('b2bListBox', ['keymap', 'b2bDOMHelper', '$rootScope', function(keymap, b2bDOMHelper, $rootScope) {
4802 templateUrl: 'b2bTemplate/listbox/listbox.html',
4803 link: function(scope, elem, attr) {
4805 if (attr.ariaMultiselectable !== undefined || attr.ariaMultiselectable === 'true') {
4806 scope.multiselectable = true;
4808 scope.multiselectable = false;
4811 var shiftKey = false;
4813 var prevDirection = undefined; // previous direction is used for an edge case when shifting
4814 var shiftKeyPressed = false; // Used to handle shift clicking
4815 var ctrlKeyPressed = false;
4817 var currentIndexSet = {
4819 'listboxDataIndex': 0
4822 function isTrue(item) {
4823 if (item.selected === true) {
4828 function incrementIndex(elem) {
4829 $rootScope.$apply();
4831 var nextElem = elem.next();
4832 if (!angular.isDefined(nextElem) || nextElem.length === 0) {
4836 currentIndexSet.elementIndex += 1;
4837 currentIndexSet.listboxDataIndex = parseInt(nextElem.attr('data-index'), 10);
4838 scope.currentIndex = currentIndexSet.listboxDataIndex;
4840 if (currentIndexSet.elementIndex >= elements.length - 1) {
4841 currentIndexSet.elementIndex = elements.length-1;
4845 function decrementIndex(elem) {
4846 $rootScope.$apply();
4847 var prevElem = angular.element(b2bDOMHelper.previousElement(elem));
4848 if (!angular.isDefined(prevElem) || prevElem.length === 0) {
4852 currentIndexSet.elementIndex -= 1;
4853 currentIndexSet.listboxDataIndex = parseInt(prevElem.attr('data-index'), 10);
4854 scope.currentIndex = currentIndexSet.listboxDataIndex;
4856 if (currentIndexSet.elementIndex <= 0) {
4857 currentIndexSet.elementIndex = 0;
4861 var focusOnElement = function(index) {
4863 elements[index].focus();
4867 function selectItems(startIndex, endIndex, forceValue) {
4868 for (var i = startIndex; i < endIndex; i++) {
4869 if (forceValue === undefined) {
4870 // We will flip the value
4871 scope.listboxData[i].selected = !scope.listboxData[i].selected;
4873 scope.listboxData[i].selected = forceValue;
4877 if (!scope.$$phase) {
4882 elem.bind('focus', function(evt) {
4883 // If multiselectable or not and nothing is selected, put focus on first element
4884 // If multiselectable and a range is set, put focus on first element of range
4885 // If not multiselectable and something selected, put focus on element
4886 elements = elem.children();
4887 var selectedItems = scope.listboxData.filter(isTrue);
4888 var elementsIndies = Array.prototype.map.call(elements, function(item) {
4889 return parseInt(angular.element(item).attr('data-index'), 10);
4892 if (selectedItems.length == 0) {
4894 currentIndexSet.listboxDataIndex = 0;
4895 } else if (attr.ariaMultiselectable) {
4896 var index = scope.listboxData.indexOf(selectedItems[0]);
4897 var indies = elementsIndies.filter(function(item) {
4898 return (item === index);
4901 if (indies.length === 0 || indies[0] != index) {
4903 currentIndexSet.elementIndex = elementsIndies[0];
4904 currentIndexSet.listboxDataIndex = 0;
4905 focusOnElement(currentIndexSet.elementIndex);
4907 focusOnElement(indies[0]);
4908 currentIndexSet.elementIndex = indies[0];
4909 currentIndexSet.listboxDataIndex = index;
4912 focusOnElement(currentIndexSet.elementIndex);
4914 scope.currentIndex = currentIndexSet.listboxDataIndex;
4916 if (!scope.$$phase) {
4920 elem.bind('keyup', function(evt) {
4921 if (evt.keyCode === keymap.KEY.SHIFT) {
4922 shiftKeyPressed = false;
4923 } else if (evt.keyCode === keymap.KEY.CTRL) {
4924 ctrlKeyPressed = false;
4928 elem.bind('keydown', function(evt) {
4929 var keyCode = evt.keyCode;
4930 elements = elem.children();
4931 if (keyCode === keymap.KEY.SHIFT) {
4932 shiftKeyPressed = true;
4933 } else if (evt.keyCode === keymap.KEY.CTRL) {
4934 ctrlKeyPressed = true;
4940 if (scope.multiselectable && evt.ctrlKey) {
4941 var arr = scope.listboxData.filter(isTrue);
4942 var elementsIndies = Array.prototype.map.call(elements, function(item) {
4943 return parseInt(angular.element(item).attr('data-index'), 10);
4945 var val = !(arr.length === scope.listboxData.length);
4946 for (var i = 0; i < elementsIndies.length; i++) {
4947 scope.listboxData[elementsIndies[i]].selected = val;
4950 if (!scope.$$phase) {
4954 evt.preventDefault();
4955 evt.stopPropagation();
4959 case keymap.KEY.END:
4961 if (scope.multiselectable && evt.ctrlKey && evt.shiftKey) {
4962 var elementsIndies = Array.prototype.map.call(elements, function(item) {
4963 return parseInt(angular.element(item).attr('data-index'), 10);
4964 }).filter(function(item) {
4965 return (item >= currentIndexSet.listboxDataIndex);
4967 for (var i = 0; i < elementsIndies.length; i++) {
4968 scope.listboxData[elementsIndies[i]].selected = true;
4970 evt.preventDefault();
4971 evt.stopPropagation();
4973 if (!scope.$$phase) {
4979 case keymap.KEY.HOME:
4981 if (scope.multiselectable && evt.ctrlKey && evt.shiftKey) {
4982 selectItems(0, currentIndexSet.listboxDataIndex+1, true); // currentIndex+1 is what is being focused on
4983 evt.preventDefault();
4984 evt.stopPropagation();
4988 case keymap.KEY.LEFT:
4991 if (currentIndexSet.listboxDataIndex === 0) {
4992 evt.preventDefault();
4993 evt.stopPropagation();
4997 decrementIndex(elements.eq(currentIndexSet.elementIndex));
4998 if (scope.multiselectable && (evt.shiftKey || evt.ctrlKey)) {
5000 if (prevDirection === 'DOWN') {
5001 scope.listboxData[currentIndexSet.listboxDataIndex+1].selected = !scope.listboxData[currentIndexSet.listboxDataIndex+1].selected;
5003 scope.listboxData[currentIndexSet.listboxDataIndex].selected = !scope.listboxData[currentIndexSet.listboxDataIndex].selected;
5005 prevDirection = 'UP';
5007 // If no modifier keys are selected, all other items need to be unselected.
5008 prevDirection = undefined;
5009 selectItems(0, scope.listboxData.length, false);
5010 if(currentIndexSet.listboxDataIndex !== undefined && !isNaN(currentIndexSet.listboxDataIndex)){
5011 scope.listboxData[currentIndexSet.listboxDataIndex].selected = true;
5014 focusOnElement(currentIndexSet.elementIndex);
5015 if(!scope.$$phase) {
5018 evt.preventDefault();
5019 evt.stopPropagation();
5022 case keymap.KEY.RIGHT:
5023 case keymap.KEY.DOWN:
5025 if (currentIndexSet.listboxDataIndex === scope.listboxData.length-1) {
5026 evt.preventDefault();
5027 evt.stopPropagation();
5031 incrementIndex(elements.eq(currentIndexSet.elementIndex));
5033 if (scope.multiselectable && (evt.shiftKey || evt.ctrlKey)) {
5035 if (prevDirection === 'UP') {
5036 scope.listboxData[currentIndexSet.listboxDataIndex-1].selected = !scope.listboxData[currentIndexSet.listboxDataIndex-1].selected;
5039 scope.listboxData[currentIndexSet.listboxDataIndex].selected = !scope.listboxData[currentIndexSet.listboxDataIndex].selected;
5041 prevDirection = 'DOWN';
5043 // If no modifier keys are selected, all other items need to be unselected.
5044 prevDirection = undefined;
5045 selectItems(0, scope.listboxData.length, false);
5046 if(currentIndexSet.listboxDataIndex !== undefined && !isNaN(currentIndexSet.listboxDataIndex)){
5047 scope.listboxData[currentIndexSet.listboxDataIndex].selected = true;
5051 focusOnElement(currentIndexSet.elementIndex);
5052 if(!scope.$$phase) {
5055 evt.preventDefault();
5056 evt.stopPropagation();
5059 case keymap.KEY.TAB:
5061 var previousElement = b2bDOMHelper.previousElement(elem.parent().parent(), true);
5062 evt.preventDefault();
5063 previousElement.focus();
5071 elem.bind('click', function(evt) {
5072 var index = parseInt(evt.target.dataset.index, 10);
5073 if (index === undefined || isNaN(index)) {
5076 if (scope.multiselectable && currentIndexSet.listboxDataIndex !== undefined) {
5077 if (shiftKeyPressed) {
5078 var min = Math.min(index, currentIndexSet.listboxDataIndex);
5079 var max = Math.max(index, currentIndexSet.listboxDataIndex);
5081 if (index === min) { // clicking up
5082 var firstIndex = scope.listboxData.findIndex(function(item) { return item.selected === true;});
5083 // Given the firstIndex, let's find the matching element to get proper element match
5084 elements = elem.children();
5085 elements.eq(firstIndex)
5086 var elementsThatMatch = Array.prototype.filter.call(elements, function(item) {
5087 if (parseInt(angular.element(item).attr('data-index'), 10) === firstIndex) {
5091 firstIndex = parseInt(angular.element(elementsThatMatch).attr('data-index'), 10);
5093 if (index <= firstIndex && scope.listboxData.filter(isTrue).length > 1) {
5094 // Break the selection into 2
5095 selectItems(firstIndex + 1, max + 1, undefined); // + 1 needed because selectItems only selects up to MAX
5096 selectItems(min, firstIndex, undefined);
5097 } else if (scope.listboxData.filter(isTrue).length == 1){
5098 selectItems(min, max, undefined);
5100 selectItems(min + 1, max + 1, undefined);
5102 } else { // clicking down
5103 selectItems(min + 1, max + 1, scope.listboxData[min].selected);
5105 } else if (ctrlKeyPressed) {
5106 scope.listboxData[index].selected = !scope.listboxData[index].selected;
5108 selectItems(0, scope.listboxData.length, false);
5109 scope.listboxData[index].selected = !scope.listboxData[index].selected;
5112 selectItems(0, scope.listboxData.length, false);
5113 scope.listboxData[index].selected = !scope.listboxData[index].selected;
5115 currentIndexSet.elementIndex = index;
5116 currentIndexSet.listboxDataIndex = index;
5117 scope.currentIndex = currentIndexSet.listboxDataIndex;
5118 if (!scope.$$phase) {
5121 focusOnElement(index);
5128 * @name Videos, audio & animation.att:loaderAnimation
5131 * <file src="src/loaderAnimation/docs/readme.md" />
5134 * <!-- Below demo js shows-->
5135 * Angular library uses Global.css's icon-primary-spinner.
5138 * <section id="code">
5139 <example module="b2b.att">
5140 <file src="src/loaderAnimation/docs/demo.html" />
5141 <file src="src/loaderAnimation/docs/demo.js" />
5146 angular.module('b2b.att.loaderAnimation', [])
5147 .constant('b2bSpinnerConfig', {
5148 loadingText: 'Loading...',
5149 startEvent: 'startButtonSpinner',
5150 stopEvent: 'stopButtonSpinner'
5152 .constant("progressTrackerConfig", {
5153 loadingText: 'Loading...',
5155 activationDelay: "",
5156 minDurationPromise: "",
5157 activationDelayPromise: ""
5160 .provider('progressTracker', function () {
5161 this.$get = ['$q', '$timeout', function ($q, $timeout) {
5162 function cancelTimeout(promise) {
5164 $timeout.cancel(promise);
5167 return function ProgressTracker(options) {
5168 //do new if user doesn't
5169 if (!(this instanceof ProgressTracker)) {
5170 return new ProgressTracker(options);
5173 options = options || {};
5174 //Array of promises being tracked
5177 //Allow an optional "minimum duration" that the tracker has to stay active for.
5178 var minDuration = options.minDuration;
5179 //Allow a delay that will stop the tracker from activating until that time is reached
5180 var activationDelay = options.activationDelay;
5181 var minDurationPromise;
5182 var activationDelayPromise;
5183 self.active = function () {
5184 //Even if we have a promise in our tracker, we aren't active until delay is elapsed
5185 if (activationDelayPromise) {
5188 return tracked.length > 0;
5190 self.tracking = function () {
5191 //Even if we aren't active, we could still have a promise in our tracker
5192 return tracked.length > 0;
5194 self.destroy = self.cancel = function () {
5195 minDurationPromise = cancelTimeout(minDurationPromise);
5196 activationDelayPromise = cancelTimeout(activationDelayPromise);
5197 for (var i = tracked.length - 1; i >= 0; i--) {
5198 tracked[i].resolve();
5202 //Create a promise that will make our tracker active until it is resolved.
5203 // @return deferred - our deferred object that is being tracked
5204 self.createPromise = function () {
5205 var deferred = $q.defer();
5206 tracked.push(deferred);
5207 //If the tracker was just inactive and this the first in the list of promises, we reset our delay and minDuration again.
5208 if (tracked.length === 1) {
5209 if (activationDelay) {
5210 activationDelayPromise = $timeout(function () {
5211 activationDelayPromise = cancelTimeout(activationDelayPromise);
5213 }, activationDelay);
5218 deferred.promise.then(onDone(false), onDone(true));
5221 function startMinDuration() {
5223 minDurationPromise = $timeout(angular.noop, minDuration);
5226 //Create a callback for when this promise is done. It will remove our tracked promise from the array if once minDuration is complete
5228 return function () {
5229 (minDurationPromise || $q.when()).then(function () {
5230 var index = tracked.indexOf(deferred);
5231 tracked.splice(index, 1);
5232 //If this is the last promise, cleanup the timeouts for activationDelay
5233 if (tracked.length === 0) {
5234 activationDelayPromise = cancelTimeout(activationDelayPromise);
5240 self.addPromise = function (promise) {
5242 // we cannot assign then function in other var and then add the resolve and reject
5243 var thenFxn = promise && (promise.then || promise.$then || (promise.$promise && promise.$promise.then));
5245 throw new Error("progressTracker expects a promise object :: Not found");
5247 var deferred = self.createPromise();
5248 //When given promise is done, resolve our created promise
5249 //Allow $then for angular-resource objects
5251 promise.then(function (value) {
5252 deferred.resolve(value);
5254 }, function (value) {
5255 deferred.reject(value);
5256 return $q.reject(value);
5265 .config(['$httpProvider', function ($httpProvider) {
5266 $httpProvider.interceptors.push(['$q', 'progressTracker', function ($q) {
5268 request: function (config) {
5269 if (config.tracker) {
5270 if (!angular.isArray(config.tracker)) {
5271 config.tracker = [config.tracker];
5273 config.$promiseTrackerDeferred = config.$promiseTrackerDeferred || [];
5275 angular.forEach(config.tracker, function (tracker) {
5276 var deferred = tracker.createPromise();
5277 config.$promiseTrackerDeferred.push(deferred);
5280 return $q.when(config);
5282 response: function (response) {
5283 if (response.config && response.config.$promiseTrackerDeferred) {
5284 angular.forEach(response.config.$promiseTrackerDeferred, function (deferred) {
5285 deferred.resolve(response);
5288 return $q.when(response);
5290 responseError: function (response) {
5291 if (response.config && response.config.$promiseTrackerDeferred) {
5292 angular.forEach(response.config.$promiseTrackerDeferred, function (deferred) {
5293 deferred.reject(response);
5296 return $q.reject(response);
5302 .directive('b2bClickSpin', ['$timeout', '$parse', '$rootScope', 'progressTracker', function ($timeout, $parse, $rootScope, progressTracker) {
5305 link: function (scope, elm, attrs) {
5306 var fn = $parse(attrs.b2bClickSpin);
5307 elm.on('click', function (event) {
5308 var promise = $timeout(function () {console.log("inside Promise")}, 5000);
5309 scope.$apply(function () {
5314 //comment this line if not running unit test
5315 $rootScope.loadingTracker = progressTracker({
5318 $rootScope.loadingTracker.addPromise(promise);
5319 angular.forEach("$routeChangeSuccess $viewContentLoaded $locationChangeSuccess".split(" "), function (event) {
5320 $rootScope.$on(event, function () {
5322 $timeout.cancel(promise);
5330 .directive('b2bProgressTracker', ['progressTrackerConfig', function (ptc) {
5334 template: '<div><div ng-show="loadingTracker.active()" style="width:100%; text-align:center"><i class=\"icon-primary-spinner\"></i></div><div ng-show="loadingTracker.active()" style="width:100%;margin-top:10px; text-align:center">'+ ptc.loadingText+'</div></div>'
5338 .directive('b2bLoadButton', ['b2bSpinnerConfig', '$timeout', function (spinnerConfig, $timeout) {
5339 var spinButton = function (state, element, data) {
5341 var attr = element.html() ? 'html' : 'val';
5342 state = state + 'Text';
5343 if (state === 'loadingText') {
5344 element[attr](data[state]);
5345 element.attr("disabled",'disabled');
5346 element.addClass('disabled');
5347 } else if (state === 'resetText') {
5348 element[attr](data[state]);
5349 element.removeAttr("disabled");
5350 element.removeClass('disabled');
5358 promise: '=promise',
5359 startEvent: '@startEvent',
5360 stopEvent: '@stopEvent'
5362 link: function (scope, element, attr) {
5363 var validAttr = element.html() ? 'html' : 'val';
5369 var updateLoadingText = function (val) {
5370 var loadingText = val;
5371 if (!angular.isDefined(loadingText) || loadingText === "") {
5372 loadingText = spinnerConfig.loadingText;
5374 data.loadingText = validAttr === 'html' ? "<i class=\"icon-primary-spinner small\"></i>" + loadingText : loadingText;
5376 var updateResetText = function (val) {
5377 data.resetText = val;
5380 attr.$observe('b2bLoadButton', function (val) {
5381 updateLoadingText(val);
5383 $timeout(function () {
5384 updateResetText(element[validAttr]());
5387 if (!angular.isDefined(scope.startEvent) || scope.startEvent === "") {
5388 scope.startEvent = spinnerConfig.startEvent;
5391 if (!angular.isDefined(scope.stopEvent) || scope.stopEvent === "") {
5392 scope.stopEvent = spinnerConfig.stopEvent;
5395 scope.$watch('promise', function () {
5396 if (angular.isDefined(scope.promise) && angular.isFunction(scope.promise.then)) {
5397 spinButton('loading', element, data);
5398 scope.promise.then(function () {
5399 spinButton('reset', element, data);
5401 spinButton('reset', element, data);
5406 scope.$on(scope.startEvent, function () {
5407 spinButton('loading', element, data);
5408 scope.$on(scope.stopEvent, function () {
5409 spinButton('reset', element, data);
5417 * @name Misc.att:messageWrapper
5419 * @param {boolean} trigger - A boolean that triggers directive to switch focus
5420 * @param {integer} delay - Extra delay added to trigger code to allow for DOM to be ready. Default is 10ms.
5421 * @param {string} noFocus - Attribute-based API to trigger whether first focusable element receives focus on trigger or whole message (assumes tabindex="-1" set on first child)
5422 * @param {string} trapFocus - Attribute-based API to trap focus within the message. This should be enabled by default on all toast messages.
5424 * <file src="src/messageWrapper/docs/readme.md" />
5426 * <b2b-message-wrapper>Code that contains at least one focusable element and will be shown/hidden on some logic. This must have tabindex="-1".</b2b-message-wrapper>
5429 * <section id="code">
5430 <b>HTML + AngularJS</b>
5431 <example module="b2b.att">
5432 <file src="src/messageWrapper/docs/demo.html" />
5433 <file src="src/messageWrapper/docs/demo.js" />
5438 angular.module('b2b.att.messageWrapper', ['b2b.att.utilities'])
5439 .directive('b2bMessageWrapper', ['b2bDOMHelper', '$compile', '$timeout', '$log', function(b2bDOMHelper, $compile, $timeout, $log) {
5448 template: '<div ng-transclude></div>',
5449 link: function(scope, elem, attrs) {
5450 scope.delay = scope.delay || 10;
5452 if (attrs.trapFocus != undefined && !elem.children().eq(0).attr('b2b-trap-focus-inside-element')) {
5453 // Append b2bTrapFocusInsideElement onto first child and recompile
5454 elem.children().eq(0).attr('b2b-trap-focus-inside-element', 'false');
5455 elem.children().eq(0).attr('trigger', scope.trigger);
5456 $compile(elem.contents())(scope);
5459 var firstElement = undefined,
5460 launchingElement = undefined;
5462 scope.$watch('trigger', function(oldVal, newVal) {
5463 if (oldVal === newVal) return;
5464 if (!angular.isDefined(launchingElement)) {
5465 launchingElement = document.activeElement;
5467 $timeout(function() {
5468 if (scope.trigger) {
5470 if (attrs.noFocus === true || attrs.noFocus === "") {
5471 elem.children()[0].focus();
5473 firstElement = b2bDOMHelper.firstTabableElement(elem);
5475 if (angular.isDefined(firstElement)) {
5476 firstElement.focus();
5481 if (angular.isDefined(launchingElement) && launchingElement.nodeName !== 'BODY') {
5482 if (launchingElement === document.activeElement) {
5486 if (b2bDOMHelper.isInDOM(launchingElement) && b2bDOMHelper.isTabable(launchingElement)) {
5487 // At this point, launchingElement is still a valid element, but focus will fail and
5488 // activeElement will become body, hence we want to apply custom logic and find previousElement
5489 var prevLaunchingElement = launchingElement;
5490 launchingElement.focus();
5492 if (document.activeElement !== launchingElement || document.activeElement.nodeName === 'BODY') {
5493 launchingElement = b2bDOMHelper.previousElement(angular.element(prevLaunchingElement), true);
5494 launchingElement.focus();
5497 launchingElement = b2bDOMHelper.previousElement(launchingElement, true);
5498 launchingElement.focus();
5509 * @name Messages, modals & alerts.att:modalsAndAlerts
5512 * <file src="src/modalsAndAlerts/docs/readme.md" />
5515 * <button class="btn" b2b-modal="b2bTemplate/modalsAndAlerts/demo_modal.html" modal-ok="ok()" modal-cancel="cancel()">Launch demo modal</button>
5518 * <section id="code">
5519 <example module="b2b.att">
5520 <file src="src/modalsAndAlerts/docs/demo.html" />
5521 <file src="src/modalsAndAlerts/docs/demo.js" />
5526 angular.module('b2b.att.modalsAndAlerts', ['b2b.att.position', 'b2b.att.transition', 'b2b.att.utilities'])
5529 * A helper, internal data structure that acts as a map but also allows getting / removing
5530 * elements in the LIFO order
5532 .factory('$$stackedMap', function () {
5534 createNew: function () {
5538 add: function (key, value) {
5544 get: function (key) {
5545 for (var i = 0; i < stack.length; i++) {
5546 if (key === stack[i].key) {
5553 for (var i = 0; i < stack.length; i++) {
5554 keys.push(stack[i].key);
5559 return stack[stack.length - 1];
5561 remove: function (key) {
5563 for (var i = 0; i < stack.length; i++) {
5564 if (key === stack[i].key) {
5569 return stack.splice(idx, 1)[0];
5571 removeTop: function () {
5572 return stack.splice(stack.length - 1, 1)[0];
5574 length: function () {
5575 return stack.length;
5580 }).factory('trapFocusInElement', ['$document', '$isElement', 'b2bDOMHelper', 'keymap', function ($document, $isElement, b2bDOMHelper, keymap) {
5581 var elementStack = [];
5582 var stackHead = undefined;
5583 var firstTabableElement, lastTabableElement;
5585 var trapKeyboardFocusInFirstElement = function (e) {
5587 e.keyCode = e.which;
5590 if (e.shiftKey === true && e.keyCode === keymap.KEY.TAB) {
5591 lastTabableElement[0].focus();
5592 e.preventDefault(e);
5593 e.stopPropagation(e);
5598 var trapKeyboardFocusInLastElement = function (e) {
5600 e.keyCode = e.which;
5603 if (e.shiftKey === false && e.keyCode === keymap.KEY.TAB) {
5604 firstTabableElement[0].focus();
5605 e.preventDefault(e);
5606 e.stopPropagation(e);
5610 var trapFocusInElement = function (flag, firstTabableElementParam, lastTabableElementParam) {
5611 var bodyElements = $document.find('body').children();
5613 firstTabableElement = firstTabableElementParam ? firstTabableElementParam : angular.element(b2bDOMHelper.firstTabableElement(stackHead));
5614 lastTabableElement = lastTabableElementParam ? lastTabableElementParam : angular.element(b2bDOMHelper.lastTabableElement(stackHead));
5617 for (var i = 0; i < bodyElements.length; i++) {
5618 if (bodyElements[i] !== stackHead[0]) {
5619 bodyElements.eq(i).attr('aria-hidden', true);
5622 firstTabableElement.bind('keydown', trapKeyboardFocusInFirstElement);
5623 lastTabableElement.bind('keydown', trapKeyboardFocusInLastElement);
5625 for (var j = 0; j < bodyElements.length; j++) {
5626 if (bodyElements[j] !== stackHead[0]) {
5627 bodyElements.eq(j).removeAttr('aria-hidden');
5630 firstTabableElement.unbind('keydown', trapKeyboardFocusInFirstElement);
5631 lastTabableElement.unbind('keydown', trapKeyboardFocusInLastElement);
5634 var toggleTrapFocusInElement = function (flag, element) {
5635 if (angular.isDefined(flag) && angular.isDefined(element)) {
5636 if (angular.isUndefined(stackHead)) {
5637 stackHead = element;
5638 trapFocusInElement(flag);
5641 trapFocusInElement(false);
5642 elementStack.push(stackHead);
5643 stackHead = element;
5644 trapFocusInElement(true);
5646 if (stackHead.prop('$$hashKey') === element.prop('$$hashKey')) {
5647 trapFocusInElement(false);
5648 stackHead = elementStack.pop();
5649 if (angular.isDefined(stackHead)) {
5650 trapFocusInElement(true);
5656 if (angular.isDefined(stackHead)) {
5657 trapFocusInElement(false, firstTabableElement, lastTabableElement);
5658 trapFocusInElement(true);
5663 return toggleTrapFocusInElement;
5667 * A helper directive for the $modal service. It creates a backdrop element.
5669 .directive('b2bModalBackdrop', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
5673 templateUrl: 'b2bTemplate/modalsAndAlerts/b2b-backdrop.html',
5674 link: function (scope, element, attrs) {
5675 scope.close = function (evt) {
5676 var modal = $modalStack.getTop();
5677 if (modal && modal.value.backdrop && modal.value.backdrop !== 'static') {
5678 evt.preventDefault();
5679 evt.stopPropagation();
5680 $modalStack.dismiss(modal.key, 'backdrop click');
5687 .directive('b2bModalWindow', ['$timeout', 'windowOrientation', '$window', 'keymap', function ($timeout, windowOrientation, $window, keymap) {
5695 templateUrl: 'b2bTemplate/modalsAndAlerts/b2b-window.html',
5696 controller: ['$scope', '$element', '$attrs', function (scope, element, attrs) {
5697 scope.windowClass = attrs.windowClass || '';
5698 scope.sizeClass = attrs.sizeClass || '';
5699 scope.isNotifDialog = false;
5700 scope.modalClose = attrs.modalClose || false;
5702 this.setTitle = function (title) {
5703 scope.title = title;
5705 this.setContent = function (content) {
5706 scope.content = content;
5707 scope.isNotifDialog = true;
5709 this.isDockedModal = scope.windowClass.indexOf('modal-docked') > -1;
5711 link: function (scope, element, attrs, ctrl) {
5712 if (ctrl.isDockedModal) {
5713 scope.isModalLandscape = false;
5715 var window = angular.element($window);
5716 scope.updateCss = function () {
5717 if (windowOrientation.isPotrait()) { // Potrait Mode
5718 scope.isModalLandscape = false;
5719 } else if (windowOrientation.isLandscape()) { // Landscape Mode
5720 scope.isModalLandscape = true;
5724 $timeout(function () {
5728 window.bind('orientationchange', function () {
5732 window.bind('resize', function () {
5737 angular.element(element[0].querySelectorAll(".awd-select-list")).css({
5738 "max-height": "200px"
5742 var isIE = /msie|trident/i.test(navigator.userAgent);
5744 if(angular.element(element[0].querySelector('.corner-button button.close')).length > 0){
5745 angular.element(element[0].querySelector('.corner-button button.close')).bind('focus', function () {
5746 angular.element(element[0].querySelector('.b2b-modal-header'))[0].scrollLeft = 0;
5747 angular.element(element[0].querySelector('.b2b-modal-header'))[0].scrollTop = 0;
5752 if(scope.modalClose){
5753 element.bind('keydown', function (e) {
5754 if(e.keyCode == keymap.KEY.ESC){
5756 e.stopPropagation();
5764 .directive('b2bModalTitle', [function () {
5767 require: '^b2bModalWindow',
5768 link: function (scope, elem, attr, ctrl) {
5769 ctrl.setTitle(attr.id);
5774 .directive('b2bModalContent', [function () {
5777 require: '^b2bModalWindow',
5778 link: function (scope, elem, attr, ctrl) {
5779 ctrl.setContent(attr.id);
5785 .directive('b2bModalBody', ['$timeout', '$position', '$document', '$window', 'windowOrientation', 'b2bAwdBreakpoints', function ($timeout, $position, $document, $window, windowOrientation, b2bAwdBreakpoints) {
5791 require: '^b2bModalWindow',
5792 link: function (scope, element, attrs, ctrl) {
5793 var window = angular.element($window);
5794 var body = $document.find('body').eq(0);
5795 scope.setModalHeight = function () {
5796 var modalHeaderHeight, modalFooterHeight, modalBodyHeight, windowHeight, windowWidth, modalHeight;
5797 modalHeaderHeight = 0;
5798 modalFooterHeight = 0;
5799 windowHeight = $window.innerHeight;
5800 windowWidth = $window.innerWidth;
5802 'height': windowHeight + 'px'
5805 if (ctrl.isDockedModal) {
5806 var modalElements = element.parent().children();
5807 for (var i = 0; i < modalElements.length; i++) {
5808 if (modalElements.eq(i).hasClass('b2b-modal-header')) {
5809 modalHeaderHeight = $position.position(modalElements.eq(i)).height;
5810 } else if (modalElements.eq(i).hasClass('b2b-modal-footer')) {
5811 modalFooterHeight = $position.position(modalElements.eq(i)).height;
5815 modalHeight = $position.position(element.parent()).height;
5817 modalBodyHeight = modalHeight - (modalHeaderHeight + modalFooterHeight) + 'px';
5819 if (windowOrientation.isPotrait()) { // Potrait Mode
5820 element.removeAttr('style').css({
5821 height: modalBodyHeight
5823 } else if (windowOrientation.isLandscape() && windowWidth < b2bAwdBreakpoints.breakpoints.mobile.max) { // Landscape Mode Mobile
5824 element.removeAttr('style');
5825 } else if (windowOrientation.isLandscape() && windowWidth >= b2bAwdBreakpoints.breakpoints.mobile.max) { // Landscape Mode Non-Mobile
5826 element.removeAttr('style').css({
5827 height: modalBodyHeight
5833 $timeout(function () {
5834 scope.setModalHeight();
5837 window.bind('orientationchange', function () {
5838 scope.setModalHeight();
5841 window.bind('resize', function () {
5842 scope.setModalHeight();
5849 .directive('b2bModalFooter', ['windowOrientation', '$window', function (windowOrientation, $window) {
5855 link: function (scope, element, attrs) {
5861 .factory('$modalStack', ['$document', '$compile', '$rootScope', '$$stackedMap', '$log', '$timeout', 'trapFocusInElement', function ($document, $compile, $rootScope, $$stackedMap, $log, $timeout, trapFocusInElement) {
5862 var backdropjqLiteEl, backdropDomEl;
5863 var backdropScope = $rootScope.$new(true);
5864 var body = $document.find('body').eq(0);
5865 var html = $document.find('html').eq(0);
5866 var openedWindows = $$stackedMap.createNew();
5867 var $modalStack = {};
5869 function backdropIndex() {
5870 var topBackdropIndex = -1;
5871 var opened = openedWindows.keys();
5872 for (var i = 0; i < opened.length; i++) {
5873 if (openedWindows.get(opened[i]).value.backdrop) {
5874 topBackdropIndex = i;
5877 return topBackdropIndex;
5880 $rootScope.$watch(backdropIndex, function (newBackdropIndex) {
5881 backdropScope.index = newBackdropIndex;
5884 function removeModalWindow(modalInstance) {
5885 //background scroll fix
5886 html.removeAttr('style');
5887 body.removeAttr('style');
5888 body.removeClass('styled-by-modal');
5890 var modalWindow = openedWindows.get(modalInstance).value;
5891 trapFocusInElement(false, modalWindow.modalDomEl);
5893 //clean up the stack
5894 openedWindows.remove(modalInstance);
5896 //remove window DOM element
5897 modalWindow.modalDomEl.remove();
5899 //remove backdrop if no longer needed
5900 if (backdropDomEl && backdropIndex() === -1) {
5901 backdropDomEl.remove();
5902 backdropDomEl = undefined;
5906 modalWindow.modalScope.$destroy();
5909 $document.bind('keydown', function (evt) {
5912 if (evt.which === 27) {
5913 modal = openedWindows.top();
5914 if (modal && modal.value.keyboard) {
5915 $rootScope.$apply(function () {
5916 $modalStack.dismiss(modal.key);
5922 $modalStack.open = function (modalInstance, modal) {
5924 openedWindows.add(modalInstance, {
5925 deferred: modal.deferred,
5926 modalScope: modal.scope,
5927 backdrop: modal.backdrop,
5928 keyboard: modal.keyboard
5931 var angularDomEl = angular.element('<div b2b-modal-window></div>');
5932 angularDomEl.attr('window-class', modal.windowClass);
5933 angularDomEl.attr('size-class', modal.sizeClass);
5934 angularDomEl.attr('index', openedWindows.length() - 1);
5935 angularDomEl.attr('modal-close', modal.modalClose);
5936 angularDomEl.html(modal.content);
5938 var modalDomEl = $compile(angularDomEl)(modal.scope);
5939 openedWindows.top().value.modalDomEl = modalDomEl;
5940 //background page scroll fix
5942 'overflow-y': 'hidden'
5945 'overflow-y': 'hidden',
5947 'height': window.innerHeight + 'px'
5949 body.addClass('styled-by-modal');
5950 body.append(modalDomEl);
5952 if (backdropIndex() >= 0 && !backdropDomEl) {
5953 backdropjqLiteEl = angular.element('<div b2b-modal-backdrop></div>');
5954 backdropDomEl = $compile(backdropjqLiteEl)(backdropScope);
5955 body.append(backdropDomEl);
5958 $timeout(function () {
5960 if (modal.scope.$$childHead.isNotifDialog) {
5961 angular.element(modalDomEl).find('button')[0].focus();
5963 angular.element(modalDomEl)[0].focus();
5965 trapFocusInElement(true, angular.element(modalDomEl).eq(0));
5969 $modalStack.close = function (modalInstance, result) {
5970 var modal = openedWindows.get(modalInstance);
5972 modal.value.deferred.resolve(result);
5973 removeModalWindow(modalInstance);
5977 $modalStack.dismiss = function (modalInstance, reason) {
5978 var modalWindow = openedWindows.get(modalInstance).value;
5980 modalWindow.deferred.reject(reason);
5981 removeModalWindow(modalInstance);
5985 $modalStack.getTop = function () {
5986 return openedWindows.top();
5992 .provider('$modal', function () {
5993 var $modalProvider = {
5995 backdrop: true, //can be also false or 'static'
5998 $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {
6001 function getTemplatePromise(options) {
6002 return options.template ? $q.when(options.template) :
6003 $http.get(options.templateUrl, {
6004 cache: $templateCache
6005 }).then(function (result) {
6010 function getResolvePromises(resolves) {
6011 var promisesArr = [];
6012 angular.forEach(resolves, function (value, key) {
6013 if (angular.isFunction(value) || angular.isArray(value)) {
6014 promisesArr.push($q.when($injector.invoke(value)));
6020 $modal.open = function (modalOptions) {
6022 var modalResultDeferred = $q.defer();
6023 var modalOpenedDeferred = $q.defer();
6024 //prepare an instance of a modal to be injected into controllers and returned to a caller
6025 var modalInstance = {
6026 result: modalResultDeferred.promise,
6027 opened: modalOpenedDeferred.promise,
6028 close: function (result) {
6029 $modalStack.close(modalInstance, result);
6031 dismiss: function (reason) {
6032 $modalStack.dismiss(modalInstance, reason);
6036 //merge and clean up options
6037 modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
6038 modalOptions.resolve = modalOptions.resolve || {};
6041 if (!modalOptions.template && !modalOptions.templateUrl) {
6042 throw new Error('One of template or templateUrl options is required.');
6045 var templateAndResolvePromise =
6046 $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
6049 templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
6051 var modalScope = (modalOptions.scope || $rootScope).$new();
6052 modalScope.$close = modalInstance.close;
6053 modalScope.$dismiss = modalInstance.dismiss;
6055 var ctrlInstance, ctrlLocals = {};
6056 var resolveIter = 1;
6059 if (modalOptions.controller) {
6060 ctrlLocals.$scope = modalScope;
6061 ctrlLocals.$modalInstance = modalInstance;
6062 angular.forEach(modalOptions.resolve, function (value, key) {
6063 ctrlLocals[key] = tplAndVars[resolveIter++];
6066 ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
6069 $modalStack.open(modalInstance, {
6071 deferred: modalResultDeferred,
6072 content: tplAndVars[0],
6073 backdrop: modalOptions.backdrop,
6074 keyboard: modalOptions.keyboard,
6075 windowClass: modalOptions.windowClass,
6076 sizeClass: modalOptions.sizeClass,
6077 modalClose: modalOptions.modalClose
6080 }, function resolveError(reason) {
6081 modalResultDeferred.reject(reason);
6084 templateAndResolvePromise.then(function () {
6085 modalOpenedDeferred.resolve(true);
6087 modalOpenedDeferred.reject(false);
6090 return modalInstance;
6097 return $modalProvider;
6100 .directive("b2bModal", ["$modal", "$log", '$scrollTo', function ($modal, $log, $scrollTo) {
6105 modalController: '@',
6112 link: function (scope, elm, attr) {
6113 elm.bind('click', function (ev) {
6114 var currentPosition = ev.pageY - ev.clientY;
6115 ev.preventDefault();
6116 if (angular.isDefined(elm.attr("href")) && elm.attr("href") !== "") {
6117 scope.b2bModal = elm.attr("href");
6120 templateUrl: scope.b2bModal,
6121 controller: scope.modalController,
6122 windowClass: scope.windowClass,
6123 sizeClass: scope.sizeClass,
6124 modalClose: scope.modalClose
6125 }).result.then(function (value) {
6130 }, function (value) {
6141 .directive("utilityFilter", ["$modal", "$log", '$scrollTo', function ($modal, $log, $scrollTo) {
6148 templateUrl: 'b2bTemplate/modal/u-filter.html',
6149 link: function (scope, element, attribute, ctrl) {
6150 //controller to be passed to $modal service
6151 scope.options = angular.copy(scope.$parent.$eval(attribute.ngModel));
6152 scope.$parent.$watch(attribute.ngModel, function (newVal, oldVal) {
6153 if (newVal !== oldVal) {
6154 scope.options = newVal;
6157 var modalCtrl = function ($scope, options) {
6158 $scope.options = angular.copy(options);
6161 if (angular.isDefined(scope.utilityFilter)) {
6162 scope.templateUrl = scope.utilityFilter;
6164 scope.templateUrl = 'b2bTemplate/modal/u-filter-window.html';
6166 element.bind('click', function (ev) {
6167 var currentPosition = ev.pageY - ev.clientY;
6169 templateUrl: scope.templateUrl,
6170 controller: modalCtrl,
6172 options: function () {
6173 return scope.options;
6176 }).result.then(function (value) {
6177 ctrl.$setViewValue(value);
6179 $scrollTo(0, currentPosition, 0);
6182 $scrollTo(0, currentPosition, 0);
6190 * @name Forms.att:monthSelector
6193 * <file src="src/monthSelector/docs/readme.md" />
6196 * <div b2b-monthpicker ng-model="dt" min="minDate" max="maxDate" mode="monthpicker"></div>
6199 * <section id="code">
6200 <example module="b2b.att">
6201 <file src="src/monthSelector/docs/demo.html" />
6202 <file src="src/monthSelector/docs/demo.js" />
6207 angular.module('b2b.att.monthSelector', ['b2b.att.position', 'b2b.att.utilities'])
6209 .constant('b2bMonthpickerConfig', {
6210 dateFormat: 'MM/dd/yyyy',
6212 monthFormat: 'MMMM',
6214 dayHeaderFormat: 'EEEE',
6215 dayTitleFormat: 'MMMM yyyy',
6216 disableWeekend: false,
6217 disableSunday: false,
6219 onSelectClose: null,
6226 legendMessage: null,
6227 calendarDisabled: false,
6229 orientation: 'left',
6232 helperText: 'The date you selected is $date. Double tap to open calendar. Select a date to close the calendar.',
6233 descriptionText: 'Use tab to navigate between previous button, next button and month. Use arrow keys to navigate between months. Use space or enter to select a month.',
6234 MonthpickerEvalAttributes: ['dateFormat', 'dayFormat', 'monthFormat', 'yearFormat', 'dayHeaderFormat', 'dayTitleFormat', 'disableWeekend', 'disableSunday', 'startingDay', 'collapseWait', 'orientation','mode','id'],
6235 MonthpickerWatchAttributes: ['min', 'max', 'due', 'from', 'legendIcon', 'legendMessage', 'ngDisabled'],
6236 MonthpickerFunctionAttributes: ['disableDates', 'onSelectClose']
6239 .factory('b2bMonthpickerService', ['b2bMonthpickerConfig', 'dateFilter', function (b2bMonthpickerConfig, dateFilter) {
6240 var setAttributes = function (attr, elem) {
6241 if (angular.isDefined(attr) && attr !== null && angular.isDefined(elem) && elem !== null) {
6242 var attributes = b2bMonthpickerConfig.MonthpickerEvalAttributes.concat(b2bMonthpickerConfig.MonthpickerWatchAttributes, b2bMonthpickerConfig.MonthpickerFunctionAttributes);
6243 for (var key in attr) {
6244 var val = attr[key];
6245 if (attributes.indexOf(key) !== -1 && angular.isDefined(val)) {
6246 elem.attr(key.toSnakeCase(), key);
6252 var bindScope = function (attr, scope) {
6253 if (angular.isDefined(attr) && attr !== null && angular.isDefined(scope) && scope !== null) {
6254 var evalFunction = function (key, val) {
6255 scope[key] = scope.$parent.$eval(val);
6258 var watchFunction = function (key, val) {
6259 scope.$parent.$watch(val, function (value) {
6262 scope.$watch(key, function (value) {
6263 scope.$parent[val] = value;
6267 var evalAttributes = b2bMonthpickerConfig.MonthpickerEvalAttributes;
6268 var watchAttributes = b2bMonthpickerConfig.MonthpickerWatchAttributes;
6269 for (var key in attr) {
6270 var val = attr[key];
6271 if (evalAttributes.indexOf(key) !== -1 && angular.isDefined(val)) {
6272 evalFunction(key, val);
6273 } else if (watchAttributes.indexOf(key) !== -1 && angular.isDefined(val)) {
6274 watchFunction(key, val);
6281 setAttributes: setAttributes,
6282 bindScope: bindScope
6286 .controller('b2bMonthpickerController', ['$scope', '$attrs', 'dateFilter', '$element', '$position', 'b2bMonthpickerConfig', function ($scope, $attrs, dateFilter, $element, $position, dtConfig) {
6288 date: getValue($attrs.dateFormat, dtConfig.dateFormat),
6289 day: getValue($attrs.dayFormat, dtConfig.dayFormat),
6290 month: getValue($attrs.monthFormat, dtConfig.monthFormat),
6291 year: getValue($attrs.yearFormat, dtConfig.yearFormat),
6292 dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat),
6293 dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat),
6294 disableWeekend: getValue($attrs.disableWeekend, dtConfig.disableWeekend),
6295 disableSunday: getValue($attrs.disableSunday, dtConfig.disableSunday),
6296 disableDates: getValue($attrs.disableDates, dtConfig.disableDates)
6298 startingDay = getValue($attrs.startingDay, dtConfig.startingDay);
6300 $scope.minDate = dtConfig.minDate ? $scope.resetTime(dtConfig.minDate) : null;
6301 $scope.maxDate = dtConfig.maxDate ? $scope.resetTime(dtConfig.maxDate) : null;
6302 $scope.dueDate = dtConfig.dueDate ? $scope.resetTime(dtConfig.dueDate) : null;
6303 $scope.fromDate = dtConfig.fromDate ? $scope.resetTime(dtConfig.fromDate) : null;
6304 $scope.legendIcon = dtConfig.legendIcon ? dtConfig.legendIcon : null;
6305 $scope.legendMessage = dtConfig.legendMessage ? dtConfig.legendMessage : null;
6306 $scope.ngDisabled = dtConfig.calendarDisabled ? dtConfig.calendarDisabled : null;
6307 $scope.collapseWait = getValue($attrs.collapseWait, dtConfig.collapseWait);
6308 $scope.orientation = getValue($attrs.orientation, dtConfig.orientation);
6309 $scope.onSelectClose = getValue($attrs.onSelectClose, dtConfig.onSelectClose);
6310 $scope.mode = getValue($attrs.mode, dtConfig.mode);
6312 $scope.inline = $attrs.inline === 'true' ? true : dtConfig.inline;
6314 function getValue(value, defaultValue) {
6315 return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue;
6318 function getDaysInMonth(year, month) {
6319 return new Date(year, month, 0).getDate();
6322 function getDates(startDate, n) {
6323 var dates = new Array(n);
6324 var current = startDate,
6327 dates[i++] = new Date(current);
6328 current.setDate(current.getDate() + 1);
6333 this.updatePosition = function (b2bMonthpickerPopupTemplate) {
6334 $scope.position = $position.offset($element);
6335 if($element.find('input').length > 0 ){
6336 $scope.position.top += $element.find('input').prop('offsetHeight');
6338 $scope.position.top += $element.find('a').prop('offsetHeight');
6341 if ($scope.orientation === 'right') {
6342 $scope.position.left -= (((b2bMonthpickerPopupTemplate && b2bMonthpickerPopupTemplate.prop('offsetWidth')) || 290) - $element.find('input').prop('offsetWidth'));
6346 function isSelected(dt) {
6347 if (dt && angular.isDate($scope.currentDate) && compare(dt, $scope.currentDate) === 0) {
6353 function isFromDate(dt) {
6354 if (dt && angular.isDate($scope.fromDate) && compare(dt, $scope.fromDate) === 0) {
6360 function isDateRange(dt) {
6361 if (dt && $scope.fromDate && angular.isDate($scope.currentDate) && (compare(dt, $scope.fromDate) >= 0) && (compare(dt, $scope.currentDate) <= 0)) {
6363 } else if (dt && $scope.fromDate && compare(dt, $scope.fromDate) === 0) {
6369 function isOld(date, currentMonthDate) {
6370 if (date && currentMonthDate && (new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0).getTime() < new Date(currentMonthDate.getFullYear(), currentMonthDate.getMonth(), 1, 0, 0, 0).getTime())) {
6377 function isNew(date, currentMonthDate) {
6378 if (date && currentMonthDate && (new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0).getTime() > new Date(currentMonthDate.getFullYear(), currentMonthDate.getMonth(), 1, 0, 0, 0).getTime())) {
6385 function isPastDue(dt) {
6386 if ($scope.dueDate) {
6387 return (dt > $scope.dueDate);
6392 function isDueDate(dt) {
6393 if ($scope.dueDate) {
6394 return (dt.getTime() === $scope.dueDate.getTime());
6399 var isDisabled = function (date, currentMonthDate) {
6400 if ($attrs.from && !angular.isDate($scope.fromDate)) {
6403 if (format.disableWeekend === true && (dateFilter(date, format.dayHeader) === "Saturday" || dateFilter(date, format.dayHeader) === "Sunday")) {
6406 if (format.disableSunday === true && (dateFilter(date, format.dayHeader) === "Sunday")) {
6409 if (isOld(date, currentMonthDate) || isNew(date, currentMonthDate)) {
6412 return (($scope.minDate && compare(date, $scope.minDate) < 0) || ($scope.maxDate && compare(date, $scope.maxDate) > 0) || (format.disableDates && format.disableDates({
6417 var isDisabledMonth = function (date, currentMonthDate) {
6418 if ($attrs.from && !angular.isDate($scope.fromDate)) {
6421 if (format.disableWeekend === true && (dateFilter(date, format.dayHeader) === "Saturday" || dateFilter(date, format.dayHeader) === "Sunday")) {
6424 if (format.disableSunday === true && (dateFilter(date, format.dayHeader) === "Sunday")) {
6427 return (($scope.minDate && compare(date, $scope.minDate) < 0) || ($scope.maxDate && compare(date, $scope.maxDate) > 0) || (format.disableDates && format.disableDates({
6432 var compare = function (date1, date2) {
6433 return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()));
6436 function isMinDateAvailable(startDate, endDate) {
6437 if (($scope.minDate && $scope.minDate.getTime() >= startDate.getTime()) && ($scope.minDate.getTime() <= endDate.getTime())) {
6438 $scope.disablePrev = true;
6439 $scope.visibilityPrev = "hidden";
6441 $scope.disablePrev = false;
6442 $scope.visibilityPrev = "visible";
6446 function isMaxDateAvailable(startDate, endDate) {
6447 if (($scope.maxDate && $scope.maxDate.getTime() >= startDate.getTime()) && ($scope.maxDate.getTime() <= endDate.getTime())) {
6448 $scope.disableNext = true;
6449 $scope.visibilityNext = "hidden";
6451 $scope.disableNext = false;
6452 $scope.visibilityNext = "visible";
6456 function isYearInRange(currentYear) {
6458 if ($scope.minDate && currentYear === $scope.minDate.getFullYear()) {
6459 $scope.disablePrev = true;
6460 $scope.visibilityPrev = "hidden";
6462 $scope.disablePrev = false;
6463 $scope.visibilityPrev = "visible";
6466 if ($scope.maxDate && currentYear === $scope.maxDate.getFullYear()) {
6467 $scope.disableNext = true;
6468 $scope.visibilityNext = "hidden";
6470 $scope.disableNext = false;
6471 $scope.visibilityNext = "visible";
6476 this.focusNextPrev = function(b2bMonthpickerPopupTemplate,init){
6478 if (!$scope.disablePrev){
6479 b2bMonthpickerPopupTemplate[0].querySelector('th.prev').focus();
6480 }else if (!$scope.disableNext){
6481 b2bMonthpickerPopupTemplate[0].querySelector('th.next').focus();
6483 b2bMonthpickerPopupTemplate[0].querySelector('th.b2b-monthSelector-label').focus();
6486 if ($scope.disableNext || $scope.disablePrev){
6487 b2bMonthpickerPopupTemplate[0].querySelector('th.b2b-monthSelector-label').focus();
6492 function getLabel(label) {
6495 pre: label.substr(0, 1).toUpperCase(),
6503 function makeDate(date, dayFormat, dayHeaderFormat, isSelected, isFromDate, isDateRange, isOld, isNew, isDisabled, dueDate, pastDue) {
6506 label: dateFilter(date, dayFormat),
6507 header: dateFilter(date, dayHeaderFormat),
6508 selected: !!isSelected,
6509 fromDate: !!isFromDate,
6510 dateRange: !!isDateRange,
6513 disabled: !!isDisabled,
6516 focusable: !((isDisabled && !(isSelected || isDateRange)) || (isOld || isNew))
6523 getVisibleDates: function (date) {
6524 var year = date.getFullYear(),
6525 month = date.getMonth(),
6526 firstDayOfMonth = new Date(year, month, 1),
6527 lastDayOfMonth = new Date(year, month + 1, 0);
6528 var difference = startingDay - firstDayOfMonth.getDay(),
6529 numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : -difference,
6530 firstDate = new Date(firstDayOfMonth),
6533 if (numDisplayedFromPreviousMonth > 0) {
6534 firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
6535 numDates += numDisplayedFromPreviousMonth; // Previous
6537 numDates += getDaysInMonth(year, month + 1); // Current
6538 numDates += (7 - numDates % 7) % 7; // Next
6540 var days = getDates(firstDate, numDates),
6541 labels = new Array(7);
6542 for (var i = 0; i < numDates; i++) {
6543 var dt = new Date(days[i]);
6544 days[i] = makeDate(dt,
6552 isDisabled(dt, date),
6556 for (var j = 0; j < 7; j++) {
6557 labels[j] = getLabel(dateFilter(days[j].date, format.dayHeader));
6559 isMinDateAvailable(firstDayOfMonth, lastDayOfMonth);
6560 isMaxDateAvailable(firstDayOfMonth, lastDayOfMonth);
6563 title: dateFilter(date, format.dayTitle),
6574 getVisibleDates: function(date) {
6577 year = date.getFullYear();
6578 for (var i = 0; i < 12; i++) {
6579 var dt = new Date(year,i,1);
6580 months[i] = makeDate(dt,
6588 isDisabledMonth(dt, date),
6592 isYearInRange(year);
6593 return {objects: months, title: dateFilter(date, format.year), labels: labels};
6601 .directive('b2bMonthpickerPopup', ['$parse', '$log', '$timeout', '$document', '$documentBind', '$isElement', '$templateCache', '$compile','$interval', 'trapFocusInElement', 'keymap', function ($parse, $log, $timeout, $document, $documentBind, $isElement, $templateCache, $compile, $interval,trapFocusInElement, keymap) {
6609 templateUrl: function (elem, attr) {
6610 if (attr.inline === 'true') {
6611 return 'b2bTemplate/monthSelector/monthSelector-popup.html';
6612 }else if (attr.link === 'true') {
6613 return 'b2bTemplate/monthSelector/monthSelectorLink.html';
6615 return 'b2bTemplate/monthSelector/monthSelector.html';
6619 require: ['b2bMonthpickerPopup', 'ngModel', '?^b2bMonthpickerGroup'],
6620 controller: 'b2bMonthpickerController',
6621 link: function (scope, element, attrs, ctrls) {
6622 var MonthpickerCtrl = ctrls[0],
6624 b2bMonthpickerGroupCtrl = ctrls[2];
6625 var b2bMonthpickerPopupTemplate;
6628 $log.error("ng-model is required.");
6629 return; // do nothing if no ng-model
6632 // Configuration parameters
6633 var mode = scope.mode,
6635 scope.isOpen = false;
6639 scope.triggerInterval=undefined;
6642 if (b2bMonthpickerGroupCtrl) {
6643 b2bMonthpickerGroupCtrl.registerMonthpickerScope(scope);
6646 element.bind('keydown', function (ev) {
6649 ev.keyCode = ev.which;
6650 } else if (ev.charCode) {
6651 ev.keyCode = ev.charCode;
6654 if(ev.keyCode === keymap.KEY.ESC)
6656 scope.isOpen = false;
6657 toggleCalendar(scope.isOpen);
6662 element.find('button').bind('click', function () {
6666 element.find('a').bind('click', function () {
6671 element.find('input').bind('click', function () {
6675 var onClicked = function() {
6676 if (!scope.ngDisabled) {
6677 scope.isOpen = !scope.isOpen;
6678 toggleCalendar(scope.isOpen);
6679 MonthpickerCtrl.updatePosition(b2bMonthpickerPopupTemplate);
6684 var toggleCalendar = function (flag) {
6685 if (!scope.inline) {
6687 b2bMonthpickerPopupTemplate = angular.element($templateCache.get('b2bTemplate/monthSelector/monthSelector-popup.html'));
6688 b2bMonthpickerPopupTemplate.attr('b2b-trap-focus-inside-element', 'false');
6689 b2bMonthpickerPopupTemplate.attr('trigger', 'true');
6690 b2bMonthpickerPopupTemplate = $compile(b2bMonthpickerPopupTemplate)(scope);
6691 $document.find('body').append(b2bMonthpickerPopupTemplate);
6692 b2bMonthpickerPopupTemplate.bind('keydown', escPress);
6693 $timeout(function () {
6694 scope.getFocus = true;
6697 $timeout(function () {
6698 scope.getFocus = false;
6700 MonthpickerCtrl.focusNextPrev(b2bMonthpickerPopupTemplate,true);
6703 scope.triggerInterval = $interval(function () {
6704 //This value is updated to trigger init() function of directive on year change.
6705 scope.trigger=(scope.trigger === 0 ? 1 : 0);
6709 b2bMonthpickerPopupTemplate.unbind('keydown', escPress);
6710 if(scope.triggerInterval)
6712 $interval.cancel(scope.triggerInterval);
6713 scope.triggerInterval=undefined;
6715 b2bMonthpickerPopupTemplate.remove();
6716 if(element.find('button').length > 0){
6717 element.find('button')[0].focus();
6719 element.find('a')[0].focus();
6722 scope.getFocus = false;
6727 var outsideClick = function (e) {
6728 var isElement = $isElement(angular.element(e.target), element, $document);
6729 var isb2bMonthpickerPopupTemplate = $isElement(angular.element(e.target), b2bMonthpickerPopupTemplate, $document);
6730 if (!(isElement || isb2bMonthpickerPopupTemplate)) {
6731 scope.isOpen = false;
6732 toggleCalendar(scope.isOpen);
6737 var escPress = function (ev) {
6740 ev.keyCode = ev.which;
6741 } else if (ev.charCode) {
6742 ev.keyCode = ev.charCode;
6746 if (ev.keyCode === keymap.KEY.ESC) {
6747 scope.isOpen = false;
6748 toggleCalendar(scope.isOpen);
6749 ev.preventDefault();
6750 ev.stopPropagation();
6751 } else if (ev.keyCode === 33) {
6752 !scope.disablePrev && scope.move(-1);
6753 $timeout(function () {
6754 scope.getFocus = true;
6756 $timeout(function () {
6757 scope.getFocus = false;
6761 ev.preventDefault();
6762 ev.stopPropagation();
6763 } else if (ev.keyCode === 34) {
6764 !scope.disableNext && scope.move(1);
6765 $timeout(function () {
6766 scope.getFocus = true;
6768 $timeout(function () {
6769 scope.getFocus = false;
6773 ev.preventDefault();
6774 ev.stopPropagation();
6780 $documentBind.click('isOpen', outsideClick, scope);
6782 scope.$on('$destroy', function () {
6784 scope.isOpen = false;
6785 toggleCalendar(scope.isOpen);
6789 scope.resetTime = function (date) {
6790 if (typeof date === 'string') {
6791 date = date + 'T12:00:00';
6794 if (!isNaN(new Date(date))) {
6795 dt = new Date(date);
6796 if(scope.mode === 1){
6797 dt = new Date(dt.getFullYear(), dt.getMonth());
6799 dt = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate());
6804 return new Date(dt.getFullYear(), dt.getMonth(), dt.getDate());
6808 scope.$parent.$watch($parse(attrs.min), function (value) {
6809 scope.minDate = value ? scope.resetTime(value) : null;
6814 scope.$parent.$watch($parse(attrs.max), function (value) {
6815 scope.maxDate = value ? scope.resetTime(value) : null;
6820 scope.$parent.$watch($parse(attrs.due), function (value) {
6821 scope.dueDate = value ? scope.resetTime(value) : null;
6826 scope.$parent.$watch($parse(attrs.from), function (value) {
6827 scope.fromDate = value ? scope.resetTime(value) : null;
6832 if (attrs.legendIcon) {
6833 scope.$parent.$watch(attrs.legendIcon, function (value) {
6834 scope.legendIcon = value ? value : null;
6838 if (attrs.legendMessage) {
6839 scope.$parent.$watch(attrs.legendMessage, function (value) {
6840 scope.legendMessage = value ? value : null;
6844 if (attrs.ngDisabled) {
6845 scope.$parent.$watch(attrs.ngDisabled, function (value) {
6846 scope.ngDisabled = value ? value : null;
6851 // Split array into smaller arrays
6852 function split(arr, size) {
6854 while (arr.length > 0) {
6855 arrays.push(arr.splice(0, size));
6860 var moveMonth = function(selectedDate, direction) {
6861 var step = MonthpickerCtrl.modes[scope.mode].step;
6862 selectedDate.setDate(1);
6863 selectedDate.setMonth(selectedDate.getMonth() + direction * (step.months || 0));
6864 selectedDate.setFullYear(selectedDate.getFullYear() + direction * (step.years || 0));
6866 return selectedDate;
6869 function refill(date) {
6870 if (angular.isDate(date) && !isNaN(date)) {
6871 selected = new Date(date);
6874 selected = new Date();
6879 var selectedCalendar;
6880 if(scope.mode === 1){
6881 if(!angular.isDate(selected))
6883 selected = new Date();
6885 selectedCalendar = moveMonth(angular.copy(selected), -1);
6887 selectedCalendar = angular.copy(selected);
6890 var currentMode = MonthpickerCtrl.modes[mode],
6891 data = currentMode.getVisibleDates(selected);
6893 scope.rows = split(data.objects, currentMode.split);
6896 var startFlag=false;
6897 var firstSelected = false;
6898 for(var i=0; i<scope.rows.length; i++)
6900 for(var j=0; j<scope.rows[i].length; j++)
6902 if(!scope.rows[i][j].disabled && !firstSelected)
6905 var firstDay = scope.rows[i][j];
6908 if(scope.rows[i][j].selected)
6919 if(!flag && firstSelected)
6921 firstDay.firstFocus=true;
6924 scope.labels = data.labels || [];
6925 scope.title = data.title;
6929 scope.select = function (date,$event) {
6930 var dt = new Date(date.getFullYear(), date.getMonth(), date.getDate());
6931 scope.currentDate = dt;
6932 if (!scope.onSelectClose || (scope.onSelectClose && scope.onSelectClose({
6935 if (angular.isNumber(scope.collapseWait)) {
6936 $timeout(function () {
6937 scope.isOpen = false;
6938 toggleCalendar(scope.isOpen);
6939 }, scope.collapseWait);
6941 scope.isOpen = false;
6942 toggleCalendar(scope.isOpen);
6947 scope.move = function (direction,$event) {
6948 var step = MonthpickerCtrl.modes[mode].step;
6949 selected.setDate(1);
6950 selected.setMonth(selected.getMonth() + direction * (step.months || 0));
6951 selected.setFullYear(selected.getFullYear() + direction * (step.years || 0));
6953 scope.getFocus = true;
6954 $timeout(function () {
6955 if (attrs.inline === 'true') {
6956 MonthpickerCtrl.focusNextPrev(element,false);
6958 MonthpickerCtrl.focusNextPrev(b2bMonthpickerPopupTemplate,false);
6961 $event.preventDefault();
6962 $event.stopPropagation();
6965 scope.$watch('currentDate', function (value) {
6966 if (angular.isDefined(value) && value !== null) {
6971 ngModel.$setViewValue(value);
6974 ngModel.$render = function () {
6975 scope.currentDate = ngModel.$viewValue;
6978 var stringToDate = function (value) {
6979 if (!isNaN(new Date(value))) {
6980 value = new Date(value);
6984 ngModel.$formatters.unshift(stringToDate);
6989 .directive('b2bMonthpicker', ['$compile', '$log', 'b2bMonthpickerConfig', 'b2bMonthpickerService', function ($compile, $log, b2bMonthpickerConfig, b2bMonthpickerService) {
6997 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
6998 var dateFormatString = angular.isDefined(attr.dateFormat) ? scope.$parent.$eval(attr.dateFormat) : b2bMonthpickerConfig.dateFormat;
6999 var helperText = angular.isDefined(attr.helperText) ? scope.$parent.$eval(attr.helperText) : b2bMonthpickerConfig.helperText;
7000 helperText = helperText.replace('$date', '{{dt | date : \'' + dateFormatString + '\'}}');
7002 var descriptionText = angular.isDefined(attr.descriptionText) ? scope.$parent.$eval(attr.descriptionText) : b2bMonthpickerConfig.descriptionText;
7006 if (elem.prop('nodeName') !== 'INPUT' && elem.prop('nodeName') !== 'A') {
7010 var selectedDateMessage = "";
7012 if (elem.prop('nodeName') !== 'A'){
7013 selectedDateMessage = '<button type="button" class="span12 faux-input" ng-disabled="ngDisabled" aria-describedby="monthpicker-description'+scope.$id+'"><span class="hidden-spoken" aria-live="assertive" aria-atomic="false">' + helperText + '</span></button>';
7014 elem.attr('tabindex', '-1');
7015 elem.attr('aria-hidden', 'true');
7016 elem.attr('readonly', 'true');
7018 selectedDateMessage = ''
7019 elem.attr('aria-label', helperText);
7022 var descriptionTextSpan = '<span class="offscreen-text" id="monthpicker-description'+scope.$id+'">'+descriptionText+'</span>';
7023 elem.removeAttr('b2b-Monthpicker');
7024 elem.removeAttr('ng-model');
7025 elem.removeAttr('ng-disabled');
7026 elem.addClass('Monthpicker-input');
7027 elem.attr('ng-model', 'dt');
7028 elem.attr('aria-describedby', 'monthpicker-description'+scope.$id);
7032 elem.attr('ng-disabled', 'ngDisabled');
7033 elem.attr('b2b-format-date', dateFormatString);
7035 var wrapperElement = angular.element('<div></div>');
7036 wrapperElement.attr('b2b-Monthpicker-popup', '');
7037 wrapperElement.attr('ng-model', 'dt');
7039 wrapperElement.attr('inline', inline);
7041 if (elem.prop('nodeName') === 'A'){
7042 wrapperElement.attr('link', true);
7044 b2bMonthpickerService.setAttributes(attr, wrapperElement);
7045 b2bMonthpickerService.bindScope(attr, scope);
7047 wrapperElement.html('');
7048 wrapperElement.append(selectedDateMessage);
7049 wrapperElement.append('');
7050 wrapperElement.append(descriptionTextSpan);
7051 wrapperElement.append('');
7052 wrapperElement.append(elem.prop('outerHTML'));
7054 var elm = wrapperElement.prop('outerHTML');
7055 elm = $compile(elm)(scope);
7056 elem.replaceWith(elm);
7058 link: function (scope, elem, attr, ctrl) {
7060 $log.error("ng-model is required.");
7061 return; // do nothing if no ng-model
7064 scope.$watch('dt', function (value) {
7065 ctrl.$setViewValue(value);
7067 ctrl.$render = function () {
7068 scope.dt = ctrl.$viewValue;
7074 .directive('b2bMonthpickerGroup', [function () {
7077 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
7078 this.$$headers = [];
7079 this.$$footers = [];
7080 this.registerMonthpickerScope = function (MonthpickerScope) {
7081 MonthpickerScope.headers = this.$$headers;
7082 MonthpickerScope.footers = this.$$footers;
7085 link: function (scope, elem, attr, ctrl) {}
7089 .directive('b2bFormatDate', ['dateFilter', function (dateFilter) {
7093 link: function (scope, elem, attr, ctrl) {
7094 var b2bFormatDate = "";
7095 attr.$observe('b2bFormatDate', function (value) {
7096 b2bFormatDate = value;
7098 var dateToString = function (value) {
7099 if (!isNaN(new Date(value))) {
7100 return dateFilter(new Date(value), b2bFormatDate);
7104 ctrl.$formatters.unshift(dateToString);
7109 .directive('b2bMonthpickerHeader', [function () {
7112 require: '^b2bMonthpickerGroup',
7116 compile: function (elem, attr, transclude) {
7117 return function link(scope, elem, attr, ctrl) {
7119 ctrl.$$headers.push(transclude(scope, function () {}));
7127 .directive('b2bMonthpickerFooter', [function () {
7130 require: '^b2bMonthpickerGroup',
7134 compile: function (elem, attr, transclude) {
7135 return function link(scope, elem, attr, ctrl) {
7137 ctrl.$$footers.push(transclude(scope, function () {}));
7146 * @name Navigation.att:multiLevelNavigation
7149 * <file src="src/multiLevelNavigation/docs/readme.md" />
7152 * <div class="b2b-ml-nav">
7154 * <li aria-label="{{child.name}}" tabindex="-1" b2b-ml-nav="{{child.type}}" role="treeitem" ng-repeat="child in treeStructure">
7155 * <a tabindex="-1" href="javascript:void(0);" title="{{child.name}}">{{child.name}}<span><i class="{{child.type=='endNode'?'icon-primary-circle':'icon-primary-collapsed'}}"></i></span></a>
7156 * <!-- Below UL tag is RECURSIVE to generate n-childs -->
7157 * <ul role="group" ng-if="child.child">
7158 * <li aria-label="{{child.name}}" b2b-ml-nav="{{child.type}}" tabindex="-1" role="treeitem" ng-repeat="child in child.child">
7159 * <a tabindex="-1" href="javascript:void(0);" title="{{child.name}}">{{child.name}}<span><i class="{{child.type=='endNode'?'icon-primary-circle':'icon-primary-collapsed'}}"></i></span></a>
7160 * <!-- RECURSIVE UL tag goes here -->
7168 * <section id="code">
7169 <example module="b2b.att">
7170 <file src="src/multiLevelNavigation/docs/demo.html" />
7171 <file src="src/multiLevelNavigation/docs/demo.js" />
7176 angular.module('b2b.att.multiLevelNavigation', ['b2b.att.utilities'])
7177 //directive b2bMlNav Test coverage 100% on 5/13
7178 .directive('b2bMlNav', ['keymap', function (keymap) {
7181 link: function (scope, element) {
7182 var rootE, parentE, upE, downE, lastE, homeE, endE;
7183 //default root tree element tabindex set zero
7184 if (element.parent().parent().hasClass('b2b-ml-nav') && (element[0].previousElementSibling === null)) {
7185 element.attr('tabindex', 0);
7187 //check root via class
7188 var isRoot = function (elem) {
7189 if (elem.parent().parent().eq(0).hasClass('b2b-ml-nav')) {
7196 //for any expandable tree item on click
7197 var toggleState = function (e) {
7199 if (angular.element(e.target).attr("b2b-ml-nav") !== "endNode") {
7200 var eLink = element.find('a').eq(0);
7201 if (eLink.hasClass('active')) {
7202 eLink.removeClass('active');
7203 eLink.parent().attr("aria-expanded", "false");
7204 eLink.find('i').eq(0).removeClass('icon-primary-expanded');
7205 eLink.find('i').eq(0).addClass('icon-primary-collapsed');
7207 eLink.addClass('active');
7208 eLink.parent().attr("aria-expanded", "true");
7209 eLink.find('i').eq(0).removeClass('icon-primary-collapsed');
7210 eLink.find('i').eq(0).addClass('icon-primary-expanded');
7214 //function finds the main root-item from particular tree-group
7215 var findRoot = function (elem) {
7220 if (elem.attr("b2b-ml-nav") === "middleNode" || elem.attr("b2b-ml-nav") === "endNode") {
7221 parentE = elem.parent().parent();
7225 if (parentE.attr("b2b-ml-nav") === "rootNode") {
7231 //finds the last visible node of the previous tree-group
7232 var findPreActive = function (elem) {
7233 if (!(elem.hasClass("active"))) {
7236 var childElems = angular.element(elem[0].nextElementSibling.children);
7237 lastE = angular.element(childElems[childElems.length - 1]);
7238 if (lastE.attr("b2b-ml-nav") === "middleNode" && lastE.find('a').eq(0).hasClass('active')) {
7239 findPreActive(lastE.find('a').eq(0));
7244 //find above visible link
7245 var findUp = function (elem) {
7246 if (elem[0].previousElementSibling !== null) {
7247 upE = angular.element(elem[0].previousElementSibling);
7249 upE = elem.parent().parent();
7251 if (isRoot(elem) || (upE.attr('b2b-ml-nav') === "middleNode" && upE[0] !== elem.parent().parent()[0])) {
7252 findPreActive(upE.find('a').eq(0));
7255 //find below visible link
7256 var findDown = function (elem) {
7257 if (elem.hasClass('active')) {
7258 downE = elem.next().find('li').eq(0);
7260 if (elem.parent().next().length !== 0) {
7261 downE = elem.parent().next().eq(0);
7263 if (elem.parent().parent().parent().next().length !== 0) {
7264 downE = elem.parent().parent().parent().next().eq(0);
7267 downE = elem.parent().eq(0);
7271 //finds last root-group element of the tree
7272 var findEnd = function (elem) {
7274 endE = angular.element(rootE.parent()[0].children[rootE.parent()[0].children.length - 1]);
7276 //finds first root element of tree
7277 var findHome = function (elem) {
7279 homeE = angular.element(rootE.parent()[0].children[0]);
7281 element.bind('click', function (e) {
7282 if(element.attr("b2b-ml-nav") !== "endNode") {
7285 if (rootE==undefined){
7288 var currSelected = rootE.parent()[0].querySelector('.selected');
7290 angular.element(currSelected).removeClass('selected');
7292 element.find('a').eq(0).addClass('selected');
7293 e.stopPropagation();
7295 element.bind('focus', function (e) {
7296 if(element.attr("b2b-ml-nav") !== "endNode") {
7297 if(element.find('a').eq(0).hasClass('active')) {
7298 element.attr("aria-expanded", true);
7301 element.attr("aria-expanded", false);
7306 //Keyboard functionality approach:
7308 //set set tabindex -1 on the current focus element
7309 //find the next element to be focussed, set tabindex 0 and throw focus
7310 element.bind('keydown', function (evt) {
7311 switch (evt.keyCode) {
7312 case keymap.KEY.ENTER:
7313 case keymap.KEY.SPACE:
7314 element.triggerHandler('click');
7315 evt.stopPropagation();
7316 evt.preventDefault();
7318 case keymap.KEY.END:
7319 evt.preventDefault();
7320 element.attr('tabindex', -1);
7322 endE.eq(0).attr('tabindex', 0);
7324 evt.stopPropagation();
7326 case keymap.KEY.HOME:
7327 evt.preventDefault();
7328 element.attr('tabindex', -1);
7330 homeE.eq(0).attr('tabindex', 0);
7332 evt.stopPropagation();
7334 case keymap.KEY.LEFT:
7335 evt.preventDefault();
7336 if (!isRoot(element)) {
7337 element.attr('tabindex', -1);
7338 parentE = element.parent().parent();
7339 parentE.eq(0).attr('tabindex', 0);
7341 parentE.eq(0).triggerHandler('click');
7343 if (element.find('a').eq(0).hasClass('active')) {
7344 element.triggerHandler('click');
7347 evt.stopPropagation();
7350 evt.preventDefault();
7351 if (!(isRoot(element) && element[0].previousElementSibling === null)) {
7352 element.attr('tabindex', -1);
7354 upE.eq(0).attr('tabindex', 0);
7357 evt.stopPropagation();
7359 case keymap.KEY.RIGHT:
7360 evt.preventDefault();
7361 if (element.attr("b2b-ml-nav") !== "endNode") {
7362 if (!element.find('a').eq(0).hasClass('active')) {
7363 element.triggerHandler('click');
7365 element.attr('tabindex', -1);
7366 findDown(element.find('a').eq(0));
7367 downE.eq(0).attr('tabindex', 0);
7370 evt.stopPropagation();
7372 case keymap.KEY.DOWN:
7373 evt.preventDefault();
7374 element.attr('tabindex', -1);
7375 if (!(element.attr("b2b-ml-nav") === "middleNode" && element.find('a').eq(0).hasClass('active')) && (element.next().length === 0)) {
7376 if(element.parent().parent().attr("b2b-ml-nav") !== "rootNode" && element.parent().parent()[0].nextElementSibling !== null)
7378 findDown(element.find('a').eq(0));
7379 downE.eq(0).attr('tabindex', 0);
7381 evt.stopPropagation();
7385 if (!(rootE.next().length === 0)) {
7386 rootE.next().eq(0).attr('tabindex', 0);
7387 rootE.next()[0].focus();
7389 rootE.eq(0).attr('tabindex', 0);
7392 evt.stopPropagation();
7395 findDown(element.find('a').eq(0));
7396 downE.eq(0).attr('tabindex', 0);
7398 evt.stopPropagation();
7409 * @name Tabs, tables & accordions.att:multipurposeExpander
7412 * <file src="src/multipurposeExpander/docs/readme.md" />
7415 * <!--With Close Other -->
7416 * <b2b-expander-group close-others="true">
7417 * <b2b-expanders class="mpc-expanders" is-open="testmpc">
7418 * <b2b-expander-heading ng-class=" { 'b2b-toggle-header-active': !testmpc, 'b2b-toggle-header-inactive': testmpc } ">Heading content goes here</b2b-expander-heading>
7419 * <b2b-expander-body>
7420 <p>body content goes here</p>
7421 </b2b-expander-body>
7423 * </b2b-expander-group>
7425 * <!-- Without Close Other -->
7426 * <b2b-expanders class="mpc-expanders" is-open="testmpc2">
7427 * <b2b-expander-heading ng-class=" { 'b2b-toggle-header-active': !testmpc2, 'b2b-toggle-header-inactive': testmpc2 } ">Heading content goes here</b2b-expander-heading>
7428 * <b2b-expander-body>
7429 <p>body content goes here</p>
7430 </b2b-expander-body>
7434 * <section id="code">
7435 <example module="b2b.att.multipurposeExpander">
7436 <file src="src/multipurposeExpander/docs/demo.html" />
7437 <file src="src/multipurposeExpander/docs/demo.js" />
7443 angular.module('b2b.att.multipurposeExpander', ['b2b.att', 'b2b.att.collapse'])
7444 .directive('b2bExpanderGroup', function () {
7448 template: "<ng-transclude></ng-transclude>",
7449 controller:['$scope','$attrs', function($scope,$attrs){
7452 this.scope = $scope;
7454 this.addGroup = function (groupScope) {
7456 groupScope.index = this.groups.length;
7457 this.groups.push(groupScope);
7458 if(this.groups.length > 0){
7461 groupScope.$on('$destroy', function () {
7462 that.removeGroup(groupScope);
7466 this.closeOthers = function (openGroup) {
7467 var closeOthers = angular.isDefined($attrs.closeOthers);
7468 if (closeOthers && !$scope.forceExpand) {
7469 angular.forEach(this.groups, function (group) {
7470 if (group !== openGroup) {
7471 group.isOpen = false;
7475 if (this.groups.indexOf(openGroup) === (this.groups.length - 1) && $scope.forceExpand) {
7476 $scope.forceExpand = false;
7479 this.removeGroup = function (group) {
7480 var index = this.groups.indexOf(group);
7482 this.groups.splice(this.groups.indexOf(group), 1);
7490 .directive('b2bExpanders', function () {
7494 require:['b2bExpanders','?^b2bExpanderGroup'],
7496 scope:{isOpen:'=?'},
7497 template: "<div ng-transclude></div>",
7498 controller: ['$scope', function ($scope){
7499 var bodyScope = null;
7500 var expanderScope = null;
7501 this.isOpened = function(){
7510 this.setScope = function (scope) {
7512 bodyScope.isOpen = $scope.isOpen;
7514 this.setExpanderScope = function (scope) {
7515 expanderScope = scope;
7517 this.toggle = function () {
7518 $scope.isOpen = bodyScope.isOpen = !bodyScope.isOpen;
7519 return bodyScope.isOpen;
7522 this.watchToggle = function(io){
7523 bodyScope.isOpen = io;
7524 expanderScope.updateIcons(io);
7527 link: function (scope, elem, attr, myCtrl)
7529 //scope.isOpen = false;
7531 myCtrl[1].addGroup(scope);
7533 scope.$watch('isOpen', function(val){
7534 myCtrl[0].watchToggle(scope.isOpen);
7535 if(val && myCtrl[1]){
7536 myCtrl[1].closeOthers(scope);
7543 .directive('b2bExpanderHeading', function () {
7545 require: "^b2bExpanders",
7550 template: "<div style='padding:10px 10px 10px 0 !important' ng-transclude></div>"
7554 .directive('b2bExpanderBody', function () {
7557 require: "^b2bExpanders",
7561 template: "<div b2b-collapse='!isOpen' ><div ng-transclude></div></div>",
7562 link: function (scope, elem, attr, myCtrl) {
7563 scope.isOpen = false;
7564 myCtrl.setScope(scope);
7569 .directive('b2bExpanderToggle', function () {
7572 require: "^b2bExpanders",
7578 link: function (scope, element, attr, myCtrl)
7580 myCtrl.setExpanderScope(scope);
7581 var isOpen = myCtrl.isOpened();
7583 scope.setIcon = function () {
7584 element.attr("role", "button");
7586 if (scope.expandIcon && scope.collapseIcon)
7589 element.removeClass(scope.expandIcon);
7590 element.addClass(scope.collapseIcon);
7592 element.attr("aria-expanded", "true");
7595 element.removeClass(scope.collapseIcon);
7596 element.addClass(scope.expandIcon);
7598 element.attr("aria-expanded", "false");
7603 element.bind('click', function (){
7606 scope.updateIcons = function(nStat){
7610 scope.toggleit = function (){
7611 isOpen = myCtrl.toggle();
7621 * @name Messages, modals & alerts.att:notesMessagesAndErrors
7624 * <file src="src/notesMessagesAndErrors/docs/readme.md" />
7630 * <section id="code">
7631 <example module="b2b.att">
7632 <file src="src/notesMessagesAndErrors/docs/demo.html" />
7633 <file src="src/notesMessagesAndErrors/docs/demo.js" />
7638 angular.module('b2b.att.notesMessagesAndErrors', []);
7641 * @name Template.att:Notification Card
7644 * <file src="src/notificationCardTemplate/docs/readme.md" />
7647 * <section id="code">
7648 <b>HTML + AngularJS</b>
7649 <example module="b2b.att">
7650 <file src="src/notificationCardTemplate/docs/demo.html" />
7651 <file src="src/notificationCardTemplate/docs/demo.js" />
7656 angular.module('b2b.att.notificationCardTemplate', [])
7660 * @name Template.att:Order Confirmation Template
7663 * <file src="src/orderConfirmationTemplate/docs/readme.md" />
7666 * <section id="code">
7667 <b>HTML + AngularJS</b>
7668 <example module="b2b.att">
7669 <file src="src/orderConfirmationTemplate/docs/demo.html" />
7670 <file src="src/orderConfirmationTemplate/docs/demo.js" />
7675 angular.module('b2b.att.orderConfirmationTemplate', []);
7679 * @name Navigation.att:pagination
7682 * <file src="src/pagination/docs/readme.md" />
7683 * @param {int} total-pages - Total # of pages, set in your controller $scope
7684 * @param {int} current-page - Current selected page, set in your controller $scope
7685 * @param {function} click-handler - Handler function on click of page number, defined in your controller $scope
7686 * @param {string} input-id - _UNIQUE ID_ __MUST__ be provided for 508 compliance, set in your HTML as static text
7687 * @param {string} input-class - optional class that can be given to use for the go to page container
7690 * <div b2b-pagination="" input-id="goto-page-2" total-pages="totalPages1" current-page="currentPage1" click-handler="customHandler"></div>
7693 * <section id="code">
7694 <example module="b2b.att">
7695 <file src="src/pagination/docs/demo.html" />
7696 <file src="src/pagination/docs/demo.js" />
7701 angular.module('b2b.att.pagination', ['b2b.att.utilities', 'ngTouch'])
7702 .directive('b2bPagination', ['b2bUserAgent', 'keymap', '$window', '$timeout', function (b2bUserAgent, keymap, $window, $timeout) {
7713 templateUrl: 'b2bTemplate/pagination/b2b-pagination.html',
7714 link: function (scope, elem, attr) {
7715 scope.isMobile = b2bUserAgent.isMobile();
7716 scope.notMobile = b2bUserAgent.notMobile();
7719 scope.inputClass = attr.inputClass;
7720 scope.droppableAttribute = scope.isDroppable ? true : false;
7721 scope.$watch('totalPages', function (value) {
7722 if (angular.isDefined(value) && value !== null) {
7725 scope.totalPages = 1;
7729 for (var i = 1; i <= value; i++) {
7730 scope.pages.push(i);
7732 } else if (value > 10) {
7733 var midVal = Math.ceil(value / 2);
7734 scope.pages = [midVal - 2, midVal - 1, midVal, midVal + 1, midVal + 2];
7736 if(scope.currentPage === undefined || scope.currentPage === 1)
7738 currentPageChanged(1);
7742 scope.$watch('currentPage', function (value) {
7743 currentPageChanged(value);
7744 callbackHandler(value);
7746 var callbackHandler = function (num) {
7747 if (angular.isFunction(scope.clickHandler)) {
7748 scope.clickHandler(num);
7751 var getBoundary = function(value){
7752 if ( value < 100 ) {
7754 } else if ( 100 <= value && value < 1000 ) {
7756 } else if ( 1000 <= value ) {
7762 function currentPageChanged(value) {
7763 if (angular.isDefined(value) && value !== null) {
7764 if (!value || value < 1) {
7767 if (value > scope.totalPages) {
7768 value = scope.totalPages;
7770 if (scope.currentPage !== value) {
7771 scope.currentPage = value;
7772 callbackHandler(scope.currentPage);
7774 if (scope.totalPages > 10) {
7775 var val = parseInt(value);
7776 scope.boundary = getBoundary(val);
7777 if (val <= 6) { // Left (first) section
7778 scope.pages = [1, 2, 3, 4, 5, 6, 7];
7779 } else if ( val <= (scope.totalPages - scope.boundary) ) { // Middle section
7780 if ( 7 <= val && val < 9 ) {
7781 if(scope.totalPages < 100) {
7782 scope.pages = [val - 3, val - 2, val - 1, val, val + 1, val + 2];
7783 } else if(scope.totalPages < 1000) {
7784 scope.pages = [val - 2, val - 1, val, val + 1, val + 2];
7785 } else if(scope.totalPages < 1000) {
7786 scope.pages = [val - 2, val - 1, val, val + 1, val + 2];
7788 } else if ( 9 <= val && val < 100 ) {
7789 scope.pages = [val - 3, val - 2, val - 1, val, val + 1, val + 2];
7790 } else if ( 100 <= val && val < 1000 ) {
7791 scope.pages = [val - 2, val - 1, val, val + 1];
7792 } else if ( 1000 <= val ) {
7793 scope.pages = [val - 1, val, val + 1];
7795 } else if ( (scope.totalPages - scope.boundary) < val ) { // Right (last) section
7797 scope.pages = [scope.totalPages - 5, scope.totalPages - 4, scope.totalPages - 3, scope.totalPages - 2, scope.totalPages - 1, scope.totalPages];
7798 } else if ( 100 <= val && val < 1000 ) {
7799 scope.pages = [scope.totalPages - 4, scope.totalPages - 3, scope.totalPages - 2, scope.totalPages - 1, scope.totalPages];
7800 } else if ( 1000 <= val ) {
7801 scope.pages = [scope.totalPages - 3, scope.totalPages - 2, scope.totalPages - 1, scope.totalPages];
7805 if (scope.isMobile) {
7806 var inWidth = $window.innerWidth;
7808 if (inWidth <= 400) {
7810 } else if (inWidth > 400 && inWidth < 500) {
7812 } else if (inWidth >= 500 && inWidth < 600) {
7814 } else if (inWidth >= 600 && inWidth < 700) {
7816 } else if (inWidth >= 700 && inWidth < 800) {
7820 var val = parseInt(value);
7822 scope.meanVal = Math.floor(viewLimit / 2);
7823 var lowerLimit = (val - scope.meanVal) < 1 ? 1 : val - scope.meanVal;
7824 var upperLimit = (lowerLimit + viewLimit - 1) > scope.totalPages ? scope.totalPages : lowerLimit + viewLimit - 1;
7826 for (var i = lowerLimit; i <= upperLimit; i++) {
7827 scope.pages.push(i);
7832 scope.gotoKeyClick = function (keyEvent) {
7833 if (keyEvent.which === keymap.KEY.ENTER) {
7834 scope.gotoBtnClick()
7837 scope.gotoBtnClick = function () {
7838 currentPageChanged(parseInt(scope.gotoPage));
7839 callbackHandler(scope.currentPage);
7840 var qResult = elem[0].querySelector('button');
7841 angular.element(qResult).attr('disabled','true');
7842 $timeout(function(){
7843 elem[0].querySelector('.b2b-pager__item--active').focus();
7845 scope.gotoPage = null;
7847 scope.onfocusIn = function(evt)
7849 var qResult = elem[0].querySelector('button');
7850 angular.element(qResult).removeAttr('disabled');
7852 scope.onfocusOut = function(evt)
7854 if(evt.target.value === "")
7856 var qResult = elem[0].querySelector('button');
7857 angular.element(qResult).attr('disabled','true');
7860 scope.next = function (event) {
7861 if (event != undefined) {
7862 event.preventDefault();
7864 if (scope.currentPage < scope.totalPages) {
7865 scope.currentPage += 1;
7866 callbackHandler(scope.currentPage);
7869 scope.prev = function (event) {
7870 if (event != undefined) {
7871 event.preventDefault();
7873 if (scope.currentPage > 1) {
7874 scope.currentPage -= 1;
7875 callbackHandler(scope.currentPage);
7878 scope.selectPage = function (value, event) {
7879 event.preventDefault();
7880 scope.currentPage = value;
7881 scope.focusedPage = value;
7882 callbackHandler(scope.currentPage);
7884 scope.checkSelectedPage = function (value) {
7885 if (scope.currentPage === value) {
7890 scope.isFocused = function (page) {
7891 return scope.focusedPage === page;
7899 * @name Navigation.att:paneSelector
7902 * <file src="src/paneSelector/docs/readme.md" />
7905 * Please refer demo.html tab in Example section below.
7909 <b>HTML + AngularJS</b>
7910 <example module="b2b.att">
7911 <file src="src/paneSelector/docs/demo.html" />
7912 <file src="src/paneSelector/docs/demo.js" />
7917 angular.module('b2b.att.paneSelector', ['b2b.att.tabs', 'b2b.att.utilities'])
7919 .filter('paneSelectorSelectedItemsFilter', [function () {
7920 return function (listOfItemsArray) {
7922 if (!listOfItemsArray) {
7923 listOfItemsArray = [];
7926 var returnArray = [];
7928 for (var i = 0; i < listOfItemsArray.length; i++) {
7929 if (listOfItemsArray[i].isSelected) {
7930 returnArray.push(listOfItemsArray[i]);
7938 .filter('paneSelectorFetchChildItemsFilter', [function () {
7939 return function (listOfItemsArray) {
7941 if (!listOfItemsArray) {
7942 listOfItemsArray = [];
7945 var returnArray = [];
7947 for (var i = 0; i < listOfItemsArray.length; i++) {
7948 for (var j = 0; j < listOfItemsArray[i].childItems.length; j++) {
7949 returnArray.push(listOfItemsArray[i].childItems[j]);
7957 .directive('b2bPaneSelector', [function () {
7961 templateUrl: 'b2bTemplate/paneSelector/paneSelector.html',
7967 .directive('b2bPaneSelectorPane', [ function () {
7971 templateUrl: 'b2bTemplate/paneSelector/paneSelectorPane.html',
7977 .directive('b2bTabVertical', ['$timeout', 'keymap', function ($timeout, keymap) {
7981 link: function (scope, element, attr, b2bTabCtrl) {
7987 // retreive the isolateScope
7988 var iScope = angular.element(element).isolateScope();
7990 $timeout(function () {
7991 angular.element(element[0].querySelector('a')).unbind('keydown');
7992 angular.element(element[0].querySelector('a')).bind('keydown', function (evt) {
7994 if (!(evt.keyCode)) {
7995 evt.keyCode = evt.which;
7998 switch (evt.keyCode) {
7999 case keymap.KEY.DOWN:
8000 evt.preventDefault();
8005 evt.preventDefault();
8006 iScope.previousKey();
8018 * @name Forms.att:phoneNumberInput
8021 * <file src="src/phoneNumberInput/docs/readme.md" />
8024 <form name="userForm1">
8025 <div class="form-row" ng-class="{'error':!userForm1.text.$valid && userForm1.text.$dirty}">
8026 <label>Phone Mask<span style="color:red">*</span>: (npa) nxx-line Model Value: {{mask.text}}</label>
8028 <input b2b-phone-mask="phoneMask" name="text" ng-model="mask.text" type="text" placeholder="e.g. (123) 456-7890" title="Phone Number" class="b2b-phone-mask-input" required />
8029 <div ng-messages="userForm1.text.$error" class="error-msg" aria-live="polite" aria-atomic="true">
8030 <span ng-message="required" role="alert">This field is mandatory!</span>
8031 <span ng-message="invalidPhoneNumber" role="alert">Please enter valid phone number!</span>
8032 <span ng-message="mask" role="alert">Please enter valid phone number!</span>
8039 * <section id="code">
8040 <example module="b2b.att">
8041 <file src="src/phoneNumberInput/docs/demo.html" />
8042 <file src="src/phoneNumberInput/docs/demo.js" />
8047 angular.module('b2b.att.phoneNumberInput', ['ngMessages', 'b2b.att.utilities'])
8048 .constant("CoreFormsUiConfig", {
8049 phoneMask: '(___) ___-____',
8050 phoneMaskDot: '___.___.____',
8051 phoneMaskHyphen: '___-___-____'
8053 .directive('b2bPhoneMask', ['$parse', 'CoreFormsUiConfig', 'keymap', 'b2bUserAgent', function ($parse, CoreFormsUiConfig, keymap, b2bUserAgent) {
8059 link: function (scope, iElement, iAttrs, ctrl) {
8062 var validPhoneNumber = false;
8063 var currentKey = '';
8064 if (b2bUserAgent.isMobile()) {
8065 mask = "__________";
8067 switch (iAttrs.b2bPhoneMask) {
8069 mask = CoreFormsUiConfig.phoneMask;
8071 case "phoneMaskDot":
8072 mask = CoreFormsUiConfig.phoneMaskDot;
8074 case "phoneMaskHyphen":
8075 mask = CoreFormsUiConfig.phoneMaskHyphen;
8078 mask = CoreFormsUiConfig.phoneMask;
8081 iElement.attr("maxlength", mask.length);
8082 var checkValidity = function (unmaskedValue, rawValue) {
8084 if (angular.isUndefined(rawValue) || rawValue === '') {
8086 } else if (unmaskedValue) {
8087 valid = (unmaskedValue.length === 10);
8089 ctrl.$setValidity('invalidPhoneNumber', validPhoneNumber);
8090 ctrl.$setValidity('mask', valid);
8093 var handleKeyup = function (evt) {
8095 if (evt && evt.keyCode === keymap.KEY.SHIFT) {
8099 var index, formattedNumber;
8100 if (ctrl.$modelValue) {
8101 formattedNumber = ctrl.$modelValue;
8103 formattedNumber = iElement.val();
8105 if (!formattedNumber.length && currentKey === '') {
8108 var maskLength, inputNumbers, maskArray, tempArray, maskArrayLength;
8110 maskArray = mask.split("");
8111 maskArrayLength = maskArray.length;
8112 maskLength = formattedNumber.substring(0, mask.length);
8113 inputNumbers = formattedNumber.replace(/[^0-9]/g, "").split("");
8114 for (index = 0; index < maskArrayLength; index++) {
8115 tempArray.push(maskArray[index] === "_" ? inputNumbers.shift() : maskArray[index]);
8116 if (inputNumbers.length === 0) {
8120 formattedNumber = tempArray.join("");
8121 if (formattedNumber === '(') {
8122 formattedNumber = '';
8125 if ( (angular.isDefined(evt) && evt.which) && currentKey !== '') {
8126 if (maskArray[0] === currentKey && formattedNumber === '') {
8127 formattedNumber = '(';
8128 } else if (evt.which === keymap.KEY.SPACE && currentKey === ' ') {
8129 formattedNumber = formattedNumber + ') ';
8130 } else if (maskArray[0] === currentKey && formattedNumber === '') {
8131 formattedNumber = formattedNumber + currentKey;
8132 } else if (maskArray[formattedNumber.length] === currentKey) {
8133 formattedNumber = formattedNumber + currentKey;
8138 ctrl.$setViewValue(formattedNumber);
8140 return formattedNumber;
8144 // since we are only allowing 0-9, why even let the keypress go forward?
8145 // also added in delete... in case they want to delete :)
8146 var handlePress = function (e) {
8148 if ((e.which < 48 || e.which > 57) && (e.which < 96 || e.which > 105)) {
8149 if (e.which !== keymap.KEY.BACKSPACE && e.which !== keymap.KEY.TAB && e.which !== keymap.KEY.DELETE && e.which !== keymap.KEY.ENTER && e.which !== keymap.KEY.LEFT && e.which !== keymap.KEY.RIGHT &&
8151 (!(e.ctrlKey) && (e.which !== '118' || e.which !== '86')) &&
8153 (!(e.ctrlKey) && (e.which !== '99' || e.which !== '67')) &&
8155 (!(e.ctrlKey) && (e.which !== '120' || e.which !== '88')) &&
8156 /* 229 key code will sent as placeholder key for andriod devices */
8157 (e.which != 229 )) {
8158 e.preventDefault ? e.preventDefault() : e.returnValue = false;
8159 validPhoneNumber = false;
8162 validPhoneNumber = true;
8169 // i moved this out because i thought i might need focus as well..
8170 // to handle setting the model as the view changes
8171 var parser = function (fromViewValue) {
8172 var letters = /^[A-Za-z]+$/;
8173 var numbers = /^[0-9]+$/;
8174 if (angular.isUndefined(fromViewValue) || fromViewValue === '') {
8175 validPhoneNumber = true;
8177 if (fromViewValue.match(letters)) {
8178 validPhoneNumber = false;
8180 if (fromViewValue.match(numbers)) {
8181 validPhoneNumber = true;
8185 if (fromViewValue && fromViewValue.length > 0) {
8186 clean = fromViewValue.replace(/[^0-9]/g, '');
8188 checkValidity(clean, fromViewValue);
8192 //to handle reading the model and formatting it
8193 var formatter = function (fromModelView) {
8195 checkValidity(fromModelView);
8196 if (fromModelView) {
8197 input = handleKeyup();
8202 var setCurrentKey = function (e) {
8213 if (e.shiftKey === true) {
8218 if (e.shiftKey === true) {
8228 if (angular.isDefined(scope.ngModel)) {
8229 parser(scope.ngModel);
8232 ctrl.$parsers.push(parser);
8233 ctrl.$formatters.push(formatter);
8234 iElement.bind('keyup', handleKeyup);
8235 iElement.bind('keydown', handlePress);
8241 * @name Template.att:Profile Blocks
8244 * <file src="src/profileBlockTemplate/docs/readme.md" />
8246 * <section id="code">
8247 <example module="b2b.att">
8248 <file src="src/profileBlockTemplate/docs/demo.html" />
8249 <file src="src/profileBlockTemplate/docs/demo.js" />
8255 angular.module('b2b.att.profileBlockTemplate', [])
8261 * @name Layouts.att:profileCard
8264 * <file src="src/profileCard/docs/readme.md" />
8267 * <b2b-profile-card></b2b-profile-card>
8271 <example module="b2b.att">
8272 <file src="src/profileCard/docs/demo.html" />
8273 <file src="src/profileCard/docs/demo.js" />
8278 angular.module('b2b.att.profileCard', ['b2b.att'])
8279 .constant('profileStatus',{
8286 status: "Deactivated",
8302 role: "COMPANY ADMINISTRATOR"
8305 .directive('b2bProfileCard',['$http','$q','profileStatus', function($http,$q,profileStatus) {
8309 templateUrl: function(element, attrs){
8311 return 'b2bTemplate/profileCard/profileCard.html';
8314 return 'b2bTemplate/profileCard/profileCard-addUser.html';
8321 link: function(scope, elem, attr){
8322 scope.characterLimit = parseInt(attr.characterLimit, 10) || 25;
8323 scope.shouldClip = function(str) {
8324 return str.length > scope.characterLimit;
8327 scope.showEmailTooltip = false;
8330 function isImage(src) {
8331 var deferred = $q.defer();
8332 var image = new Image();
8333 image.onerror = function() {
8334 deferred.reject(false);
8336 image.onload = function() {
8337 deferred.resolve(true);
8339 if(src !== undefined && src.length>0 ){
8342 deferred.reject(false);
8344 return deferred.promise;
8348 isImage(scope.profile.img).then(function(img) {
8351 var splitName=(scope.profile.name).split(' ');
8353 for(var i=0;i<splitName.length;i++){
8354 scope.initials += splitName[i][0];
8356 if(scope.profile.role.toUpperCase() === profileStatus.role){
8359 var profileState=profileStatus.status[scope.profile.state.toUpperCase()];
8361 scope.profile.state=profileStatus.status[scope.profile.state.toUpperCase()].status;
8362 scope.colorIcon=profileStatus.status[scope.profile.state.toUpperCase()].color;
8363 if(scope.profile.state.toUpperCase()===profileStatus.status.PENDING.status.toUpperCase()||scope.profile.state.toUpperCase()===profileStatus.status.LOCKED.status.toUpperCase()){
8364 scope.profile.lastLogin=scope.profile.state;
8367 var today=new Date().getTime();
8368 var lastlogin=new Date(scope.profile.lastLogin).getTime();
8369 var diff=(today-lastlogin)/(1000*60*60*24);
8371 scope.profile.lastLogin="Today";
8374 scope.profile.lastLogin="Yesterday";
8382 * @name Forms.att:radios
8385 * <file src="src/radios/docs/readme.md" />
8390 * @param {boolean} refreshRadioGroup - A trigger that recalculates and updates the accessibility roles on radios in a group.
8394 <b>HTML + AngularJS</b>
8395 <example module="b2b.att">
8396 <file src="src/radios/docs/demo.html" />
8397 <file src="src/radios/docs/demo.js" />
8401 angular.module('b2b.att.radios', ['b2b.att.utilities'])
8402 .directive('b2bRadioGroupAccessibility', ['$timeout', 'b2bUserAgent', function($timeout, b2bUserAgent) {
8406 refreshRadioGroup: "=",
8408 link: function(scope, ele, attr) {
8410 var roleRadioElement, radioProductSelectElement, radioInputTypeElement;
8412 $timeout(calculateNumberOfRadio);
8414 scope.$watch('refreshRadioGroup', function(value) {
8415 if (value === true) {
8416 addingRoleAttribute();
8417 $timeout(calculateNumberOfRadio);
8418 scope.refreshRadioGroup = false;
8425 function calculateNumberOfRadio() {
8426 roleRadioElement = ele[0].querySelectorAll('[role="radio"]');
8428 radioProductSelectElement = ele[0].querySelectorAll('[role="radiogroup"] li.radio-box');
8430 radioInputTypeElement = ele[0].querySelectorAll('input[type="radio"]');
8432 for (var i = 0; i < radioInputTypeElement.length; i++) {
8433 var isChecked = radioInputTypeElement[i].checked ? 'true' : 'false';
8434 var isDisabled = radioInputTypeElement[i].disabled ? 'true' : 'false';
8435 var numOfx = i + 1 + ' of ' + radioInputTypeElement.length;
8436 angular.element(roleRadioElement[i]).attr({
8437 'aria-checked': isChecked,
8438 'aria-disabled': isDisabled,
8439 'data-opNum': numOfx
8441 if (b2bUserAgent.notMobile()) {
8442 angular.element(roleRadioElement[i]).removeAttr("role");
8445 if (radioProductSelectElement.length) {
8446 isChecked === 'true' ? angular.element(radioProductSelectElement[i]).addClass('active') : angular.element(radioProductSelectElement[i]).removeClass('active');
8449 if (/Android/i.test(navigator.userAgent)) {
8450 angular.element(roleRadioElement[i]).append('<span class="hidden-spoken">. ' + numOfx + '.</span>');
8454 angular.element(radioInputTypeElement[i]).bind('click', radioStateChangeonClick);
8459 function addingRoleAttribute() {
8460 for (var i = 0; i < radioInputTypeElement.length; i++) {
8461 if (b2bUserAgent.notMobile()) {
8462 angular.element(roleRadioElement[i]).attr("role", "radio");
8467 function radioStateChangeonClick() {
8468 for (var i = 0; i < radioInputTypeElement.length; i++) {
8469 var isChecked = radioInputTypeElement[i].checked ? 'true' : 'false';
8470 var isDisabled = radioInputTypeElement[i].disabled ? 'true' : 'false';
8471 if (radioProductSelectElement.length) {
8472 isChecked === 'true' ? angular.element(radioProductSelectElement[i]).addClass('active') : angular.element(radioProductSelectElement[i]).removeClass('active');
8474 angular.element(roleRadioElement[i]).attr({
8475 'aria-checked': isChecked,
8476 'aria-disabled': isDisabled
8488 * @name Forms.att:searchField
8491 * <file src="src/searchField/docs/readme.md" />
8494 * <div b2b-search-field dropdown-list="listdata" on-click-callback="clickFn(value)" class="span12" input-model='typedString' config-obj='keyConfigObj'></div>
8498 <example module="b2b.att">
8499 <file src="src/searchField/docs/demo.html" />
8500 <file src="src/searchField/docs/demo.js" />
8505 angular.module('b2b.att.searchField', ['b2b.att.utilities', 'b2b.att.position'])
8506 .filter('b2bFilterInput', [function() {
8507 return function(list, str, keyArray, displayListKey, isContainsSearch, searchSeperator) {
8510 var searchCondition;
8511 var conditionCheck = function(searchSeperator, listItem, displayListKey, splitString) {
8512 var displayTitle = null;
8514 for (var i = 0; i < displayListKey.length; i++) {
8516 displayTitle = listItem[displayListKey[i]].toLowerCase().indexOf(splitString[i].toLowerCase()) > -1;
8518 displayTitle = (splitString[i]) ? displayTitle && listItem[displayListKey[i]].toLowerCase().indexOf(splitString[i].toLowerCase().trim()) > -1 : displayTitle;
8522 angular.forEach(displayListKey, function(value) {
8523 if (!displayTitle) {
8524 displayTitle = listItem[value];
8526 displayTitle = displayTitle + (listItem[value] ? searchSeperator + ' ' + listItem[value] : '');
8530 return displayTitle;
8532 angular.forEach(list, function(listItem) {
8533 var splitString = str.indexOf(searchSeperator) > -1 ? str.split(searchSeperator) : false;
8534 var displayList = conditionCheck(searchSeperator, listItem, displayListKey, splitString)
8535 for (var i = 0; i < keyArray.length; i++) {
8536 searchLabel = keyArray[i];
8537 if (listItem[searchLabel]) {
8538 if (isContainsSearch) {
8539 var displaySearchList = listItem[searchLabel].toLowerCase().indexOf(str.toLowerCase()) > -1;
8540 if (splitString.length > 1) {
8541 displaySearchList = (splitString.length <= keyArray.length) ? displayList : false;
8543 searchCondition = displaySearchList;
8545 searchCondition = listItem[searchLabel].match(new RegExp('^' + str, 'gi'));
8547 if (searchCondition) {
8549 'title': conditionCheck(searchSeperator, listItem, displayListKey),
8550 'valueObj': listItem
8559 }]).directive('b2bSearchField', ['$filter', 'b2bFilterInputFilter', 'keymap', '$documentBind', '$isElement', '$document', 'events', '$timeout', function($filter, b2bFilterInput, keymap, $documentBind, $isElement, $document, events, $timeout) {
8563 dataList: '=dropdownList',
8564 onClickCallback: '&',
8572 templateUrl: 'b2bTemplate/searchField/searchField.html',
8573 controller: ['$scope', function($scope) {
8574 this.searchKeyArray = [];
8575 if ($scope.configObj.searchKeys) {
8576 this.searchKeyArray = $scope.configObj.searchKeys;
8578 if (angular.isUndefined($scope.disabled)) {
8579 $scope.disabled = false;
8581 this.triggerInput = function(searchString) {
8582 $scope.originalInputModel = searchString;
8583 if (searchString === '') {
8584 $scope.currentIndex = -1;
8585 $scope.filterList = [];
8586 $scope.showListFlag = false;
8587 } else if (searchString !== '' && !$scope.isFilterEnabled) {
8588 $scope.filterList = $filter('b2bFilterInput')($scope.dataList, searchString, this.searchKeyArray, $scope.configObj.displayListDataKey, $scope.configObj.isContainsSearch, $scope.configObj.searchSeperator);
8589 $scope.showListFlag = true;
8592 this.denyRegex = function() {
8593 return $scope.inputDeny;
8596 link: function(scope, elem) {
8597 scope.isFilterEnabled = false;
8598 scope.showListFlag = false;
8599 scope.currentIndex = -1;
8600 scope.setCurrentIdx = function(idx) {
8601 scope.currentIndex = idx;
8603 scope.inputModel = scope.filterList[idx].title;
8604 scope.objModel = scope.filterList[idx];
8607 scope.isActive = function(index, dropdownLength) {
8608 scope.dropdownLength = dropdownLength;
8609 return scope.currentIndex === index;
8611 scope.selectItem = function(idx) {
8612 scope.setCurrentIdx(idx);
8613 scope.onClickCallback({
8614 value: scope.inputModel,
8615 objValue: scope.objModel
8617 scope.showListFlag = false;
8618 $timeout(function() {
8619 elem.find('div').find('input')[0].focus();
8622 scope.startSearch = function() {
8623 scope.onClickCallback({
8624 value: scope.inputModel,
8625 objValue: scope.objModel
8628 scope.selectPrev = function() {
8629 if (scope.currentIndex > 0 && scope.filterList.length > 0) {
8630 scope.currentIndex = scope.currentIndex - 1;
8631 scope.setCurrentIdx(scope.currentIndex);
8632 } else if (scope.currentIndex === 0) {
8633 scope.currentIndex = scope.currentIndex - 1;
8634 scope.inputModel = scope.originalInputModel;
8635 scope.isFilterEnabled = true;
8638 scope.selectNext = function() {
8639 if (scope.currentIndex < scope.configObj.noOfItemsDisplay - 1) {
8640 if (scope.currentIndex < scope.filterList.length - 1) {
8641 scope.currentIndex = scope.currentIndex + 1;
8642 scope.setCurrentIdx(scope.currentIndex);
8646 scope.selectCurrent = function() {
8647 scope.selectItem(scope.currentIndex);
8649 scope.selectionIndex = function(e) {
8650 switch (e.keyCode) {
8651 case keymap.KEY.DOWN:
8652 events.preventDefault(e);
8653 scope.isFilterEnabled = true;
8657 events.preventDefault(e);
8658 scope.isFilterEnabled = true;
8661 case keymap.KEY.ENTER:
8662 events.preventDefault(e);
8663 scope.isFilterEnabled = true;
8664 scope.selectCurrent();
8666 case keymap.KEY.ESC:
8667 events.preventDefault(e);
8668 scope.isFilterEnabled = false;
8669 scope.showListFlag = false;
8670 scope.inputModel = '';
8673 scope.isFilterEnabled = false;
8676 if (elem[0].querySelector('.filtercontainer')) {
8677 elem[0].querySelector('.filtercontainer').scrollTop = (scope.currentIndex - 1) * 35;
8680 scope.$watch('filterList', function(newVal, oldVal) {
8681 if (newVal !== oldVal) {
8682 scope.currentIndex = -1;
8685 scope.blurInput = function() {
8686 $timeout(function() {
8687 scope.showListFlag = false;
8690 var outsideClick = function(e) {
8691 var isElement = $isElement(angular.element(e.target), elem.find('ul').eq(0), $document);
8692 if (!isElement && document.activeElement.tagName.toLowerCase() !== 'input') {
8693 scope.showListFlag = false;
8697 $documentBind.click('showListFlag', outsideClick, scope);
8701 .directive('b2bSearchInput', [function() {
8704 require: ['ngModel', '^b2bSearchField'],
8705 link: function(scope, elem, attr, ctrl) {
8706 var ngModelCtrl = ctrl[0];
8707 var attSearchBarCtrl = ctrl[1];
8708 var REGEX = ctrl[1].denyRegex();
8709 var parser = function(viewValue) {
8710 attSearchBarCtrl.triggerInput(viewValue);
8713 ngModelCtrl.$parsers.push(parser);
8715 if (REGEX !== undefined || REGEX !== '') {
8716 elem.bind('input', function() {
8717 var inputString = ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.replace(REGEX, '');
8718 if (inputString !== ngModelCtrl.$viewValue) {
8719 ngModelCtrl.$setViewValue(inputString);
8720 ngModelCtrl.$render();
8731 * @name Buttons, links & UI controls.att:Seek bar
8734 * <file src="src/seekBar/docs/readme.md" />
8737 * Horizontal Seek Bar
8738 * <b2b-seek-bar min="0" max="400" step="1" skip-interval="1" data-ng-model="horizontalSeekBarVal" style="width:180px; margin: auto;" on-drag-end="onDragEnd()" on-drag-init="onDragStart()"></b2b-seek-bar>
8741 * <b2b-seek-bar min="0" max="1" step="0.01" skip-interval="0.1" vertical data-ng-model="verticalSeekBarVal" style=" width: 6px; height: 180px; margin: auto;"></b2b-seek-bar>
8745 <b>HTML + AngularJS</b>
8746 <example module="b2b.att">
8747 <file src="src/seekBar/docs/demo.html" />
8748 <file src="src/seekBar/docs/demo.js" />
8753 angular.module('b2b.att.seekBar', ['b2b.att.utilities','b2b.att.position'])
8754 .constant('b2bSeekBarConfig', {
8760 .directive('b2bSeekBar', ['$documentBind', 'events', 'b2bSeekBarConfig', 'keymap', '$compile', function($documentBind, events, b2bSeekBarConfig, keymap, $compile) {
8765 templateUrl: 'b2bTemplate/seekBar/seekBar.html',
8770 link: function(scope, elm, attr, ngModelCtrl) {
8771 scope.isDragging = false;
8772 scope.verticalSeekBar = false;
8775 var step = b2bSeekBarConfig.step;
8776 var skipInterval = b2bSeekBarConfig.skipInterval;
8777 var knob = angular.element(elm[0].querySelector('.b2b-seek-bar-knob-container'));
8778 var seekBarKnob = angular.element(knob[0].querySelector('.b2b-seek-bar-knob'));
8779 var trackContainer = angular.element(elm[0].querySelector('.b2b-seek-bar-track-container'));
8780 var trackFill = angular.element(elm[0].querySelector('.b2b-seek-bar-track-fill'));
8781 var trackContainerRect = {};
8783 var trackFillOrderPositioning;
8785 if (angular.isDefined(attr.vertical)) {
8786 scope.verticalSeekBar = true;
8787 axisPosition = "clientY";
8790 scope.verticalSeekBar = false;
8791 axisPosition = "clientX";
8793 var getValidStep = function(val) {
8794 val = parseFloat(val);
8795 // in case $modelValue came in string number
8796 if (angular.isNumber(val)) {
8797 val = Math.round((val - min) / step) * step + min;
8798 return Math.round(val * 1000) / 1000;
8802 var getPositionToPercent = function(x) {
8803 if (scope.verticalSeekBar) {
8804 return Math.max(0, Math.min(1, (trackContainerRect.bottom - x) / (trackFillOrderPositioning)));
8807 return Math.max(0, Math.min(1, (x - trackContainerRect.left) / (trackFillOrderPositioning)));
8811 var getPercentToValue = function(percent) {
8812 return (min + percent * (max - min));
8815 var getValueToPercent = function(val) {
8816 return (val - min) / (max - min);
8819 var getValidMinMax = function(val) {
8820 return Math.max(min, Math.min(max, val));
8823 var updateTrackContainerRect = function() {
8824 trackContainerRect = trackContainer[0].getBoundingClientRect();
8825 if (scope.verticalSeekBar) {
8826 if (!trackContainerRect.height) {
8827 trackFillOrderPositioning = trackContainer[0].scrollHeight;
8829 trackFillOrderPositioning = trackContainerRect.height;
8833 if (!trackContainerRect.width) {
8834 trackFillOrderPositioning = trackContainer[0].scrollWidth;
8836 trackFillOrderPositioning = trackContainerRect.width;
8843 var updateKnobPosition = function(percent) {
8844 var percentStr = (percent * 100) + '%';
8845 if (scope.verticalSeekBar) {
8846 knob.css('bottom', percentStr);
8847 trackFill.css('height', percentStr);
8850 knob.css('left', percentStr);
8851 trackFill.css('width', percentStr);
8855 var modelRenderer = function() {
8856 if (isNaN(ngModelCtrl.$viewValue)) {
8857 ngModelCtrl.$viewValue = ngModelCtrl.$modelValue || min;
8860 var viewVal = ngModelCtrl.$viewValue;
8861 scope.currentModelValue = viewVal;
8863 //wait for min, max and step to be set then only update UI to avoid NaN on percent calculation
8864 if ((min || min === 0) && max && step) {
8865 updateKnobPosition(getValueToPercent(viewVal));
8869 var setModelValue = function(val) {
8870 scope.currentModelValue = getValidMinMax(getValidStep(val));
8871 ngModelCtrl.$setViewValue(scope.currentModelValue);
8874 var updateMin = function(val) {
8875 min = parseFloat(val);
8877 min = b2bSeekBarConfig.min;
8882 var updateMax = function(val) {
8883 max = parseFloat(val);
8885 max = b2bSeekBarConfig.max;
8890 var updateStep = function(val) {
8891 step = parseFloat(val);
8892 if (!attr['skipInterval']) {
8893 skipInterval = step;
8897 var updateSkipInterval = function(val) {
8898 skipInterval = step * Math.ceil(val / (step!==0?step:1));
8901 angular.isDefined(attr.min) ? attr.$observe('min', updateMin) : updateMin(b2bSeekBarConfig.min);
8902 angular.isDefined(attr.max) ? attr.$observe('max', updateMax) : updateMax(b2bSeekBarConfig.max);
8903 if (angular.isDefined(attr.step)) {
8904 attr.$observe('step', updateStep);
8906 if (angular.isDefined(attr.skipInterval)) {
8907 attr.$observe('skipInterval', updateSkipInterval);
8909 scope.currentModelValue = getValidMinMax(getValidStep(min));
8910 var onMouseDown = function(e) {
8913 // left mouse button
8917 // right or middle mouse button
8922 scope.isDragging = true;
8923 seekBarKnob[0].focus();
8924 updateTrackContainerRect();
8925 if (attr['onDragInit']) {
8928 events.stopPropagation(e);
8929 events.preventDefault(e);
8930 scope.$apply(function() {
8931 setModelValue(getPercentToValue(getPositionToPercent(e[axisPosition])));
8935 var onMouseUp = function() {
8937 if (attr['onDragEnd']) {
8940 scope.isDragging = false;
8944 var onMouseMove = function(e) {
8945 if (scope.isDragging) {
8946 events.stopPropagation(e);
8947 events.preventDefault(e);
8949 scope.$apply(function() {
8950 setModelValue(getPercentToValue(getPositionToPercent(e[axisPosition])));
8955 function onKeyDown(e) {
8957 e.keyCode = e.which;
8960 switch (e.keyCode) {
8961 case keymap.KEY.LEFT:
8962 if (!scope.verticalSeekBar) {
8963 updateStep = -skipInterval;
8966 case keymap.KEY.RIGHT:
8967 if (!scope.verticalSeekBar) {
8968 updateStep = skipInterval;
8972 if (scope.verticalSeekBar) {
8973 updateStep = skipInterval;
8976 case keymap.KEY.DOWN:
8977 if (scope.verticalSeekBar) {
8978 updateStep = -skipInterval;
8986 events.stopPropagation(e);
8987 events.preventDefault(e);
8988 scope.$apply(function() {
8989 setModelValue(ngModelCtrl.$viewValue + updateStep);
8991 if (attr['onDragEnd']) {
8997 elm.on('keydown', onKeyDown);
8998 elm.on('mousedown', onMouseDown);
9000 $documentBind.event('mousemove', 'isDragging', onMouseMove, scope, true, 0);
9001 $documentBind.event('mouseup', 'isDragging', onMouseUp, scope, true, 0);
9003 ngModelCtrl.$render = function() {
9004 if (!scope.isDragging) {
9008 ngModelCtrl.$viewChangeListeners.push(modelRenderer);
9009 ngModelCtrl.$formatters.push(getValidMinMax);
9010 ngModelCtrl.$formatters.push(getValidStep);
9016 * @name Layouts.att:separators
9019 * <file src="src/separators/docs/readme.md" />
9025 * <section id="code">
9026 <b>HTML + AngularJS</b>
9027 <example module="b2b.att">
9028 <file src="src/separators/docs/demo.html" />
9029 <file src="src/separators/docs/demo.js" />
9035 angular.module('b2b.att.separators', []);
9038 * @name Buttons, links & UI controls.att:slider
9041 * <file src="src/slider/docs/readme.md" />
9044 * <b2b-slider min="0" max="400" step="1" no-aria-label skip-interval="1" ng-model="horizontalSliderVal" style="width:180px; margin: auto;" on-drag-end="onDragEnd()" on-drag-init="onDragStart()" label-id="slider-label" post-aria-label="postAriaLabel"></b2b-slider>
9048 <b>HTML + AngularJS</b>
9049 <example module="b2b.att">
9050 <file src="src/slider/docs/demo.html" />
9051 <file src="src/slider/docs/demo.js" />
9056 angular.module('b2b.att.slider', ['b2b.att.utilities'])
9057 .constant('SliderConfig', {
9063 .directive('b2bSlider', ['$documentBind', 'SliderConfig', 'keymap', '$compile', '$log', function($documentBind, SliderConfig, keymap, $compile, $log) {
9068 templateUrl: 'b2bTemplate/slider/slider.html',
9072 trackFillColor: '=?',
9074 postAriaLabel: '=?',
9076 sliderSnapPoints: '=?',
9077 customAriaLabel: '=?',
9080 link: function(scope, elm, attr, ngModelCtrl) {
9081 scope.isDragging = false;
9082 scope.verticalSlider = false;
9085 var step = SliderConfig.step;
9086 var skipInterval = SliderConfig.skipInterval;
9087 var knob = angular.element(elm[0].querySelector('.slider-knob-container'));
9088 var sliderKnob = angular.element(knob[0].querySelector('.slider-knob'));
9089 var trackContainer = angular.element(elm[0].querySelector('.slider-track-container'));
9090 var trackFill = angular.element(elm[0].querySelector('.slider-track-fill'));
9091 var trackContainerRect = {};
9092 var axisPosition = "clientX";
9093 var trackFillOrderPositioning;
9095 //Forcefully disabling the vertical Slider code.
9096 if (angular.isDefined(attr.vertical)) {
9097 scope.verticalSlider = true;
9098 axisPosition = "clientY";
9101 if (angular.isDefined(scope.noAriaLabel) && scope.noAriaLabel !== '') {
9102 $log.warn('no-aria-label has been deprecated. This will be removed in v0.6.0.');
9104 if (angular.isDefined(scope.preAriaLabel) && scope.preAriaLabel !== '') {
9105 $log.warn('pre-aria-label has been deprecated. Please use label-id instead. This will be removed in v0.6.0.');
9107 if (angular.isDefined(scope.customAriaLabel) && scope.customAriaLabel !== '') {
9108 $log.warn('custom-aria-label has been deprecated. Please use label-id and post-aria-label instead. This will be removed in v0.6.0.');
9111 var binarySearchNearest = function (num, arr) {
9114 var hi = arr.length - 1;
9116 while (hi - lo > 1) {
9117 mid = Math.floor((lo + hi) / 2);
9118 if (arr[mid] < num) {
9124 if (num - arr[lo] < arr[hi] - num) {
9130 var getValidStep = function(val) {
9131 val = parseFloat(val);
9132 // in case $modelValue came in string number
9135 if(attr['sliderSnapPoints'] && scope.sliderSnapPoints.length > 0) {
9136 val = binarySearchNearest(val, scope.sliderSnapPoints);
9139 val = Math.round((val - min) / step) * step + min;
9142 return Math.round(val * 1000) / 1000;
9146 var getPositionToPercent = function(x) {
9147 if (scope.verticalSlider) {
9148 return Math.max(0, Math.min(1, (trackContainerRect.bottom - x) / (trackFillOrderPositioning)));
9151 return Math.max(0, Math.min(1, (x - trackContainerRect.left) / (trackFillOrderPositioning)));
9155 var getPercentToValue = function(percent) {
9156 return (min + percent * (max - min));
9159 var getValueToPercent = function(val) {
9160 return (val - min) / (max - min);
9163 var getValidMinMax = function(val) {
9164 return Math.max(min, Math.min(max, val));
9167 var updateTrackContainerRect = function() {
9168 trackContainerRect = trackContainer[0].getBoundingClientRect();
9169 if (scope.verticalSlider) {
9170 if (!trackContainerRect.height) {
9171 trackFillOrderPositioning = trackContainer[0].scrollHeight;
9173 trackFillOrderPositioning = trackContainerRect.height;
9177 if (!trackContainerRect.width) {
9178 trackFillOrderPositioning = trackContainer[0].scrollWidth;
9180 trackFillOrderPositioning = trackContainerRect.width;
9187 var updateKnobPosition = function(percent) {
9188 var percentStr = (percent * 100) + '%';
9189 if (scope.verticalSlider) {
9190 knob.css('bottom', percentStr);
9191 trackFill.css('height', percentStr);
9194 knob.css('left', percentStr);
9195 trackFill.css('width', percentStr);
9199 var modelRenderer = function() {
9201 if(attr['disabled']){
9205 if (isNaN(ngModelCtrl.$viewValue)) {
9206 ngModelCtrl.$viewValue = ngModelCtrl.$modelValue || min;
9209 var viewVal = ngModelCtrl.$viewValue;
9210 scope.currentModelValue = viewVal;
9212 //wait for min, max and step to be set then only update UI to avoid NaN on percent calculation
9213 if ((min || min === 0) && max && step) {
9214 updateKnobPosition(getValueToPercent(viewVal));
9218 var setModelValue = function(val) {
9219 scope.currentModelValue = getValidMinMax(getValidStep(val));
9220 ngModelCtrl.$setViewValue(scope.currentModelValue);
9223 var updateMin = function(val) {
9224 min = parseFloat(val);
9226 min = SliderConfig.min;
9232 var updateMax = function(val) {
9233 max = parseFloat(val);
9235 max = SliderConfig.max;
9241 var updateStep = function(val) {
9242 step = parseFloat(val);
9243 if (!attr['skipInterval']) {
9244 skipInterval = step;
9248 var updateSkipInterval = function(val) {
9249 skipInterval = step * Math.ceil(val / (step!==0?step:1));
9252 angular.isDefined(attr.min) ? attr.$observe('min', updateMin) : updateMin(SliderConfig.min);
9253 angular.isDefined(attr.max) ? attr.$observe('max', updateMax) : updateMax(SliderConfig.max);
9254 if (angular.isDefined(attr.step)) {
9255 attr.$observe('step', updateStep);
9257 if (angular.isDefined(attr.skipInterval)) {
9258 attr.$observe('skipInterval', updateSkipInterval);
9260 scope.currentModelValue = getValidMinMax(getValidStep(min));
9261 var onMouseDown = function(e) {
9263 if(attr['disabled']){
9269 // left mouse button
9273 // right or middle mouse button
9277 scope.isDragging = true;
9278 sliderKnob[0].focus();
9279 updateTrackContainerRect();
9280 if (attr['onDragInit']) {
9283 e.stopPropagation();
9285 scope.$apply(function() {
9286 setModelValue(getPercentToValue(getPositionToPercent(e[axisPosition])));
9290 var onMouseUp = function() {
9292 if (attr['onDragEnd']) {
9295 scope.isDragging = false;
9299 var onMouseMove = function(e) {
9300 if (scope.isDragging) {
9301 e.stopPropagation();
9304 scope.$apply(function() {
9305 setModelValue(getPercentToValue(getPositionToPercent(e[axisPosition])));
9310 function onKeyDown(e) {
9312 e.keyCode = e.which;
9315 switch (e.keyCode) {
9316 case keymap.KEY.DOWN:
9317 case keymap.KEY.LEFT:
9318 if(attr['sliderSnapPoints'] && scope.sliderSnapPoints.length > 0) {
9319 var currentIndex = scope.sliderSnapPoints.indexOf(ngModelCtrl.$viewValue);
9320 if (currentIndex > 0) {
9323 updateStep = scope.sliderSnapPoints[currentIndex];
9326 updateStep = ngModelCtrl.$viewValue - skipInterval;
9330 case keymap.KEY.RIGHT:
9331 if(attr['sliderSnapPoints'] && scope.sliderSnapPoints.length > 0) {
9332 var currentIndex = scope.sliderSnapPoints.indexOf(ngModelCtrl.$viewValue);
9333 if (currentIndex < scope.sliderSnapPoints.length-1) {
9336 updateStep = scope.sliderSnapPoints[currentIndex];
9339 updateStep = ngModelCtrl.$viewValue + skipInterval;
9342 case keymap.KEY.END:
9343 if(attr['sliderSnapPoints'] && scope.sliderSnapPoints.length > 0) {
9344 currentIndex = scope.sliderSnapPoints.length-1;
9345 updateStep = scope.sliderSnapPoints[currentIndex];
9347 setModelValue(scope.max);
9350 e.stopPropagation();
9352 case keymap.KEY.HOME:
9353 if(attr['sliderSnapPoints'] && scope.sliderSnapPoints.length > 0) {
9355 updateStep = scope.sliderSnapPoints[currentIndex];
9357 setModelValue(scope.min);
9360 e.stopPropagation();
9366 if (angular.isNumber(updateStep) && !attr['disabled']) {
9367 e.stopPropagation();
9369 scope.$apply(function() {
9370 setModelValue(updateStep);
9372 if (attr['onDragEnd']) {
9378 elm.on('keydown', onKeyDown);
9379 elm.on('mousedown', onMouseDown);
9381 $documentBind.event('mousemove', 'isDragging', onMouseMove, scope, true, 0);
9382 $documentBind.event('mouseup', 'isDragging', onMouseUp, scope, true, 0);
9384 attr.$observe('disabled', function (disabled) {
9386 sliderKnob.removeAttr('tabindex');
9388 sliderKnob.attr('tabindex', '0');
9392 elm.toggleClass("slider-disabled", disabled);
9394 if (angular.isDefined(attr.hideDisabledKnob)) {
9395 scope.hideKnob = disabled;
9399 ngModelCtrl.$render = function() {
9400 if (!scope.isDragging) {
9402 if (attr['onRenderEnd'] && !attr['disabled']) {
9403 scope.onRenderEnd({currentModelValue: scope.currentModelValue});
9407 ngModelCtrl.$viewChangeListeners.push(modelRenderer);
9408 ngModelCtrl.$formatters.push(getValidMinMax);
9409 ngModelCtrl.$formatters.push(getValidStep);
9415 * @name Forms.att:spinButton
9417 * @param {String} spin-button-id - An ID for the input field
9418 * @param {Integer} min - Minimum value for the input
9419 * @param {Integer} max - Maximum value for the input
9420 * @param {Integer} step - Value by which input field increments or decrements on up/down arrow keys
9421 * @param {Integer} page-step - Value by which input field increments or decrements on page up/down keys
9422 * @param {boolean} input-model-key - Default value for input field
9423 * @param {boolean} disabled-flag - A boolean that triggers directive once the min or max value has reached
9426 * <file src="src/spinButton/docs/readme.md" />
9429 * <section id="code">
9430 <example module="b2b.att">
9431 <file src="src/spinButton/docs/demo.html" />
9432 <file src="src/spinButton/docs/demo.js" />
9437 angular.module('b2b.att.spinButton', ['b2b.att.utilities'])
9438 .constant('b2bSpinButtonConfig', {
9443 inputModelKey: 'value',
9446 .directive('b2bSpinButton', ['keymap', 'b2bSpinButtonConfig', 'b2bUserAgent', function (keymap, b2bSpinButtonConfig, userAgent) {
9449 require: '?ngModel',
9456 pageStep: '=pageStep',
9458 inputValue: '=ngModel',
9462 templateUrl: 'b2bTemplate/spinButton/spinButton.html',
9463 controller: ['$scope', '$element', '$attrs', function (scope, element, attrs) {
9465 scope.isMobile = userAgent.isMobile();
9466 scope.notMobile = userAgent.notMobile();
9468 scope.min = attrs.min ? scope.min : b2bSpinButtonConfig.min;
9469 scope.max = attrs.max ? scope.max : b2bSpinButtonConfig.max;
9470 scope.step = attrs.step ? scope.step : b2bSpinButtonConfig.step;
9471 scope.pageStep = attrs.pageStep ? scope.pageStep : b2bSpinButtonConfig.pageStep;
9472 scope.inputModelKey = attrs.inputModelKey ? scope.inputModelKey : b2bSpinButtonConfig.inputModelKey;
9473 scope.disabledFlag = attrs.disabledFlag ? scope.disabledFlag : b2bSpinButtonConfig.disabledFlag;
9475 if (scope.min < 0) {
9478 if (scope.max > 999) {
9482 scope.isPlusDisabled = function () {
9483 return (scope.disabledFlag || scope.inputValue[scope.inputModelKey] >= scope.max);
9485 scope.isMinusDisabled = function () {
9486 return (scope.disabledFlag || scope.inputValue[scope.inputModelKey] <= scope.min);
9489 scope.getValidateInputValue = function (value) {
9490 if (value <= scope.min) {
9492 } else if (value >= scope.max) {
9499 scope.plus = function () {
9500 scope.inputValue[scope.inputModelKey] = scope.getValidateInputValue(parseInt(scope.inputValue[scope.inputModelKey], 10) + scope.step);
9502 scope.minus = function () {
9503 scope.inputValue[scope.inputModelKey] = scope.getValidateInputValue(parseInt(scope.inputValue[scope.inputModelKey], 10) - scope.step);
9505 scope.pagePlus = function () {
9506 scope.inputValue[scope.inputModelKey] = scope.getValidateInputValue(parseInt(scope.inputValue[scope.inputModelKey], 10) + scope.pageStep);
9508 scope.pageMinus = function () {
9509 scope.inputValue[scope.inputModelKey] = scope.getValidateInputValue(parseInt(scope.inputValue[scope.inputModelKey], 10) - scope.pageStep);
9513 link: function (scope, elem) {
9515 if (scope.notMobile) {
9516 angular.element(elem).find('input').attr('aria-live', 'off');
9517 angular.element(elem).find('input').attr('role', 'spinbutton');
9520 elem.find('input').bind('keydown', function (e) {
9521 if (e.keyCode === keymap.KEY.UP) {
9523 } else if (e.keyCode === keymap.KEY.DOWN){
9525 } else if (e.keyCode === keymap.KEY.HOME) {
9526 scope.inputValue[scope.inputModelKey] = parseInt(scope.min);
9527 } else if (e.keyCode === keymap.KEY.END) {
9528 scope.inputValue[scope.inputModelKey] = parseInt(scope.max);
9529 } else if (e.keyCode === keymap.KEY.PAGE_UP) {
9531 } else if (e.keyCode === keymap.KEY.PAGE_DOWN) {
9537 elem.find('input').bind('keyup', function () {
9538 if (scope.inputValue[scope.inputModelKey] === null ||
9539 scope.inputValue[scope.inputModelKey] === '' ||
9540 scope.inputValue[scope.inputModelKey] < scope.min) {
9541 scope.inputValue[scope.inputModelKey] = scope.min;
9543 } else if (angular.isUndefined(scope.inputValue[scope.inputModelKey]) ||
9544 scope.inputValue[scope.inputModelKey] > scope.max) {
9545 scope.inputValue[scope.inputModelKey] = scope.max;
9550 scope.focusInputSpinButton = function (evt) {
9551 evt.preventDefault();
9552 if (scope.notMobile) {
9553 elem[0].querySelector('input').focus();
9562 * @name Template.att:Static Route
9565 * <file src="src/staticRouteTemplate/docs/readme.md" />
9568 * <section id="code">
9569 <example module="b2b.att">
9570 <file src="src/staticRouteTemplate/docs/demo.html" />
9571 <file src="src/staticRouteTemplate/docs/demo.js" />
9576 angular.module('b2b.att.staticRouteTemplate', ['b2b.att.utilities'])
9580 * @name Progress & usage indicators.att:statusTracker
9583 * @param {array} statusObject - An array of status objects that accept heading, estimate, description and state
9585 * <file src="src/statusTracker/docs/readme.md" />
9589 <div ng-controller="statusTrackerController">
9590 <b2b-status-tracker statuses="statusObject"></b2b-status-tracker>
9595 <example module="b2b.att">
9596 <file src="src/statusTracker/docs/demo.html" />
9597 <file src="src/statusTracker/docs/demo.js" />
9602 angular.module('b2b.att.statusTracker', ['b2b.att.utilities'])
9603 .constant('b2bStatusTrackerConfig', {
9606 'complete': 'icoControls-approval',
9607 'current': 'icon-misc-time',
9608 'pending': 'icoControls-statusokay',
9609 'actionRequired': 'icon-primary-securityalerts-alert',
9610 'notAvailable': 'icoControls-restricted'
9613 .directive('b2bStatusTracker', ['b2bStatusTrackerConfig', function(b2bStatusTrackerConfig) {
9621 templateUrl: function(scope) {
9622 return 'b2bTemplate/statusTracker/statusTracker.html';
9624 link: function(scope, element, attr) {
9625 scope.currentViewIndex = 0;
9626 scope.b2bStatusTrackerConfig = b2bStatusTrackerConfig;
9628 scope.nextStatus = function() {
9629 if (scope.currentViewIndex+1 <= scope.statuses.length) {
9630 scope.currentViewIndex++;
9633 scope.previousStatus = function() {
9634 if (scope.currentViewIndex-1 >= 0) {
9635 scope.currentViewIndex--;
9638 scope.isInViewport = function(index) {
9639 return (index < scope.currentViewIndex+3 && index >= scope.currentViewIndex); // && index > scope.currentViewIndex-2
9642 scope.removeCamelCase = function(str) {
9643 return str.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase();
9650 * @name Progress & usage indicators.att:stepTracker
9653 * @param {array} stepsItemsObject - An array of step objects
9654 * @param {Integer} currenIindex - This indicates the current running step
9655 * @param {Integer} viewportIndex - This is optional. This can used to start the view port rather than 1 item.
9657 * <file src="src/stepTracker/docs/readme.md" />
9661 * <b2b-step-tracker steps-items-object="stepsObject" current-index="currentStepIndex" step-indicator-heading="stepHeading"></b2b-step-tracker>
9666 <b>HTML + AngularJS</b>
9667 <example module="b2b.att">
9668 <file src="src/stepTracker/docs/demo.html" />
9669 <file src="src/stepTracker/docs/demo.js" />
9673 angular.module('b2b.att.stepTracker', ['b2b.att.utilities'])
9674 .constant('b2bStepTrackerConfig', {
9677 .directive('b2bStepTracker', ['b2bStepTrackerConfig', function(b2bStepTrackerConfig) {
9682 stepsItemsObject:"=",
9686 templateUrl: 'b2bTemplate/stepTracker/stepTracker.html',
9687 link: function(scope, ele, attr) {
9688 if (angular.isDefined(scope.viewportIndex)) {
9689 scope.currentViewIndex = scope.viewportIndex - 1;
9691 scope.currentViewIndex = 0;
9694 scope.b2bStepTrackerConfig = b2bStepTrackerConfig;
9695 scope.nextStatus = function() {
9696 if (scope.currentViewIndex+1 <= scope.stepsItemsObject.length) {
9697 scope.currentViewIndex++;
9700 scope.previousStatus = function() {
9701 if (scope.currentViewIndex-1 >= 0) {
9702 scope.currentViewIndex--;
9705 scope.isInViewport = function(index) {
9706 return (index < scope.currentViewIndex+b2bStepTrackerConfig.maxViewItems && index >= scope.currentViewIndex);
9714 * @name Buttons, links & UI controls.att:switches
9717 * <file src="src/switches/docs/readme.md" />
9721 * <!-- On / Off Toggle switch -->
9722 * <label for="switch1" class="controlled-text-wrap"> This is ON
9723 * <input type="checkbox" role="checkbox" b2b-switches id="switch1" ng-model="switch1.value">
9726 * <!-- On / Off Toggle switch and DISABLED -->
9727 * <label for="switch2" class="controlled-text-wrap"> This is ON (disabled)
9728 * <input type="checkbox" role="checkbox" b2b-switches id="switch2" ng-model="switch1.value" ng-disabled="true" >
9733 * <section id="code">
9734 <b>HTML + AngularJS</b>
9735 <example module="b2b.att">
9736 <file src="src/switches/docs/demo.js" />
9737 <file src="src/switches/docs/demo.html" />
9741 angular.module('b2b.att.switches', ['b2b.att.utilities'])
9742 .directive('b2bSwitches', ['$compile', '$templateCache', 'keymap', 'events', function ($compile, $templateCache, keymap, events) {
9745 require: ['ngModel'],
9746 link: function (scope, element, attrs, ctrl) {
9747 var ngModelController = ctrl[0];
9749 element.parent().bind("keydown", function (e) {
9750 if (!attrs.disabled && (e.keyCode === keymap.KEY.ENTER || e.keyCode === keymap.KEY.SPACE)) {
9751 events.preventDefault(e);
9752 ngModelController.$setViewValue(!ngModelController.$viewValue);
9753 element.prop("checked", ngModelController.$viewValue);
9757 element.wrap('<div class="btn-switch">');
9758 //element.attr("tabindex", -1);
9759 if (navigator.userAgent.match(/iphone/i)){
9760 element.attr("aria-live", "polite");
9763 element.removeAttr('aria-live');
9766 var templateSwitch = angular.element($templateCache.get("b2bTemplate/switches/switches.html"));
9767 if (angular.isDefined(attrs.typeSpanish)) {
9768 templateSwitch = angular.element($templateCache.get("b2bTemplate/switches/switches-spanish.html"));
9771 templateSwitch = $compile(templateSwitch)(scope);
9772 element.parent().append(templateSwitch);
9774 element.bind("focus", function (e) {
9775 element.parent().addClass('focused');
9778 element.bind("blur", function (e) {
9779 element.parent().removeClass('focused');
9786 * @name Template.att:Table with Drag and Drop
9789 * <file src="src/tableDragAndDrop/docs/readme.md" />
9792 * <section id="code">
9793 <example module="b2b.att">
9794 <file src="src/tableDragAndDrop/docs/demo.html" />
9795 <file src="src/tableDragAndDrop/docs/demo.js" />
9800 angular.module('b2b.att.tableDragAndDrop', ['b2b.att.utilities','b2b.att.tables'])
9804 * @name Messages, modals & alerts.att:tableMessages
9807 * <file src="src/tableMessages/docs/readme.md" />
9810 <!-- no matching results -->
9811 <b2b-table-message msg-type="'noMatchingResults'">
9812 <p>No Matching Results</p>
9813 </b2b-table-message>
9815 <!-- info could not load -->
9816 <b2b-table-message msg-type="'infoCouldNotLoad'" on-refresh-click="refreshClicked()">
9817 </b2b-table-message>
9819 <!-- magnify search -->
9820 <b2b-table-message msg-type="'magnifySearch'">
9821 </b2b-table-message>
9823 <!-- loading data -->
9824 <b2b-table-message msg-type="'loadingTable'">
9825 <!-- custom html -->
9826 <p>The data is currently loading...</p>
9827 </b2b-table-message>
9831 <b>HTML + AngularJS</b>
9832 <example module="b2b.att">
9833 <file src="src/tableMessages/docs/demo.html" />
9834 <file src="src/tableMessages/docs/demo.js" />
9838 angular.module('b2b.att.tableMessages', [])
9839 .directive('b2bTableMessage', [function() {
9848 templateUrl: 'b2bTemplate/tableMessages/tableMessage.html',
9849 link: function(scope) {
9850 scope.refreshAction = function(evt) {
9851 scope.onRefreshClick(evt);
9859 * @name Tabs, tables & accordions.att:tableScrollbar
9862 * <file src="src/tableScrollbar/docs/readme.md" />
9866 <b2b-table-scrollbar>
9868 <thead type="header">
9870 <th role="columnheader" scope="col" key="Id" id="col1">Id</th>
9876 <td id="rowheader0" headers="col1">1002</td>
9881 </b2b-table-scrollbar>
9884 * <section id="code">
9885 <example module="b2b.att">
9886 <file src="src/tableScrollbar/docs/demo.html" />
9887 <file src="src/tableScrollbar/docs/demo.js" />
9892 angular.module('b2b.att.tableScrollbar', [])
9893 .directive('b2bTableScrollbar', ['$timeout', function ($timeout) {
9898 templateUrl: 'b2bTemplate/tableScrollbar/tableScrollbar.html',
9899 link: function (scope, element, attrs, ctrl) {
9900 var firstThWidth, firstTdWidth, firstColumnWidth, firstColumnHeight, trHeight = 0;
9901 var pxToScroll = '';
9902 var tableElement = element.find('table');
9903 var thElements = element.find('th');
9904 var tdElements = element.find('td');
9905 var innerContainer = angular.element(element[0].querySelector('.b2b-table-inner-container'));
9906 var outerContainer = angular.element(element[0].querySelector('.b2b-table-scrollbar'));
9908 scope.disableLeft = true;
9909 scope.disableRight = false;
9911 if (angular.isDefined(thElements[0])) {
9912 firstThWidth = thElements[0].offsetWidth;
9914 if (angular.isDefined(tdElements[0])) {
9915 firstTdWidth = tdElements[0].offsetWidth;
9917 firstColumnWidth = (firstThWidth > firstTdWidth) ? firstThWidth : firstTdWidth;
9919 innerContainer.css({
9920 'padding-left': (firstColumnWidth + 2) + 'px'
9923 angular.forEach(element.find('tr'), function (eachTr, index) {
9924 trObject = angular.element(eachTr);
9925 firstColumn = angular.element(trObject.children()[0]);
9927 angular.element(firstColumn).css({
9929 'width': (firstColumnWidth + 2) + 'px',
9930 'position': 'absolute'
9933 trHeight = trObject[0].offsetHeight;
9934 firstColumnHeight = firstColumn[0].offsetHeight;
9935 if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
9936 firstColumnHeight += 1;
9939 if (trHeight !== firstColumnHeight - 1) {
9940 if (trHeight > firstColumnHeight) {
9941 if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
9944 angular.element(firstColumn).css({
9945 'height': (trHeight + 1) + 'px'
9948 angular.element(trObject).css({
9949 'height': (firstColumnHeight - 1) + 'px'
9956 pxToScroll = outerContainer[0].offsetWidth - firstColumnWidth;
9958 scope.scrollLeft = function () {
9959 innerContainer[0].scrollLeft = innerContainer[0].scrollLeft + 20 - pxToScroll;
9962 scope.scrollRight = function () {
9963 innerContainer[0].scrollLeft = innerContainer[0].scrollLeft + pxToScroll - 20;
9966 scope.checkScrollArrows = function () {
9967 if (innerContainer[0].scrollLeft == 0) {
9968 scope.disableLeft = true;
9970 scope.disableLeft = false;
9973 if (((innerContainer[0].offsetWidth - firstColumnWidth) + innerContainer[0].scrollLeft) >= tableElement[0].offsetWidth) {
9974 scope.disableRight = true;
9976 scope.disableRight = false;
9981 innerContainer.bind('scroll', function () {
9982 $timeout(function () {
9983 scope.checkScrollArrows();
9992 * @name Tabs, tables & accordions.att:tables
9995 * <file src="src/tables/docs/readme.md" />
10000 <table b2b-table table-data="tableData" search-string="searchString">
10001 <thead b2b-table-row type="header">
10003 <th b2b-table-header key="requestId" default-sort="a" id="col1">Header 1</th>
10004 <th b2b-table-header key="requestType" sortable="false" id="col2">Header 2</th>
10007 <tbody b2b-table-row type="body" row-repeat="rowData in tableData">
10009 <td b2b-table-body id="rowheader{{$index}}" headers="col1" ng-bind="rowData['requestId']"> </td>
10010 <td b2b-table-body ng-bind="rowData['requestType']"></td>
10016 * <section id="code">
10017 <example module="b2b.att">
10018 <file src="src/tables/docs/demo.html" />
10019 <file src="src/tables/docs/demo.js" />
10024 angular.module('b2b.att.tables', ['b2b.att.utilities'])
10025 .constant('b2bTableConfig', {
10026 defaultSortPattern: false, // true for descending & false for ascending
10027 highlightSearchStringClass: 'tablesorter-search-highlight',
10028 zebraStripCutOff: 6, // > zebraStripCutOff
10029 tableBreakpoints: [ // breakpoints are >= min and < max
10052 .directive('b2bTable', ['$filter', '$window', 'b2bTableConfig', '$timeout', function ($filter, $window, b2bTableConfig, $timeout) {
10062 searchCategory: "=",
10066 require: 'b2bTable',
10067 templateUrl: 'b2bTemplate/tables/b2bTable.html',
10068 controller: ['$scope', '$attrs', function ($scope, $attrs) {
10070 this.currentSortIndex = null;
10071 this.responsive = $scope.responsive = $attrs.responsive;
10072 this.maxTableColumns = -1;
10073 this.totalTableColums = 0;
10074 this.active = $scope.active = false;
10075 this.responsiveRowScopes = [];
10076 this.hideColumnPriority = [];
10077 this.hiddenColumn = [];
10078 this.setIndex = function (headerScope, priority) {
10079 this.headers.push(headerScope);
10080 if (this.responsive) {
10081 this.totalTableColums++;
10082 if (!isNaN(priority)) {
10083 this.hideColumnPriority[priority] = this.totalTableColums - 1;
10085 this.hideColumnPriority[this.totalTableColums - 1] = this.totalTableColums - 1;
10088 return this.totalTableColums - 1;
10090 this.getIndex = function (headerName) {
10091 for (var i = 0; i < this.headers.length; i++) {
10092 if (this.headers[i].headerName === headerName) {
10093 return this.headers[i].index;
10098 this.setResponsiveRow = function (responsiveRowScope) {
10099 this.responsiveRowScopes.push(responsiveRowScope);
10101 $scope.nextSort = '';
10102 this.sortData = function (columnIndex, reverse, externalSort) {
10103 if ($scope.$parent && $scope.$parent !== undefined) {
10104 $scope.$parent.columnIndex = columnIndex;
10105 $scope.$parent.reverse = reverse;
10107 this.currentSortIndex = columnIndex;
10108 if (externalSort === true) {
10110 $scope.nextSort = 'd'
10112 $scope.nextSort = 'a'
10115 $scope.currentPage = 1;
10116 this.resetSortPattern();
10118 this.getSearchString = function () {
10119 return $scope.searchString;
10121 this.resetSortPattern = function () {
10122 for (var i = 0; i < this.headers.length; i++) {
10123 var currentScope = this.headers[i];
10124 if (currentScope.index !== this.currentSortIndex) {
10125 currentScope.resetSortPattern();
10130 $scope.$watch('nextSort', function (val) {
10131 if ($scope.$parent && $scope.$parent !== undefined) {
10132 $scope.$parent.nextSort = val;
10137 link: function (scope, elem, attr, ctrl) {
10138 scope.searchCriteria = {};
10139 scope.tableBreakpoints = attr.tableConfig ? scope.$parent.$eval(attr.tableConfig) : angular.copy(b2bTableConfig.tableBreakpoints);
10140 scope.$watchCollection('tableData', function (value) {
10141 if (value && !isNaN(value.length)) {
10142 scope.totalRows = value.length;
10145 scope.$watch('currentPage', function (val) {
10146 if (scope.$parent && scope.$parent !== undefined) {
10147 scope.$parent.currentPage = val;
10151 scope.$watch('viewPerPage', function (val) {
10152 if (scope.$parent && scope.$parent !== undefined) {
10153 scope.$parent.viewPerPage = val;
10156 scope.$watch('totalRows', function (val) {
10157 if (scope.$parent && scope.$parent !== undefined) {
10158 if (val > b2bTableConfig.zebraStripCutOff) {
10159 scope.$parent.zebraStripFlag = true;
10161 scope.$parent.zebraStripFlag = false;
10165 scope.$watch(function () {
10166 return scope.totalRows / scope.viewPerPage;
10167 }, function (value) {
10168 if (!isNaN(value)) {
10169 scope.totalPage = Math.ceil(value);
10170 scope.currentPage = 1;
10173 var searchValCheck = function (val) {
10174 if (angular.isDefined(val) && val !== null && val !== "") {
10178 var setSearchCriteria = function (v1, v2) {
10179 if (searchValCheck(v1) && searchValCheck(v2)) {
10180 var index = ctrl.getIndex(v2);
10181 scope.searchCriteria = {};
10182 if (index !== null) {
10183 scope.searchCriteria[index] = v1;
10185 } else if (searchValCheck(v1) && (!angular.isDefined(v2) || v2 === null || v2 === "")) {
10186 scope.searchCriteria = {
10190 scope.searchCriteria = {};
10193 scope.$watch('searchCategory', function (newVal, oldVal) {
10194 if (newVal !== oldVal) {
10195 setSearchCriteria(scope.searchString, newVal);
10198 scope.$watch('searchString', function (newVal, oldVal) {
10199 if (newVal !== oldVal) {
10200 setSearchCriteria(newVal, scope.searchCategory);
10203 scope.$watchCollection('searchCriteria', function (val) {
10204 if (scope.$parent && scope.$parent !== undefined) {
10205 scope.$parent.searchCriteria = val;
10207 scope.totalRows = scope.tableData && ($filter('filter')(scope.tableData, val, false)).length || 0;
10208 scope.currentPage = 1;
10210 var window = angular.element($window);
10211 var findMaxTableColumns = function () {
10213 windowWidth = $window.innerWidth;
10214 ctrl.maxTableColumns = -1;
10215 for (var i in scope.tableBreakpoints) {
10216 if (windowWidth >= scope.tableBreakpoints[i].min && windowWidth < scope.tableBreakpoints[i].max) {
10217 ctrl.maxTableColumns = scope.tableBreakpoints[i].columns;
10221 if (ctrl.maxTableColumns > -1 && ctrl.totalTableColums > ctrl.maxTableColumns) {
10222 ctrl.active = true;
10224 ctrl.active = false;
10226 for (var i in ctrl.responsiveRowScopes) {
10227 ctrl.responsiveRowScopes[i].setActive(ctrl.active);
10230 var findHiddenColumn = function () {
10231 var columnDiffenence = ctrl.maxTableColumns > -1 ? ctrl.totalTableColums - ctrl.maxTableColumns : 0;
10232 ctrl.hiddenColumn = [];
10233 if (columnDiffenence > 0) {
10234 var tempHideColumnPriority = angular.copy(ctrl.hideColumnPriority);
10235 for (var i = 0; i < columnDiffenence; i++) {
10236 ctrl.hiddenColumn.push(tempHideColumnPriority.pop());
10240 var resizeListener = function () {
10241 findMaxTableColumns();
10242 findHiddenColumn();
10244 if (ctrl.responsive) {
10245 window.bind('resize', function () {
10249 $timeout(function () {
10256 .directive('b2bTableRow', [function () {
10259 compile: function (elem, attr) {
10260 if (attr.type === 'header') {
10262 } else if (attr.type === 'body') {
10263 var html = elem.children();
10264 if (attr.rowRepeat) {
10265 html.attr('ng-repeat', attr.rowRepeat.concat(" | orderBy : (reverse?'-':'')+ columnIndex | filter : searchCriteria : false "));
10267 html.attr('ng-class', "{'odd': $odd && zebraStripFlag}");
10268 html.attr('class', 'data-row');
10269 html.attr('b2b-responsive-row', '{{$index}}');
10275 .directive('b2bTableHeader', ['b2bTableConfig', function (b2bTableConfig) {
10285 require: '^b2bTable',
10286 templateUrl: function (elem, attr) {
10287 if (attr.sortable === 'false') {
10288 return 'b2bTemplate/tables/b2bTableHeaderUnsortable.html';
10290 return 'b2bTemplate/tables/b2bTableHeaderSortable.html';
10293 link: function (scope, elem, attr, ctrl) {
10294 var reverse = b2bTableConfig.defaultSortPattern;
10295 scope.headerName = elem.text();
10296 scope.headerId = elem.attr('id');
10297 scope.sortPattern = null;
10298 var priority = parseInt(attr.priority, 10);
10299 scope.columnIndex = ctrl.setIndex(scope, priority);
10301 scope.isHidden = function () {
10302 return (ctrl.hiddenColumn.indexOf(scope.columnIndex) > -1);
10305 scope.$watch(function () {
10306 return elem.text();
10307 }, function (value) {
10308 scope.headerName = value;
10310 scope.sort = function (sortType) {
10311 if (typeof sortType === 'boolean') {
10312 reverse = sortType;
10314 ctrl.sortData(scope.index, reverse, false);
10315 scope.sortPattern = reverse ? 'descending' : 'ascending';
10316 reverse = !reverse;
10318 scope.$watch(function () {
10319 return ctrl.currentSortIndex;
10320 }, function (value) {
10321 if (value !== scope.index) {
10322 scope.sortPattern = null;
10326 if (scope.sortable === undefined || scope.sortable === 'true' || scope.sortable === true) {
10327 scope.sortable = 'true';
10328 } else if (scope.sortable === false || scope.sortable === 'false') {
10329 scope.sortable = 'false';
10332 if (scope.sortable !== 'false') {
10333 if (scope.defaultSort === 'A' || scope.defaultSort === 'a') {
10335 } else if (scope.defaultSort === 'D' || scope.defaultSort === 'd') {
10339 scope.resetSortPattern = function () {
10340 reverse = b2bTableConfig.defaultSortPattern;
10345 .directive('b2bResponsiveRow', ['$templateCache', '$timeout', '$compile', function ($templateCache, $timeout, $compile) {
10348 require: '^b2bTable',
10349 controller: ['$scope', function ($scope) {
10350 this.rowValues = $scope.rowValues = [];
10351 this.setRowValues = function (rowValue) {
10352 this.rowValues.push(rowValue);
10354 var columnIndexCounter = -1;
10355 this.getIndex = function () {
10356 columnIndexCounter++;
10357 return columnIndexCounter;
10360 link: function (scope, elem, attr, ctrl) {
10361 if (ctrl.responsive) {
10362 scope.rowIndex = attr.b2bResponsiveRow;
10363 scope.active = false;
10364 scope.expandFlag = false;
10365 scope.headerValues = ctrl.headers;
10366 ctrl.setResponsiveRow(scope);
10367 var firstTd = elem.find('td').eq(0);
10368 scope.setActive = function (activeFlag) {
10369 scope.active = activeFlag;
10370 if (scope.active) {
10371 elem.addClass('has-button');
10372 firstTd.attr('role', 'rowheader');
10373 firstTd.parent().attr('role', 'row');
10375 elem.removeClass('has-button');
10376 firstTd.removeAttr('role');
10377 firstTd.parent().removeAttr('role');
10380 scope.toggleExpandFlag = function (expandFlag) {
10381 if (angular.isDefined(expandFlag)) {
10382 scope.expandFlag = expandFlag;
10384 scope.expandFlag = !scope.expandFlag;
10386 if (scope.expandFlag) {
10387 elem.addClass('opened');
10389 elem.removeClass('opened');
10393 firstTd.attr('scope', 'row');
10394 firstTd.addClass('col-1');
10395 scope.$on('$destroy', function () {
10396 elem.next().remove();
10398 $timeout(function () {
10399 scope.firstTdId = firstTd.attr('id');
10400 var firstTdContent = firstTd.html();
10401 var toggleButtonTemplate = '<span ng-show="!active">' + firstTdContent + '</span><button type="button" aria-describedby="sup-actNum{{$id}}" aria-expanded="{{expandFlag}}" ng-show="active" ng-click="toggleExpandFlag()"><i ng-class="{\'icon-primary-accordion-plus\': !expandFlag, \'icon-primary-accordion-minus\': expandFlag}" aria-hidden="true"></i>' + firstTdContent + '</button><span id="sup-actNum{{$id}}" style="display:none">{{expandFlag && "Hide row below." || "Show row below."}}</span>';
10402 toggleButtonTemplate = $compile(toggleButtonTemplate)(scope);
10404 firstTd.prepend(toggleButtonTemplate);
10406 var template = $templateCache.get('b2bTemplate/tables/b2bResponsiveRow.html');
10407 template = $compile(template)(scope);
10408 elem.after(template);
10414 .directive('b2bResponsiveList', ['$templateCache', '$timeout', '$compile', function ($templateCache, $timeout, $compile) {
10417 require: '^b2bTable',
10418 link: function (scope, elem, attr, ctrl) {
10419 scope.columnIndex = parseInt(attr.b2bResponsiveList, 10);
10420 scope.isVisible = function () {
10421 return (ctrl.hiddenColumn.indexOf(scope.columnIndex) > -1);
10426 .directive('b2bTableBody', ['$filter', '$timeout', 'b2bTableConfig', function ($filter, $timeout, b2bTableConfig) {
10429 require: ['^b2bTable', '?^b2bResponsiveRow'],
10433 templateUrl: 'b2bTemplate/tables/b2bTableBody.html',
10434 link: function (scope, elem, attr, ctrl) {
10435 var b2bTableCtrl = ctrl[0];
10436 var b2bResponsiveRowCtrl = ctrl[1];
10437 var highlightSearchStringClass = b2bTableConfig.highlightSearchStringClass;
10438 var searchString = "";
10439 var wrapElement = function (elem) {
10440 var text = elem.text();
10441 elem.html($filter('b2bHighlight')(text, searchString, highlightSearchStringClass));
10443 var traverse = function (elem) {
10444 var innerHtml = elem.children();
10445 if (innerHtml.length > 0) {
10446 for (var i = 0; i < innerHtml.length; i++) {
10447 traverse(innerHtml.eq(i));
10454 var clearWrap = function (elem) {
10455 var elems = elem.find('*');
10456 for (var i = 0; i < elems.length; i++) {
10457 if (elems.eq(i).attr('class') && elems.eq(i).attr('class').indexOf(highlightSearchStringClass) !== -1) {
10458 var text = elems.eq(i).text();
10459 elems.eq(i).replaceWith(text);
10463 if (b2bResponsiveRowCtrl) {
10464 scope.columnIndex = b2bResponsiveRowCtrl.getIndex();
10465 scope.isHidden = function () {
10466 return (b2bTableCtrl.hiddenColumn.indexOf(scope.columnIndex) > -1);
10469 $timeout(function () {
10470 var actualHtml = elem.children();
10471 scope.$watch(function () {
10472 return b2bTableCtrl.getSearchString();
10473 }, function (val) {
10474 searchString = val;
10476 if (actualHtml.length > 0) {
10482 if (b2bResponsiveRowCtrl) {
10483 b2bResponsiveRowCtrl.setRowValues(elem.html());
10489 .directive('b2bTableSort', ['b2bTableConfig','$timeout', function (b2bTableConfig,$timeout) {
10493 require: '^b2bTable',
10494 link: function (scope, elem, attr, ctrl) {
10495 var initialSort = '',
10498 initialSort = attr.initialSort;
10500 scope.sortTable = function (msg,trigger) {
10501 if(trigger == 'dropdown'){
10502 if (nextSort === 'd' || nextSort === 'D') {
10503 ctrl.sortData(msg, false, false);
10505 ctrl.sortData(msg, true, false);
10509 $timeout(function(){
10510 if (nextSort.length > 0) {
10512 if (nextSort === 'd' || nextSort === 'D') {
10513 tempsort = nextSort
10514 ctrl.sortData(msg, true, true);
10516 $timeout(function(){
10517 if(!angular.isUndefined(elem[0].querySelector('.sortButton')) || elem[0].querySelector('.sortButton') !== null ){
10518 angular.element(elem[0].querySelector('.sortButton'))[0].focus();
10523 tempsort = nextSort
10524 ctrl.sortData(msg, false, true);
10526 $timeout(function(){
10527 if(!angular.isUndefined(elem[0].querySelector('.sortButton')) || elem[0].querySelector('.sortButton') !== null ){
10528 angular.element(elem[0].querySelector('.sortButton'))[0].focus();
10532 } else if (initialSort.length > 0) {
10534 if (initialSort === 'd' || initialSort === 'D') {
10535 tempsort = nextSort
10536 ctrl.sortData(msg, true, true);
10538 $timeout(function(){
10539 if(!angular.isUndefined(elem[0].querySelector('.sortButton')) || elem[0].querySelector('.sortButton') !== null ){
10540 angular.element(elem[0].querySelector('.sortButton'))[0].focus();
10545 tempsort = nextSort
10546 ctrl.sortData(msg, false, true);
10548 $timeout(function(){
10549 if(!angular.isUndefined(elem[0].querySelector('.sortButton')) || elem[0].querySelector('.sortButton') !== null ){
10550 angular.element(elem[0].querySelector('.sortButton'))[0].focus();
10561 scope.sortDropdown = function(msg) {
10563 if(tempsort==='') {
10567 if(tempsort === 'd' || tempsort === 'D' ) {
10568 ctrl.sortData(msg, true, false);
10570 ctrl.sortData(msg, false, false);
10579 * @name Tabs, tables & accordions.att:tabs
10582 * <file src="src/tabs/docs/readme.md" />
10585 * <b2b-tabset tab-id-selected="activeTabsId">
10586 <b2b-tab ng-repeat="tab in gTabs" tab-item="tab"
10587 id="{{tab.uniqueId}}" aria-controls="{{tab.tabPanelId}}"
10588 ng-disabled="tab.disabled">
10594 * <section id="code">
10595 <example module="b2b.att">
10596 <file src="src/tabs/docs/demo.html" />
10597 <file src="src/tabs/docs/demo.js" />
10603 angular.module('b2b.att.tabs', ['b2b.att.utilities'])
10604 .directive('b2bTabset', function () {
10612 templateUrl: 'b2bTemplate/tabs/b2bTabset.html',
10613 controller: ['$scope', function ($scope) {
10615 this.setTabIdSelected = function (tab) {
10616 $scope.tabIdSelected = tab.id;
10619 this.getTabIdSelected = function () {
10620 return $scope.tabIdSelected;
10625 .directive('b2bTab', ['keymap', function (keymap) {
10630 require: '^b2bTabset',
10634 templateUrl: 'b2bTemplate/tabs/b2bTab.html',
10635 controller: [function(){}],
10636 link: function (scope, element, attr, b2bTabsetCtrl) {
10638 if (scope.tabItem && !scope.tabItem.disabled) {
10639 scope.tabItem.disabled = false;
10642 scope.isTabActive = function () {
10643 return (scope.tabItem.id === b2bTabsetCtrl.getTabIdSelected());
10646 scope.clickTab = function () {
10647 if (attr.disabled) {
10650 b2bTabsetCtrl.setTabIdSelected(scope.tabItem);
10653 scope.nextKey = function () {
10654 var el = angular.element(element[0])[0];
10655 var elementToFocus = null;
10656 while (el && el.nextElementSibling) {
10657 el = el.nextElementSibling;
10658 if (!el.querySelector('a').disabled) {
10659 elementToFocus = el.querySelector('a');
10664 if (!elementToFocus) {
10665 var childTabs = element.parent().children();
10666 for (var i = 0; i < childTabs.length; i++) {
10667 if (!childTabs[i].querySelector('a').disabled) {
10668 elementToFocus = childTabs[i].querySelector('a');
10674 if (elementToFocus) {
10675 elementToFocus.focus();
10679 scope.previousKey = function () {
10680 var el = angular.element(element[0])[0];
10681 var elementToFocus = null;
10683 while (el && el.previousElementSibling) {
10684 el = el.previousElementSibling;
10685 if (!el.querySelector('a').disabled) {
10686 elementToFocus = el.querySelector('a');
10691 if (!elementToFocus) {
10692 var childTabs = element.parent().children();
10693 for (var i = childTabs.length - 1; i > 0; i--) {
10694 if (!childTabs[i].querySelector('a').disabled) {
10695 elementToFocus = childTabs[i].querySelector('a');
10701 if (elementToFocus) {
10702 elementToFocus.focus();
10706 angular.element(element[0].querySelector('a')).bind('keydown', function (evt) {
10708 if (!(evt.keyCode)) {
10709 evt.keyCode = evt.which;
10712 switch (evt.keyCode) {
10713 case keymap.KEY.RIGHT:
10714 evt.preventDefault();
10718 case keymap.KEY.LEFT:
10719 evt.preventDefault();
10720 scope.previousKey();
10731 * @name Messages, modals & alerts.att:tagBadges
10734 * <file src="src/tagBadges/docs/readme.md" />
10737 * <section id="code">
10738 <example module="b2b.att">
10739 <file src="src/tagBadges/docs/demo.html" />
10740 <file src="src/tagBadges/docs/demo.js" />
10745 angular.module('b2b.att.tagBadges', ['b2b.att.utilities'])
10746 .directive('b2bTagBadge',['$timeout',function($timeout){
10749 link: function(scope,elem,attr,ctrl){
10750 elem.addClass('b2b-tags');
10751 if(angular.element(elem[0].querySelector('.icon-primary-close')).length>0) {
10752 var item = angular.element(elem[0].querySelector('.icon-primary-close'));
10753 item.bind('click',function(){
10754 elem.css({'height':'0','width':'0','padding':'0','border':'0'});
10755 elem.attr('tabindex','0');
10757 item.parent().remove();
10758 elem[0].bind('blur',function(){
10772 * @name Forms.att:textArea
10775 * <file src="src/textArea/docs/readme.md" />
10778 * <textarea b2b-reset b2b-reset-textarea ng-model="textAreaModel" ng-disabled="disabled" ng-trim="false" placeholder="{{placeholderText}}" rows="{{textAreaRows}}" maxlength="{{textAreaMaxlength}}" role="textarea"></textarea>
10781 <section id="code">
10782 <b>HTML + AngularJS</b>
10783 <example module="b2b.att">
10784 <file src="src/textArea/docs/demo.html" />
10785 <file src="src/textArea/docs/demo.js" />
10789 angular.module('b2b.att.textArea', ['b2b.att.utilities'])
10791 .directive('b2bResetTextarea', [ function () {
10794 require: 'b2bReset',
10795 link: function (scope, element, attrs, ctrl) {
10797 var resetButton = ctrl.getResetButton();
10799 var computeScrollbarAndAddClass = function () {
10800 if (element.prop('scrollHeight') > element[0].clientHeight) {
10801 element.addClass('hasScrollbar');
10803 element.removeClass('hasScrollbar');
10807 computeScrollbarAndAddClass();
10809 element.on('focus keyup', function(){
10810 computeScrollbarAndAddClass();
10818 * @name Forms.att:timeInputField
10821 * <file src="src/timeInputField/docs/readme.md" />
10825 * <section id="code">
10826 <example module="b2b.att">
10827 <file src="src/timeInputField/docs/demo.html" />
10828 <file src="src/timeInputField/docs/demo.js" />
10833 angular.module('b2b.att.timeInputField',['ngMessages', 'b2b.att.utilities']).directive('b2bTimeFormat',function(){
10836 require : '^ngModel',
10837 link : function(scope,elem,attr,ctrl){
10838 elem.on('keyup',function(evt){
10839 var modelValue = ctrl.$modelValue;
10840 var format = attr.b2bTimeFormat;
10841 modelValue = modelValue.split(':');
10842 if(format == "12"){
10843 if(!(modelValue[0] <= 12 && modelValue[0] > 0 ) || !(modelValue[1] <= 59)){
10844 ctrl.$setValidity('inValidTime',false);
10846 ctrl.$setValidity('inValidTime',true);
10848 }else if(format =="24"){
10849 if(!(modelValue[0] <= 23) || !(modelValue[1] <= 59)){
10850 ctrl.$setValidity('inValidTime',false);
10852 ctrl.$setValidity('inValidTime',true);
10863 * @name Forms.att:tooltipsForForms
10866 * <file src="src/tooltipsForForms/docs/readme.md" />
10869 <example module="b2b.att">
10870 <file src="src/tooltipsForForms/docs/demo.html" />
10871 <file src="src/tooltipsForForms/docs/demo.js" />
10874 angular.module('b2b.att.tooltipsForForms', ['b2b.att.utilities'])
10875 .directive('b2bTooltip', ['$document', '$window', '$isElement', function ($document, $window, $isElement) {
10878 link: function (scope, elem, attr, ctrl) {
10879 var icon = elem[0].querySelector('a.tooltip-element');
10880 var btnIcon = elem[0].querySelector('.btn.tooltip-element');
10881 var tooltipText = elem[0].querySelector('.helpertext');
10882 var tooltipWrapper = elem[0].querySelector('.tooltip-size-control');
10883 if (elem.hasClass('tooltip-onfocus')) {
10884 var inputElm = angular.element(elem[0].querySelector("input"));
10885 var textAreaElm = angular.element(elem[0].querySelector("textarea"));
10887 angular.element(icon).attr({'aria-expanded': false});
10888 angular.element(btnIcon).attr({'aria-expanded': false});
10889 var calcTooltip = function () {
10890 if (!elem.hasClass('tooltip active')) {
10891 if (elem.hasClass('tooltip-onfocus')) {
10892 angular.element(elem[0].querySelector("input")).triggerHandler('focusout');
10894 if (elem.hasClass('tooltip-onclick')) {
10897 angular.element(icon).removeClass('active');
10898 angular.element(icon).attr({'aria-expanded': true});
10899 angular.element(icon).attr({'aria-describedby': angular.element(tooltipText).attr('id')});
10900 angular.element(tooltipText).attr({'aria-hidden': false});
10901 elem.addClass('active');
10903 var tooltipIconPos = angular.element(icon).prop('offsetLeft'),
10904 tooltipPosition = angular.element(tooltipText).prop('offsetWidth') / 2,
10905 tipOffset = (tooltipIconPos - 30) - tooltipPosition,
10906 maxRightPos = (($window.innerWidth - 72) - (tooltipPosition * 2)) - 14.5;
10908 if ($window.innerWidth >= '768') {
10909 if (tipOffset < 0) {// if icon on far left side of page
10912 else if (tooltipIconPos > maxRightPos) {// if icon is far right side of page
10913 tipOffset = maxRightPos;
10915 else {// if tooltip in the middle somewhere
10916 tipOffset = tipOffset;
10918 angular.element(tooltipWrapper).css({left: tipOffset + 'px'});
10923 // TOOLTIP LINK ONCLICK AND FOCUS
10924 angular.element(icon).on('click mouseover mouseout focus blur', function (e) {
10925 if (e.type == 'mouseover') {
10928 else if (e.type == 'mouseout' && elem.hasClass('active')) {
10929 if (!elem.hasClass('activeClick')) {
10930 angular.element(tooltipText).attr({
10931 'aria-hidden': true,
10934 elem.removeClass('active');
10935 } else if (elem.hasClass('activeClick') && navigator.userAgent.match(/iphone/i)) {
10936 elem.removeClass('active activeClick');
10941 if (elem.hasClass('activeClick')) {
10942 angular.element(icon).attr({'aria-expanded': false});
10943 angular.element(tooltipText).attr({'aria-hidden': true});
10944 angular.element(icon).removeAttr('aria-describedby');
10945 elem.removeClass('activeClick active');
10946 e.preventDefault();
10948 else if (e.type == 'click') {
10949 elem.addClass('activeClick');
10951 e.preventDefault();
10954 angular.element(icon).on('keydown', function (e) {
10955 if (e.keyCode == '32') {
10956 (elem.hasClass('active')) ? elem.removeClass('active') : elem.addClass('value');
10957 angular.element(icon).triggerHandler('click');
10958 e.preventDefault();
10959 } else if (e.keyCode == '27') {
10960 (elem.hasClass('active')) ? elem.removeClass('active activeClick') : elem.addClass('value');
10963 e.preventDefault();
10966 e.preventDefault();
10969 // TOOLTIP BUTTON INSIDE A TEXT FIELD
10970 angular.element(btnIcon).on('click', function (e) {
10971 var $this = angular.element(this);
10972 if ($this.hasClass('active') && elem.hasClass('tooltip-onclick')) {
10973 elem.removeClass('active');
10974 $this.removeClass('active');
10975 angular.element(tooltipText).removeAttr('aria-live');
10976 $this.attr({'aria-expanded': 'false'});
10977 $this.removeAttr('aria-describedby');
10979 elem.addClass('active');
10980 $this.addClass('active');
10981 $this.attr({'aria-expanded': 'true', 'aria-describedby': angular.element(tooltipText).attr('id')});
10982 angular.element(tooltipText).attr({'aria-live': 'polite'});
10986 angular.element(btnIcon).on('blur', function (e) {
10987 var $this = angular.element(this);
10988 if ($this.hasClass('active') && elem.hasClass('tooltip-onclick')) {
10989 elem.removeClass('active');
10990 $this.removeClass('active');
10991 angular.element(tooltipText).removeAttr('aria-live');
10992 $this.attr({'aria-expanded': 'false'});
10993 $this.removeAttr('aria-describedby');
10997 angular.element(btnIcon).on('keydown', function (e) {
10998 var $this = angular.element(this);
10999 if (e.keyCode == '27') {
11000 var $this = angular.element(this);
11001 if ($this.hasClass('active') && elem.hasClass('tooltip-onclick')) {
11002 elem.removeClass('active');
11003 $this.removeClass('active');
11004 angular.element(tooltipText).removeAttr('aria-live');
11005 $this.attr({'aria-expanded': 'false'});
11006 $this.removeAttr('aria-describedby');
11011 // close all tooltips if clicking something else
11012 $document.bind('click', function (e) {
11013 var isElement = $isElement(angular.element(e.target), elem, $document);
11015 elem.removeClass('active');
11016 angular.element(elem[0].querySelector('.tooltip-element')).removeClass('active');
11017 angular.element(tooltipText).removeAttr('aria-live');
11018 angular.element(elem[0].querySelector('.tooltip-element')).attr({'aria-expanded': 'false'});
11019 angular.element(elem[0].querySelector('.tooltip-element')).removeAttr('aria-describedby');
11023 angular.element(inputElm).on('keydown', function (e) {
11024 if (e.keyCode == '27'){
11025 elem.removeClass('active');
11026 angular.element(tooltipText).css('display', 'none');
11027 angular.element(tooltipText).removeAttr('aria-live');
11029 if (angular.element(this).attr('aria-describedby') === undefined){
11033 else if ((spaceIndex = angular.element(this).attr('aria-describedby').lastIndexOf(' ')) >= 0){
11035 var describedByValue = angular.element(this).attr('aria-describedby').slice(0, spaceIndex);
11037 angular.element(this).attr('aria-describedby', describedByValue);
11041 angular.element(this).removeAttr('aria-describedby');
11046 angular.element(textAreaElm).on('keydown', function (e) {
11047 if (e.keyCode == '27'){
11048 elem.removeClass('active');
11049 angular.element(tooltipText).css('display', 'none');
11050 angular.element(tooltipText).removeAttr('aria-live');
11051 if (angular.element(this).attr('aria-describedby') === undefined){
11055 else if ((spaceIndex = angular.element(this).attr('aria-describedby').lastIndexOf(' ')) >= 0){
11057 var describedByValue = angular.element(this).attr('aria-describedby').slice(0, spaceIndex);
11059 angular.element(this).attr('aria-describedby', describedByValue);
11063 angular.element(this).removeAttr('aria-describedby');
11068 // TOOLTIP TRIGGERED AUTOMATICALLY INSIDE A TEXT FIELD
11069 angular.element(inputElm).on('focus', function (e) {
11070 var allTooltip = $document[0].querySelectorAll('[class*="tooltip"]');
11071 for (var i = 0; i < allTooltip.length; i++) {
11072 if (angular.element(allTooltip[i]).hasClass('active')) {
11073 angular.element(allTooltip[i]).triggerHandler('click');
11076 angular.element(this).attr({'aria-describedby': angular.element(tooltipText).attr('id')});
11077 angular.element(tooltipText).css('display', 'block');
11078 angular.element(tooltipText).attr({'aria-live': 'polite'});
11079 elem.addClass('active');
11081 angular.element(inputElm).on('blur', function (e) {
11082 elem.removeClass('active');
11083 angular.element(tooltipText).css('display', 'none');
11084 angular.element(tooltipText).removeAttr('aria-live');
11085 angular.element(this).removeAttr('aria-describedby');
11088 // TOOLTIP TRIGGERED AUTOMATICALLY INSIDE A TEXTAREA
11089 angular.element(textAreaElm).on('focus', function (e) {
11090 var allTooltip = $document[0].querySelectorAll('[class*="tooltip"]');
11091 for (var i = 0; i < allTooltip.length; i++) {
11092 if (angular.element(allTooltip[i]).hasClass('active')) {
11093 angular.element(allTooltip[i]).triggerHandler('click');
11096 elem.addClass('active');
11097 angular.element(tooltipText).css('display', 'block');
11098 angular.element(tooltipText).attr({'aria-live': 'polite'});
11099 angular.element(this).attr({'aria-describedby': angular.element(tooltipText).attr('id')});
11101 angular.element(textAreaElm).on('blur', function (e) {
11102 elem.removeClass('active');
11103 angular.element(tooltipText).css('display', 'none');
11104 angular.element(tooltipText).removeAttr('aria-live');
11105 angular.element(this).removeAttr('aria-describedby');
11112 * @name Navigation.att:TreeNavigation
11116 * @param {String} setRole - This value needs to be "tree". This is required to incorporate CATO requirements.
11117 * @param {Boolean} groupIt - This value needs to be "false" for top-level tree rendered.
11120 * <file src="src/treeNav/docs/readme.md" />
11123 * <div class="b2b-tree">
11124 * <b2b-tree-nav collection="treeStructure" set-role="tree" group-it="false"></b2b-tree-nav>
11127 * <section id="code">
11128 <example module="b2b.att">
11129 <file src="src/treeNav/docs/demo.html" />
11130 <file src="src/treeNav/docs/demo.js" />
11135 angular.module('b2b.att.treeNav', ['b2b.att.utilities'])
11136 .directive('b2bTreeNav', function () {
11145 templateUrl: function (element, attrs) {
11146 if (attrs.groupIt === 'true') {
11147 return "b2bTemplate/treeNav/groupedTree.html";
11149 return "b2bTemplate/treeNav/ungroupedTree.html";
11152 link: function (scope) {
11153 if (!(scope.setRole === 'tree')) {
11154 scope.setRole = 'group';
11159 .directive('b2bMember', ['$compile', '$timeout', 'keymap', function ($compile, $timeout, keymap) {
11167 templateUrl: 'b2bTemplate/treeNav/treeMember.html',
11168 link: function (scope, element, attrs) {
11169 scope.elemArr = [];
11170 var removeRootTabIndex = function (elem) {
11171 if (elem.parent().parent().eq(0).hasClass('b2b-tree')) {
11172 elem.attr('tabindex', -1);
11175 removeRootTabIndex(elem.parent());
11177 scope.$watch('member.child', function(newVal, oldVal){
11178 if(newVal !== oldVal){
11182 scope.showChild = function () {
11183 if (!element.hasClass('grouped')) {
11184 if (angular.isArray(scope.member.child) && scope.member.child.length > 0 && (scope.member.divide === undefined || scope.member.child.length < scope.member.divide)) {
11185 scope.groupIt = false;
11186 element.addClass('grouped');
11187 element.append("<b2b-tree-nav collection='member.child' group-it='" + scope.groupIt + "'></b2b-tree-nav>");
11188 $compile(element.contents())(scope);
11189 if(scope.member.active && scope.member.active === true){
11190 element.find('i').eq(0).removeClass('icon-primary-collapsed');
11192 if(scope.member.selected && scope.member.selected === true){
11193 element.attr('aria-selected', true);
11194 element.attr('tabindex', 0);
11195 removeRootTabIndex(element);
11197 if(scope.member.active && scope.member.active == undefined){
11198 element.find('i').eq(0).addClass('icon-primary-collapsed');
11200 } else if (scope.member.child && scope.member.divide && scope.member.child.length > scope.member.divide) {
11201 element.addClass('grouped');
11202 scope.groupIt = true;
11203 // FILTER - GROUPBY - APPROACH
11206 if(scope.member.child[0].groupName !== undefined){
11207 grpName = scope.member.child[0].groupName;
11210 var toSlice = scope.member.child[0].name.search(' ');
11211 grpName = scope.member.child[0].name.slice(0, toSlice);
11214 for (i = 0; i < scope.member.child.length; i += scope.member.divide) {
11216 for (j = j + i; j < (i + scope.member.divide); j++) {
11217 if (j === scope.member.child.length) {
11218 scope.member.child[j - 1].grpChild = grpName + ' ' + (i + 1) + ' - ' + (scope.member.child.length);
11221 if(scope.member.child[j-1].active && scope.member.child[j-1].active===true){
11222 scope.member.child[j-1].activeGrp = true;
11226 if (i + scope.member.divide > scope.member.child.length) {
11227 scope.member.child[j].grpChild = grpName + ' ' + (i + 1) + ' - ' + (scope.member.child.length);
11228 if(scope.member.child[j].active && scope.member.child[j].active===true){
11229 scope.member.child[j].activeGrp = true;
11233 scope.member.child[j].grpChild = grpName + ' ' + (i + 1) + ' - ' + (i + scope.member.divide);
11234 if(scope.member.child[j].active && scope.member.child[j].active===true){
11235 scope.member.child[j].activeGrp = true;
11240 if(scope.member.divide){
11241 element.append("<b2b-tree-nav collection='member.child' group-it='" + scope.groupIt + "'></b2b-tree-nav>");
11243 element.append("<b2b-tree-nav collection='member.child' group-it='" + scope.groupIt + "'></b2b-tree-nav>");
11245 $compile(element.contents())(scope);
11246 if(scope.member.active && scope.member.active === true){
11247 element.find('i').eq(0).removeClass('icon-primary-collapsed');
11249 if(scope.member.selected && scope.member.selected === true){
11250 element.attr('aria-selected', true);
11252 if( scope.member.active && scope.member.active == undefined){
11253 element.find('i').eq(0).addClass('icon-primary-collapsed');
11258 //Below condition opens node for opening on json load.
11259 if(scope.member.active && scope.member.active == true){
11262 if(scope.member.active == undefined && !element.find('a').eq(0).hasClass('active') && scope.member.child !== undefined){
11263 element.find('i').eq(0).addClass('icon-primary-collapsed');
11265 else if(scope.member.child == undefined){
11266 element.find('i').eq(0).addClass('icon-primary-circle');
11268 element.bind('keydown', function (evt) {
11269 switch (evt.keyCode) {
11270 case keymap.KEY.ENTER:
11271 if (element.hasClass('bg') && scope.member.onSelect !== undefined) {
11272 scope.member.onSelect(scope.member);
11274 evt.stopPropagation();
11281 //else getting true in every case .. so better use switch case .. that makes more sense you dumb.
11282 element.bind('click', function (evt) {
11284 var expandFunc = scope.member.onExpand;
11287 if (element.hasClass('bg') && scope.member.onSelect !== undefined) {
11288 scope.member.onSelect(scope.member);
11290 if (element.find('a').eq(0).hasClass('active') && scope.member.onExpand !== undefined) {
11291 var eValue = scope.member.onExpand(scope.member);
11293 if (!element.find('a').eq(0).hasClass('active') && scope.member.onCollapse !== undefined) {
11294 scope.member.onCollapse(scope.member);
11300 .directive('b2bTreeLink', ['keymap', '$timeout', function (keymap, $timeout) {
11303 link: function (scope, element, attr, ctrl) {
11304 var rootE, parentE, upE, downE;
11305 var closeOthersUp = function (elem,isKeyPress,passiveClose) {
11306 //For accordion functionality on sibling nodes
11307 if (elem.find('a').eq(0).hasClass('active')) {
11308 activeToggle(elem,isKeyPress,passiveClose);
11311 if (elem.hasClass('bg') && !isKeyPress) {
11312 elem.removeClass('bg');
11313 if (elem.attr('aria-selected')) {
11314 elem.attr('aria-selected', 'false');
11317 if (elem[0].previousElementSibling !== null) {
11318 closeOthersUp(angular.element(elem[0].previousElementSibling),isKeyPress);
11321 var closeOthersDown = function (elem,isKeyPress,passiveClose) {
11322 //For accordion functionality on sibling nodes
11323 if (elem.find('a').eq(0).hasClass('active')) {
11324 activeToggle(elem,isKeyPress,passiveClose);
11327 if (elem.hasClass('bg') && !isKeyPress) {
11328 elem.removeClass('bg');
11329 if (elem.attr('aria-selected')) {
11330 elem.attr('aria-selected', 'false');
11333 if (elem[0].nextElementSibling !== null) {
11334 closeOthersDown(angular.element(elem[0].nextElementSibling),isKeyPress);
11339 var removeBackground = function(elem){
11341 if(elem.hasClass('b2b-tree')){
11342 angular.element(elem[0].getElementsByClassName('bg')).removeClass('bg');
11345 removeBackground(elem.parent().parent());
11351 * These two functions used for setting heights on parent nodes as the child node closes
11352 * Retaining this code for future reference
11354 var addParentHeight = function(e, h) {
11355 var parentLi = e.parent().parent();
11356 var parentUl = e.parent();
11357 if(!parentLi.hasClass('b2b-tree')) {
11358 var addHeight = parentUl[0].offsetHeight + h;
11359 parentLi.find('ul').eq(0).css({
11360 height: addHeight+'px'
11362 addParentHeight(parentLi, h);
11366 var removeParentHeight = function(e, h) {
11367 var parentLi = e.parent().parent();
11368 var parentUl = e.parent();
11369 if(!parentLi.hasClass('b2b-tree')) {
11370 var addHeight = parentUl[0].offsetHeight - h;
11371 parentLi.find('ul').eq(0).css({
11372 height: addHeight+'px'
11374 removeParentHeight(parentLi, h);
11379 // isKeyPress - to notify that the function is called by Right Key press
11380 // passiveClose - prevents firing of oncollapse events during the action
11381 // of expand function(check the function definition)
11383 var activeToggle = function (elem,isKeyPress,passiveClose) {
11384 var element = elem.find('a').eq(0);
11385 if (element.hasClass('active')) {
11387 elem.removeClass('bg');
11390 if (elem.attr('aria-selected') && !isKeyPress) {
11391 elem.attr('aria-selected', 'false');
11393 if (!element.find('i').eq(0).hasClass('icon-primary-circle')) {
11394 if(isKeyPress && scope.member){
11395 if (scope.member.onCollapse !== undefined && !passiveClose) {
11396 scope.member.onCollapse(scope.member);
11399 element.removeClass('active');
11400 elem.attr('aria-expanded', 'false');
11401 element.find('i').eq(0).removeClass('icon-primary-expanded');
11402 element.find('i').eq(0).addClass('icon-primary-collapsed');
11403 //For Animation: below commented code is used to manually set height of UL to zero
11404 //retaining code for future reference
11406 var totalHeight = elem.find('ul')[0].scrollHeight;
11407 removeParentHeight(elem, totalHeight);
11408 elem.find('ul').eq(0).css({
11414 elem.addClass('bg');
11415 elem.attr('aria-selected', 'true');
11418 if (!element.find('i').eq(0).hasClass('icon-primary-circle')) {
11420 if(typeof scope.showChild === 'function' ){
11424 if (scope.member.onExpand !== undefined) {
11425 scope.member.onExpand(scope.member);
11429 element.addClass('active');
11430 elem.attr('aria-expanded', 'true');
11431 element.find('i').eq(0).removeClass('icon-primary-collapsed');
11432 element.find('i').eq(0).addClass('icon-primary-expanded');
11433 //For Animation: below commented code is used to manually set height of the ul generatedon the click of parent LI.
11434 //retaining code for future reference
11436 var totalHeight = elem.find('ul')[0].scrollHeight;
11437 addParentHeight(elem, totalHeight);
11438 elem.find('ul').eq(0).css({
11439 height: totalHeight+'px'
11445 element.bind('click', function (evt) {
11446 //first we close others and then we open the clicked element
11447 if (element[0].previousElementSibling) {
11448 closeOthersUp(angular.element(element[0].previousElementSibling));
11450 if (element[0].nextElementSibling) {
11451 closeOthersDown(angular.element(element[0].nextElementSibling));
11453 removeBackground(element);
11454 activeToggle(element);
11456 evt.stopPropagation();
11458 //default root tree element tabindex set zero
11459 if (element.parent().parent().hasClass('b2b-tree') && (element.parent()[0].previousElementSibling === null)) {
11460 element.attr('tabindex', 0);
11462 //check root via class
11463 var isRoot = function (elem) {
11464 if (elem.parent().parent().eq(0).hasClass('b2b-tree')) {
11470 var findRoot = function (elem) {
11471 if (isRoot(elem)) {
11475 findRoot(elem.parent());
11478 var findPreActive = function (elem) {
11480 if (!(elem.hasClass("active"))) {
11483 var childElems = angular.element(elem[0].nextElementSibling.children);
11484 lastE = angular.element(childElems[childElems.length - 1]);
11485 if (lastE.find('a').eq(0).hasClass('active')) {
11486 findPreActive(lastE.find('a').eq(0));
11492 var findUp = function (elem) {
11493 if (isRoot(elem)) {
11497 if (elem[0].previousElementSibling !== null && !angular.element(elem[0].previousElementSibling).hasClass('tree-hide')) {
11498 upE = angular.element(elem[0].previousElementSibling);
11499 if (upE.find('a').eq(0).hasClass('active')) {
11500 findPreActive(upE.find('a').eq(0));
11503 upE = elem.parent().parent();
11507 var downElement = function (elem) {
11508 if (elem.next().hasClass('tree-hide')) {
11509 downElement(elem.next());
11511 downE = elem.next();
11514 var isBottomElem = false;
11515 var downParent = function(liElem){
11516 if(liElem.eq(0).parent().parent().eq(0).hasClass('b2b-tree')){
11517 isBottomElem = true;
11520 if(liElem.next().length !== 0){
11521 downE = liElem.next().eq(0);
11525 downParent(liElem.parent().parent());
11529 var findDown = function (elem) {
11530 if (isRoot(elem.parent()) && !elem.hasClass('active')) {
11531 downE = elem.parent();
11534 if (elem.hasClass('active')) {
11535 downE = elem.next().find('li').eq(0);
11536 if (downE.hasClass('tree-hide')) {
11537 downElement(downE);
11541 downParent(elem.parent());
11542 if(isBottomElem === true){
11543 downE = elem.parent();
11544 isBottomElem = false;
11550 var resetTabPosition = function(element){
11552 angular.element(rootE.parent().parent()[0].querySelector("li[tabindex='0']")).attr('tabindex','-1');
11553 var elemToFocus = rootE.parent().parent()[0].querySelector(".bg")|| rootE;
11555 angular.element(elemToFocus).attr('tabindex','0');
11557 // Function to control the expansion of nodes when the user tabs into the tree and
11558 // the slected node is not visible
11559 var expand = function(elemArr){
11560 var elem= elemArr.pop();
11561 var element = elem.find('a').eq(0);
11562 var selectedNode = elem.parent().parent()[0].querySelector(".bg");
11563 if(selectedNode != null){
11565 element = elem.find('a').eq(0);
11566 if(!element.hasClass('active') ){
11569 if (elem[0].previousElementSibling) {
11570 closeOthersUp(angular.element(elem[0].previousElementSibling),true,true);
11572 if (elem[0].nextElementSibling) {
11573 closeOthersDown(angular.element(elem[0].nextElementSibling),true,true);
11576 if (!element.find('i').eq(0).hasClass('icon-primary-circle')) {
11577 if(typeof scope.showChild === 'function' ){
11580 element.addClass('active');
11581 elem.attr('aria-expanded', 'true');
11582 element.find('i').eq(0).removeClass('icon-primary-collapsed');
11583 element.find('i').eq(0).addClass('icon-primary-expanded');
11587 elem = elemArr.pop();
11595 element.find('a').eq(0).bind('mouseenter', function (evt) {
11596 angular.forEach(document.querySelectorAll('.activeTooltip'), function(value, key) {
11597 angular.element(value).removeClass('activeTooltip')
11599 element.addClass('activeTooltip');
11601 element.find('a').eq(0).bind('mouseleave', function (evt) {
11602 element.removeClass('activeTooltip');
11604 element.bind('focus', function (evt) {
11605 angular.forEach(document.querySelectorAll('.activeTooltip'), function(value, key) {
11606 angular.element(value).removeClass('activeTooltip')
11608 element.addClass('activeTooltip');
11610 element.bind('blur', function (evt) {
11611 element.removeClass('activeTooltip');
11613 element.bind('keydown', function (evt) {
11614 switch (evt.keyCode) {
11615 case keymap.KEY.HOME:
11616 evt.preventDefault();
11617 evt.stopPropagation();
11618 element.attr('tabindex', -1);
11620 rootE.eq(0).attr('tabindex', 0);
11623 case keymap.KEY.LEFT:
11624 evt.preventDefault();
11625 evt.stopPropagation();
11627 if(element.find('a').eq(0).hasClass('active')){
11628 if (element[0].previousElementSibling) {
11629 closeOthersUp(angular.element(element[0].previousElementSibling),true);
11631 if (element[0].nextElementSibling) {
11632 closeOthersDown(angular.element(element[0].nextElementSibling),true);
11634 activeToggle(element,true);
11637 element.attr('tabindex', -1);
11638 parentE = element.parent().parent();
11639 parentE.attr('tabindex', 0);
11640 parentE[0].focus();
11642 case keymap.KEY.UP:
11643 evt.preventDefault();
11644 evt.stopPropagation();
11645 element.attr('tabindex', -1);
11647 upE.eq(0).attr('tabindex', 0);
11650 case keymap.KEY.RIGHT:
11651 evt.preventDefault();
11652 evt.stopPropagation();
11653 if(element.find('i').eq(0).hasClass('icon-primary-circle')){
11656 if (!element.find('a').eq(0).hasClass('active')) {
11657 if (element[0].previousElementSibling) {
11658 closeOthersUp(angular.element(element[0].previousElementSibling),true);
11660 if (element[0].nextElementSibling) {
11661 closeOthersDown(angular.element(element[0].nextElementSibling),true);
11663 activeToggle(element,true);
11667 element.attr('tabindex', -1);
11668 findDown(element.find('a').eq(0));
11669 downE.eq(0).attr('tabindex', 0);
11673 case keymap.KEY.DOWN:
11674 evt.preventDefault();
11675 element.attr('tabindex', -1);
11676 findDown(element.find('a').eq(0));
11677 downE.eq(0).attr('tabindex', 0);
11679 evt.stopPropagation();
11681 case keymap.KEY.ENTER:
11682 var isSelectedElem = element.hasClass('bg');
11683 var enterFunc = function(element){
11684 if (isSelectedElem) {
11685 element.removeClass('bg');
11686 if (element.attr('aria-selected')) {
11687 element.attr('aria-selected', 'false');
11691 element.addClass('bg');
11692 element.attr('aria-selected', 'true');
11695 if (element[0].previousElementSibling) {
11696 closeOthersUp(angular.element(element[0].previousElementSibling));
11698 if (element[0].nextElementSibling) {
11699 closeOthersDown(angular.element(element[0].nextElementSibling));
11702 removeBackground(element);
11703 enterFunc(element);
11704 evt.stopPropagation();
11706 case keymap.KEY.TAB:
11707 $timeout(function(){
11708 resetTabPosition(element);
11710 evt.stopPropagation();
11717 element.bind('keyup',function(evt){
11718 if(evt.keyCode === keymap.KEY.TAB){
11720 var tempElem = element;
11722 while(!tempElem.hasClass('b2b-tree')){
11723 elemArr.push(tempElem);
11724 tempElem = tempElem.parent().parent();
11726 elemArr.push(tempElem);
11730 evt.stopPropagation();
11737 * @name Navigation.att:Tree nodes with checkboxes
11739 * @param {String} setRole - The value needs to be "tree". This is required to incorporate CATO requirements.
11740 * @param {boolean} groupIt - The value needs to be "false" for top-level tree rendered.
11741 * @param {Object} collection - The JSON object of tree to be rendered.
11743 * <file src="src/treeNodeCheckbox/docs/readme.md" />
11746 * <div class="b2b-tree-checkbox">
11747 * <b2b-tree-node-checkbox collection="treeStructure" set-role="tree" group-it="false"></b2b-tree-node-checkbox>
11750 * <section id="code">
11751 <example module="b2b.att">
11752 <file src="src/treeNodeCheckbox/docs/demo.html" />
11753 <file src="src/treeNodeCheckbox/docs/demo.js" />
11758 angular.module('b2b.att.treeNodeCheckbox', ['b2b.att.utilities'])
11759 .directive('b2bTreeNodeCheckbox', function () {
11768 templateUrl: function (element, attrs) {
11769 if (attrs.groupIt === 'true') {
11770 return "b2bTemplate/treeNodeCheckbox/groupedTree.html";
11772 return "b2bTemplate/treeNodeCheckbox/ungroupedTree.html";
11775 link: function (scope) {
11776 if (!(scope.setRole === 'tree')) {
11777 scope.setRole = 'group';
11782 .directive('b2bTreeMember', ['$compile', '$timeout', 'keymap', function ($compile, $timeout, keymap) {
11790 templateUrl: 'b2bTemplate/treeNodeCheckbox/treeMember.html',
11791 link: function (scope, element, attrs) {
11792 scope.elemArr = [];
11793 var removeRootTabIndex = function (elem) {
11794 if (elem.parent().parent().eq(0).hasClass('b2b-tree-checkbox')) {
11795 elem.attr('tabindex', -1);
11798 removeRootTabIndex(elem.parent());
11800 scope.$watch('member.child', function(newVal, oldVal){
11801 if(newVal !== oldVal){
11806 var checkedCount = 0;
11807 var nonCheckedCount = 0;
11808 var checkBoxesCount = 0;
11810 if(element.find('a').eq(0).find('input')){
11811 if(scope.member.indeterminate){
11812 element.find('a').eq(0).find('input').prop('indeterminate', true);
11813 element.attr('aria-checked',"mixed");
11815 element.attr('aria-checked',scope.member.isSelected);
11818 element.find('a').eq(0).find('input').bind('change',function(){
11819 scope.member.indeterminate = false;
11820 downwardModalUpdate(scope.member);
11821 downwardSelection(element);
11822 upwardSelection(element);
11823 element.attr('aria-checked',scope.member.isSelected);
11824 if (scope.member.onSelect !== undefined) {
11825 scope.member.onSelect(scope.member);
11829 element.find('a').eq(0).find('input').bind('click',function(){
11830 var elem = angular.element(this);
11831 if(scope.member.indeterminate){
11832 scope.member.indeterminate = false;
11833 scope.member.isSelected = true;
11834 elem.prop('indeterminate', false);
11835 elem.prop('checked', true);
11836 elem.triggerHandler('change');
11840 var groupNode = false;
11841 var checkedTreeNode = false;
11843 var isCheckboxSelected = function(elem){
11844 checkedTreeNode = false;
11845 checkedTreeNode = angular.element(angular.element(elem).find('a').eq(0))[0].querySelector('input.treeCheckBox').checked;
11848 var findCheckbox = function(elem){
11849 return angular.element(angular.element(elem).find('a').eq(0))[0].querySelector('input.treeCheckBox');
11852 var updateGrpNodeCheckboxes = function(elem, checked){
11853 angular.element(angular.element(elem).find('a').eq(0))[0].querySelector('input.treeCheckBox').checked = checked;
11857 var isGroupNode = function(elem){
11859 if(angular.element(angular.element(elem).find('a').eq(0))[0].querySelector('input.grpTreeCheckbox')){
11864 var downwardModalUpdate = function(curMember){
11865 angular.forEach(curMember.child, function(childMember, key) {
11866 childMember.isSelected = curMember.isSelected;
11867 childMember.indeterminate = false;
11868 if(angular.isArray(childMember.child) && scope.member.child.length > 0){
11869 downwardModalUpdate(childMember);
11874 var downwardSelection = function(elem){
11875 if(findCheckbox(elem)){
11876 isCheckboxSelected(elem)
11878 if(angular.element(elem).find('ul').length > 0){
11879 var childNodes = angular.element(elem).find('ul').eq(0).children('li');
11880 for(var i=0; i<childNodes.length; i++){
11881 if(findCheckbox(childNodes[i])){
11882 isGroupNode(childNodes[i]);
11883 angular.element(findCheckbox(childNodes[i])).prop('indeterminate', false);
11884 angular.element(childNodes[i]).attr('aria-checked',checkedTreeNode);
11886 updateGrpNodeCheckboxes(childNodes[i],checkedTreeNode);
11888 angular.element(childNodes[i]).scope().member.isSelected = checkedTreeNode;
11889 angular.element(childNodes[i]).scope().member.indeterminate = false
11890 angular.element(childNodes[i]).scope().$apply();
11892 downwardSelection(childNodes[i]);
11898 var upwardSelection = function(elem){
11899 if(!elem.parent().parent().eq(0).hasClass('b2b-tree-checkbox')){
11900 var childNodes = elem.parent().parent().find('ul').eq(0).children('li');
11902 nonCheckedCount = 0;
11903 checkBoxesCount = 0;
11904 for(i=0; i<childNodes.length; i++){
11905 if(findCheckbox(childNodes[i])){
11906 isGroupNode(childNodes[i]);
11907 isCheckboxSelected(childNodes[i]);
11909 if(checkedTreeNode){
11911 }else if(!angular.element(angular.element(angular.element(childNodes[i]).find('a').eq(0))[0].querySelector('input.treeCheckBox')).prop('indeterminate')){
11916 var parentNodeScope;
11917 parentNodeScope = angular.element(elem.parent().parent()).scope();
11918 if(findCheckbox(elem.parent().parent())){
11919 if(nonCheckedCount == checkBoxesCount){
11920 angular.element(findCheckbox(elem.parent().parent())).prop('indeterminate', false);
11921 if(parentNodeScope && parentNodeScope.member){
11922 parentNodeScope.member.isSelected = false;
11923 parentNodeScope.member.indeterminate = false;
11925 updateGrpNodeCheckboxes(elem.parent().parent(),false);
11927 angular.element(elem.parent().parent()).attr('aria-checked',false);
11928 }else if(checkedCount == checkBoxesCount){
11929 angular.element(findCheckbox(elem.parent().parent())).prop('indeterminate', false);
11930 if(parentNodeScope && parentNodeScope.member){
11931 parentNodeScope.member.isSelected = true;
11932 parentNodeScope.member.indeterminate = false;
11934 updateGrpNodeCheckboxes(elem.parent().parent(),true);
11936 angular.element(elem.parent().parent()).attr('aria-checked',true);
11938 angular.element(findCheckbox(elem.parent().parent())).prop('indeterminate', true);
11939 if(parentNodeScope && parentNodeScope.member){
11940 parentNodeScope.member.isSelected = false;
11941 parentNodeScope.member.indeterminate = true;
11943 updateGrpNodeCheckboxes(elem.parent().parent(),false);
11945 angular.element(elem.parent().parent()).attr('aria-checked',"mixed");
11947 if(parentNodeScope && parentNodeScope.member){
11948 parentNodeScope.$apply();
11954 if(elem.parent().parent().attr('role') == "treeitem"){
11955 upwardSelection(elem.parent().parent());
11960 scope.showChild = function () {
11961 if (!element.hasClass('grouped')) {
11962 if (angular.isArray(scope.member.child) && scope.member.child.length > 0 && (scope.member.divide === undefined || scope.member.child.length < scope.member.divide)) {
11963 scope.groupIt = false;
11964 element.addClass('grouped');
11965 element.append("<b2b-tree-node-checkbox collection='member.child' group-it='" + scope.groupIt + "'></b2b-tree-node-checkbox>");
11966 $compile(element.contents())(scope);
11967 if(scope.member.active && scope.member.active === true){
11968 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).removeClass('icon-primary-collapsed');
11970 if(scope.member.selected && scope.member.selected === true){
11971 element.attr('tabindex', 0);
11972 removeRootTabIndex(element);
11974 if(scope.member.active && scope.member.active == undefined){
11975 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).addClass('icon-primary-collapsed');
11977 } else if (scope.member.child && scope.member.divide && scope.member.child.length > scope.member.divide) {
11978 element.addClass('grouped');
11979 scope.groupIt = true;
11982 if(scope.member.child[0].groupName !== undefined){
11983 grpName = scope.member.child[0].groupName;
11986 var toSlice = scope.member.child[0].name.search(' ');
11987 grpName = scope.member.child[0].name.slice(0, toSlice);
11990 for (i = 0; i < scope.member.child.length; i += scope.member.divide) {
11992 for (j = j + i; j < (i + scope.member.divide); j++) {
11993 if (j === scope.member.child.length) {
11994 scope.member.child[j - 1].grpChild = grpName + ' ' + (i + 1) + ' - ' + (scope.member.child.length);
11997 if(scope.member.child[j-1].active && scope.member.child[j-1].active===true){
11998 scope.member.child[j-1].activeGrp = true;
12002 if (i + scope.member.divide > scope.member.child.length) {
12003 scope.member.child[j].grpChild = grpName + ' ' + (i + 1) + ' - ' + (scope.member.child.length);
12004 if(scope.member.child[j].active && scope.member.child[j].active===true){
12005 scope.member.child[j].activeGrp = true;
12009 scope.member.child[j].grpChild = grpName + ' ' + (i + 1) + ' - ' + (i + scope.member.divide);
12010 if(scope.member.child[j].active && scope.member.child[j].active===true){
12011 scope.member.child[j].activeGrp = true;
12016 if(scope.member.divide){
12017 element.append("<b2b-tree-node-checkbox collection='member.child' group-it='" + scope.groupIt + "'></b2b-tree-node-checkbox>");
12019 element.append("<b2b-tree-node-checkbox collection='member.child' group-it='" + scope.groupIt + "'></b2b-tree-node-checkbox>");
12021 $compile(element.contents())(scope);
12022 if(scope.member.active && scope.member.active === true){
12023 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).removeClass('icon-primary-collapsed');
12026 if( scope.member.active && scope.member.active == undefined){
12027 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).addClass('icon-primary-collapsed');
12031 $timeout(function () {
12032 if(!scope.member.indeterminate){
12033 downwardSelection(element);
12039 if(scope.member.active && scope.member.active == true){
12042 if(scope.member.active == undefined && !element.find('a').eq(0).hasClass('active') && scope.member.child !== undefined){
12043 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).addClass('icon-primary-collapsed');
12045 else if(scope.member.child == undefined){
12046 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).addClass('icon-primary-circle');
12047 if(scope.$parent.$index === 0) {
12048 element.find('a').eq(0).append('<span class="first-link"></span>');
12052 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).bind('click', function (evt) {
12054 var expandFunc = scope.member.onExpand;
12055 if (element.find('a').eq(0).hasClass('active') && scope.member.onExpand !== undefined) {
12056 var eValue = scope.member.onExpand(scope.member);
12058 if (!element.find('a').eq(0).hasClass('active') && scope.member.onCollapse !== undefined) {
12059 scope.member.onCollapse(scope.member);
12063 angular.element(element[0].querySelectorAll('.treeNodeName')).eq(0).bind('click', function (evt) {
12070 .directive('b2bTreeNodeLink', ['keymap', '$timeout', function (keymap, $timeout) {
12073 link: function (scope, element, attr, ctrl) {
12074 var rootE, parentE, upE, downE;
12075 var closeOthersUp = function (elem) {
12077 if (elem.find('a').eq(0).hasClass('active')) {
12078 activeToggle(elem);
12081 if (elem.hasClass('bg')) {
12082 elem.removeClass('bg');
12084 if (elem[0].previousElementSibling !== null) {
12085 closeOthersUp(angular.element(elem[0].previousElementSibling));
12088 var closeOthersDown = function (elem) {
12090 if (elem.find('a').eq(0).hasClass('active')) {
12091 activeToggle(elem);
12094 if (elem.hasClass('bg')) {
12095 elem.removeClass('bg');
12097 if (elem[0].nextElementSibling !== null) {
12098 closeOthersDown(angular.element(elem[0].nextElementSibling));
12102 var removeBackgroundUp = function (elem) {
12104 if (elem.hasClass('b2b-tree-checkbox')) {
12107 elem.parent().parent().removeClass('bg');
12108 removeBackgroundUp(elem.parent().parent());
12112 var removeBackgroundDown = function (elem) {
12114 angular.element(elem[0].querySelector('.bg')).removeClass('bg');
12119 var activeToggle = function (elem) {
12120 var element = elem.find('a').eq(0);
12121 if (element.hasClass('active')) {
12122 elem.removeClass('bg');
12123 if (!angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).hasClass('icon-primary-circle')) {
12124 element.removeClass('active');
12125 elem.attr('aria-expanded', 'false');
12126 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).removeClass('icon-primary-expanded');
12127 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).addClass('icon-primary-collapsed');
12130 elem.addClass('bg');
12131 if (!angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).hasClass('icon-primary-circle')) {
12132 element.addClass('active');
12133 elem.attr('aria-expanded', 'true');
12134 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).removeClass('icon-primary-collapsed');
12135 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).addClass('icon-primary-expanded');
12139 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).bind('click', function (evt) {
12141 if (element[0].previousElementSibling) {
12142 closeOthersUp(angular.element(element[0].previousElementSibling));
12144 if (element[0].nextElementSibling) {
12145 closeOthersDown(angular.element(element[0].nextElementSibling));
12148 activeToggle(element);
12150 removeBackgroundDown(element);
12151 removeBackgroundUp(element);
12152 evt.stopPropagation();
12155 if (element.parent().parent().hasClass('b2b-tree-checkbox') && (element.parent()[0].previousElementSibling === null)) {
12156 element.attr('tabindex', 0);
12159 var isRoot = function (elem) {
12160 if (elem.parent().parent().eq(0).hasClass('b2b-tree-checkbox')) {
12166 var findRoot = function (elem) {
12167 if (isRoot(elem)) {
12171 findRoot(elem.parent());
12174 var findPreActive = function (elem) {
12176 if (!(elem.hasClass("active"))) {
12179 var childElems = angular.element(elem[0].nextElementSibling.children);
12180 lastE = angular.element(childElems[childElems.length - 1]);
12181 if (lastE.find('a').eq(0).hasClass('active')) {
12182 findPreActive(lastE.find('a').eq(0));
12188 var findUp = function (elem) {
12189 if (isRoot(elem)) {
12193 if (elem[0].previousElementSibling !== null && !angular.element(elem[0].previousElementSibling).hasClass('tree-hide')) {
12194 upE = angular.element(elem[0].previousElementSibling);
12195 if (upE.find('a').eq(0).hasClass('active')) {
12196 findPreActive(upE.find('a').eq(0));
12199 upE = elem.parent().parent();
12203 var downElement = function (elem) {
12204 if (elem.next().hasClass('tree-hide')) {
12205 downElement(elem.next());
12207 downE = elem.next();
12210 var isBottomElem = false;
12211 var downParent = function(liElem){
12212 if(liElem.eq(0).parent().parent().eq(0).hasClass('b2b-tree-checkbox')){
12213 isBottomElem = true;
12216 if(liElem.next().length !== 0){
12217 downE = liElem.next().eq(0);
12221 downParent(liElem.parent().parent());
12225 var findDown = function (elem) {
12226 if (isRoot(elem.parent()) && !elem.hasClass('active')) {
12227 downE = elem.parent();
12230 if (elem.hasClass('active')) {
12231 downE = elem.next().find('li').eq(0);
12232 if (downE.hasClass('tree-hide')) {
12233 downElement(downE);
12237 downParent(elem.parent());
12238 if(isBottomElem === true){
12239 downE = elem.parent();
12240 isBottomElem = false;
12244 element.bind('keydown', function (evt) {
12245 switch (evt.keyCode) {
12246 case keymap.KEY.HOME:
12247 evt.preventDefault();
12248 evt.stopPropagation();
12249 element.attr('tabindex', -1);
12251 rootE.eq(0).attr('tabindex', 0);
12254 case keymap.KEY.LEFT:
12255 evt.preventDefault();
12256 evt.stopPropagation();
12257 if (!isRoot(element)) {
12258 if(element.find('a').eq(0).hasClass('active')){
12259 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).triggerHandler('click');
12262 element.attr('tabindex', -1);
12263 parentE = element.parent().parent();
12264 parentE.attr('tabindex', 0);
12265 parentE[0].focus();
12267 if (element.find('a').eq(0).hasClass('active')) {
12268 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).triggerHandler('click');
12272 case keymap.KEY.UP:
12273 evt.preventDefault();
12274 evt.stopPropagation();
12275 element.attr('tabindex', -1);
12277 upE.eq(0).attr('tabindex', 0);
12280 case keymap.KEY.RIGHT:
12281 evt.preventDefault();
12282 evt.stopPropagation();
12283 if(angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).hasClass('icon-primary-circle')){
12286 if (!element.find('a').eq(0).hasClass('active')) {
12287 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).triggerHandler('click');
12290 element.attr('tabindex', -1);
12291 findDown(element.find('a').eq(0));
12292 downE.eq(0).attr('tabindex', 0);
12296 case keymap.KEY.DOWN:
12297 evt.preventDefault();
12298 element.attr('tabindex', -1);
12299 findDown(element.find('a').eq(0));
12300 downE.eq(0).attr('tabindex', 0);
12302 evt.stopPropagation();
12304 case keymap.KEY.SPACE:
12305 case keymap.KEY.ENTER:
12306 evt.preventDefault();
12307 evt.stopPropagation();
12308 if(angular.isDefined(element.scope().member.isSelected)){
12309 element.scope().member.isSelected = !element.scope().member.isSelected;
12310 element.scope().member.indeterminate = false;
12311 element.scope().$apply();
12312 element.find('a').eq(0).find('input').prop('indeterminate', false);
12313 element.find('a').eq(0).find('input').triggerHandler('change');
12326 * UPDATES AND DOCS AT: http://www.greensock.com
12328 * @license Copyright (c) 2008-2014, GreenSock. All rights reserved.
12329 * This work is subject to the terms at http://www.greensock.com/terms_of_use.html or for
12330 * Club GreenSock members, the software agreement that was issued with your membership.
12332 * @author: Jack Doyle, jack@greensock.com
12334 (window._gsQueue || (window._gsQueue = [])).push( function() {
12338 var _doc = document.documentElement,
12340 _max = function(element, axis) {
12341 var dim = (axis === "x") ? "Width" : "Height",
12342 scroll = "scroll" + dim,
12343 client = "client" + dim,
12344 body = document.body;
12345 return (element === _window || element === _doc || element === body) ? Math.max(_doc[scroll], body[scroll]) - (_window["inner" + dim] || Math.max(_doc[client], body[client])) : element[scroll] - element["offset" + dim];
12348 ScrollToPlugin = window._gsDefine.plugin({
12349 propName: "scrollTo",
12353 //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.
12354 init: function(target, value, tween) {
12355 this._wdw = (target === _window);
12356 this._target = target;
12357 this._tween = tween;
12358 if (typeof(value) !== "object") {
12359 value = {y:value}; //if we don't receive an object as the parameter, assume the user intends "y".
12361 this._autoKill = (value.autoKill !== false);
12362 this.x = this.xPrev = this.getX();
12363 this.y = this.yPrev = this.getY();
12364 if (value.x != null) {
12365 this._addTween(this, "x", this.x, (value.x === "max") ? _max(target, "x") : value.x, "scrollTo_x", true);
12366 this._overwriteProps.push("scrollTo_x");
12370 if (value.y != null) {
12371 this._addTween(this, "y", this.y, (value.y === "max") ? _max(target, "y") : value.y, "scrollTo_y", true);
12372 this._overwriteProps.push("scrollTo_y");
12379 //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.)
12381 this._super.setRatio.call(this, v);
12383 var x = (this._wdw || !this.skipX) ? this.getX() : this.xPrev,
12384 y = (this._wdw || !this.skipY) ? this.getY() : this.yPrev,
12385 yDif = y - this.yPrev,
12386 xDif = x - this.xPrev;
12388 if (this._autoKill) {
12389 //note: iOS has a bug that throws off the scroll by several pixels, so we need to check if it's within 7 pixels of the previous one that we set instead of just looking for an exact match.
12390 if (!this.skipX && (xDif > 7 || xDif < -7) && x < _max(this._target, "x")) {
12391 this.skipX = true; //if the user scrolls separately, we should stop tweening!
12393 if (!this.skipY && (yDif > 7 || yDif < -7) && y < _max(this._target, "y")) {
12394 this.skipY = true; //if the user scrolls separately, we should stop tweening!
12396 if (this.skipX && this.skipY) {
12397 this._tween.kill();
12401 _window.scrollTo((!this.skipX) ? this.x : x, (!this.skipY) ? this.y : y);
12404 this._target.scrollTop = this.y;
12407 this._target.scrollLeft = this.x;
12410 this.xPrev = this.x;
12411 this.yPrev = this.y;
12415 p = ScrollToPlugin.prototype;
12417 ScrollToPlugin.max = _max;
12419 p.getX = function() {
12420 return (!this._wdw) ? this._target.scrollLeft : (_window.pageXOffset != null) ? _window.pageXOffset : (_doc.scrollLeft != null) ? _doc.scrollLeft : document.body.scrollLeft;
12423 p.getY = function() {
12424 return (!this._wdw) ? this._target.scrollTop : (_window.pageYOffset != null) ? _window.pageYOffset : (_doc.scrollTop != null) ? _doc.scrollTop : document.body.scrollTop;
12427 p._kill = function(lookup) {
12428 if (lookup.scrollTo_x) {
12431 if (lookup.scrollTo_y) {
12434 return this._super._kill.call(this, lookup);
12437 }); if (window._gsDefine) { window._gsQueue.pop()(); }
12441 * UPDATES AND DOCS AT: http://www.greensock.com
12443 * Includes all of the following: TweenLite, TweenMax, TimelineLite, TimelineMax, EasePack, CSSPlugin, RoundPropsPlugin, BezierPlugin, AttrPlugin, DirectionalRotationPlugin
12445 * @license Copyright (c) 2008-2014, GreenSock. All rights reserved.
12446 * This work is subject to the terms at http://www.greensock.com/terms_of_use.html or for
12447 * Club GreenSock members, the software agreement that was issued with your membership.
12449 * @author: Jack Doyle, jack@greensock.com
12452 (window._gsQueue || (window._gsQueue = [])).push( function() {
12456 window._gsDefine("TweenMax", ["core.Animation","core.SimpleTimeline","TweenLite"], function(Animation, SimpleTimeline, TweenLite) {
12458 var _slice = [].slice,
12459 TweenMax = function(target, duration, vars) {
12460 TweenLite.call(this, target, duration, vars);
12462 this._yoyo = (this.vars.yoyo === true);
12463 this._repeat = this.vars.repeat || 0;
12464 this._repeatDelay = this.vars.repeatDelay || 0;
12465 this._dirty = true; //ensures that if there is any repeat, the totalDuration will get recalculated to accurately report it.
12466 this.render = TweenMax.prototype.render; //speed optimization (avoid prototype lookup on this "hot" method)
12468 _tinyNum = 0.0000000001,
12469 TweenLiteInternals = TweenLite._internals,
12470 _isSelector = TweenLiteInternals.isSelector,
12471 _isArray = TweenLiteInternals.isArray,
12472 p = TweenMax.prototype = TweenLite.to({}, 0.1, {}),
12475 TweenMax.version = "1.12.1";
12476 p.constructor = TweenMax;
12477 p.kill()._gc = false;
12478 TweenMax.killTweensOf = TweenMax.killDelayedCallsTo = TweenLite.killTweensOf;
12479 TweenMax.getTweensOf = TweenLite.getTweensOf;
12480 TweenMax.lagSmoothing = TweenLite.lagSmoothing;
12481 TweenMax.ticker = TweenLite.ticker;
12482 TweenMax.render = TweenLite.render;
12484 p.invalidate = function() {
12485 this._yoyo = (this.vars.yoyo === true);
12486 this._repeat = this.vars.repeat || 0;
12487 this._repeatDelay = this.vars.repeatDelay || 0;
12488 this._uncache(true);
12489 return TweenLite.prototype.invalidate.call(this);
12492 p.updateTo = function(vars, resetDuration) {
12493 var curRatio = this.ratio, p;
12494 if (resetDuration && this._startTime < this._timeline._time) {
12495 this._startTime = this._timeline._time;
12496 this._uncache(false);
12498 this._enabled(true, false);
12500 this._timeline.insert(this, this._startTime - this._delay); //ensures that any necessary re-sequencing of Animations in the timeline occurs to make sure the rendering order is correct.
12504 this.vars[p] = vars[p];
12506 if (this._initted) {
12507 if (resetDuration) {
12508 this._initted = false;
12511 this._enabled(true, false);
12513 if (this._notifyPluginsOfEnabled && this._firstPT) {
12514 TweenLite._onPluginEvent("_onDisable", this); //in case a plugin like MotionBlur must perform some cleanup tasks
12516 if (this._time / this._duration > 0.998) { //if the tween has finished (or come extremely close to finishing), we just need to rewind it to 0 and then render it again at the end which forces it to re-initialize (parsing the new vars). We allow tweens that are close to finishing (but haven't quite finished) to work this way too because otherwise, the values are so small when determining where to project the starting values that binary math issues creep in and can make the tween appear to render incorrectly when run backwards.
12517 var prevTime = this._time;
12518 this.render(0, true, false);
12519 this._initted = false;
12520 this.render(prevTime, true, false);
12521 } else if (this._time > 0) {
12522 this._initted = false;
12524 var inv = 1 / (1 - curRatio),
12525 pt = this._firstPT, endValue;
12527 endValue = pt.s + pt.c;
12529 pt.s = endValue - pt.c;
12538 p.render = function(time, suppressEvents, force) {
12539 if (!this._initted) if (this._duration === 0 && this.vars.repeat) { //zero duration tweens that render immediately have render() called from TweenLite's constructor, before TweenMax's constructor has finished setting _repeat, _repeatDelay, and _yoyo which are critical in determining totalDuration() so we need to call invalidate() which is a low-kb way to get those set properly.
12542 var totalDur = (!this._dirty) ? this._totalDuration : this.totalDuration(),
12543 prevTime = this._time,
12544 prevTotalTime = this._totalTime,
12545 prevCycle = this._cycle,
12546 duration = this._duration,
12547 prevRawPrevTime = this._rawPrevTime,
12548 isComplete, callback, pt, cycleDuration, r, type, pow, rawPrevTime, i;
12549 if (time >= totalDur) {
12550 this._totalTime = totalDur;
12551 this._cycle = this._repeat;
12552 if (this._yoyo && (this._cycle & 1) !== 0) {
12554 this.ratio = this._ease._calcEnd ? this._ease.getRatio(0) : 0;
12556 this._time = duration;
12557 this.ratio = this._ease._calcEnd ? this._ease.getRatio(1) : 1;
12559 if (!this._reversed) {
12561 callback = "onComplete";
12563 if (duration === 0) if (this._initted || !this.vars.lazy || force) { //zero-duration tweens are tricky because we must discern the momentum/direction of time in order to determine whether the starting values should be rendered or the ending values. If the "playhead" of its timeline goes past the zero-duration tween in the forward direction or lands directly on it, the end values should be rendered, but if the timeline's "playhead" moves past it in the backward direction (from a postitive time to a negative time), the starting values must be rendered.
12564 if (this._startTime === this._timeline._duration) { //if a zero-duration tween is at the VERY end of a timeline and that timeline renders at its end, it will typically add a tiny bit of cushion to the render time to prevent rounding errors from getting in the way of tweens rendering their VERY end. If we then reverse() that timeline, the zero-duration tween will trigger its onReverseComplete even though technically the playhead didn't pass over it again. It's a very specific edge case we must accommodate.
12567 if (time === 0 || prevRawPrevTime < 0 || prevRawPrevTime === _tinyNum) if (prevRawPrevTime !== time) {
12569 if (prevRawPrevTime > _tinyNum) {
12570 callback = "onReverseComplete";
12573 this._rawPrevTime = rawPrevTime = (!suppressEvents || time || prevRawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
12576 } else if (time < 0.0000001) { //to work around occasional floating point math artifacts, round super small values to 0.
12577 this._totalTime = this._time = this._cycle = 0;
12578 this.ratio = this._ease._calcEnd ? this._ease.getRatio(0) : 0;
12579 if (prevTotalTime !== 0 || (duration === 0 && prevRawPrevTime > 0 && prevRawPrevTime !== _tinyNum)) {
12580 callback = "onReverseComplete";
12581 isComplete = this._reversed;
12584 this._active = false;
12585 if (duration === 0) if (this._initted || !this.vars.lazy || force) { //zero-duration tweens are tricky because we must discern the momentum/direction of time in order to determine whether the starting values should be rendered or the ending values. If the "playhead" of its timeline goes past the zero-duration tween in the forward direction or lands directly on it, the end values should be rendered, but if the timeline's "playhead" moves past it in the backward direction (from a postitive time to a negative time), the starting values must be rendered.
12586 if (prevRawPrevTime >= 0) {
12589 this._rawPrevTime = rawPrevTime = (!suppressEvents || time || prevRawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
12591 } else if (!this._initted) { //if we render the very beginning (time == 0) of a fromTo(), we must force the render (normal tweens wouldn't need to render at a time of 0 when the prevTime was also 0). This is also mandatory to make sure overwriting kicks in immediately.
12595 this._totalTime = this._time = time;
12597 if (this._repeat !== 0) {
12598 cycleDuration = duration + this._repeatDelay;
12599 this._cycle = (this._totalTime / cycleDuration) >> 0; //originally _totalTime % cycleDuration but floating point errors caused problems, so I normalized it. (4 % 0.8 should be 0 but Flash reports it as 0.79999999!)
12600 if (this._cycle !== 0) if (this._cycle === this._totalTime / cycleDuration) {
12601 this._cycle--; //otherwise when rendered exactly at the end time, it will act as though it is repeating (at the beginning)
12603 this._time = this._totalTime - (this._cycle * cycleDuration);
12604 if (this._yoyo) if ((this._cycle & 1) !== 0) {
12605 this._time = duration - this._time;
12607 if (this._time > duration) {
12608 this._time = duration;
12609 } else if (this._time < 0) {
12614 if (this._easeType) {
12615 r = this._time / duration;
12616 type = this._easeType;
12617 pow = this._easePower;
12618 if (type === 1 || (type === 3 && r >= 0.5)) {
12626 } else if (pow === 2) {
12628 } else if (pow === 3) {
12630 } else if (pow === 4) {
12631 r *= r * r * r * r;
12635 this.ratio = 1 - r;
12636 } else if (type === 2) {
12638 } else if (this._time / duration < 0.5) {
12639 this.ratio = r / 2;
12641 this.ratio = 1 - (r / 2);
12645 this.ratio = this._ease.getRatio(this._time / duration);
12650 if (prevTime === this._time && !force && prevCycle === this._cycle) {
12651 if (prevTotalTime !== this._totalTime) if (this._onUpdate) if (!suppressEvents) { //so that onUpdate fires even during the repeatDelay - as long as the totalTime changed, we should trigger onUpdate.
12652 this._onUpdate.apply(this.vars.onUpdateScope || this, this.vars.onUpdateParams || _blankArray);
12655 } else if (!this._initted) {
12657 if (!this._initted || this._gc) { //immediateRender tweens typically won't initialize until the playhead advances (_time is greater than 0) in order to ensure that overwriting occurs properly. Also, if all of the tweening properties have been overwritten (which would cause _gc to be true, as set in _init()), we shouldn't continue otherwise an onStart callback could be called for example.
12659 } else if (!force && this._firstPT && ((this.vars.lazy !== false && this._duration) || (this.vars.lazy && !this._duration))) { //we stick it in the queue for rendering at the very end of the tick - this is a performance optimization because browsers invalidate styles and force a recalculation if you read, write, and then read style data (so it's better to read/read/read/write/write/write than read/write/read/write/read/write). The down side, of course, is that usually you WANT things to render immediately because you may have code running right after that which depends on the change. Like imagine running TweenLite.set(...) and then immediately after that, creating a nother tween that animates the same property to another value; the starting values of that 2nd tween wouldn't be accurate if lazy is true.
12660 this._time = prevTime;
12661 this._totalTime = prevTotalTime;
12662 this._rawPrevTime = prevRawPrevTime;
12663 this._cycle = prevCycle;
12664 TweenLiteInternals.lazyTweens.push(this);
12668 //_ease is initially set to defaultEase, so now that init() has run, _ease is set properly and we need to recalculate the ratio. Overall this is faster than using conditional logic earlier in the method to avoid having to set ratio twice because we only init() once but renderTime() gets called VERY frequently.
12669 if (this._time && !isComplete) {
12670 this.ratio = this._ease.getRatio(this._time / duration);
12671 } else if (isComplete && this._ease._calcEnd) {
12672 this.ratio = this._ease.getRatio((this._time === 0) ? 0 : 1);
12675 if (this._lazy !== false) {
12676 this._lazy = false;
12679 if (!this._active) if (!this._paused && this._time !== prevTime && time >= 0) {
12680 this._active = true; //so that if the user renders a tween (as opposed to the timeline rendering it), the timeline is forced to re-render and align it with the proper time/frame on the next rendering cycle. Maybe the tween already finished but the user manually re-renders it as halfway done.
12682 if (prevTotalTime === 0) {
12683 if (this._initted === 2 && time > 0) {
12684 //this.invalidate();
12685 this._init(); //will just apply overwriting since _initted of (2) means it was a from() tween that had immediateRender:true
12687 if (this._startAt) {
12689 this._startAt.render(time, suppressEvents, force);
12690 } else if (!callback) {
12691 callback = "_dummyGS"; //if no callback is defined, use a dummy value just so that the condition at the end evaluates as true because _startAt should render AFTER the normal render loop when the time is negative. We could handle this in a more intuitive way, of course, but the render loop is the MOST important thing to optimize, so this technique allows us to avoid adding extra conditional logic in a high-frequency area.
12694 if (this.vars.onStart) if (this._totalTime !== 0 || duration === 0) if (!suppressEvents) {
12695 this.vars.onStart.apply(this.vars.onStartScope || this, this.vars.onStartParams || _blankArray);
12699 pt = this._firstPT;
12702 pt.t[pt.p](pt.c * this.ratio + pt.s);
12704 pt.t[pt.p] = pt.c * this.ratio + pt.s;
12709 if (this._onUpdate) {
12710 if (time < 0) if (this._startAt && this._startTime) { //if the tween is positioned at the VERY beginning (_startTime 0) of its parent timeline, it's illegal for the playhead to go back further, so we should not render the recorded startAt values.
12711 this._startAt.render(time, suppressEvents, force); //note: for performance reasons, we tuck this conditional logic inside less traveled areas (most tweens don't have an onUpdate). We'd just have it at the end before the onComplete, but the values should be updated before any onUpdate is called, so we ALSO put it here and then if it's not called, we do so later near the onComplete.
12713 if (!suppressEvents) if (this._totalTime !== prevTotalTime || isComplete) {
12714 this._onUpdate.apply(this.vars.onUpdateScope || this, this.vars.onUpdateParams || _blankArray);
12717 if (this._cycle !== prevCycle) if (!suppressEvents) if (!this._gc) if (this.vars.onRepeat) {
12718 this.vars.onRepeat.apply(this.vars.onRepeatScope || this, this.vars.onRepeatParams || _blankArray);
12720 if (callback) if (!this._gc) { //check gc because there's a chance that kill() could be called in an onUpdate
12721 if (time < 0 && this._startAt && !this._onUpdate && this._startTime) { //if the tween is positioned at the VERY beginning (_startTime 0) of its parent timeline, it's illegal for the playhead to go back further, so we should not render the recorded startAt values.
12722 this._startAt.render(time, suppressEvents, force);
12725 if (this._timeline.autoRemoveChildren) {
12726 this._enabled(false, false);
12728 this._active = false;
12730 if (!suppressEvents && this.vars[callback]) {
12731 this.vars[callback].apply(this.vars[callback + "Scope"] || this, this.vars[callback + "Params"] || _blankArray);
12733 if (duration === 0 && this._rawPrevTime === _tinyNum && rawPrevTime !== _tinyNum) { //the onComplete or onReverseComplete could trigger movement of the playhead and for zero-duration tweens (which must discern direction) that land directly back on their start time, we don't want to fire again on the next render. Think of several addPause()'s in a timeline that forces the playhead to a certain spot, but what if it's already paused and another tween is tweening the "time" of the timeline? Each time it moves [forward] past that spot, it would move back, and since suppressEvents is true, it'd reset _rawPrevTime to _tinyNum so that when it begins again, the callback would fire (so ultimately it could bounce back and forth during that tween). Again, this is a very uncommon scenario, but possible nonetheless.
12734 this._rawPrevTime = 0;
12739 //---- STATIC FUNCTIONS -----------------------------------------------------------------------------------------------------------
12741 TweenMax.to = function(target, duration, vars) {
12742 return new TweenMax(target, duration, vars);
12745 TweenMax.from = function(target, duration, vars) {
12746 vars.runBackwards = true;
12747 vars.immediateRender = (vars.immediateRender != false);
12748 return new TweenMax(target, duration, vars);
12751 TweenMax.fromTo = function(target, duration, fromVars, toVars) {
12752 toVars.startAt = fromVars;
12753 toVars.immediateRender = (toVars.immediateRender != false && fromVars.immediateRender != false);
12754 return new TweenMax(target, duration, toVars);
12757 TweenMax.staggerTo = TweenMax.allTo = function(targets, duration, vars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
12758 stagger = stagger || 0;
12759 var delay = vars.delay || 0,
12761 finalComplete = function() {
12762 if (vars.onComplete) {
12763 vars.onComplete.apply(vars.onCompleteScope || this, arguments);
12765 onCompleteAll.apply(onCompleteAllScope || this, onCompleteAllParams || _blankArray);
12768 if (!_isArray(targets)) {
12769 if (typeof(targets) === "string") {
12770 targets = TweenLite.selector(targets) || targets;
12772 if (_isSelector(targets)) {
12773 targets = _slice.call(targets, 0);
12776 l = targets.length;
12777 for (i = 0; i < l; i++) {
12782 copy.delay = delay;
12783 if (i === l - 1 && onCompleteAll) {
12784 copy.onComplete = finalComplete;
12786 a[i] = new TweenMax(targets[i], duration, copy);
12792 TweenMax.staggerFrom = TweenMax.allFrom = function(targets, duration, vars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
12793 vars.runBackwards = true;
12794 vars.immediateRender = (vars.immediateRender != false);
12795 return TweenMax.staggerTo(targets, duration, vars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope);
12798 TweenMax.staggerFromTo = TweenMax.allFromTo = function(targets, duration, fromVars, toVars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
12799 toVars.startAt = fromVars;
12800 toVars.immediateRender = (toVars.immediateRender != false && fromVars.immediateRender != false);
12801 return TweenMax.staggerTo(targets, duration, toVars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope);
12804 TweenMax.delayedCall = function(delay, callback, params, scope, useFrames) {
12805 return new TweenMax(callback, 0, {delay:delay, onComplete:callback, onCompleteParams:params, onCompleteScope:scope, onReverseComplete:callback, onReverseCompleteParams:params, onReverseCompleteScope:scope, immediateRender:false, useFrames:useFrames, overwrite:0});
12808 TweenMax.set = function(target, vars) {
12809 return new TweenMax(target, 0, vars);
12812 TweenMax.isTweening = function(target) {
12813 return (TweenLite.getTweensOf(target, true).length > 0);
12816 var _getChildrenOf = function(timeline, includeTimelines) {
12819 tween = timeline._first;
12821 if (tween instanceof TweenLite) {
12824 if (includeTimelines) {
12827 a = a.concat(_getChildrenOf(tween, includeTimelines));
12830 tween = tween._next;
12834 getAllTweens = TweenMax.getAllTweens = function(includeTimelines) {
12835 return _getChildrenOf(Animation._rootTimeline, includeTimelines).concat( _getChildrenOf(Animation._rootFramesTimeline, includeTimelines) );
12838 TweenMax.killAll = function(complete, tweens, delayedCalls, timelines) {
12839 if (tweens == null) {
12842 if (delayedCalls == null) {
12843 delayedCalls = true;
12845 var a = getAllTweens((timelines != false)),
12847 allTrue = (tweens && delayedCalls && timelines),
12849 for (i = 0; i < l; i++) {
12851 if (allTrue || (tween instanceof SimpleTimeline) || ((isDC = (tween.target === tween.vars.onComplete)) && delayedCalls) || (tweens && !isDC)) {
12853 tween.totalTime(tween._reversed ? 0 : tween.totalDuration());
12855 tween._enabled(false, false);
12861 TweenMax.killChildTweensOf = function(parent, complete) {
12862 if (parent == null) {
12865 var tl = TweenLiteInternals.tweenLookup,
12866 a, curParent, p, i, l;
12867 if (typeof(parent) === "string") {
12868 parent = TweenLite.selector(parent) || parent;
12870 if (_isSelector(parent)) {
12871 parent = _slice.call(parent, 0);
12873 if (_isArray(parent)) {
12876 TweenMax.killChildTweensOf(parent[i], complete);
12882 curParent = tl[p].target.parentNode;
12883 while (curParent) {
12884 if (curParent === parent) {
12885 a = a.concat(tl[p].tweens);
12887 curParent = curParent.parentNode;
12891 for (i = 0; i < l; i++) {
12893 a[i].totalTime(a[i].totalDuration());
12895 a[i]._enabled(false, false);
12899 var _changePause = function(pause, tweens, delayedCalls, timelines) {
12900 tweens = (tweens !== false);
12901 delayedCalls = (delayedCalls !== false);
12902 timelines = (timelines !== false);
12903 var a = getAllTweens(timelines),
12904 allTrue = (tweens && delayedCalls && timelines),
12909 if (allTrue || (tween instanceof SimpleTimeline) || ((isDC = (tween.target === tween.vars.onComplete)) && delayedCalls) || (tweens && !isDC)) {
12910 tween.paused(pause);
12915 TweenMax.pauseAll = function(tweens, delayedCalls, timelines) {
12916 _changePause(true, tweens, delayedCalls, timelines);
12919 TweenMax.resumeAll = function(tweens, delayedCalls, timelines) {
12920 _changePause(false, tweens, delayedCalls, timelines);
12923 TweenMax.globalTimeScale = function(value) {
12924 var tl = Animation._rootTimeline,
12925 t = TweenLite.ticker.time;
12926 if (!arguments.length) {
12927 return tl._timeScale;
12929 value = value || _tinyNum; //can't allow zero because it'll throw the math off
12930 tl._startTime = t - ((t - tl._startTime) * tl._timeScale / value);
12931 tl = Animation._rootFramesTimeline;
12932 t = TweenLite.ticker.frame;
12933 tl._startTime = t - ((t - tl._startTime) * tl._timeScale / value);
12934 tl._timeScale = Animation._rootTimeline._timeScale = value;
12939 //---- GETTERS / SETTERS ----------------------------------------------------------------------------------------------------------
12941 p.progress = function(value) {
12942 return (!arguments.length) ? this._time / this.duration() : this.totalTime( this.duration() * ((this._yoyo && (this._cycle & 1) !== 0) ? 1 - value : value) + (this._cycle * (this._duration + this._repeatDelay)), false);
12945 p.totalProgress = function(value) {
12946 return (!arguments.length) ? this._totalTime / this.totalDuration() : this.totalTime( this.totalDuration() * value, false);
12949 p.time = function(value, suppressEvents) {
12950 if (!arguments.length) {
12954 this.totalDuration();
12956 if (value > this._duration) {
12957 value = this._duration;
12959 if (this._yoyo && (this._cycle & 1) !== 0) {
12960 value = (this._duration - value) + (this._cycle * (this._duration + this._repeatDelay));
12961 } else if (this._repeat !== 0) {
12962 value += this._cycle * (this._duration + this._repeatDelay);
12964 return this.totalTime(value, suppressEvents);
12967 p.duration = function(value) {
12968 if (!arguments.length) {
12969 return this._duration; //don't set _dirty = false because there could be repeats that haven't been factored into the _totalDuration yet. Otherwise, if you create a repeated TweenMax and then immediately check its duration(), it would cache the value and the totalDuration would not be correct, thus repeats wouldn't take effect.
12971 return Animation.prototype.duration.call(this, value);
12974 p.totalDuration = function(value) {
12975 if (!arguments.length) {
12977 //instead of Infinity, we use 999999999999 so that we can accommodate reverses
12978 this._totalDuration = (this._repeat === -1) ? 999999999999 : this._duration * (this._repeat + 1) + (this._repeatDelay * this._repeat);
12979 this._dirty = false;
12981 return this._totalDuration;
12983 return (this._repeat === -1) ? this : this.duration( (value - (this._repeat * this._repeatDelay)) / (this._repeat + 1) );
12986 p.repeat = function(value) {
12987 if (!arguments.length) {
12988 return this._repeat;
12990 this._repeat = value;
12991 return this._uncache(true);
12994 p.repeatDelay = function(value) {
12995 if (!arguments.length) {
12996 return this._repeatDelay;
12998 this._repeatDelay = value;
12999 return this._uncache(true);
13002 p.yoyo = function(value) {
13003 if (!arguments.length) {
13006 this._yoyo = value;
13023 * ----------------------------------------------------------------
13025 * ----------------------------------------------------------------
13027 window._gsDefine("TimelineLite", ["core.Animation","core.SimpleTimeline","TweenLite"], function(Animation, SimpleTimeline, TweenLite) {
13029 var TimelineLite = function(vars) {
13030 SimpleTimeline.call(this, vars);
13032 this.autoRemoveChildren = (this.vars.autoRemoveChildren === true);
13033 this.smoothChildTiming = (this.vars.smoothChildTiming === true);
13034 this._sortChildren = true;
13035 this._onUpdate = this.vars.onUpdate;
13040 if (_isArray(val)) if (val.join("").indexOf("{self}") !== -1) {
13041 v[p] = this._swapSelfInParams(val);
13044 if (_isArray(v.tweens)) {
13045 this.add(v.tweens, 0, v.align, v.stagger);
13048 _tinyNum = 0.0000000001,
13049 _isSelector = TweenLite._internals.isSelector,
13050 _isArray = TweenLite._internals.isArray,
13052 _globals = window._gsDefine.globals,
13053 _copy = function(vars) {
13060 _pauseCallback = function(tween, callback, params, scope) {
13061 tween._timeline.pause(tween._startTime);
13063 callback.apply(scope || tween._timeline, params || _blankArray);
13066 _slice = _blankArray.slice,
13067 p = TimelineLite.prototype = new SimpleTimeline();
13069 TimelineLite.version = "1.12.1";
13070 p.constructor = TimelineLite;
13071 p.kill()._gc = false;
13073 p.to = function(target, duration, vars, position) {
13074 var Engine = (vars.repeat && _globals.TweenMax) || TweenLite;
13075 return duration ? this.add( new Engine(target, duration, vars), position) : this.set(target, vars, position);
13078 p.from = function(target, duration, vars, position) {
13079 return this.add( ((vars.repeat && _globals.TweenMax) || TweenLite).from(target, duration, vars), position);
13082 p.fromTo = function(target, duration, fromVars, toVars, position) {
13083 var Engine = (toVars.repeat && _globals.TweenMax) || TweenLite;
13084 return duration ? this.add( Engine.fromTo(target, duration, fromVars, toVars), position) : this.set(target, toVars, position);
13087 p.staggerTo = function(targets, duration, vars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
13088 var tl = new TimelineLite({onComplete:onCompleteAll, onCompleteParams:onCompleteAllParams, onCompleteScope:onCompleteAllScope, smoothChildTiming:this.smoothChildTiming}),
13090 if (typeof(targets) === "string") {
13091 targets = TweenLite.selector(targets) || targets;
13093 if (_isSelector(targets)) { //senses if the targets object is a selector. If it is, we should translate it into an array.
13094 targets = _slice.call(targets, 0);
13096 stagger = stagger || 0;
13097 for (i = 0; i < targets.length; i++) {
13098 if (vars.startAt) {
13099 vars.startAt = _copy(vars.startAt);
13101 tl.to(targets[i], duration, _copy(vars), i * stagger);
13103 return this.add(tl, position);
13106 p.staggerFrom = function(targets, duration, vars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
13107 vars.immediateRender = (vars.immediateRender != false);
13108 vars.runBackwards = true;
13109 return this.staggerTo(targets, duration, vars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope);
13112 p.staggerFromTo = function(targets, duration, fromVars, toVars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
13113 toVars.startAt = fromVars;
13114 toVars.immediateRender = (toVars.immediateRender != false && fromVars.immediateRender != false);
13115 return this.staggerTo(targets, duration, toVars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope);
13118 p.call = function(callback, params, scope, position) {
13119 return this.add( TweenLite.delayedCall(0, callback, params, scope), position);
13122 p.set = function(target, vars, position) {
13123 position = this._parseTimeOrLabel(position, 0, true);
13124 if (vars.immediateRender == null) {
13125 vars.immediateRender = (position === this._time && !this._paused);
13127 return this.add( new TweenLite(target, 0, vars), position);
13130 TimelineLite.exportRoot = function(vars, ignoreDelayedCalls) {
13132 if (vars.smoothChildTiming == null) {
13133 vars.smoothChildTiming = true;
13135 var tl = new TimelineLite(vars),
13136 root = tl._timeline,
13138 if (ignoreDelayedCalls == null) {
13139 ignoreDelayedCalls = true;
13141 root._remove(tl, true);
13143 tl._rawPrevTime = tl._time = tl._totalTime = root._time;
13144 tween = root._first;
13146 next = tween._next;
13147 if (!ignoreDelayedCalls || !(tween instanceof TweenLite && tween.target === tween.vars.onComplete)) {
13148 tl.add(tween, tween._startTime - tween._delay);
13156 p.add = function(value, position, align, stagger) {
13157 var curTime, l, i, child, tl, beforeRawTime;
13158 if (typeof(position) !== "number") {
13159 position = this._parseTimeOrLabel(position, 0, true, value);
13161 if (!(value instanceof Animation)) {
13162 if ((value instanceof Array) || (value && value.push && _isArray(value))) {
13163 align = align || "normal";
13164 stagger = stagger || 0;
13165 curTime = position;
13167 for (i = 0; i < l; i++) {
13168 if (_isArray(child = value[i])) {
13169 child = new TimelineLite({tweens:child});
13171 this.add(child, curTime);
13172 if (typeof(child) !== "string" && typeof(child) !== "function") {
13173 if (align === "sequence") {
13174 curTime = child._startTime + (child.totalDuration() / child._timeScale);
13175 } else if (align === "start") {
13176 child._startTime -= child.delay();
13179 curTime += stagger;
13181 return this._uncache(true);
13182 } else if (typeof(value) === "string") {
13183 return this.addLabel(value, position);
13184 } else if (typeof(value) === "function") {
13185 value = TweenLite.delayedCall(0, value);
13187 throw("Cannot add " + value + " into the timeline; it is not a tween, timeline, function, or string.");
13191 SimpleTimeline.prototype.add.call(this, value, position);
13193 //if the timeline has already ended but the inserted tween/timeline extends the duration, we should enable this timeline again so that it renders properly. We should also align the playhead with the parent timeline's when appropriate.
13194 if (this._gc || this._time === this._duration) if (!this._paused) if (this._duration < this.duration()) {
13195 //in case any of the ancestors had completed but should now be enabled...
13197 beforeRawTime = (tl.rawTime() > value._startTime); //if the tween is placed on the timeline so that it starts BEFORE the current rawTime, we should align the playhead (move the timeline). This is because sometimes users will create a timeline, let it finish, and much later append a tween and expect it to run instead of jumping to its end state. While technically one could argue that it should jump to its end state, that's not what users intuitively expect.
13198 while (tl._timeline) {
13199 if (beforeRawTime && tl._timeline.smoothChildTiming) {
13200 tl.totalTime(tl._totalTime, true); //moves the timeline (shifts its startTime) if necessary, and also enables it.
13201 } else if (tl._gc) {
13202 tl._enabled(true, false);
13211 p.remove = function(value) {
13212 if (value instanceof Animation) {
13213 return this._remove(value, false);
13214 } else if (value instanceof Array || (value && value.push && _isArray(value))) {
13215 var i = value.length;
13217 this.remove(value[i]);
13220 } else if (typeof(value) === "string") {
13221 return this.removeLabel(value);
13223 return this.kill(null, value);
13226 p._remove = function(tween, skipDisable) {
13227 SimpleTimeline.prototype._remove.call(this, tween, skipDisable);
13228 var last = this._last;
13230 this._time = this._totalTime = this._duration = this._totalDuration = 0;
13231 } else if (this._time > last._startTime + last._totalDuration / last._timeScale) {
13232 this._time = this.duration();
13233 this._totalTime = this._totalDuration;
13238 p.append = function(value, offsetOrLabel) {
13239 return this.add(value, this._parseTimeOrLabel(null, offsetOrLabel, true, value));
13242 p.insert = p.insertMultiple = function(value, position, align, stagger) {
13243 return this.add(value, position || 0, align, stagger);
13246 p.appendMultiple = function(tweens, offsetOrLabel, align, stagger) {
13247 return this.add(tweens, this._parseTimeOrLabel(null, offsetOrLabel, true, tweens), align, stagger);
13250 p.addLabel = function(label, position) {
13251 this._labels[label] = this._parseTimeOrLabel(position);
13255 p.addPause = function(position, callback, params, scope) {
13256 return this.call(_pauseCallback, ["{self}", callback, params, scope], this, position);
13259 p.removeLabel = function(label) {
13260 delete this._labels[label];
13264 p.getLabelTime = function(label) {
13265 return (this._labels[label] != null) ? this._labels[label] : -1;
13268 p._parseTimeOrLabel = function(timeOrLabel, offsetOrLabel, appendIfAbsent, ignore) {
13270 //if we're about to add a tween/timeline (or an array of them) that's already a child of this timeline, we should remove it first so that it doesn't contaminate the duration().
13271 if (ignore instanceof Animation && ignore.timeline === this) {
13272 this.remove(ignore);
13273 } else if (ignore && ((ignore instanceof Array) || (ignore.push && _isArray(ignore)))) {
13276 if (ignore[i] instanceof Animation && ignore[i].timeline === this) {
13277 this.remove(ignore[i]);
13281 if (typeof(offsetOrLabel) === "string") {
13282 return this._parseTimeOrLabel(offsetOrLabel, (appendIfAbsent && typeof(timeOrLabel) === "number" && this._labels[offsetOrLabel] == null) ? timeOrLabel - this.duration() : 0, appendIfAbsent);
13284 offsetOrLabel = offsetOrLabel || 0;
13285 if (typeof(timeOrLabel) === "string" && (isNaN(timeOrLabel) || this._labels[timeOrLabel] != null)) { //if the string is a number like "1", check to see if there's a label with that name, otherwise interpret it as a number (absolute value).
13286 i = timeOrLabel.indexOf("=");
13288 if (this._labels[timeOrLabel] == null) {
13289 return appendIfAbsent ? (this._labels[timeOrLabel] = this.duration() + offsetOrLabel) : offsetOrLabel;
13291 return this._labels[timeOrLabel] + offsetOrLabel;
13293 offsetOrLabel = parseInt(timeOrLabel.charAt(i-1) + "1", 10) * Number(timeOrLabel.substr(i+1));
13294 timeOrLabel = (i > 1) ? this._parseTimeOrLabel(timeOrLabel.substr(0, i-1), 0, appendIfAbsent) : this.duration();
13295 } else if (timeOrLabel == null) {
13296 timeOrLabel = this.duration();
13298 return Number(timeOrLabel) + offsetOrLabel;
13301 p.seek = function(position, suppressEvents) {
13302 return this.totalTime((typeof(position) === "number") ? position : this._parseTimeOrLabel(position), (suppressEvents !== false));
13305 p.stop = function() {
13306 return this.paused(true);
13309 p.gotoAndPlay = function(position, suppressEvents) {
13310 return this.play(position, suppressEvents);
13313 p.gotoAndStop = function(position, suppressEvents) {
13314 return this.pause(position, suppressEvents);
13317 p.render = function(time, suppressEvents, force) {
13319 this._enabled(true, false);
13321 var totalDur = (!this._dirty) ? this._totalDuration : this.totalDuration(),
13322 prevTime = this._time,
13323 prevStart = this._startTime,
13324 prevTimeScale = this._timeScale,
13325 prevPaused = this._paused,
13326 tween, isComplete, next, callback, internalForce;
13327 if (time >= totalDur) {
13328 this._totalTime = this._time = totalDur;
13329 if (!this._reversed) if (!this._hasPausedChild()) {
13331 callback = "onComplete";
13332 if (this._duration === 0) if (time === 0 || this._rawPrevTime < 0 || this._rawPrevTime === _tinyNum) if (this._rawPrevTime !== time && this._first) {
13333 internalForce = true;
13334 if (this._rawPrevTime > _tinyNum) {
13335 callback = "onReverseComplete";
13339 this._rawPrevTime = (this._duration || !suppressEvents || time || this._rawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration timeline or tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
13340 time = totalDur + 0.0001; //to avoid occasional floating point rounding errors - sometimes child tweens/timelines were not being fully completed (their progress might be 0.999999999999998 instead of 1 because when _time - tween._startTime is performed, floating point errors would return a value that was SLIGHTLY off). Try (999999999999.7 - 999999999999) * 1 = 0.699951171875 instead of 0.7.
13342 } else if (time < 0.0000001) { //to work around occasional floating point math artifacts, round super small values to 0.
13343 this._totalTime = this._time = 0;
13344 if (prevTime !== 0 || (this._duration === 0 && this._rawPrevTime !== _tinyNum && (this._rawPrevTime > 0 || (time < 0 && this._rawPrevTime >= 0)))) {
13345 callback = "onReverseComplete";
13346 isComplete = this._reversed;
13349 this._active = false;
13350 if (this._duration === 0) if (this._rawPrevTime >= 0 && this._first) { //zero-duration timelines are tricky because we must discern the momentum/direction of time in order to determine whether the starting values should be rendered or the ending values. If the "playhead" of its timeline goes past the zero-duration tween in the forward direction or lands directly on it, the end values should be rendered, but if the timeline's "playhead" moves past it in the backward direction (from a postitive time to a negative time), the starting values must be rendered.
13351 internalForce = true;
13353 this._rawPrevTime = time;
13355 this._rawPrevTime = (this._duration || !suppressEvents || time || this._rawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration timeline or tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
13357 time = 0; //to avoid occasional floating point rounding errors (could cause problems especially with zero-duration tweens at the very beginning of the timeline)
13358 if (!this._initted) {
13359 internalForce = true;
13364 this._totalTime = this._time = this._rawPrevTime = time;
13366 if ((this._time === prevTime || !this._first) && !force && !internalForce) {
13368 } else if (!this._initted) {
13369 this._initted = true;
13372 if (!this._active) if (!this._paused && this._time !== prevTime && time > 0) {
13373 this._active = true; //so that if the user renders the timeline (as opposed to the parent timeline rendering it), it is forced to re-render and align it with the proper time/frame on the next rendering cycle. Maybe the timeline already finished but the user manually re-renders it as halfway done, for example.
13376 if (prevTime === 0) if (this.vars.onStart) if (this._time !== 0) if (!suppressEvents) {
13377 this.vars.onStart.apply(this.vars.onStartScope || this, this.vars.onStartParams || _blankArray);
13380 if (this._time >= prevTime) {
13381 tween = this._first;
13383 next = tween._next; //record it here because the value could change after rendering...
13384 if (this._paused && !prevPaused) { //in case a tween pauses the timeline when rendering
13386 } else if (tween._active || (tween._startTime <= this._time && !tween._paused && !tween._gc)) {
13387 if (!tween._reversed) {
13388 tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
13390 tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
13396 tween = this._last;
13398 next = tween._prev; //record it here because the value could change after rendering...
13399 if (this._paused && !prevPaused) { //in case a tween pauses the timeline when rendering
13401 } else if (tween._active || (tween._startTime <= prevTime && !tween._paused && !tween._gc)) {
13402 if (!tween._reversed) {
13403 tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
13405 tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
13412 if (this._onUpdate) if (!suppressEvents) {
13413 this._onUpdate.apply(this.vars.onUpdateScope || this, this.vars.onUpdateParams || _blankArray);
13416 if (callback) if (!this._gc) if (prevStart === this._startTime || prevTimeScale !== this._timeScale) if (this._time === 0 || totalDur >= this.totalDuration()) { //if one of the tweens that was rendered altered this timeline's startTime (like if an onComplete reversed the timeline), it probably isn't complete. If it is, don't worry, because whatever call altered the startTime would complete if it was necessary at the new time. The only exception is the timeScale property. Also check _gc because there's a chance that kill() could be called in an onUpdate
13418 if (this._timeline.autoRemoveChildren) {
13419 this._enabled(false, false);
13421 this._active = false;
13423 if (!suppressEvents && this.vars[callback]) {
13424 this.vars[callback].apply(this.vars[callback + "Scope"] || this, this.vars[callback + "Params"] || _blankArray);
13429 p._hasPausedChild = function() {
13430 var tween = this._first;
13432 if (tween._paused || ((tween instanceof TimelineLite) && tween._hasPausedChild())) {
13435 tween = tween._next;
13440 p.getChildren = function(nested, tweens, timelines, ignoreBeforeTime) {
13441 ignoreBeforeTime = ignoreBeforeTime || -9999999999;
13443 tween = this._first,
13446 if (tween._startTime < ignoreBeforeTime) {
13448 } else if (tween instanceof TweenLite) {
13449 if (tweens !== false) {
13453 if (timelines !== false) {
13456 if (nested !== false) {
13457 a = a.concat(tween.getChildren(true, tweens, timelines));
13461 tween = tween._next;
13466 p.getTweensOf = function(target, nested) {
13467 var disabled = this._gc,
13472 this._enabled(true, true); //getTweensOf() filters out disabled tweens, and we have to mark them as _gc = true when the timeline completes in order to allow clean garbage collection, so temporarily re-enable the timeline here.
13474 tweens = TweenLite.getTweensOf(target);
13477 if (tweens[i].timeline === this || (nested && this._contains(tweens[i]))) {
13478 a[cnt++] = tweens[i];
13482 this._enabled(false, true);
13487 p._contains = function(tween) {
13488 var tl = tween.timeline;
13498 p.shiftChildren = function(amount, adjustLabels, ignoreBeforeTime) {
13499 ignoreBeforeTime = ignoreBeforeTime || 0;
13500 var tween = this._first,
13501 labels = this._labels,
13504 if (tween._startTime >= ignoreBeforeTime) {
13505 tween._startTime += amount;
13507 tween = tween._next;
13509 if (adjustLabels) {
13510 for (p in labels) {
13511 if (labels[p] >= ignoreBeforeTime) {
13512 labels[p] += amount;
13516 return this._uncache(true);
13519 p._kill = function(vars, target) {
13520 if (!vars && !target) {
13521 return this._enabled(false, false);
13523 var tweens = (!target) ? this.getChildren(true, true, false) : this.getTweensOf(target),
13527 if (tweens[i]._kill(vars, target)) {
13534 p.clear = function(labels) {
13535 var tweens = this.getChildren(false, true, true),
13537 this._time = this._totalTime = 0;
13539 tweens[i]._enabled(false, false);
13541 if (labels !== false) {
13544 return this._uncache(true);
13547 p.invalidate = function() {
13548 var tween = this._first;
13550 tween.invalidate();
13551 tween = tween._next;
13556 p._enabled = function(enabled, ignoreTimeline) {
13557 if (enabled === this._gc) {
13558 var tween = this._first;
13560 tween._enabled(enabled, true);
13561 tween = tween._next;
13564 return SimpleTimeline.prototype._enabled.call(this, enabled, ignoreTimeline);
13567 p.duration = function(value) {
13568 if (!arguments.length) {
13570 this.totalDuration(); //just triggers recalculation
13572 return this._duration;
13574 if (this.duration() !== 0 && value !== 0) {
13575 this.timeScale(this._duration / value);
13580 p.totalDuration = function(value) {
13581 if (!arguments.length) {
13584 tween = this._last,
13585 prevStart = 999999999999,
13588 prev = tween._prev; //record it here in case the tween changes position in the sequence...
13589 if (tween._dirty) {
13590 tween.totalDuration(); //could change the tween._startTime, so make sure the tween's cache is clean before analyzing it.
13592 if (tween._startTime > prevStart && this._sortChildren && !tween._paused) { //in case one of the tweens shifted out of order, it needs to be re-inserted into the correct position in the sequence
13593 this.add(tween, tween._startTime - tween._delay);
13595 prevStart = tween._startTime;
13597 if (tween._startTime < 0 && !tween._paused) { //children aren't allowed to have negative startTimes unless smoothChildTiming is true, so adjust here if one is found.
13598 max -= tween._startTime;
13599 if (this._timeline.smoothChildTiming) {
13600 this._startTime += tween._startTime / this._timeScale;
13602 this.shiftChildren(-tween._startTime, false, -9999999999);
13605 end = tween._startTime + (tween._totalDuration / tween._timeScale);
13611 this._duration = this._totalDuration = max;
13612 this._dirty = false;
13614 return this._totalDuration;
13616 if (this.totalDuration() !== 0) if (value !== 0) {
13617 this.timeScale(this._totalDuration / value);
13622 p.usesFrames = function() {
13623 var tl = this._timeline;
13624 while (tl._timeline) {
13627 return (tl === Animation._rootFramesTimeline);
13630 p.rawTime = function() {
13631 return this._paused ? this._totalTime : (this._timeline.rawTime() - this._startTime) * this._timeScale;
13634 return TimelineLite;
13651 * ----------------------------------------------------------------
13653 * ----------------------------------------------------------------
13655 window._gsDefine("TimelineMax", ["TimelineLite","TweenLite","easing.Ease"], function(TimelineLite, TweenLite, Ease) {
13657 var TimelineMax = function(vars) {
13658 TimelineLite.call(this, vars);
13659 this._repeat = this.vars.repeat || 0;
13660 this._repeatDelay = this.vars.repeatDelay || 0;
13662 this._yoyo = (this.vars.yoyo === true);
13663 this._dirty = true;
13665 _tinyNum = 0.0000000001,
13667 _easeNone = new Ease(null, null, 1, 0),
13668 p = TimelineMax.prototype = new TimelineLite();
13670 p.constructor = TimelineMax;
13671 p.kill()._gc = false;
13672 TimelineMax.version = "1.12.1";
13674 p.invalidate = function() {
13675 this._yoyo = (this.vars.yoyo === true);
13676 this._repeat = this.vars.repeat || 0;
13677 this._repeatDelay = this.vars.repeatDelay || 0;
13678 this._uncache(true);
13679 return TimelineLite.prototype.invalidate.call(this);
13682 p.addCallback = function(callback, position, params, scope) {
13683 return this.add( TweenLite.delayedCall(0, callback, params, scope), position);
13686 p.removeCallback = function(callback, position) {
13688 if (position == null) {
13689 this._kill(null, callback);
13691 var a = this.getTweensOf(callback, false),
13693 time = this._parseTimeOrLabel(position);
13695 if (a[i]._startTime === time) {
13696 a[i]._enabled(false, false);
13704 p.tweenTo = function(position, vars) {
13706 var copy = {ease:_easeNone, overwrite:(vars.delay ? 2 : 1), useFrames:this.usesFrames(), immediateRender:false},//note: set overwrite to 1 (true/all) by default unless there's a delay so that we avoid a racing situation that could happen if, for example, an onmousemove creates the same tweenTo() over and over again.
13711 copy.time = this._parseTimeOrLabel(position);
13712 duration = (Math.abs(Number(copy.time) - this._time) / this._timeScale) || 0.001;
13713 t = new TweenLite(this, duration, copy);
13714 copy.onStart = function() {
13715 t.target.paused(true);
13716 if (t.vars.time !== t.target.time() && duration === t.duration()) { //don't make the duration zero - if it's supposed to be zero, don't worry because it's already initting the tween and will complete immediately, effectively making the duration zero anyway. If we make duration zero, the tween won't run at all.
13717 t.duration( Math.abs( t.vars.time - t.target.time()) / t.target._timeScale );
13719 if (vars.onStart) { //in case the user had an onStart in the vars - we don't want to overwrite it.
13720 vars.onStart.apply(vars.onStartScope || t, vars.onStartParams || _blankArray);
13726 p.tweenFromTo = function(fromPosition, toPosition, vars) {
13728 fromPosition = this._parseTimeOrLabel(fromPosition);
13729 vars.startAt = {onComplete:this.seek, onCompleteParams:[fromPosition], onCompleteScope:this};
13730 vars.immediateRender = (vars.immediateRender !== false);
13731 var t = this.tweenTo(toPosition, vars);
13732 return t.duration((Math.abs( t.vars.time - fromPosition) / this._timeScale) || 0.001);
13735 p.render = function(time, suppressEvents, force) {
13737 this._enabled(true, false);
13739 var totalDur = (!this._dirty) ? this._totalDuration : this.totalDuration(),
13740 dur = this._duration,
13741 prevTime = this._time,
13742 prevTotalTime = this._totalTime,
13743 prevStart = this._startTime,
13744 prevTimeScale = this._timeScale,
13745 prevRawPrevTime = this._rawPrevTime,
13746 prevPaused = this._paused,
13747 prevCycle = this._cycle,
13748 tween, isComplete, next, callback, internalForce, cycleDuration;
13749 if (time >= totalDur) {
13750 if (!this._locked) {
13751 this._totalTime = totalDur;
13752 this._cycle = this._repeat;
13754 if (!this._reversed) if (!this._hasPausedChild()) {
13756 callback = "onComplete";
13757 if (this._duration === 0) if (time === 0 || prevRawPrevTime < 0 || prevRawPrevTime === _tinyNum) if (prevRawPrevTime !== time && this._first) {
13758 internalForce = true;
13759 if (prevRawPrevTime > _tinyNum) {
13760 callback = "onReverseComplete";
13764 this._rawPrevTime = (this._duration || !suppressEvents || time || this._rawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration timeline or tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
13765 if (this._yoyo && (this._cycle & 1) !== 0) {
13766 this._time = time = 0;
13769 time = dur + 0.0001; //to avoid occasional floating point rounding errors - sometimes child tweens/timelines were not being fully completed (their progress might be 0.999999999999998 instead of 1 because when _time - tween._startTime is performed, floating point errors would return a value that was SLIGHTLY off). Try (999999999999.7 - 999999999999) * 1 = 0.699951171875 instead of 0.7. We cannot do less then 0.0001 because the same issue can occur when the duration is extremely large like 999999999999 in which case adding 0.00000001, for example, causes it to act like nothing was added.
13772 } else if (time < 0.0000001) { //to work around occasional floating point math artifacts, round super small values to 0.
13773 if (!this._locked) {
13774 this._totalTime = this._cycle = 0;
13777 if (prevTime !== 0 || (dur === 0 && prevRawPrevTime !== _tinyNum && (prevRawPrevTime > 0 || (time < 0 && prevRawPrevTime >= 0)) && !this._locked)) { //edge case for checking time < 0 && prevRawPrevTime >= 0: a zero-duration fromTo() tween inside a zero-duration timeline (yeah, very rare)
13778 callback = "onReverseComplete";
13779 isComplete = this._reversed;
13782 this._active = false;
13783 if (dur === 0) if (prevRawPrevTime >= 0 && this._first) { //zero-duration timelines are tricky because we must discern the momentum/direction of time in order to determine whether the starting values should be rendered or the ending values. If the "playhead" of its timeline goes past the zero-duration tween in the forward direction or lands directly on it, the end values should be rendered, but if the timeline's "playhead" moves past it in the backward direction (from a postitive time to a negative time), the starting values must be rendered.
13784 internalForce = true;
13786 this._rawPrevTime = time;
13788 this._rawPrevTime = (dur || !suppressEvents || time || this._rawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration timeline or tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
13789 time = 0; //to avoid occasional floating point rounding errors (could cause problems especially with zero-duration tweens at the very beginning of the timeline)
13790 if (!this._initted) {
13791 internalForce = true;
13796 if (dur === 0 && prevRawPrevTime < 0) { //without this, zero-duration repeating timelines (like with a simple callback nested at the very beginning and a repeatDelay) wouldn't render the first time through.
13797 internalForce = true;
13799 this._time = this._rawPrevTime = time;
13800 if (!this._locked) {
13801 this._totalTime = time;
13802 if (this._repeat !== 0) {
13803 cycleDuration = dur + this._repeatDelay;
13804 this._cycle = (this._totalTime / cycleDuration) >> 0; //originally _totalTime % cycleDuration but floating point errors caused problems, so I normalized it. (4 % 0.8 should be 0 but it gets reported as 0.79999999!)
13805 if (this._cycle !== 0) if (this._cycle === this._totalTime / cycleDuration) {
13806 this._cycle--; //otherwise when rendered exactly at the end time, it will act as though it is repeating (at the beginning)
13808 this._time = this._totalTime - (this._cycle * cycleDuration);
13809 if (this._yoyo) if ((this._cycle & 1) !== 0) {
13810 this._time = dur - this._time;
13812 if (this._time > dur) {
13814 time = dur + 0.0001; //to avoid occasional floating point rounding error
13815 } else if (this._time < 0) {
13816 this._time = time = 0;
13824 if (this._cycle !== prevCycle) if (!this._locked) {
13826 make sure children at the end/beginning of the timeline are rendered properly. If, for example,
13827 a 3-second long timeline rendered at 2.9 seconds previously, and now renders at 3.2 seconds (which
13828 would get transated to 2.8 seconds if the timeline yoyos or 0.2 seconds if it just repeats), there
13829 could be a callback or a short tween that's at 2.95 or 3 seconds in which wouldn't render. So
13830 we need to push the timeline to the end (and/or beginning depending on its yoyo value). Also we must
13831 ensure that zero-duration tweens at the very beginning or end of the TimelineMax work.
13833 var backwards = (this._yoyo && (prevCycle & 1) !== 0),
13834 wrap = (backwards === (this._yoyo && (this._cycle & 1) !== 0)),
13835 recTotalTime = this._totalTime,
13836 recCycle = this._cycle,
13837 recRawPrevTime = this._rawPrevTime,
13838 recTime = this._time;
13840 this._totalTime = prevCycle * dur;
13841 if (this._cycle < prevCycle) {
13842 backwards = !backwards;
13844 this._totalTime += dur;
13846 this._time = prevTime; //temporarily revert _time so that render() renders the children in the correct order. Without this, tweens won't rewind correctly. We could arhictect things in a "cleaner" way by splitting out the rendering queue into a separate method but for performance reasons, we kept it all inside this method.
13848 this._rawPrevTime = (dur === 0) ? prevRawPrevTime - 0.0001 : prevRawPrevTime;
13849 this._cycle = prevCycle;
13850 this._locked = true; //prevents changes to totalTime and skips repeat/yoyo behavior when we recursively call render()
13851 prevTime = (backwards) ? 0 : dur;
13852 this.render(prevTime, suppressEvents, (dur === 0));
13853 if (!suppressEvents) if (!this._gc) {
13854 if (this.vars.onRepeat) {
13855 this.vars.onRepeat.apply(this.vars.onRepeatScope || this, this.vars.onRepeatParams || _blankArray);
13859 prevTime = (backwards) ? dur + 0.0001 : -0.0001;
13860 this.render(prevTime, true, false);
13862 this._locked = false;
13863 if (this._paused && !prevPaused) { //if the render() triggered callback that paused this timeline, we should abort (very rare, but possible)
13866 this._time = recTime;
13867 this._totalTime = recTotalTime;
13868 this._cycle = recCycle;
13869 this._rawPrevTime = recRawPrevTime;
13872 if ((this._time === prevTime || !this._first) && !force && !internalForce) {
13873 if (prevTotalTime !== this._totalTime) if (this._onUpdate) if (!suppressEvents) { //so that onUpdate fires even during the repeatDelay - as long as the totalTime changed, we should trigger onUpdate.
13874 this._onUpdate.apply(this.vars.onUpdateScope || this, this.vars.onUpdateParams || _blankArray);
13877 } else if (!this._initted) {
13878 this._initted = true;
13881 if (!this._active) if (!this._paused && this._totalTime !== prevTotalTime && time > 0) {
13882 this._active = true; //so that if the user renders the timeline (as opposed to the parent timeline rendering it), it is forced to re-render and align it with the proper time/frame on the next rendering cycle. Maybe the timeline already finished but the user manually re-renders it as halfway done, for example.
13885 if (prevTotalTime === 0) if (this.vars.onStart) if (this._totalTime !== 0) if (!suppressEvents) {
13886 this.vars.onStart.apply(this.vars.onStartScope || this, this.vars.onStartParams || _blankArray);
13889 if (this._time >= prevTime) {
13890 tween = this._first;
13892 next = tween._next; //record it here because the value could change after rendering...
13893 if (this._paused && !prevPaused) { //in case a tween pauses the timeline when rendering
13895 } else if (tween._active || (tween._startTime <= this._time && !tween._paused && !tween._gc)) {
13896 if (!tween._reversed) {
13897 tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
13899 tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
13906 tween = this._last;
13908 next = tween._prev; //record it here because the value could change after rendering...
13909 if (this._paused && !prevPaused) { //in case a tween pauses the timeline when rendering
13911 } else if (tween._active || (tween._startTime <= prevTime && !tween._paused && !tween._gc)) {
13912 if (!tween._reversed) {
13913 tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
13915 tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
13922 if (this._onUpdate) if (!suppressEvents) {
13923 this._onUpdate.apply(this.vars.onUpdateScope || this, this.vars.onUpdateParams || _blankArray);
13925 if (callback) if (!this._locked) if (!this._gc) if (prevStart === this._startTime || prevTimeScale !== this._timeScale) if (this._time === 0 || totalDur >= this.totalDuration()) { //if one of the tweens that was rendered altered this timeline's startTime (like if an onComplete reversed the timeline), it probably isn't complete. If it is, don't worry, because whatever call altered the startTime would complete if it was necessary at the new time. The only exception is the timeScale property. Also check _gc because there's a chance that kill() could be called in an onUpdate
13927 if (this._timeline.autoRemoveChildren) {
13928 this._enabled(false, false);
13930 this._active = false;
13932 if (!suppressEvents && this.vars[callback]) {
13933 this.vars[callback].apply(this.vars[callback + "Scope"] || this, this.vars[callback + "Params"] || _blankArray);
13938 p.getActive = function(nested, tweens, timelines) {
13939 if (nested == null) {
13942 if (tweens == null) {
13945 if (timelines == null) {
13949 all = this.getChildren(nested, tweens, timelines),
13953 for (i = 0; i < l; i++) {
13955 if (tween.isActive()) {
13963 p.getLabelAfter = function(time) {
13964 if (!time) if (time !== 0) { //faster than isNan()
13967 var labels = this.getLabelsArray(),
13970 for (i = 0; i < l; i++) {
13971 if (labels[i].time > time) {
13972 return labels[i].name;
13978 p.getLabelBefore = function(time) {
13979 if (time == null) {
13982 var labels = this.getLabelsArray(),
13985 if (labels[i].time < time) {
13986 return labels[i].name;
13992 p.getLabelsArray = function() {
13996 for (p in this._labels) {
13997 a[cnt++] = {time:this._labels[p], name:p};
13999 a.sort(function(a,b) {
14000 return a.time - b.time;
14006 //---- GETTERS / SETTERS -------------------------------------------------------------------------------------------------------
14008 p.progress = function(value) {
14009 return (!arguments.length) ? this._time / this.duration() : this.totalTime( this.duration() * ((this._yoyo && (this._cycle & 1) !== 0) ? 1 - value : value) + (this._cycle * (this._duration + this._repeatDelay)), false);
14012 p.totalProgress = function(value) {
14013 return (!arguments.length) ? this._totalTime / this.totalDuration() : this.totalTime( this.totalDuration() * value, false);
14016 p.totalDuration = function(value) {
14017 if (!arguments.length) {
14019 TimelineLite.prototype.totalDuration.call(this); //just forces refresh
14020 //Instead of Infinity, we use 999999999999 so that we can accommodate reverses.
14021 this._totalDuration = (this._repeat === -1) ? 999999999999 : this._duration * (this._repeat + 1) + (this._repeatDelay * this._repeat);
14023 return this._totalDuration;
14025 return (this._repeat === -1) ? this : this.duration( (value - (this._repeat * this._repeatDelay)) / (this._repeat + 1) );
14028 p.time = function(value, suppressEvents) {
14029 if (!arguments.length) {
14033 this.totalDuration();
14035 if (value > this._duration) {
14036 value = this._duration;
14038 if (this._yoyo && (this._cycle & 1) !== 0) {
14039 value = (this._duration - value) + (this._cycle * (this._duration + this._repeatDelay));
14040 } else if (this._repeat !== 0) {
14041 value += this._cycle * (this._duration + this._repeatDelay);
14043 return this.totalTime(value, suppressEvents);
14046 p.repeat = function(value) {
14047 if (!arguments.length) {
14048 return this._repeat;
14050 this._repeat = value;
14051 return this._uncache(true);
14054 p.repeatDelay = function(value) {
14055 if (!arguments.length) {
14056 return this._repeatDelay;
14058 this._repeatDelay = value;
14059 return this._uncache(true);
14062 p.yoyo = function(value) {
14063 if (!arguments.length) {
14066 this._yoyo = value;
14070 p.currentLabel = function(value) {
14071 if (!arguments.length) {
14072 return this.getLabelBefore(this._time + 0.00000001);
14074 return this.seek(value, true);
14077 return TimelineMax;
14093 * ----------------------------------------------------------------
14095 * ----------------------------------------------------------------
14099 var _RAD2DEG = 180 / Math.PI,
14104 Segment = function(a, b, c, d) {
14113 _correlate = ",x,y,z,left,top,right,bottom,marginTop,marginLeft,marginRight,marginBottom,paddingLeft,paddingTop,paddingRight,paddingBottom,backgroundPosition,backgroundPosition_y,",
14114 cubicToQuadratic = function(a, b, c, d) {
14122 mabc = (mab + mbc) / 2,
14123 mbcd = (mbc + mcd) / 2,
14124 m8 = (mbcd - mabc) / 8;
14125 q1.b = mab + (a - mab) / 4;
14127 q1.c = q2.a = (q1.b + q2.b) / 2;
14128 q2.c = q3.a = (mabc + mbcd) / 2;
14130 q4.b = mcd + (d - mcd) / 4;
14131 q3.c = q4.a = (q3.b + q4.b) / 2;
14132 return [q1, q2, q3, q4];
14134 _calculateControlPoints = function(a, curviness, quad, basic, correlate) {
14135 var l = a.length - 1,
14138 i, p1, p2, p3, seg, m1, m2, mm, cp2, qb, r1, r2, tl;
14139 for (i = 0; i < l; i++) {
14148 tl = ((r2 + r1) * curviness * 0.25) / (basic ? 0.5 : _r3[i] || 0.5);
14149 m1 = p2 - (p2 - p1) * (basic ? curviness * 0.5 : (r1 !== 0 ? tl / r1 : 0));
14150 m2 = p2 + (p3 - p2) * (basic ? curviness * 0.5 : (r2 !== 0 ? tl / r2 : 0));
14151 mm = p2 - (m1 + (((m2 - m1) * ((r1 * 3 / (r1 + r2)) + 0.5) / 4) || 0));
14153 m1 = p2 - (p2 - p1) * curviness * 0.5;
14154 m2 = p2 + (p3 - p2) * curviness * 0.5;
14155 mm = p2 - (m1 + m2) / 2;
14164 seg.b = cp1 = seg.a + (seg.c - seg.a) * 0.6; //instead of placing b on a exactly, we move it inline with c so that if the user specifies an ease like Back.easeIn or Elastic.easeIn which goes BEYOND the beginning, it will do so smoothly.
14172 qb = cubicToQuadratic(p1, cp1, cp2, p2);
14173 a.splice(ii, 1, qb[0], qb[1], qb[2], qb[3]);
14183 seg.c = cp1 + (seg.d - cp1) * 0.4; //instead of placing c on d exactly, we move it inline with b so that if the user specifies an ease like Back.easeOut or Elastic.easeOut which goes BEYOND the end, it will do so smoothly.
14184 seg.da = seg.d - seg.a;
14185 seg.ca = seg.c - seg.a;
14186 seg.ba = cp1 - seg.a;
14188 qb = cubicToQuadratic(seg.a, cp1, seg.c, seg.d);
14189 a.splice(ii, 1, qb[0], qb[1], qb[2], qb[3]);
14192 _parseAnchors = function(values, p, correlate, prepend) {
14194 l, i, p1, p2, p3, tmp;
14196 values = [prepend].concat(values);
14199 if (typeof( (tmp = values[i][p]) ) === "string") if (tmp.charAt(1) === "=") {
14200 values[i][p] = prepend[p] + Number(tmp.charAt(0) + tmp.substr(2)); //accommodate relative values. Do it inline instead of breaking it out into a function for speed reasons
14204 l = values.length - 2;
14206 a[0] = new Segment(values[0][p], 0, 0, values[(l < -1) ? 0 : 1][p]);
14209 for (i = 0; i < l; i++) {
14211 p2 = values[i+1][p];
14212 a[i] = new Segment(p1, 0, 0, p2);
14214 p3 = values[i+2][p];
14215 _r1[i] = (_r1[i] || 0) + (p2 - p1) * (p2 - p1);
14216 _r2[i] = (_r2[i] || 0) + (p3 - p2) * (p3 - p2);
14219 a[i] = new Segment(values[i][p], 0, 0, values[i+1][p]);
14222 bezierThrough = function(values, curviness, quadratic, basic, correlate, prepend) {
14225 first = prepend || values[0],
14226 i, p, a, j, r, l, seamless, last;
14227 correlate = (typeof(correlate) === "string") ? ","+correlate+"," : _correlate;
14228 if (curviness == null) {
14231 for (p in values[0]) {
14234 //check to see if the last and first values are identical (well, within 0.05). If so, make seamless by appending the second element to the very end of the values array and the 2nd-to-last element to the very beginning (we'll remove those segments later)
14235 if (values.length > 1) {
14236 last = values[values.length - 1];
14241 if (Math.abs(first[p] - last[p]) > 0.05) { //build in a tolerance of +/-0.05 to accommodate rounding errors. For example, if you set an object's position to 4.945, Flash will make it 4.9
14247 values = values.concat(); //duplicate the array to avoid contaminating the original which the user may be reusing for other tweens
14249 values.unshift(prepend);
14251 values.push(values[1]);
14252 prepend = values[values.length - 3];
14255 _r1.length = _r2.length = _r3.length = 0;
14259 _corProps[p] = (correlate.indexOf(","+p+",") !== -1);
14260 obj[p] = _parseAnchors(values, p, _corProps[p], prepend);
14264 _r1[i] = Math.sqrt(_r1[i]);
14265 _r2[i] = Math.sqrt(_r2[i]);
14270 if (_corProps[p]) {
14273 for (j = 0; j < l; j++) {
14274 r = a[j+1].da / _r2[j] + a[j].da / _r1[j];
14275 _r3[j] = (_r3[j] || 0) + r * r;
14281 _r3[i] = Math.sqrt(_r3[i]);
14285 j = quadratic ? 4 : 1;
14289 _calculateControlPoints(a, curviness, quadratic, basic, _corProps[p]); //this method requires that _parseAnchors() and _setSegmentRatios() ran first so that _r1, _r2, and _r3 values are populated for all properties
14292 a.splice(a.length - j, j);
14297 _parseBezierData = function(values, type, prepend) {
14298 type = type || "soft";
14300 inc = (type === "cubic") ? 3 : 2,
14301 soft = (type === "soft"),
14303 a, b, c, d, cur, i, j, l, p, cnt, tmp;
14304 if (soft && prepend) {
14305 values = [prepend].concat(values);
14307 if (values == null || values.length < inc + 1) { throw "invalid Bezier data"; }
14308 for (p in values[0]) {
14317 for (j = 0; j < l; j++) {
14318 a = (prepend == null) ? values[j][p] : (typeof( (tmp = values[j][p]) ) === "string" && tmp.charAt(1) === "=") ? prepend[p] + Number(tmp.charAt(0) + tmp.substr(2)) : Number(tmp);
14319 if (soft) if (j > 1) if (j < l - 1) {
14320 cur[cnt++] = (a + cur[cnt-2]) / 2;
14326 for (j = 0; j < l; j += inc) {
14330 d = (inc === 2) ? 0 : cur[j+3];
14331 cur[cnt++] = tmp = (inc === 3) ? new Segment(a, b, c, d) : new Segment(a, (2 * b + a) / 3, (2 * b + c) / 3, c);
14337 _addCubicLengths = function(a, steps, resolution) {
14338 var inc = 1 / resolution,
14340 d, d1, s, da, ca, ba, p, i, inv, bez, index;
14348 for (i = 1; i <= resolution; i++) {
14351 d = d1 - (d1 = (p * p * da + 3 * inv * (p * ca + inv * ba)) * p);
14352 index = j * resolution + i - 1;
14353 steps[index] = (steps[index] || 0) + d * d;
14357 _parseLengthData = function(obj, resolution) {
14358 resolution = resolution >> 0 || 6;
14363 threshold = resolution - 1,
14365 curLS = [], //current length segments array
14368 _addCubicLengths(obj[p], a, resolution);
14371 for (i = 0; i < l; i++) {
14372 d += Math.sqrt(a[i]);
14373 index = i % resolution;
14375 if (index === threshold) {
14377 index = (i / resolution) >> 0;
14378 segments[index] = curLS;
14379 lengths[index] = total;
14384 return {length:total, lengths:lengths, segments:segments};
14389 BezierPlugin = window._gsDefine.plugin({
14390 propName: "bezier",
14396 //gets called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.
14397 init: function(target, vars, tween) {
14398 this._target = target;
14399 if (vars instanceof Array) {
14400 vars = {values:vars};
14405 this._timeRes = (vars.timeResolution == null) ? 6 : parseInt(vars.timeResolution, 10);
14406 var values = vars.values || [],
14408 second = values[0],
14409 autoRotate = vars.autoRotate || tween.vars.orientToBezier,
14410 p, isFunc, i, j, prepend;
14412 this._autoRotate = autoRotate ? (autoRotate instanceof Array) ? autoRotate : [["x","y","rotation",((autoRotate === true) ? 0 : Number(autoRotate) || 0)]] : null;
14413 for (p in second) {
14414 this._props.push(p);
14417 i = this._props.length;
14419 p = this._props[i];
14421 this._overwriteProps.push(p);
14422 isFunc = this._func[p] = (typeof(target[p]) === "function");
14423 first[p] = (!isFunc) ? parseFloat(target[p]) : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]();
14424 if (!prepend) if (first[p] !== values[0][p]) {
14428 this._beziers = (vars.type !== "cubic" && vars.type !== "quadratic" && vars.type !== "soft") ? bezierThrough(values, isNaN(vars.curviness) ? 1 : vars.curviness, false, (vars.type === "thruBasic"), vars.correlate, prepend) : _parseBezierData(values, vars.type, first);
14429 this._segCount = this._beziers[p].length;
14431 if (this._timeRes) {
14432 var ld = _parseLengthData(this._beziers, this._timeRes);
14433 this._length = ld.length;
14434 this._lengths = ld.lengths;
14435 this._segments = ld.segments;
14436 this._l1 = this._li = this._s1 = this._si = 0;
14437 this._l2 = this._lengths[0];
14438 this._curSeg = this._segments[0];
14439 this._s2 = this._curSeg[0];
14440 this._prec = 1 / this._curSeg.length;
14443 if ((autoRotate = this._autoRotate)) {
14444 this._initialRotations = [];
14445 if (!(autoRotate[0] instanceof Array)) {
14446 this._autoRotate = autoRotate = [autoRotate];
14448 i = autoRotate.length;
14450 for (j = 0; j < 3; j++) {
14451 p = autoRotate[i][j];
14452 this._func[p] = (typeof(target[p]) === "function") ? target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ] : false;
14454 p = autoRotate[i][2];
14455 this._initialRotations[i] = this._func[p] ? this._func[p].call(this._target) : this._target[p];
14458 this._startRatio = tween.vars.runBackwards ? 1 : 0; //we determine the starting ratio when the tween inits which is always 0 unless the tween has runBackwards:true (indicating it's a from() tween) in which case it's 1.
14462 //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.)
14464 var segments = this._segCount,
14466 target = this._target,
14467 notStart = (v !== this._startRatio),
14468 curIndex, inv, i, p, b, t, val, l, lengths, curSeg;
14469 if (!this._timeRes) {
14470 curIndex = (v < 0) ? 0 : (v >= 1) ? segments - 1 : (segments * v) >> 0;
14471 t = (v - (curIndex * (1 / segments))) * segments;
14473 lengths = this._lengths;
14474 curSeg = this._curSeg;
14477 //find the appropriate segment (if the currently cached one isn't correct)
14478 if (v > this._l2 && i < segments - 1) {
14480 while (i < l && (this._l2 = lengths[++i]) <= v) { }
14481 this._l1 = lengths[i-1];
14483 this._curSeg = curSeg = this._segments[i];
14484 this._s2 = curSeg[(this._s1 = this._si = 0)];
14485 } else if (v < this._l1 && i > 0) {
14486 while (i > 0 && (this._l1 = lengths[--i]) >= v) { }
14487 if (i === 0 && v < this._l1) {
14492 this._l2 = lengths[i];
14494 this._curSeg = curSeg = this._segments[i];
14495 this._s1 = curSeg[(this._si = curSeg.length - 1) - 1] || 0;
14496 this._s2 = curSeg[this._si];
14499 //now find the appropriate sub-segment (we split it into the number of pieces that was defined by "precision" and measured each one)
14502 if (v > this._s2 && i < curSeg.length - 1) {
14503 l = curSeg.length - 1;
14504 while (i < l && (this._s2 = curSeg[++i]) <= v) { }
14505 this._s1 = curSeg[i-1];
14507 } else if (v < this._s1 && i > 0) {
14508 while (i > 0 && (this._s1 = curSeg[--i]) >= v) { }
14509 if (i === 0 && v < this._s1) {
14514 this._s2 = curSeg[i];
14517 t = (i + (v - this._s1) / (this._s2 - this._s1)) * this._prec;
14521 i = this._props.length;
14523 p = this._props[i];
14524 b = this._beziers[p][curIndex];
14525 val = (t * t * b.da + 3 * inv * (t * b.ca + inv * b.ba)) * t + b.a;
14526 if (this._round[p]) {
14527 val = Math.round(val);
14536 if (this._autoRotate) {
14537 var ar = this._autoRotate,
14538 b2, x1, y1, x2, y2, add, conv;
14542 add = ar[i][3] || 0;
14543 conv = (ar[i][4] === true) ? 1 : _RAD2DEG;
14544 b = this._beziers[ar[i][0]];
14545 b2 = this._beziers[ar[i][1]];
14547 if (b && b2) { //in case one of the properties got overwritten.
14551 x1 = b.a + (b.b - b.a) * t;
14552 x2 = b.b + (b.c - b.b) * t;
14553 x1 += (x2 - x1) * t;
14554 x2 += ((b.c + (b.d - b.c) * t) - x2) * t;
14556 y1 = b2.a + (b2.b - b2.a) * t;
14557 y2 = b2.b + (b2.c - b2.b) * t;
14558 y1 += (y2 - y1) * t;
14559 y2 += ((b2.c + (b2.d - b2.c) * t) - y2) * t;
14561 val = notStart ? Math.atan2(y2 - y1, x2 - x1) * conv + add : this._initialRotations[i];
14573 p = BezierPlugin.prototype;
14576 BezierPlugin.bezierThrough = bezierThrough;
14577 BezierPlugin.cubicToQuadratic = cubicToQuadratic;
14578 BezierPlugin._autoCSS = true; //indicates that this plugin can be inserted into the "css" object using the autoCSS feature of TweenLite
14579 BezierPlugin.quadraticToCubic = function(a, b, c) {
14580 return new Segment(a, (2 * b + a) / 3, (2 * b + c) / 3, c);
14583 BezierPlugin._cssRegister = function() {
14584 var CSSPlugin = window._gsDefine.globals.CSSPlugin;
14588 var _internals = CSSPlugin._internals,
14589 _parseToProxy = _internals._parseToProxy,
14590 _setPluginRatio = _internals._setPluginRatio,
14591 CSSPropTween = _internals.CSSPropTween;
14592 _internals._registerComplexSpecialProp("bezier", {parser:function(t, e, prop, cssp, pt, plugin) {
14593 if (e instanceof Array) {
14596 plugin = new BezierPlugin();
14597 var values = e.values,
14598 l = values.length - 1,
14605 for (i = 0; i <= l; i++) {
14606 data = _parseToProxy(t, values[i], cssp, pt, plugin, (l !== i));
14607 pluginValues[i] = data.end;
14610 v[p] = e[p]; //duplicate the vars object because we need to alter some things which would cause problems if the user plans to reuse the same vars object for another tween.
14612 v.values = pluginValues;
14613 pt = new CSSPropTween(t, "bezier", 0, 0, data.pt, 2);
14615 pt.plugin = plugin;
14616 pt.setRatio = _setPluginRatio;
14617 if (v.autoRotate === 0) {
14618 v.autoRotate = true;
14620 if (v.autoRotate && !(v.autoRotate instanceof Array)) {
14621 i = (v.autoRotate === true) ? 0 : Number(v.autoRotate);
14622 v.autoRotate = (data.end.left != null) ? [["left","top","rotation",i,false]] : (data.end.x != null) ? [["x","y","rotation",i,false]] : false;
14624 if (v.autoRotate) {
14625 if (!cssp._transform) {
14626 cssp._enableTransforms(false);
14628 data.autoRotate = cssp._target._gsTransform;
14630 plugin._onInitTween(data.proxy, v, cssp._tween);
14635 p._roundProps = function(lookup, value) {
14636 var op = this._overwriteProps,
14639 if (lookup[op[i]] || lookup.bezier || lookup.bezierThrough) {
14640 this._round[op[i]] = value;
14645 p._kill = function(lookup) {
14646 var a = this._props,
14648 for (p in this._beziers) {
14650 delete this._beziers[p];
14651 delete this._func[p];
14660 return this._super._kill.call(this, lookup);
14679 * ----------------------------------------------------------------
14681 * ----------------------------------------------------------------
14683 window._gsDefine("plugins.CSSPlugin", ["plugins.TweenPlugin","TweenLite"], function(TweenPlugin, TweenLite) {
14685 /** @constructor **/
14686 var CSSPlugin = function() {
14687 TweenPlugin.call(this, "css");
14688 this._overwriteProps.length = 0;
14689 this.setRatio = CSSPlugin.prototype.setRatio; //speed optimization (avoid prototype lookup on this "hot" method)
14691 _hasPriority, //turns true whenever a CSSPropTween instance is created that has a priority other than 0. This helps us discern whether or not we should spend the time organizing the linked list or not after a CSSPlugin's _onInitTween() method is called.
14692 _suffixMap, //we set this in _onInitTween() each time as a way to have a persistent variable we can use in other methods like _parse() without having to pass it around as a parameter and we keep _parse() decoupled from a particular CSSPlugin instance
14693 _cs, //computed style (we store this in a shared variable to conserve memory and make minification tighter
14694 _overwriteProps, //alias to the currently instantiating CSSPlugin's _overwriteProps array. We use this closure in order to avoid having to pass a reference around from method to method and aid in minification.
14695 _specialProps = {},
14696 p = CSSPlugin.prototype = new TweenPlugin("css");
14698 p.constructor = CSSPlugin;
14699 CSSPlugin.version = "1.12.1";
14701 CSSPlugin.defaultTransformPerspective = 0;
14702 CSSPlugin.defaultSkewType = "compensated";
14703 p = "px"; //we'll reuse the "p" variable to keep file size down
14704 CSSPlugin.suffixMap = {top:p, right:p, bottom:p, left:p, width:p, height:p, fontSize:p, padding:p, margin:p, perspective:p, lineHeight:""};
14707 var _numExp = /(?:\d|\-\d|\.\d|\-\.\d)+/g,
14708 _relNumExp = /(?:\d|\-\d|\.\d|\-\.\d|\+=\d|\-=\d|\+=.\d|\-=\.\d)+/g,
14709 _valuesExp = /(?:\+=|\-=|\-|\b)[\d\-\.]+[a-zA-Z0-9]*(?:%|\b)/gi, //finds all the values that begin with numbers or += or -= and then a number. Includes suffixes. We use this to split complex values apart like "1px 5px 20px rgb(255,102,51)"
14710 _NaNExp = /[^\d\-\.]/g,
14711 _suffixExp = /(?:\d|\-|\+|=|#|\.)*/g,
14712 _opacityExp = /opacity *= *([^)]*)/i,
14713 _opacityValExp = /opacity:([^;]*)/i,
14714 _alphaFilterExp = /alpha\(opacity *=.+?\)/i,
14715 _rgbhslExp = /^(rgb|hsl)/,
14716 _capsExp = /([A-Z])/g,
14717 _camelExp = /-([a-z])/gi,
14718 _urlExp = /(^(?:url\(\"|url\())|(?:(\"\))$|\)$)/gi, //for pulling out urls from url(...) or url("...") strings (some browsers wrap urls in quotes, some don't when reporting things like backgroundImage)
14719 _camelFunc = function(s, g) { return g.toUpperCase(); },
14720 _horizExp = /(?:Left|Right|Width)/i,
14721 _ieGetMatrixExp = /(M11|M12|M21|M22)=[\d\-\.e]+/gi,
14722 _ieSetMatrixExp = /progid\:DXImageTransform\.Microsoft\.Matrix\(.+?\)/i,
14723 _commasOutsideParenExp = /,(?=[^\)]*(?:\(|$))/gi, //finds any commas that are not within parenthesis
14724 _DEG2RAD = Math.PI / 180,
14725 _RAD2DEG = 180 / Math.PI,
14728 _tempDiv = _doc.createElement("div"),
14729 _tempImg = _doc.createElement("img"),
14730 _internals = CSSPlugin._internals = {_specialProps:_specialProps}, //provides a hook to a few internal methods that we need to access from inside other plugins
14731 _agent = navigator.userAgent,
14733 _reqSafariFix, //we won't apply the Safari transform fix until we actually come across a tween that affects a transform property (to maintain best performance).
14736 _isFirefox, //Firefox has a bug that causes 3D transformed elements to randomly disappear unless a repaint is forced after each update on each element.
14737 _isSafariLT6, //Safari (and Android 4 which uses a flavor of Safari) has a bug that prevents changes to "top" and "left" properties from rendering properly if changed on the same frame as a transform UNLESS we set the element's WebkitBackfaceVisibility to hidden (weird, I know). Doing this for Android 3 and earlier seems to actually cause other problems, though (fun!)
14739 _supportsOpacity = (function() { //we set _isSafari, _ieVers, _isFirefox, and _supportsOpacity all in one function here to reduce file size slightly, especially in the minified version.
14740 var i = _agent.indexOf("Android"),
14741 d = _doc.createElement("div"), a;
14743 _isSafari = (_agent.indexOf("Safari") !== -1 && _agent.indexOf("Chrome") === -1 && (i === -1 || Number(_agent.substr(i+8, 1)) > 3));
14744 _isSafariLT6 = (_isSafari && (Number(_agent.substr(_agent.indexOf("Version/")+8, 1)) < 6));
14745 _isFirefox = (_agent.indexOf("Firefox") !== -1);
14747 if ((/MSIE ([0-9]{1,}[\.0-9]{0,})/).exec(_agent)) {
14748 _ieVers = parseFloat( RegExp.$1 );
14751 d.innerHTML = "<a title='' style='top:1px;opacity:.55;'>a</a>";
14752 a = d.getElementsByTagName("a")[0];
14753 return a ? /^0.55/.test(a.style.opacity) : false;
14755 _getIEOpacity = function(v) {
14756 return (_opacityExp.test( ((typeof(v) === "string") ? v : (v.currentStyle ? v.currentStyle.filter : v.style.filter) || "") ) ? ( parseFloat( RegExp.$1 ) / 100 ) : 1);
14758 _log = function(s) {//for logging messages, but in a way that won't throw errors in old versions of IE.
14759 if (window.console) {
14763 _prefixCSS = "", //the non-camelCase vendor prefix like "-o-", "-moz-", "-ms-", or "-webkit-"
14764 _prefix = "", //camelCase vendor prefix like "O", "ms", "Webkit", or "Moz".
14766 // @private feed in a camelCase property name like "transform" and it will check to see if it is valid as-is or if it needs a vendor prefix. It returns the corrected camelCase property name (i.e. "WebkitTransform" or "MozTransform" or "transform" or null if no such property is found, like if the browser is IE8 or before, "transform" won't be found at all)
14767 _checkPropPrefix = function(p, e) {
14771 if (s[p] !== undefined) {
14774 p = p.charAt(0).toUpperCase() + p.substr(1);
14775 a = ["O","Moz","ms","Ms","Webkit"];
14777 while (--i > -1 && s[a[i]+p] === undefined) { }
14779 _prefix = (i === 3) ? "ms" : a[i];
14780 _prefixCSS = "-" + _prefix.toLowerCase() + "-";
14781 return _prefix + p;
14786 _getComputedStyle = _doc.defaultView ? _doc.defaultView.getComputedStyle : function() {},
14789 * @private Returns the css style for a particular property of an element. For example, to get whatever the current "left" css value for an element with an ID of "myElement", you could do:
14790 * var currentLeft = CSSPlugin.getStyle( document.getElementById("myElement"), "left");
14792 * @param {!Object} t Target element whose style property you want to query
14793 * @param {!string} p Property name (like "left" or "top" or "marginTop", etc.)
14794 * @param {Object=} cs Computed style object. This just provides a way to speed processing if you're going to get several properties on the same element in quick succession - you can reuse the result of the getComputedStyle() call.
14795 * @param {boolean=} calc If true, the value will not be read directly from the element's "style" property (if it exists there), but instead the getComputedStyle() result will be used. This can be useful when you want to ensure that the browser itself is interpreting the value.
14796 * @param {string=} dflt Default value that should be returned in the place of null, "none", "auto" or "auto auto".
14797 * @return {?string} The current property value
14799 _getStyle = CSSPlugin.getStyle = function(t, p, cs, calc, dflt) {
14801 if (!_supportsOpacity) if (p === "opacity") { //several versions of IE don't use the standard "opacity" property - they use things like filter:alpha(opacity=50), so we parse that here.
14802 return _getIEOpacity(t);
14804 if (!calc && t.style[p]) {
14806 } else if ((cs = cs || _getComputedStyle(t))) {
14807 rv = cs[p] || cs.getPropertyValue(p) || cs.getPropertyValue(p.replace(_capsExp, "-$1").toLowerCase());
14808 } else if (t.currentStyle) {
14809 rv = t.currentStyle[p];
14811 return (dflt != null && (!rv || rv === "none" || rv === "auto" || rv === "auto auto")) ? dflt : rv;
14815 * @private Pass the target element, the property name, the numeric value, and the suffix (like "%", "em", "px", etc.) and it will spit back the equivalent pixel number.
14816 * @param {!Object} t Target element
14817 * @param {!string} p Property name (like "left", "top", "marginLeft", etc.)
14818 * @param {!number} v Value
14819 * @param {string=} sfx Suffix (like "px" or "%" or "em")
14820 * @param {boolean=} recurse If true, the call is a recursive one. In some browsers (like IE7/8), occasionally the value isn't accurately reported initially, but if we run the function again it will take effect.
14821 * @return {number} value in pixels
14823 _convertToPixels = _internals.convertToPixels = function(t, p, v, sfx, recurse) {
14824 if (sfx === "px" || !sfx) { return v; }
14825 if (sfx === "auto" || !v) { return 0; }
14826 var horiz = _horizExp.test(p),
14828 style = _tempDiv.style,
14834 if (sfx === "%" && p.indexOf("border") !== -1) {
14835 pix = (v / 100) * (horiz ? t.clientWidth : t.clientHeight);
14837 style.cssText = "border:0 solid red;position:" + _getStyle(t, "position") + ";line-height:0;";
14838 if (sfx === "%" || !node.appendChild) {
14839 node = t.parentNode || _doc.body;
14840 cache = node._gsCache;
14841 time = TweenLite.ticker.frame;
14842 if (cache && horiz && cache.time === time) { //performance optimization: we record the width of elements along with the ticker frame so that we can quickly get it again on the same tick (seems relatively safe to assume it wouldn't change on the same tick)
14843 return cache.width * v / 100;
14845 style[(horiz ? "width" : "height")] = v + sfx;
14847 style[(horiz ? "borderLeftWidth" : "borderTopWidth")] = v + sfx;
14849 node.appendChild(_tempDiv);
14850 pix = parseFloat(_tempDiv[(horiz ? "offsetWidth" : "offsetHeight")]);
14851 node.removeChild(_tempDiv);
14852 if (horiz && sfx === "%" && CSSPlugin.cacheWidths !== false) {
14853 cache = node._gsCache = node._gsCache || {};
14855 cache.width = pix / v * 100;
14857 if (pix === 0 && !recurse) {
14858 pix = _convertToPixels(t, p, v, sfx, true);
14861 return neg ? -pix : pix;
14863 _calculateOffset = _internals.calculateOffset = function(t, p, cs) { //for figuring out "top" or "left" in px when it's "auto". We need to factor in margin with the offsetLeft/offsetTop
14864 if (_getStyle(t, "position", cs) !== "absolute") { return 0; }
14865 var dim = ((p === "left") ? "Left" : "Top"),
14866 v = _getStyle(t, "margin" + dim, cs);
14867 return t["offset" + dim] - (_convertToPixels(t, p, parseFloat(v), v.replace(_suffixExp, "")) || 0);
14870 // @private returns at object containing ALL of the style properties in camelCase and their associated values.
14871 _getAllStyles = function(t, cs) {
14874 if ((cs = cs || _getComputedStyle(t, null))) {
14875 if ((i = cs.length)) {
14877 s[cs[i].replace(_camelExp, _camelFunc)] = cs.getPropertyValue(cs[i]);
14879 } else { //Opera behaves differently - cs.length is always 0, so we must do a for...in loop.
14884 } else if ((cs = t.currentStyle || t.style)) {
14886 if (typeof(i) === "string" && s[i] === undefined) {
14887 s[i.replace(_camelExp, _camelFunc)] = cs[i];
14891 if (!_supportsOpacity) {
14892 s.opacity = _getIEOpacity(t);
14894 tr = _getTransform(t, cs, false);
14895 s.rotation = tr.rotation;
14896 s.skewX = tr.skewX;
14897 s.scaleX = tr.scaleX;
14898 s.scaleY = tr.scaleY;
14903 s.rotationX = tr.rotationX;
14904 s.rotationY = tr.rotationY;
14905 s.scaleZ = tr.scaleZ;
14913 // @private analyzes two style objects (as returned by _getAllStyles()) and only looks for differences between them that contain tweenable values (like a number or color). It returns an object with a "difs" property which refers to an object containing only those isolated properties and values for tweening, and a "firstMPT" property which refers to the first MiniPropTween instance in a linked list that recorded all the starting values of the different properties so that we can revert to them at the end or beginning of the tween - we don't want the cascading to get messed up. The forceLookup parameter is an optional generic object with properties that should be forced into the results - this is necessary for className tweens that are overwriting others because imagine a scenario where a rollover/rollout adds/removes a class and the user swipes the mouse over the target SUPER fast, thus nothing actually changed yet and the subsequent comparison of the properties would indicate they match (especially when px rounding is taken into consideration), thus no tweening is necessary even though it SHOULD tween and remove those properties after the tween (otherwise the inline styles will contaminate things). See the className SpecialProp code for details.
14914 _cssDif = function(t, s1, s2, vars, forceLookup) {
14919 if (p !== "cssText") if (p !== "length") if (isNaN(p)) if (s1[p] !== (val = s2[p]) || (forceLookup && forceLookup[p])) if (p.indexOf("Origin") === -1) if (typeof(val) === "number" || typeof(val) === "string") {
14920 difs[p] = (val === "auto" && (p === "left" || p === "top")) ? _calculateOffset(t, p) : ((val === "" || val === "auto" || val === "none") && typeof(s1[p]) === "string" && s1[p].replace(_NaNExp, "") !== "") ? 0 : val; //if the ending value is defaulting ("" or "auto"), we check the starting value and if it can be parsed into a number (a string which could have a suffix too, like 700px), then we swap in 0 for "" or "auto" so that things actually tween.
14921 if (style[p] !== undefined) { //for className tweens, we must remember which properties already existed inline - the ones that didn't should be removed when the tween isn't in progress because they were only introduced to facilitate the transition between classes.
14922 mpt = new MiniPropTween(style, p, style[p], mpt);
14927 for (p in vars) { //copy properties (except className)
14928 if (p !== "className") {
14933 return {difs:difs, firstMPT:mpt};
14935 _dimensions = {width:["Left","Right"], height:["Top","Bottom"]},
14936 _margins = ["marginLeft","marginRight","marginTop","marginBottom"],
14939 * @private Gets the width or height of an element
14940 * @param {!Object} t Target element
14941 * @param {!string} p Property name ("width" or "height")
14942 * @param {Object=} cs Computed style object (if one exists). Just a speed optimization.
14943 * @return {number} Dimension (in pixels)
14945 _getDimension = function(t, p, cs) {
14946 var v = parseFloat((p === "width") ? t.offsetWidth : t.offsetHeight),
14947 a = _dimensions[p],
14949 cs = cs || _getComputedStyle(t, null);
14951 v -= parseFloat( _getStyle(t, "padding" + a[i], cs, true) ) || 0;
14952 v -= parseFloat( _getStyle(t, "border" + a[i] + "Width", cs, true) ) || 0;
14957 // @private Parses position-related complex strings like "top left" or "50px 10px" or "70% 20%", etc. which are used for things like transformOrigin or backgroundPosition. Optionally decorates a supplied object (recObj) with the following properties: "ox" (offsetX), "oy" (offsetY), "oxp" (if true, "ox" is a percentage not a pixel value), and "oxy" (if true, "oy" is a percentage not a pixel value)
14958 _parsePosition = function(v, recObj) {
14959 if (v == null || v === "" || v === "auto" || v === "auto auto") { //note: Firefox uses "auto auto" as default whereas Chrome uses "auto".
14962 var a = v.split(" "),
14963 x = (v.indexOf("left") !== -1) ? "0%" : (v.indexOf("right") !== -1) ? "100%" : a[0],
14964 y = (v.indexOf("top") !== -1) ? "0%" : (v.indexOf("bottom") !== -1) ? "100%" : a[1];
14967 } else if (y === "center") {
14970 if (x === "center" || (isNaN(parseFloat(x)) && (x + "").indexOf("=") === -1)) { //remember, the user could flip-flop the values and say "bottom center" or "center bottom", etc. "center" is ambiguous because it could be used to describe horizontal or vertical, hence the isNaN(). If there's an "=" sign in the value, it's relative.
14974 recObj.oxp = (x.indexOf("%") !== -1);
14975 recObj.oyp = (y.indexOf("%") !== -1);
14976 recObj.oxr = (x.charAt(1) === "=");
14977 recObj.oyr = (y.charAt(1) === "=");
14978 recObj.ox = parseFloat(x.replace(_NaNExp, ""));
14979 recObj.oy = parseFloat(y.replace(_NaNExp, ""));
14981 return x + " " + y + ((a.length > 2) ? " " + a[2] : "");
14985 * @private Takes an ending value (typically a string, but can be a number) and a starting value and returns the change between the two, looking for relative value indicators like += and -= and it also ignores suffixes (but make sure the ending value starts with a number or +=/-= and that the starting value is a NUMBER!)
14986 * @param {(number|string)} e End value which is typically a string, but could be a number
14987 * @param {(number|string)} b Beginning value which is typically a string but could be a number
14988 * @return {number} Amount of change between the beginning and ending values (relative values that have a "+=" or "-=" are recognized)
14990 _parseChange = function(e, b) {
14991 return (typeof(e) === "string" && e.charAt(1) === "=") ? parseInt(e.charAt(0) + "1", 10) * parseFloat(e.substr(2)) : parseFloat(e) - parseFloat(b);
14995 * @private Takes a value and a default number, checks if the value is relative, null, or numeric and spits back a normalized number accordingly. Primarily used in the _parseTransform() function.
14996 * @param {Object} v Value to be parsed
14997 * @param {!number} d Default value (which is also used for relative calculations if "+=" or "-=" is found in the first parameter)
14998 * @return {number} Parsed value
15000 _parseVal = function(v, d) {
15001 return (v == null) ? d : (typeof(v) === "string" && v.charAt(1) === "=") ? parseInt(v.charAt(0) + "1", 10) * Number(v.substr(2)) + d : parseFloat(v);
15005 * @private Translates strings like "40deg" or "40" or 40rad" or "+=40deg" or "270_short" or "-90_cw" or "+=45_ccw" to a numeric radian angle. Of course a starting/default value must be fed in too so that relative values can be calculated properly.
15006 * @param {Object} v Value to be parsed
15007 * @param {!number} d Default value (which is also used for relative calculations if "+=" or "-=" is found in the first parameter)
15008 * @param {string=} p property name for directionalEnd (optional - only used when the parsed value is directional ("_short", "_cw", or "_ccw" suffix). We need a way to store the uncompensated value so that at the end of the tween, we set it to exactly what was requested with no directional compensation). Property name would be "rotation", "rotationX", or "rotationY"
15009 * @param {Object=} directionalEnd An object that will store the raw end values for directional angles ("_short", "_cw", or "_ccw" suffix). We need a way to store the uncompensated value so that at the end of the tween, we set it to exactly what was requested with no directional compensation.
15010 * @return {number} parsed angle in radians
15012 _parseAngle = function(v, d, p, directionalEnd) {
15013 var min = 0.000001,
15014 cap, split, dif, result;
15017 } else if (typeof(v) === "number") {
15021 split = v.split("_");
15022 dif = Number(split[0].replace(_NaNExp, "")) * ((v.indexOf("rad") === -1) ? 1 : _RAD2DEG) - ((v.charAt(1) === "=") ? 0 : d);
15023 if (split.length) {
15024 if (directionalEnd) {
15025 directionalEnd[p] = d + dif;
15027 if (v.indexOf("short") !== -1) {
15029 if (dif !== dif % (cap / 2)) {
15030 dif = (dif < 0) ? dif + cap : dif - cap;
15033 if (v.indexOf("_cw") !== -1 && dif < 0) {
15034 dif = ((dif + cap * 9999999999) % cap) - ((dif / cap) | 0) * cap;
15035 } else if (v.indexOf("ccw") !== -1 && dif > 0) {
15036 dif = ((dif - cap * 9999999999) % cap) - ((dif / cap) | 0) * cap;
15041 if (result < min && result > -min) {
15047 _colorLookup = {aqua:[0,255,255],
15049 silver:[192,192,192],
15055 white:[255,255,255],
15056 fuchsia:[255,0,255],
15058 yellow:[255,255,0],
15059 orange:[255,165,0],
15060 gray:[128,128,128],
15061 purple:[128,0,128],
15064 pink:[255,192,203],
15066 transparent:[255,255,255,0]},
15068 _hue = function(h, m1, m2) {
15069 h = (h < 0) ? h + 1 : (h > 1) ? h - 1 : h;
15070 return ((((h * 6 < 1) ? m1 + (m2 - m1) * h * 6 : (h < 0.5) ? m2 : (h * 3 < 2) ? m1 + (m2 - m1) * (2 / 3 - h) * 6 : m1) * 255) + 0.5) | 0;
15074 * @private Parses a color (like #9F0, #FF9900, or rgb(255,51,153)) into an array with 3 elements for red, green, and blue. Also handles rgba() values (splits into array of 4 elements of course)
15075 * @param {(string|number)} v The value the should be parsed which could be a string like #9F0 or rgb(255,102,51) or rgba(255,0,0,0.5) or it could be a number like 0xFF00CC or even a named color like red, blue, purple, etc.
15076 * @return {Array.<number>} An array containing red, green, and blue (and optionally alpha) in that order.
15078 _parseColor = function(v) {
15079 var c1, c2, c3, h, s, l;
15080 if (!v || v === "") {
15081 return _colorLookup.black;
15083 if (typeof(v) === "number") {
15084 return [v >> 16, (v >> 8) & 255, v & 255];
15086 if (v.charAt(v.length - 1) === ",") { //sometimes a trailing commma is included and we should chop it off (typically from a comma-delimited list of values like a textShadow:"2px 2px 2px blue, 5px 5px 5px rgb(255,0,0)" - in this example "blue," has a trailing comma. We could strip it out inside parseComplex() but we'd need to do it to the beginning and ending values plus it wouldn't provide protection from other potential scenarios like if the user passes in a similar value.
15087 v = v.substr(0, v.length - 1);
15089 if (_colorLookup[v]) {
15090 return _colorLookup[v];
15092 if (v.charAt(0) === "#") {
15093 if (v.length === 4) { //for shorthand like #9F0
15097 v = "#" + c1 + c1 + c2 + c2 + c3 + c3;
15099 v = parseInt(v.substr(1), 16);
15100 return [v >> 16, (v >> 8) & 255, v & 255];
15102 if (v.substr(0, 3) === "hsl") {
15103 v = v.match(_numExp);
15104 h = (Number(v[0]) % 360) / 360;
15105 s = Number(v[1]) / 100;
15106 l = Number(v[2]) / 100;
15107 c2 = (l <= 0.5) ? l * (s + 1) : l + s - l * s;
15109 if (v.length > 3) {
15110 v[3] = Number(v[3]);
15112 v[0] = _hue(h + 1 / 3, c1, c2);
15113 v[1] = _hue(h, c1, c2);
15114 v[2] = _hue(h - 1 / 3, c1, c2);
15117 v = v.match(_numExp) || _colorLookup.transparent;
15118 v[0] = Number(v[0]);
15119 v[1] = Number(v[1]);
15120 v[2] = Number(v[2]);
15121 if (v.length > 3) {
15122 v[3] = Number(v[3]);
15126 _colorExp = "(?:\\b(?:(?:rgb|rgba|hsl|hsla)\\(.+?\\))|\\B#.+?\\b"; //we'll dynamically build this Regular Expression to conserve file size. After building it, it will be able to find rgb(), rgba(), # (hexadecimal), and named color values like red, blue, purple, etc.
15128 for (p in _colorLookup) {
15129 _colorExp += "|" + p + "\\b";
15131 _colorExp = new RegExp(_colorExp+")", "gi");
15134 * @private Returns a formatter function that handles taking a string (or number in some cases) and returning a consistently formatted one in terms of delimiters, quantity of values, etc. For example, we may get boxShadow values defined as "0px red" or "0px 0px 10px rgb(255,0,0)" or "0px 0px 20px 20px #F00" and we need to ensure that what we get back is described with 4 numbers and a color. This allows us to feed it into the _parseComplex() method and split the values up appropriately. The neat thing about this _getFormatter() function is that the dflt defines a pattern as well as a default, so for example, _getFormatter("0px 0px 0px 0px #777", true) not only sets the default as 0px for all distances and #777 for the color, but also sets the pattern such that 4 numbers and a color will always get returned.
15135 * @param {!string} dflt The default value and pattern to follow. So "0px 0px 0px 0px #777" will ensure that 4 numbers and a color will always get returned.
15136 * @param {boolean=} clr If true, the values should be searched for color-related data. For example, boxShadow values typically contain a color whereas borderRadius don't.
15137 * @param {boolean=} collapsible If true, the value is a top/left/right/bottom style one that acts like margin or padding, where if only one value is received, it's used for all 4; if 2 are received, the first is duplicated for 3rd (bottom) and the 2nd is duplicated for the 4th spot (left), etc.
15138 * @return {Function} formatter function
15140 var _getFormatter = function(dflt, clr, collapsible, multi) {
15141 if (dflt == null) {
15142 return function(v) {return v;};
15144 var dColor = clr ? (dflt.match(_colorExp) || [""])[0] : "",
15145 dVals = dflt.split(dColor).join("").match(_valuesExp) || [],
15146 pfx = dflt.substr(0, dflt.indexOf(dVals[0])),
15147 sfx = (dflt.charAt(dflt.length - 1) === ")") ? ")" : "",
15148 delim = (dflt.indexOf(" ") !== -1) ? " " : ",",
15149 numVals = dVals.length,
15150 dSfx = (numVals > 0) ? dVals[0].replace(_numExp, "") : "",
15153 return function(v) {return v;};
15156 formatter = function(v) {
15157 var color, vals, i, a;
15158 if (typeof(v) === "number") {
15160 } else if (multi && _commasOutsideParenExp.test(v)) {
15161 a = v.replace(_commasOutsideParenExp, "|").split("|");
15162 for (i = 0; i < a.length; i++) {
15163 a[i] = formatter(a[i]);
15165 return a.join(",");
15167 color = (v.match(_colorExp) || [dColor])[0];
15168 vals = v.split(color).join("").match(_valuesExp) || [];
15170 if (numVals > i--) {
15171 while (++i < numVals) {
15172 vals[i] = collapsible ? vals[(((i - 1) / 2) | 0)] : dVals[i];
15175 return pfx + vals.join(delim) + delim + color + sfx + (v.indexOf("inset") !== -1 ? " inset" : "");
15180 formatter = function(v) {
15182 if (typeof(v) === "number") {
15184 } else if (multi && _commasOutsideParenExp.test(v)) {
15185 a = v.replace(_commasOutsideParenExp, "|").split("|");
15186 for (i = 0; i < a.length; i++) {
15187 a[i] = formatter(a[i]);
15189 return a.join(",");
15191 vals = v.match(_valuesExp) || [];
15193 if (numVals > i--) {
15194 while (++i < numVals) {
15195 vals[i] = collapsible ? vals[(((i - 1) / 2) | 0)] : dVals[i];
15198 return pfx + vals.join(delim) + sfx;
15204 * @private returns a formatter function that's used for edge-related values like marginTop, marginLeft, paddingBottom, paddingRight, etc. Just pass a comma-delimited list of property names related to the edges.
15205 * @param {!string} props a comma-delimited list of property names in order from top to left, like "marginTop,marginRight,marginBottom,marginLeft"
15206 * @return {Function} a formatter function
15208 _getEdgeParser = function(props) {
15209 props = props.split(",");
15210 return function(t, e, p, cssp, pt, plugin, vars) {
15211 var a = (e + "").split(" "),
15214 for (i = 0; i < 4; i++) {
15215 vars[props[i]] = a[i] = a[i] || a[(((i - 1) / 2) >> 0)];
15217 return cssp.parse(t, vars, pt, plugin);
15221 // @private used when other plugins must tween values first, like BezierPlugin or ThrowPropsPlugin, etc. That plugin's setRatio() gets called first so that the values are updated, and then we loop through the MiniPropTweens which handle copying the values into their appropriate slots so that they can then be applied correctly in the main CSSPlugin setRatio() method. Remember, we typically create a proxy object that has a bunch of uniquely-named properties that we feed to the sub-plugin and it does its magic normally, and then we must interpret those values and apply them to the css because often numbers must get combined/concatenated, suffixes added, etc. to work with css, like boxShadow could have 4 values plus a color.
15222 _setPluginRatio = _internals._setPluginRatio = function(v) {
15223 this.plugin.setRatio(v);
15230 val = proxy[mpt.v];
15232 val = Math.round(val);
15233 } else if (val < min && val > -min) {
15236 mpt.t[mpt.p] = val;
15239 if (d.autoRotate) {
15240 d.autoRotate.rotation = proxy.rotation;
15242 //at the end, we must set the CSSPropTween's "e" (end) value dynamically here because that's what is used in the final setRatio() method.
15248 pt.e = pt.s + pt.xs0;
15249 } else if (pt.type === 1) {
15250 str = pt.xs0 + pt.s + pt.xs1;
15251 for (i = 1; i < pt.l; i++) {
15252 str += pt["xn"+i] + pt["xs"+(i+1)];
15262 * @private @constructor Used by a few SpecialProps to hold important values for proxies. For example, _parseToProxy() creates a MiniPropTween instance for each property that must get tweened on the proxy, and we record the original property name as well as the unique one we create for the proxy, plus whether or not the value needs to be rounded plus the original value.
15263 * @param {!Object} t target object whose property we're tweening (often a CSSPropTween)
15264 * @param {!string} p property name
15265 * @param {(number|string|object)} v value
15266 * @param {MiniPropTween=} next next MiniPropTween in the linked list
15267 * @param {boolean=} r if true, the tweened value should be rounded to the nearest integer
15269 MiniPropTween = function(t, p, v, next, r) {
15281 * @private Most other plugins (like BezierPlugin and ThrowPropsPlugin and others) can only tween numeric values, but CSSPlugin must accommodate special values that have a bunch of extra data (like a suffix or strings between numeric values, etc.). For example, boxShadow has values like "10px 10px 20px 30px rgb(255,0,0)" which would utterly confuse other plugins. This method allows us to split that data apart and grab only the numeric data and attach it to uniquely-named properties of a generic proxy object ({}) so that we can feed that to virtually any plugin to have the numbers tweened. However, we must also keep track of which properties from the proxy go with which CSSPropTween values and instances. So we create a linked list of MiniPropTweens. Each one records a target (the original CSSPropTween), property (like "s" or "xn1" or "xn2") that we're tweening and the unique property name that was used for the proxy (like "boxShadow_xn1" and "boxShadow_xn2") and whether or not they need to be rounded. That way, in the _setPluginRatio() method we can simply copy the values over from the proxy to the CSSPropTween instance(s). Then, when the main CSSPlugin setRatio() method runs and applies the CSSPropTween values accordingly, they're updated nicely. So the external plugin tweens the numbers, _setPluginRatio() copies them over, and setRatio() acts normally, applying css-specific values to the element.
15282 * This method returns an object that has the following properties:
15283 * - proxy: a generic object containing the starting values for all the properties that will be tweened by the external plugin. This is what we feed to the external _onInitTween() as the target
15284 * - end: a generic object containing the ending values for all the properties that will be tweened by the external plugin. This is what we feed to the external plugin's _onInitTween() as the destination values
15285 * - firstMPT: the first MiniPropTween in the linked list
15286 * - pt: the first CSSPropTween in the linked list that was created when parsing. If shallow is true, this linked list will NOT attach to the one passed into the _parseToProxy() as the "pt" (4th) parameter.
15287 * @param {!Object} t target object to be tweened
15288 * @param {!(Object|string)} vars the object containing the information about the tweening values (typically the end/destination values) that should be parsed
15289 * @param {!CSSPlugin} cssp The CSSPlugin instance
15290 * @param {CSSPropTween=} pt the next CSSPropTween in the linked list
15291 * @param {TweenPlugin=} plugin the external TweenPlugin instance that will be handling tweening the numeric values
15292 * @param {boolean=} shallow if true, the resulting linked list from the parse will NOT be attached to the CSSPropTween that was passed in as the "pt" (4th) parameter.
15293 * @return An object containing the following properties: proxy, end, firstMPT, and pt (see above for descriptions)
15295 _parseToProxy = _internals._parseToProxy = function(t, vars, cssp, pt, plugin, shallow) {
15299 transform = cssp._transform,
15300 oldForce = _forcePT,
15301 i, p, xp, mpt, firstPT;
15302 cssp._transform = null;
15304 pt = firstPT = cssp.parse(t, vars, pt, plugin);
15305 _forcePT = oldForce;
15306 //break off from the linked list so the new ones are isolated.
15308 cssp._transform = transform;
15312 bpt._prev._next = null;
15316 while (pt && pt !== bpt) {
15317 if (pt.type <= 1) {
15319 end[p] = pt.s + pt.c;
15322 mpt = new MiniPropTween(pt, "s", p, mpt, pt.r);
15325 if (pt.type === 1) {
15329 p = pt.p + "_" + xp;
15330 end[p] = pt.data[xp];
15333 mpt = new MiniPropTween(pt, xp, p, mpt, pt.rxp[xp]);
15340 return {proxy:start, end:end, firstMPT:mpt, pt:firstPT};
15346 * @constructor Each property that is tweened has at least one CSSPropTween associated with it. These instances store important information like the target, property, starting value, amount of change, etc. They can also optionally have a number of "extra" strings and numeric values named xs1, xn1, xs2, xn2, xs3, xn3, etc. where "s" indicates string and "n" indicates number. These can be pieced together in a complex-value tween (type:1) that has alternating types of data like a string, number, string, number, etc. For example, boxShadow could be "5px 5px 8px rgb(102, 102, 51)". In that value, there are 6 numbers that may need to tween and then pieced back together into a string again with spaces, suffixes, etc. xs0 is special in that it stores the suffix for standard (type:0) tweens, -OR- the first string (prefix) in a complex-value (type:1) CSSPropTween -OR- it can be the non-tweening value in a type:-1 CSSPropTween. We do this to conserve memory.
15347 * CSSPropTweens have the following optional properties as well (not defined through the constructor):
15348 * - l: Length in terms of the number of extra properties that the CSSPropTween has (default: 0). For example, for a boxShadow we may need to tween 5 numbers in which case l would be 5; Keep in mind that the start/end values for the first number that's tweened are always stored in the s and c properties to conserve memory. All additional values thereafter are stored in xn1, xn2, etc.
15349 * - xfirst: The first instance of any sub-CSSPropTweens that are tweening properties of this instance. For example, we may split up a boxShadow tween so that there's a main CSSPropTween of type:1 that has various xs* and xn* values associated with the h-shadow, v-shadow, blur, color, etc. Then we spawn a CSSPropTween for each of those that has a higher priority and runs BEFORE the main CSSPropTween so that the values are all set by the time it needs to re-assemble them. The xfirst gives us an easy way to identify the first one in that chain which typically ends at the main one (because they're all prepende to the linked list)
15350 * - plugin: The TweenPlugin instance that will handle the tweening of any complex values. For example, sometimes we don't want to use normal subtweens (like xfirst refers to) to tween the values - we might want ThrowPropsPlugin or BezierPlugin some other plugin to do the actual tweening, so we create a plugin instance and store a reference here. We need this reference so that if we get a request to round values or disable a tween, we can pass along that request.
15351 * - data: Arbitrary data that needs to be stored with the CSSPropTween. Typically if we're going to have a plugin handle the tweening of a complex-value tween, we create a generic object that stores the END values that we're tweening to and the CSSPropTween's xs1, xs2, etc. have the starting values. We store that object as data. That way, we can simply pass that object to the plugin and use the CSSPropTween as the target.
15352 * - setRatio: Only used for type:2 tweens that require custom functionality. In this case, we call the CSSPropTween's setRatio() method and pass the ratio each time the tween updates. This isn't quite as efficient as doing things directly in the CSSPlugin's setRatio() method, but it's very convenient and flexible.
15353 * @param {!Object} t Target object whose property will be tweened. Often a DOM element, but not always. It could be anything.
15354 * @param {string} p Property to tween (name). For example, to tween element.width, p would be "width".
15355 * @param {number} s Starting numeric value
15356 * @param {number} c Change in numeric value over the course of the entire tween. For example, if element.width starts at 5 and should end at 100, c would be 95.
15357 * @param {CSSPropTween=} next The next CSSPropTween in the linked list. If one is defined, we will define its _prev as the new instance, and the new instance's _next will be pointed at it.
15358 * @param {number=} type The type of CSSPropTween where -1 = a non-tweening value, 0 = a standard simple tween, 1 = a complex value (like one that has multiple numbers in a comma- or space-delimited string like border:"1px solid red"), and 2 = one that uses a custom setRatio function that does all of the work of applying the values on each update.
15359 * @param {string=} n Name of the property that should be used for overwriting purposes which is typically the same as p but not always. For example, we may need to create a subtween for the 2nd part of a "clip:rect(...)" tween in which case "p" might be xs1 but "n" is still "clip"
15360 * @param {boolean=} r If true, the value(s) should be rounded
15361 * @param {number=} pr Priority in the linked list order. Higher priority CSSPropTweens will be updated before lower priority ones. The default priority is 0.
15362 * @param {string=} b Beginning value. We store this to ensure that it is EXACTLY what it was when the tween began without any risk of interpretation issues.
15363 * @param {string=} e Ending value. We store this to ensure that it is EXACTLY what the user defined at the end of the tween without any risk of interpretation issues.
15365 CSSPropTween = _internals.CSSPropTween = function(t, p, s, c, next, type, n, r, pr, b, e) {
15366 this.t = t; //target
15367 this.p = p; //property
15368 this.s = s; //starting value
15369 this.c = c; //change value
15370 this.n = n || p; //name that this CSSPropTween should be associated to (usually the same as p, but not always - n is what overwriting looks at)
15371 if (!(t instanceof CSSPropTween)) {
15372 _overwriteProps.push(this.n);
15374 this.r = r; //round (boolean)
15375 this.type = type || 0; //0 = normal tween, -1 = non-tweening (in which case xs0 will be applied to the target's property, like tp.t[tp.p] = tp.xs0), 1 = complex-value SpecialProp, 2 = custom setRatio() that does all the work
15378 _hasPriority = true;
15380 this.b = (b === undefined) ? s : b;
15381 this.e = (e === undefined) ? s + c : e;
15389 * Takes a target, the beginning value and ending value (as strings) and parses them into a CSSPropTween (possibly with child CSSPropTweens) that accommodates multiple numbers, colors, comma-delimited values, etc. For example:
15390 * sp.parseComplex(element, "boxShadow", "5px 10px 20px rgb(255,102,51)", "0px 0px 0px red", true, "0px 0px 0px rgb(0,0,0,0)", pt);
15391 * It will walk through the beginning and ending values (which should be in the same format with the same number and type of values) and figure out which parts are numbers, what strings separate the numeric/tweenable values, and then create the CSSPropTweens accordingly. If a plugin is defined, no child CSSPropTweens will be created. Instead, the ending values will be stored in the "data" property of the returned CSSPropTween like: {s:-5, xn1:-10, xn2:-20, xn3:255, xn4:0, xn5:0} so that it can be fed to any other plugin and it'll be plain numeric tweens but the recomposition of the complex value will be handled inside CSSPlugin's setRatio().
15392 * If a setRatio is defined, the type of the CSSPropTween will be set to 2 and recomposition of the values will be the responsibility of that method.
15394 * @param {!Object} t Target whose property will be tweened
15395 * @param {!string} p Property that will be tweened (its name, like "left" or "backgroundColor" or "boxShadow")
15396 * @param {string} b Beginning value
15397 * @param {string} e Ending value
15398 * @param {boolean} clrs If true, the value could contain a color value like "rgb(255,0,0)" or "#F00" or "red". The default is false, so no colors will be recognized (a performance optimization)
15399 * @param {(string|number|Object)} dflt The default beginning value that should be used if no valid beginning value is defined or if the number of values inside the complex beginning and ending values don't match
15400 * @param {?CSSPropTween} pt CSSPropTween instance that is the current head of the linked list (we'll prepend to this).
15401 * @param {number=} pr Priority in the linked list order. Higher priority properties will be updated before lower priority ones. The default priority is 0.
15402 * @param {TweenPlugin=} plugin If a plugin should handle the tweening of extra properties, pass the plugin instance here. If one is defined, then NO subtweens will be created for any extra properties (the properties will be created - just not additional CSSPropTween instances to tween them) because the plugin is expected to do so. However, the end values WILL be populated in the "data" property, like {s:100, xn1:50, xn2:300}
15403 * @param {function(number)=} setRatio If values should be set in a custom function instead of being pieced together in a type:1 (complex-value) CSSPropTween, define that custom function here.
15404 * @return {CSSPropTween} The first CSSPropTween in the linked list which includes the new one(s) added by the parseComplex() call.
15406 _parseComplex = CSSPlugin.parseComplex = function(t, p, b, e, clrs, dflt, pt, pr, plugin, setRatio) {
15407 //DEBUG: _log("parseComplex: "+p+", b: "+b+", e: "+e);
15408 b = b || dflt || "";
15409 pt = new CSSPropTween(t, p, 0, 0, pt, (setRatio ? 2 : 1), null, false, pr, b, e);
15410 e += ""; //ensures it's a string
15411 var ba = b.split(", ").join(",").split(" "), //beginning array
15412 ea = e.split(", ").join(",").split(" "), //ending array
15414 autoRound = (_autoRound !== false),
15415 i, xi, ni, bv, ev, bnums, enums, bn, rgba, temp, cv, str;
15416 if (e.indexOf(",") !== -1 || b.indexOf(",") !== -1) {
15417 ba = ba.join(" ").replace(_commasOutsideParenExp, ", ").split(" ");
15418 ea = ea.join(" ").replace(_commasOutsideParenExp, ", ").split(" ");
15421 if (l !== ea.length) {
15422 //DEBUG: _log("mismatched formatting detected on " + p + " (" + b + " vs " + e + ")");
15423 ba = (dflt || "").split(" ");
15426 pt.plugin = plugin;
15427 pt.setRatio = setRatio;
15428 for (i = 0; i < l; i++) {
15431 bn = parseFloat(bv);
15433 //if the value begins with a number (most common). It's fine if it has a suffix like px
15434 if (bn || bn === 0) {
15435 pt.appendXtra("", bn, _parseChange(ev, bn), ev.replace(_relNumExp, ""), (autoRound && ev.indexOf("px") !== -1), true);
15437 //if the value is a color
15438 } else if (clrs && (bv.charAt(0) === "#" || _colorLookup[bv] || _rgbhslExp.test(bv))) {
15439 str = ev.charAt(ev.length - 1) === "," ? ")," : ")"; //if there's a comma at the end, retain it.
15440 bv = _parseColor(bv);
15441 ev = _parseColor(ev);
15442 rgba = (bv.length + ev.length > 6);
15443 if (rgba && !_supportsOpacity && ev[3] === 0) { //older versions of IE don't support rgba(), so if the destination alpha is 0, just use "transparent" for the end color
15444 pt["xs" + pt.l] += pt.l ? " transparent" : "transparent";
15445 pt.e = pt.e.split(ea[i]).join("transparent");
15447 if (!_supportsOpacity) { //old versions of IE don't support rgba().
15450 pt.appendXtra((rgba ? "rgba(" : "rgb("), bv[0], ev[0] - bv[0], ",", true, true)
15451 .appendXtra("", bv[1], ev[1] - bv[1], ",", true)
15452 .appendXtra("", bv[2], ev[2] - bv[2], (rgba ? "," : str), true);
15454 bv = (bv.length < 4) ? 1 : bv[3];
15455 pt.appendXtra("", bv, ((ev.length < 4) ? 1 : ev[3]) - bv, str, false);
15460 bnums = bv.match(_numExp); //gets each group of numbers in the beginning value string and drops them into an array
15462 //if no number is found, treat it as a non-tweening value and just append the string to the current xs.
15464 pt["xs" + pt.l] += pt.l ? " " + bv : bv;
15466 //loop through all the numbers that are found and construct the extra values on the pt.
15468 enums = ev.match(_relNumExp); //get each group of numbers in the end value string and drop them into an array. We allow relative values too, like +=50 or -=.5
15469 if (!enums || enums.length !== bnums.length) {
15470 //DEBUG: _log("mismatched formatting detected on " + p + " (" + b + " vs " + e + ")");
15474 for (xi = 0; xi < bnums.length; xi++) {
15476 temp = bv.indexOf(cv, ni);
15477 pt.appendXtra(bv.substr(ni, temp - ni), Number(cv), _parseChange(enums[xi], cv), "", (autoRound && bv.substr(temp + cv.length, 2) === "px"), (xi === 0));
15478 ni = temp + cv.length;
15480 pt["xs" + pt.l] += bv.substr(ni);
15484 //if there are relative values ("+=" or "-=" prefix), we need to adjust the ending value to eliminate the prefixes and combine the values properly.
15485 if (e.indexOf("=") !== -1) if (pt.data) {
15486 str = pt.xs0 + pt.data.s;
15487 for (i = 1; i < pt.l; i++) {
15488 str += pt["xs" + i] + pt.data["xn" + i];
15490 pt.e = str + pt["xs" + i];
15496 return pt.xfirst || pt;
15501 p = CSSPropTween.prototype;
15502 p.l = p.pr = 0; //length (number of extra properties like xn1, xn2, xn3, etc.
15508 p._next = p._prev = p.xfirst = p.data = p.plugin = p.setRatio = p.rxp = null;
15512 * Appends and extra tweening value to a CSSPropTween and automatically manages any prefix and suffix strings. The first extra value is stored in the s and c of the main CSSPropTween instance, but thereafter any extras are stored in the xn1, xn2, xn3, etc. The prefixes and suffixes are stored in the xs0, xs1, xs2, etc. properties. For example, if I walk through a clip value like "rect(10px, 5px, 0px, 20px)", the values would be stored like this:
15513 * xs0:"rect(", s:10, xs1:"px, ", xn1:5, xs2:"px, ", xn2:0, xs3:"px, ", xn3:20, xn4:"px)"
15514 * And they'd all get joined together when the CSSPlugin renders (in the setRatio() method).
15515 * @param {string=} pfx Prefix (if any)
15516 * @param {!number} s Starting value
15517 * @param {!number} c Change in numeric value over the course of the entire tween. For example, if the start is 5 and the end is 100, the change would be 95.
15518 * @param {string=} sfx Suffix (if any)
15519 * @param {boolean=} r Round (if true).
15520 * @param {boolean=} pad If true, this extra value should be separated by the previous one by a space. If there is no previous extra and pad is true, it will automatically drop the space.
15521 * @return {CSSPropTween} returns itself so that multiple methods can be chained together.
15523 p.appendXtra = function(pfx, s, c, sfx, r, pad) {
15526 pt["xs" + l] += (pad && l) ? " " + pfx : pfx || "";
15527 if (!c) if (l !== 0 && !pt.plugin) { //typically we'll combine non-changing values right into the xs to optimize performance, but we don't combine them when there's a plugin that will be tweening the values because it may depend on the values being split apart, like for a bezier, if a value doesn't change between the first and second iteration but then it does on the 3rd, we'll run into trouble because there's no xn slot for that value!
15528 pt["xs" + l] += s + (sfx || "");
15532 pt.type = pt.setRatio ? 2 : 1;
15533 pt["xs" + pt.l] = sfx || "";
15535 pt.data["xn" + l] = s + c;
15536 pt.rxp["xn" + l] = r; //round extra property (we need to tap into this in the _parseToProxy() method)
15539 pt.xfirst = new CSSPropTween(pt, "xn" + l, s, c, pt.xfirst || pt, 0, pt.n, r, pt.pr);
15540 pt.xfirst.xs0 = 0; //just to ensure that the property stays numeric which helps modern browsers speed up processing. Remember, in the setRatio() method, we do pt.t[pt.p] = val + pt.xs0 so if pt.xs0 is "" (the default), it'll cast the end value as a string. When a property is a number sometimes and a string sometimes, it prevents the compiler from locking in the data type, slowing things down slightly.
15544 pt.data = {s:s + c};
15553 * @constructor A SpecialProp is basically a css property that needs to be treated in a non-standard way, like if it may contain a complex value like boxShadow:"5px 10px 15px rgb(255, 102, 51)" or if it is associated with another plugin like ThrowPropsPlugin or BezierPlugin. Every SpecialProp is associated with a particular property name like "boxShadow" or "throwProps" or "bezier" and it will intercept those values in the vars object that's passed to the CSSPlugin and handle them accordingly.
15554 * @param {!string} p Property name (like "boxShadow" or "throwProps")
15555 * @param {Object=} options An object containing any of the following configuration options:
15556 * - defaultValue: the default value
15557 * - parser: A function that should be called when the associated property name is found in the vars. This function should return a CSSPropTween instance and it should ensure that it is properly inserted into the linked list. It will receive 4 paramters: 1) The target, 2) The value defined in the vars, 3) The CSSPlugin instance (whose _firstPT should be used for the linked list), and 4) A computed style object if one was calculated (this is a speed optimization that allows retrieval of starting values quicker)
15558 * - formatter: a function that formats any value received for this special property (for example, boxShadow could take "5px 5px red" and format it to "5px 5px 0px 0px red" so that both the beginning and ending values have a common order and quantity of values.)
15559 * - prefix: if true, we'll determine whether or not this property requires a vendor prefix (like Webkit or Moz or ms or O)
15560 * - color: set this to true if the value for this SpecialProp may contain color-related values like rgb(), rgba(), etc.
15561 * - priority: priority in the linked list order. Higher priority SpecialProps will be updated before lower priority ones. The default priority is 0.
15562 * - multi: if true, the formatter should accommodate a comma-delimited list of values, like boxShadow could have multiple boxShadows listed out.
15563 * - collapsible: if true, the formatter should treat the value like it's a top/right/bottom/left value that could be collapsed, like "5px" would apply to all, "5px, 10px" would use 5px for top/bottom and 10px for right/left, etc.
15564 * - keyword: a special keyword that can [optionally] be found inside the value (like "inset" for boxShadow). This allows us to validate beginning/ending values to make sure they match (if the keyword is found in one, it'll be added to the other for consistency by default).
15566 var SpecialProp = function(p, options) {
15567 options = options || {};
15568 this.p = options.prefix ? _checkPropPrefix(p) || p : p;
15569 _specialProps[p] = _specialProps[this.p] = this;
15570 this.format = options.formatter || _getFormatter(options.defaultValue, options.color, options.collapsible, options.multi);
15571 if (options.parser) {
15572 this.parse = options.parser;
15574 this.clrs = options.color;
15575 this.multi = options.multi;
15576 this.keyword = options.keyword;
15577 this.dflt = options.defaultValue;
15578 this.pr = options.priority || 0;
15581 //shortcut for creating a new SpecialProp that can accept multiple properties as a comma-delimited list (helps minification). dflt can be an array for multiple values (we don't do a comma-delimited list because the default value may contain commas, like rect(0px,0px,0px,0px)). We attach this method to the SpecialProp class/object instead of using a private _createSpecialProp() method so that we can tap into it externally if necessary, like from another plugin.
15582 _registerComplexSpecialProp = _internals._registerComplexSpecialProp = function(p, options, defaults) {
15583 if (typeof(options) !== "object") {
15584 options = {parser:defaults}; //to make backwards compatible with older versions of BezierPlugin and ThrowPropsPlugin
15586 var a = p.split(","),
15587 d = options.defaultValue,
15589 defaults = defaults || [d];
15590 for (i = 0; i < a.length; i++) {
15591 options.prefix = (i === 0 && options.prefix);
15592 options.defaultValue = defaults[i] || d;
15593 temp = new SpecialProp(a[i], options);
15597 //creates a placeholder special prop for a plugin so that the property gets caught the first time a tween of it is attempted, and at that time it makes the plugin register itself, thus taking over for all future tweens of that property. This allows us to not mandate that things load in a particular order and it also allows us to log() an error that informs the user when they attempt to tween an external plugin-related property without loading its .js file.
15598 _registerPluginProp = function(p) {
15599 if (!_specialProps[p]) {
15600 var pluginName = p.charAt(0).toUpperCase() + p.substr(1) + "Plugin";
15601 _registerComplexSpecialProp(p, {parser:function(t, e, p, cssp, pt, plugin, vars) {
15602 var pluginClass = (window.GreenSockGlobals || window).com.greensock.plugins[pluginName];
15603 if (!pluginClass) {
15604 _log("Error: " + pluginName + " js file not loaded.");
15607 pluginClass._cssRegister();
15608 return _specialProps[p].parse(t, e, p, cssp, pt, plugin, vars);
15614 p = SpecialProp.prototype;
15617 * Alias for _parseComplex() that automatically plugs in certain values for this SpecialProp, like its property name, whether or not colors should be sensed, the default value, and priority. It also looks for any keyword that the SpecialProp defines (like "inset" for boxShadow) and ensures that the beginning and ending values have the same number of values for SpecialProps where multi is true (like boxShadow and textShadow can have a comma-delimited list)
15618 * @param {!Object} t target element
15619 * @param {(string|number|object)} b beginning value
15620 * @param {(string|number|object)} e ending (destination) value
15621 * @param {CSSPropTween=} pt next CSSPropTween in the linked list
15622 * @param {TweenPlugin=} plugin If another plugin will be tweening the complex value, that TweenPlugin instance goes here.
15623 * @param {function=} setRatio If a custom setRatio() method should be used to handle this complex value, that goes here.
15624 * @return {CSSPropTween=} First CSSPropTween in the linked list
15626 p.parseComplex = function(t, b, e, pt, plugin, setRatio) {
15627 var kwd = this.keyword,
15628 i, ba, ea, l, bi, ei;
15629 //if this SpecialProp's value can contain a comma-delimited list of values (like boxShadow or textShadow), we must parse them in a special way, and look for a keyword (like "inset" for boxShadow) and ensure that the beginning and ending BOTH have it if the end defines it as such. We also must ensure that there are an equal number of values specified (we can't tween 1 boxShadow to 3 for example)
15630 if (this.multi) if (_commasOutsideParenExp.test(e) || _commasOutsideParenExp.test(b)) {
15631 ba = b.replace(_commasOutsideParenExp, "|").split("|");
15632 ea = e.replace(_commasOutsideParenExp, "|").split("|");
15638 l = (ea.length > ba.length) ? ea.length : ba.length;
15639 for (i = 0; i < l; i++) {
15640 b = ba[i] = ba[i] || this.dflt;
15641 e = ea[i] = ea[i] || this.dflt;
15643 bi = b.indexOf(kwd);
15644 ei = e.indexOf(kwd);
15646 e = (ei === -1) ? ea : ba;
15654 return _parseComplex(t, this.p, b, e, this.clrs, this.dflt, pt, this.pr, plugin, setRatio);
15658 * Accepts a target and end value and spits back a CSSPropTween that has been inserted into the CSSPlugin's linked list and conforms with all the conventions we use internally, like type:-1, 0, 1, or 2, setting up any extra property tweens, priority, etc. For example, if we have a boxShadow SpecialProp and call:
15659 * this._firstPT = sp.parse(element, "5px 10px 20px rgb(2550,102,51)", "boxShadow", this);
15660 * It should figure out the starting value of the element's boxShadow, compare it to the provided end value and create all the necessary CSSPropTweens of the appropriate types to tween the boxShadow. The CSSPropTween that gets spit back should already be inserted into the linked list (the 4th parameter is the current head, so prepend to that).
15661 * @param {!Object} t Target object whose property is being tweened
15662 * @param {Object} e End value as provided in the vars object (typically a string, but not always - like a throwProps would be an object).
15663 * @param {!string} p Property name
15664 * @param {!CSSPlugin} cssp The CSSPlugin instance that should be associated with this tween.
15665 * @param {?CSSPropTween} pt The CSSPropTween that is the current head of the linked list (we'll prepend to it)
15666 * @param {TweenPlugin=} plugin If a plugin will be used to tween the parsed value, this is the plugin instance.
15667 * @param {Object=} vars Original vars object that contains the data for parsing.
15668 * @return {CSSPropTween} The first CSSPropTween in the linked list which includes the new one(s) added by the parse() call.
15670 p.parse = function(t, e, p, cssp, pt, plugin, vars) {
15671 return this.parseComplex(t.style, this.format(_getStyle(t, this.p, _cs, false, this.dflt)), this.format(e), pt, plugin);
15675 * Registers a special property that should be intercepted from any "css" objects defined in tweens. This allows you to handle them however you want without CSSPlugin doing it for you. The 2nd parameter should be a function that accepts 3 parameters:
15676 * 1) Target object whose property should be tweened (typically a DOM element)
15677 * 2) The end/destination value (could be a string, number, object, or whatever you want)
15678 * 3) The tween instance (you probably don't need to worry about this, but it can be useful for looking up information like the duration)
15680 * Then, your function should return a function which will be called each time the tween gets rendered, passing a numeric "ratio" parameter to your function that indicates the change factor (usually between 0 and 1). For example:
15682 * CSSPlugin.registerSpecialProp("myCustomProp", function(target, value, tween) {
15683 * var start = target.style.width;
15684 * return function(ratio) {
15685 * target.style.width = (start + value * ratio) + "px";
15686 * console.log("set width to " + target.style.width);
15690 * Then, when I do this tween, it will trigger my special property:
15692 * TweenLite.to(element, 1, {css:{myCustomProp:100}});
15694 * In the example, of course, we're just changing the width, but you can do anything you want.
15696 * @param {!string} name Property name (or comma-delimited list of property names) that should be intercepted and handled by your function. For example, if I define "myCustomProp", then it would handle that portion of the following tween: TweenLite.to(element, 1, {css:{myCustomProp:100}})
15697 * @param {!function(Object, Object, Object, string):function(number)} onInitTween The function that will be called when a tween of this special property is performed. The function will receive 4 parameters: 1) Target object that should be tweened, 2) Value that was passed to the tween, 3) The tween instance itself (rarely used), and 4) The property name that's being tweened. Your function should return a function that should be called on every update of the tween. That function will receive a single parameter that is a "change factor" value (typically between 0 and 1) indicating the amount of change as a ratio. You can use this to determine how to set the values appropriately in your function.
15698 * @param {number=} priority Priority that helps the engine determine the order in which to set the properties (default: 0). Higher priority properties will be updated before lower priority ones.
15700 CSSPlugin.registerSpecialProp = function(name, onInitTween, priority) {
15701 _registerComplexSpecialProp(name, {parser:function(t, e, p, cssp, pt, plugin, vars) {
15702 var rv = new CSSPropTween(t, p, 0, 0, pt, 2, p, false, priority);
15703 rv.plugin = plugin;
15704 rv.setRatio = onInitTween(t, e, cssp._tween, p);
15706 }, priority:priority});
15716 //transform-related methods and properties
15717 var _transformProps = ("scaleX,scaleY,scaleZ,x,y,z,skewX,skewY,rotation,rotationX,rotationY,perspective").split(","),
15718 _transformProp = _checkPropPrefix("transform"), //the Javascript (camelCase) transform property, like msTransform, WebkitTransform, MozTransform, or OTransform.
15719 _transformPropCSS = _prefixCSS + "transform",
15720 _transformOriginProp = _checkPropPrefix("transformOrigin"),
15721 _supports3D = (_checkPropPrefix("perspective") !== null),
15722 Transform = _internals.Transform = function() {
15727 * Parses the transform values for an element, returning an object with x, y, z, scaleX, scaleY, scaleZ, rotation, rotationX, rotationY, skewX, and skewY properties. Note: by default (for performance reasons), all skewing is combined into skewX and rotation but skewY still has a place in the transform object so that we can record how much of the skew is attributed to skewX vs skewY. Remember, a skewY of 10 looks the same as a rotation of 10 and skewX of -10.
15728 * @param {!Object} t target element
15729 * @param {Object=} cs computed style object (optional)
15730 * @param {boolean=} rec if true, the transform values will be recorded to the target element's _gsTransform object, like target._gsTransform = {x:0, y:0, z:0, scaleX:1...}
15731 * @param {boolean=} parse if true, we'll ignore any _gsTransform values that already exist on the element, and force a reparsing of the css (calculated style)
15732 * @return {object} object containing all of the transform properties/values like {x:0, y:0, z:0, scaleX:1...}
15734 _getTransform = _internals.getTransform = function(t, cs, rec, parse) {
15735 if (t._gsTransform && rec && !parse) {
15736 return t._gsTransform; //if the element already has a _gsTransform, use that. Note: some browsers don't accurately return the calculated style for the transform (particularly for SVG), so it's almost always safest to just use the values we've already applied rather than re-parsing things.
15738 var tm = rec ? t._gsTransform || new Transform() : new Transform(),
15739 invX = (tm.scaleX < 0), //in order to interpret things properly, we need to know if the user applied a negative scaleX previously so that we can adjust the rotation and skewX accordingly. Otherwise, if we always interpret a flipped matrix as affecting scaleY and the user only wants to tween the scaleX on multiple sequential tweens, it would keep the negative scaleY without that being the user's intent.
15743 minPI = minAngle * _DEG2RAD,
15744 zOrigin = _supports3D ? parseFloat(_getStyle(t, _transformOriginProp, cs, false, "0 0 0").split(" ")[2]) || tm.zOrigin || 0 : 0,
15745 s, m, i, n, dec, scaleX, scaleY, rotation, skewX, difX, difY, difR, difS;
15746 if (_transformProp) {
15747 s = _getStyle(t, _transformPropCSS, cs, true);
15748 } else if (t.currentStyle) {
15749 //for older versions of IE, we need to interpret the filter portion that is in the format: progid:DXImageTransform.Microsoft.Matrix(M11=6.123233995736766e-17, M12=-1, M21=1, M22=6.123233995736766e-17, sizingMethod='auto expand') Notice that we need to swap b and c compared to a normal matrix.
15750 s = t.currentStyle.filter.match(_ieGetMatrixExp);
15751 s = (s && s.length === 4) ? [s[0].substr(4), Number(s[2].substr(4)), Number(s[1].substr(4)), s[3].substr(4), (tm.x || 0), (tm.y || 0)].join(",") : "";
15753 //split the matrix values out into an array (m for matrix)
15754 m = (s || "").match(/(?:\-|\b)[\d\-\.e]+\b/gi) || [];
15758 m[i] = (dec = n - (n |= 0)) ? ((dec * rnd + (dec < 0 ? -0.5 : 0.5)) | 0) / rnd + n : n; //convert strings to Numbers and round to 5 decimal places to avoid issues with tiny numbers. Roughly 20x faster than Number.toFixed(). We also must make sure to round before dividing so that values like 0.9999999999 become 1 to avoid glitches in browser rendering and interpretation of flipped/rotated 3D matrices. And don't just multiply the number by rnd, floor it, and then divide by rnd because the bitwise operations max out at a 32-bit signed integer, thus it could get clipped at a relatively low value (like 22,000.00000 for example).
15760 if (m.length === 16) {
15762 //we'll only look at these position-related 6 variables first because if x/y/z all match, it's relatively safe to assume we don't need to re-parse everything which risks losing important rotational information (like rotationX:180 plus rotationY:180 would look the same as rotation:180 - there's no way to know for sure which direction was taken based solely on the matrix3d() values)
15763 var a13 = m[8], a23 = m[9], a33 = m[10],
15764 a14 = m[12], a24 = m[13], a34 = m[14];
15766 //we manually compensate for non-zero z component of transformOrigin to work around bugs in Safari
15769 a14 = a13*a34-m[12];
15770 a24 = a23*a34-m[13];
15771 a34 = a33*a34+tm.zOrigin-m[14];
15774 //only parse from the matrix if we MUST because not only is it usually unnecessary due to the fact that we store the values in the _gsTransform object, but also because it's impossible to accurately interpret rotationX, rotationY, rotationZ, scaleX, and scaleY if all are applied, so it's much better to rely on what we store. However, we must parse the first time that an object is tweened. We also assume that if the position has changed, the user must have done some styling changes outside of CSSPlugin, thus we force a parse in that scenario.
15775 if (!rec || parse || tm.rotationX == null) {
15776 var a11 = m[0], a21 = m[1], a31 = m[2], a41 = m[3],
15777 a12 = m[4], a22 = m[5], a32 = m[6], a42 = m[7],
15779 angle = Math.atan2(a32, a33),
15780 xFlip = (angle < -minPI || angle > minPI),
15781 t1, t2, t3, cos, sin, yFlip, zFlip;
15782 tm.rotationX = angle * _RAD2DEG;
15785 cos = Math.cos(-angle);
15786 sin = Math.sin(-angle);
15787 t1 = a12*cos+a13*sin;
15788 t2 = a22*cos+a23*sin;
15789 t3 = a32*cos+a33*sin;
15790 a13 = a12*-sin+a13*cos;
15791 a23 = a22*-sin+a23*cos;
15792 a33 = a32*-sin+a33*cos;
15793 a43 = a42*-sin+a43*cos;
15799 angle = Math.atan2(a13, a11);
15800 tm.rotationY = angle * _RAD2DEG;
15802 yFlip = (angle < -minPI || angle > minPI);
15803 cos = Math.cos(-angle);
15804 sin = Math.sin(-angle);
15805 t1 = a11*cos-a13*sin;
15806 t2 = a21*cos-a23*sin;
15807 t3 = a31*cos-a33*sin;
15808 a23 = a21*sin+a23*cos;
15809 a33 = a31*sin+a33*cos;
15810 a43 = a41*sin+a43*cos;
15816 angle = Math.atan2(a21, a22);
15817 tm.rotation = angle * _RAD2DEG;
15819 zFlip = (angle < -minPI || angle > minPI);
15820 cos = Math.cos(-angle);
15821 sin = Math.sin(-angle);
15822 a11 = a11*cos+a12*sin;
15823 t2 = a21*cos+a22*sin;
15824 a22 = a21*-sin+a22*cos;
15825 a32 = a31*-sin+a32*cos;
15829 if (zFlip && xFlip) {
15830 tm.rotation = tm.rotationX = 0;
15831 } else if (zFlip && yFlip) {
15832 tm.rotation = tm.rotationY = 0;
15833 } else if (yFlip && xFlip) {
15834 tm.rotationY = tm.rotationX = 0;
15837 tm.scaleX = ((Math.sqrt(a11 * a11 + a21 * a21) * rnd + 0.5) | 0) / rnd;
15838 tm.scaleY = ((Math.sqrt(a22 * a22 + a23 * a23) * rnd + 0.5) | 0) / rnd;
15839 tm.scaleZ = ((Math.sqrt(a32 * a32 + a33 * a33) * rnd + 0.5) | 0) / rnd;
15841 tm.perspective = a43 ? 1 / ((a43 < 0) ? -a43 : a43) : 0;
15847 } else if ((!_supports3D || parse || !m.length || tm.x !== m[4] || tm.y !== m[5] || (!tm.rotationX && !tm.rotationY)) && !(tm.x !== undefined && _getStyle(t, "display", cs) === "none")) { //sometimes a 6-element matrix is returned even when we performed 3D transforms, like if rotationX and rotationY are 180. In cases like this, we still need to honor the 3D transforms. If we just rely on the 2D info, it could affect how the data is interpreted, like scaleY might get set to -1 or rotation could get offset by 180 degrees. For example, do a TweenLite.to(element, 1, {css:{rotationX:180, rotationY:180}}) and then later, TweenLite.to(element, 1, {css:{rotationX:0}}) and without this conditional logic in place, it'd jump to a state of being unrotated when the 2nd tween starts. Then again, we need to honor the fact that the user COULD alter the transforms outside of CSSPlugin, like by manually applying new css, so we try to sense that by looking at x and y because if those changed, we know the changes were made outside CSSPlugin and we force a reinterpretation of the matrix values. Also, in Webkit browsers, if the element's "display" is "none", its calculated style value will always return empty, so if we've already recorded the values in the _gsTransform object, we'll just rely on those.
15848 var k = (m.length >= 6),
15855 scaleX = Math.sqrt(a * a + b * b);
15856 scaleY = Math.sqrt(d * d + c * c);
15857 rotation = (a || b) ? Math.atan2(b, a) * _RAD2DEG : tm.rotation || 0; //note: if scaleX is 0, we cannot accurately measure rotation. Same for skewX with a scaleY of 0. Therefore, we default to the previously recorded value (or zero if that doesn't exist).
15858 skewX = (c || d) ? Math.atan2(c, d) * _RAD2DEG + rotation : tm.skewX || 0;
15859 difX = scaleX - Math.abs(tm.scaleX || 0);
15860 difY = scaleY - Math.abs(tm.scaleY || 0);
15861 if (Math.abs(skewX) > 90 && Math.abs(skewX) < 270) {
15864 skewX += (rotation <= 0) ? 180 : -180;
15865 rotation += (rotation <= 0) ? 180 : -180;
15868 skewX += (skewX <= 0) ? 180 : -180;
15871 difR = (rotation - tm.rotation) % 180; //note: matching ranges would be very small (+/-0.0001) or very close to 180.
15872 difS = (skewX - tm.skewX) % 180;
15873 //if there's already a recorded _gsTransform in place for the target, we should leave those values in place unless we know things changed for sure (beyond a super small amount). This gets around ambiguous interpretations, like if scaleX and scaleY are both -1, the matrix would be the same as if the rotation was 180 with normal scaleX/scaleY. If the user tweened to particular values, those must be prioritized to ensure animation is consistent.
15874 if (tm.skewX === undefined || difX > min || difX < -min || difY > min || difY < -min || (difR > -minAngle && difR < minAngle && (difR * rnd) | 0 !== 0) || (difS > -minAngle && difS < minAngle && (difS * rnd) | 0 !== 0)) {
15875 tm.scaleX = scaleX;
15876 tm.scaleY = scaleY;
15877 tm.rotation = rotation;
15881 tm.rotationX = tm.rotationY = tm.z = 0;
15882 tm.perspective = parseFloat(CSSPlugin.defaultTransformPerspective) || 0;
15886 tm.zOrigin = zOrigin;
15888 //some browsers have a hard time with very small values like 2.4492935982947064e-16 (notice the "e-" towards the end) and would render the object slightly off. So we round to 0 in these cases. The conditional logic here is faster than calling Math.abs(). Also, browsers tend to render a SLIGHTLY rotated object in a fuzzy way, so we need to snap to exactly 0 when appropriate.
15890 if (tm[i] < min) if (tm[i] > -min) {
15894 //DEBUG: _log("parsed rotation: "+(tm.rotationX)+", "+(tm.rotationY)+", "+(tm.rotation)+", scale: "+tm.scaleX+", "+tm.scaleY+", "+tm.scaleZ+", position: "+tm.x+", "+tm.y+", "+tm.z+", perspective: "+tm.perspective);
15896 t._gsTransform = tm; //record to the object's _gsTransform which we use so that tweens can control individual properties independently (we need all the properties to accurately recompose the matrix in the setRatio() method)
15901 //for setting 2D transforms in IE6, IE7, and IE8 (must use a "filter" to emulate the behavior of modern day browser transforms)
15902 _setIETransformRatio = function(v) {
15903 var t = this.data, //refers to the element's _gsTransform object
15904 ang = -t.rotation * _DEG2RAD,
15905 skew = ang + t.skewX * _DEG2RAD,
15907 a = ((Math.cos(ang) * t.scaleX * rnd) | 0) / rnd,
15908 b = ((Math.sin(ang) * t.scaleX * rnd) | 0) / rnd,
15909 c = ((Math.sin(skew) * -t.scaleY * rnd) | 0) / rnd,
15910 d = ((Math.cos(skew) * t.scaleY * rnd) | 0) / rnd,
15911 style = this.t.style,
15912 cs = this.t.currentStyle,
15917 val = b; //just for swapping the variables an inverting them (reused "val" to avoid creating another variable in memory). IE's filter matrix uses a non-standard matrix configuration (angle goes the opposite way, and b and c are reversed and inverted)
15920 filters = cs.filter;
15921 style.filter = ""; //remove filters so that we can accurately measure offsetWidth/offsetHeight
15922 var w = this.t.offsetWidth,
15923 h = this.t.offsetHeight,
15924 clip = (cs.position !== "absolute"),
15925 m = "progid:DXImageTransform.Microsoft.Matrix(M11=" + a + ", M12=" + b + ", M21=" + c + ", M22=" + d,
15930 //if transformOrigin is being used, adjust the offset x and y
15931 if (t.ox != null) {
15932 dx = ((t.oxp) ? w * t.ox * 0.01 : t.ox) - w / 2;
15933 dy = ((t.oyp) ? h * t.oy * 0.01 : t.oy) - h / 2;
15934 ox += dx - (dx * a + dy * b);
15935 oy += dy - (dx * c + dy * d);
15939 m += ", sizingMethod='auto expand')";
15943 //translate to ensure that transformations occur around the correct origin (default is center).
15944 m += ", Dx=" + (dx - (dx * a + dy * b) + ox) + ", Dy=" + (dy - (dx * c + dy * d) + oy) + ")";
15946 if (filters.indexOf("DXImageTransform.Microsoft.Matrix(") !== -1) {
15947 style.filter = filters.replace(_ieSetMatrixExp, m);
15949 style.filter = m + " " + filters; //we must always put the transform/matrix FIRST (before alpha(opacity=xx)) to avoid an IE bug that slices part of the object when rotation is applied with alpha.
15952 //at the end or beginning of the tween, if the matrix is normal (1, 0, 0, 1) and opacity is 100 (or doesn't exist), remove the filter to improve browser performance.
15953 if (v === 0 || v === 1) if (a === 1) if (b === 0) if (c === 0) if (d === 1) if (!clip || m.indexOf("Dx=0, Dy=0") !== -1) if (!_opacityExp.test(filters) || parseFloat(RegExp.$1) === 100) if (filters.indexOf("gradient(" && filters.indexOf("Alpha")) === -1) {
15954 style.removeAttribute("filter");
15957 //we must set the margins AFTER applying the filter in order to avoid some bugs in IE8 that could (in rare scenarios) cause them to be ignored intermittently (vibration).
15959 var mult = (_ieVers < 8) ? 1 : -1, //in Internet Explorer 7 and before, the box model is broken, causing the browser to treat the width/height of the actual rotated filtered image as the width/height of the box itself, but Microsoft corrected that in IE8. We must use a negative offset in IE8 on the right/bottom
15961 dx = t.ieOffsetX || 0;
15962 dy = t.ieOffsetY || 0;
15963 t.ieOffsetX = Math.round((w - ((a < 0 ? -a : a) * w + (b < 0 ? -b : b) * h)) / 2 + ox);
15964 t.ieOffsetY = Math.round((h - ((d < 0 ? -d : d) * h + (c < 0 ? -c : c) * w)) / 2 + oy);
15965 for (i = 0; i < 4; i++) {
15966 prop = _margins[i];
15968 //we need to get the current margin in case it is being tweened separately (we want to respect that tween's changes)
15969 val = (marg.indexOf("px") !== -1) ? parseFloat(marg) : _convertToPixels(this.t, prop, parseFloat(marg), marg.replace(_suffixExp, "")) || 0;
15970 if (val !== t[prop]) {
15971 dif = (i < 2) ? -t.ieOffsetX : -t.ieOffsetY; //if another tween is controlling a margin, we cannot only apply the difference in the ieOffsets, so we essentially zero-out the dx and dy here in that case. We record the margin(s) later so that we can keep comparing them, making this code very flexible.
15973 dif = (i < 2) ? dx - t.ieOffsetX : dy - t.ieOffsetY;
15975 style[prop] = (t[prop] = Math.round( val - dif * ((i === 0 || i === 2) ? 1 : mult) )) + "px";
15980 _set3DTransformRatio = _internals.set3DTransformRatio = function(v) {
15981 var t = this.data, //refers to the element's _gsTransform object
15982 style = this.t.style,
15983 angle = t.rotation * _DEG2RAD,
15987 perspective = t.perspective,
15988 a11, a12, a13, a14, a21, a22, a23, a24, a31, a32, a33, a34, a41, a42, a43,
15989 zOrigin, rnd, cos, sin, t1, t2, t3, t4;
15990 if (v === 1 || v === 0) if (t.force3D === "auto") if (!t.rotationY && !t.rotationX && sz === 1 && !perspective && !t.z) { //on the final render (which could be 0 for a from tween), if there are no 3D aspects, render in 2D to free up memory and improve performance especially on mobile devices
15991 _set2DTransformRatio.call(this, v);
15996 if (sx < n && sx > -n) { //Firefox has a bug (at least in v25) that causes it to render the transparent part of 32-bit PNG images as black when displayed inside an iframe and the 3D scale is very small and doesn't change sufficiently enough between renders (like if you use a Power4.easeInOut to scale from 0 to 1 where the beginning values only change a tiny amount to begin the tween before accelerating). In this case, we force the scale to be 0.00002 instead which is visually the same but works around the Firefox issue.
15999 if (sy < n && sy > -n) {
16002 if (perspective && !t.z && !t.rotationX && !t.rotationY) { //Firefox has a bug that causes elements to have an odd super-thin, broken/dotted black border on elements that have a perspective set but aren't utilizing 3D space (no rotationX, rotationY, or z).
16006 if (angle || t.skewX) {
16007 cos = Math.cos(angle);
16008 sin = Math.sin(angle);
16012 angle -= t.skewX * _DEG2RAD;
16013 cos = Math.cos(angle);
16014 sin = Math.sin(angle);
16015 if (t.skewType === "simple") { //by default, we compensate skewing on the other axis to make it look more natural, but you can set the skewType to "simple" to use the uncompensated skewing that CSS does
16016 t1 = Math.tan(t.skewX * _DEG2RAD);
16017 t1 = Math.sqrt(1 + t1 * t1);
16025 } else if (!t.rotationY && !t.rotationX && sz === 1 && !perspective) { //if we're only translating and/or 2D scaling, this is faster...
16026 style[_transformProp] = "translate3d(" + t.x + "px," + t.y + "px," + t.z +"px)" + ((sx !== 1 || sy !== 1) ? " scale(" + sx + "," + sy + ")" : "");
16033 a13 = a14 = a23 = a24 = a31 = a32 = a34 = a41 = a42 = 0;
16034 a43 = (perspective) ? -1 / perspective : 0;
16035 zOrigin = t.zOrigin;
16037 angle = t.rotationY * _DEG2RAD;
16039 cos = Math.cos(angle);
16040 sin = Math.sin(angle);
16050 angle = t.rotationX * _DEG2RAD;
16052 cos = Math.cos(angle);
16053 sin = Math.sin(angle);
16054 t1 = a12*cos+a13*sin;
16055 t2 = a22*cos+a23*sin;
16056 t3 = a32*cos+a33*sin;
16057 t4 = a42*cos+a43*sin;
16058 a13 = a12*-sin+a13*cos;
16059 a23 = a22*-sin+a23*cos;
16060 a33 = a32*-sin+a33*cos;
16061 a43 = a42*-sin+a43*cos;
16089 a34 = a33*a34+zOrigin;
16091 //we round the x, y, and z slightly differently to allow even larger values.
16092 a14 = (t1 = (a14 += t.x) - (a14 |= 0)) ? ((t1 * rnd + (t1 < 0 ? -0.5 : 0.5)) | 0) / rnd + a14 : a14;
16093 a24 = (t1 = (a24 += t.y) - (a24 |= 0)) ? ((t1 * rnd + (t1 < 0 ? -0.5 : 0.5)) | 0) / rnd + a24 : a24;
16094 a34 = (t1 = (a34 += t.z) - (a34 |= 0)) ? ((t1 * rnd + (t1 < 0 ? -0.5 : 0.5)) | 0) / rnd + a34 : a34;
16095 style[_transformProp] = "matrix3d(" + [ (((a11 * rnd) | 0) / rnd), (((a21 * rnd) | 0) / rnd), (((a31 * rnd) | 0) / rnd), (((a41 * rnd) | 0) / rnd), (((a12 * rnd) | 0) / rnd), (((a22 * rnd) | 0) / rnd), (((a32 * rnd) | 0) / rnd), (((a42 * rnd) | 0) / rnd), (((a13 * rnd) | 0) / rnd), (((a23 * rnd) | 0) / rnd), (((a33 * rnd) | 0) / rnd), (((a43 * rnd) | 0) / rnd), a14, a24, a34, (perspective ? (1 + (-a34 / perspective)) : 1) ].join(",") + ")";
16098 _set2DTransformRatio = _internals.set2DTransformRatio = function(v) {
16099 var t = this.data, //refers to the element's _gsTransform object
16101 style = targ.style,
16102 ang, skew, rnd, sx, sy;
16103 if (t.rotationX || t.rotationY || t.z || t.force3D === true || (t.force3D === "auto" && v !== 1 && v !== 0)) { //if a 3D tween begins while a 2D one is running, we need to kick the rendering over to the 3D method. For example, imagine a yoyo-ing, infinitely repeating scale tween running, and then the object gets rotated in 3D space with a different tween.
16104 this.setRatio = _set3DTransformRatio;
16105 _set3DTransformRatio.call(this, v);
16108 if (!t.rotation && !t.skewX) {
16109 style[_transformProp] = "matrix(" + t.scaleX + ",0,0," + t.scaleY + "," + t.x + "," + t.y + ")";
16111 ang = t.rotation * _DEG2RAD;
16112 skew = ang - t.skewX * _DEG2RAD;
16114 sx = t.scaleX * rnd;
16115 sy = t.scaleY * rnd;
16116 //some browsers have a hard time with very small values like 2.4492935982947064e-16 (notice the "e-" towards the end) and would render the object slightly off. So we round to 5 decimal places.
16117 style[_transformProp] = "matrix(" + (((Math.cos(ang) * sx) | 0) / rnd) + "," + (((Math.sin(ang) * sx) | 0) / rnd) + "," + (((Math.sin(skew) * -sy) | 0) / rnd) + "," + (((Math.cos(skew) * sy) | 0) / rnd) + "," + t.x + "," + t.y + ")";
16121 _registerComplexSpecialProp("transform,scale,scaleX,scaleY,scaleZ,x,y,z,rotation,rotationX,rotationY,rotationZ,skewX,skewY,shortRotation,shortRotationX,shortRotationY,shortRotationZ,transformOrigin,transformPerspective,directionalRotation,parseTransform,force3D,skewType", {parser:function(t, e, p, cssp, pt, plugin, vars) {
16122 if (cssp._transform) { return pt; } //only need to parse the transform once, and only if the browser supports it.
16123 var m1 = cssp._transform = _getTransform(t, _cs, true, vars.parseTransform),
16126 i = _transformProps.length,
16129 m2, skewY, copy, orig, has3D, hasChange, dr;
16130 if (typeof(v.transform) === "string" && _transformProp) { //for values like transform:"rotate(60deg) scale(0.5, 0.8)"
16131 copy = _tempDiv.style; //don't use the original target because it might be SVG in which case some browsers don't report computed style correctly.
16132 copy[_transformProp] = v.transform;
16133 copy.display = "block"; //if display is "none", the browser often refuses to report the transform properties correctly.
16134 copy.position = "absolute";
16135 _doc.body.appendChild(_tempDiv);
16136 m2 = _getTransform(_tempDiv, null, false);
16137 _doc.body.removeChild(_tempDiv);
16138 } else if (typeof(v) === "object") { //for values like scaleX, scaleY, rotation, x, y, skewX, and skewY or transform:{...} (object)
16139 m2 = {scaleX:_parseVal((v.scaleX != null) ? v.scaleX : v.scale, m1.scaleX),
16140 scaleY:_parseVal((v.scaleY != null) ? v.scaleY : v.scale, m1.scaleY),
16141 scaleZ:_parseVal(v.scaleZ, m1.scaleZ),
16142 x:_parseVal(v.x, m1.x),
16143 y:_parseVal(v.y, m1.y),
16144 z:_parseVal(v.z, m1.z),
16145 perspective:_parseVal(v.transformPerspective, m1.perspective)};
16146 dr = v.directionalRotation;
16148 if (typeof(dr) === "object") {
16150 v[copy] = dr[copy];
16156 m2.rotation = _parseAngle(("rotation" in v) ? v.rotation : ("shortRotation" in v) ? v.shortRotation + "_short" : ("rotationZ" in v) ? v.rotationZ : m1.rotation, m1.rotation, "rotation", endRotations);
16158 m2.rotationX = _parseAngle(("rotationX" in v) ? v.rotationX : ("shortRotationX" in v) ? v.shortRotationX + "_short" : m1.rotationX || 0, m1.rotationX, "rotationX", endRotations);
16159 m2.rotationY = _parseAngle(("rotationY" in v) ? v.rotationY : ("shortRotationY" in v) ? v.shortRotationY + "_short" : m1.rotationY || 0, m1.rotationY, "rotationY", endRotations);
16161 m2.skewX = (v.skewX == null) ? m1.skewX : _parseAngle(v.skewX, m1.skewX);
16163 //note: for performance reasons, we combine all skewing into the skewX and rotation values, ignoring skewY but we must still record it so that we can discern how much of the overall skew is attributed to skewX vs. skewY. Otherwise, if the skewY would always act relative (tween skewY to 10deg, for example, multiple times and if we always combine things into skewX, we can't remember that skewY was 10 from last time). Remember, a skewY of 10 degrees looks the same as a rotation of 10 degrees plus a skewX of -10 degrees.
16164 m2.skewY = (v.skewY == null) ? m1.skewY : _parseAngle(v.skewY, m1.skewY);
16165 if ((skewY = m2.skewY - m1.skewY)) {
16167 m2.rotation += skewY;
16171 if (_supports3D && v.force3D != null) {
16172 m1.force3D = v.force3D;
16176 m1.skewType = v.skewType || m1.skewType || CSSPlugin.defaultSkewType;
16178 has3D = (m1.force3D || m1.z || m1.rotationX || m1.rotationY || m2.z || m2.rotationX || m2.rotationY || m2.perspective);
16179 if (!has3D && v.scale != null) {
16180 m2.scaleZ = 1; //no need to tween scaleZ.
16184 p = _transformProps[i];
16185 orig = m2[p] - m1[p];
16186 if (orig > min || orig < -min || _forcePT[p] != null) {
16188 pt = new CSSPropTween(m1, p, m1[p], orig, pt);
16189 if (p in endRotations) {
16190 pt.e = endRotations[p]; //directional rotations typically have compensated values during the tween, but we need to make sure they end at exactly what the user requested
16192 pt.xs0 = 0; //ensures the value stays numeric in setRatio()
16193 pt.plugin = plugin;
16194 cssp._overwriteProps.push(pt.n);
16198 orig = v.transformOrigin;
16199 if (orig || (_supports3D && has3D && m1.zOrigin)) { //if anything 3D is happening and there's a transformOrigin with a z component that's non-zero, we must ensure that the transformOrigin's z-component is set to 0 so that we can manually do those calculations to get around Safari bugs. Even if the user didn't specifically define a "transformOrigin" in this particular tween (maybe they did it via css directly).
16200 if (_transformProp) {
16202 p = _transformOriginProp;
16203 orig = (orig || _getStyle(t, p, _cs, false, "50% 50%")) + ""; //cast as string to avoid errors
16204 pt = new CSSPropTween(style, p, 0, 0, pt, -1, "transformOrigin");
16206 pt.plugin = plugin;
16209 orig = orig.split(" ");
16210 m1.zOrigin = ((orig.length > 2 && !(copy !== 0 && orig[2] === "0px")) ? parseFloat(orig[2]) : copy) || 0; //Safari doesn't handle the z part of transformOrigin correctly, so we'll manually handle it in the _set3DTransformRatio() method.
16211 pt.xs0 = pt.e = orig[0] + " " + (orig[1] || "50%") + " 0px"; //we must define a z value of 0px specifically otherwise iOS 5 Safari will stick with the old one (if one was defined)!
16212 pt = new CSSPropTween(m1, "zOrigin", 0, 0, pt, -1, pt.n); //we must create a CSSPropTween for the _gsTransform.zOrigin so that it gets reset properly at the beginning if the tween runs backward (as opposed to just setting m1.zOrigin here)
16214 pt.xs0 = pt.e = m1.zOrigin;
16216 pt.xs0 = pt.e = orig;
16219 //for older versions of IE (6-8), we need to manually calculate things inside the setRatio() function. We record origin x and y (ox and oy) and whether or not the values are percentages (oxp and oyp).
16221 _parsePosition(orig + "", m1);
16226 cssp._transformType = (has3D || this._transformType === 3) ? 3 : 2; //quicker than calling cssp._enableTransforms();
16231 _registerComplexSpecialProp("boxShadow", {defaultValue:"0px 0px 0px 0px #999", prefix:true, color:true, multi:true, keyword:"inset"});
16233 _registerComplexSpecialProp("borderRadius", {defaultValue:"0px", parser:function(t, e, p, cssp, pt, plugin) {
16234 e = this.format(e);
16235 var props = ["borderTopLeftRadius","borderTopRightRadius","borderBottomRightRadius","borderBottomLeftRadius"],
16237 ea1, i, es2, bs2, bs, es, bn, en, w, h, esfx, bsfx, rel, hn, vn, em;
16238 w = parseFloat(t.offsetWidth);
16239 h = parseFloat(t.offsetHeight);
16240 ea1 = e.split(" ");
16241 for (i = 0; i < props.length; i++) { //if we're dealing with percentages, we must convert things separately for the horizontal and vertical axis!
16242 if (this.p.indexOf("border")) { //older browsers used a prefix
16243 props[i] = _checkPropPrefix(props[i]);
16245 bs = bs2 = _getStyle(t, props[i], _cs, false, "0px");
16246 if (bs.indexOf(" ") !== -1) {
16247 bs2 = bs.split(" ");
16252 bn = parseFloat(bs);
16253 bsfx = bs.substr((bn + "").length);
16254 rel = (es.charAt(1) === "=");
16256 en = parseInt(es.charAt(0)+"1", 10);
16258 en *= parseFloat(es);
16259 esfx = es.substr((en + "").length - (en < 0 ? 1 : 0)) || "";
16261 en = parseFloat(es);
16262 esfx = es.substr((en + "").length);
16265 esfx = _suffixMap[p] || bsfx;
16267 if (esfx !== bsfx) {
16268 hn = _convertToPixels(t, "borderLeft", bn, bsfx); //horizontal number (we use a bogus "borderLeft" property just because the _convertToPixels() method searches for the keywords "Left", "Right", "Top", and "Bottom" to determine of it's a horizontal or vertical property, and we need "border" in the name so that it knows it should measure relative to the element itself, not its parent.
16269 vn = _convertToPixels(t, "borderTop", bn, bsfx); //vertical number
16270 if (esfx === "%") {
16271 bs = (hn / w * 100) + "%";
16272 bs2 = (vn / h * 100) + "%";
16273 } else if (esfx === "em") {
16274 em = _convertToPixels(t, "borderLeft", 1, "em");
16275 bs = (hn / em) + "em";
16276 bs2 = (vn / em) + "em";
16282 es = (parseFloat(bs) + en) + esfx;
16283 es2 = (parseFloat(bs2) + en) + esfx;
16286 pt = _parseComplex(style, props[i], bs + " " + bs2, es + " " + es2, false, "0px", pt);
16289 }, prefix:true, formatter:_getFormatter("0px 0px 0px 0px", false, true)});
16290 _registerComplexSpecialProp("backgroundPosition", {defaultValue:"0 0", parser:function(t, e, p, cssp, pt, plugin) {
16291 var bp = "background-position",
16292 cs = (_cs || _getComputedStyle(t, null)),
16293 bs = this.format( ((cs) ? _ieVers ? cs.getPropertyValue(bp + "-x") + " " + cs.getPropertyValue(bp + "-y") : cs.getPropertyValue(bp) : t.currentStyle.backgroundPositionX + " " + t.currentStyle.backgroundPositionY) || "0 0"), //Internet Explorer doesn't report background-position correctly - we must query background-position-x and background-position-y and combine them (even in IE10). Before IE9, we must do the same with the currentStyle object and use camelCase
16294 es = this.format(e),
16295 ba, ea, i, pct, overlap, src;
16296 if ((bs.indexOf("%") !== -1) !== (es.indexOf("%") !== -1)) {
16297 src = _getStyle(t, "backgroundImage").replace(_urlExp, "");
16298 if (src && src !== "none") {
16299 ba = bs.split(" ");
16300 ea = es.split(" ");
16301 _tempImg.setAttribute("src", src); //set the temp <img>'s src to the background-image so that we can measure its width/height
16305 pct = (bs.indexOf("%") !== -1);
16306 if (pct !== (ea[i].indexOf("%") !== -1)) {
16307 overlap = (i === 0) ? t.offsetWidth - _tempImg.width : t.offsetHeight - _tempImg.height;
16308 ba[i] = pct ? (parseFloat(bs) / 100 * overlap) + "px" : (parseFloat(bs) / overlap * 100) + "%";
16314 return this.parseComplex(t.style, bs, es, pt, plugin);
16315 }, formatter:_parsePosition});
16316 _registerComplexSpecialProp("backgroundSize", {defaultValue:"0 0", formatter:_parsePosition});
16317 _registerComplexSpecialProp("perspective", {defaultValue:"0px", prefix:true});
16318 _registerComplexSpecialProp("perspectiveOrigin", {defaultValue:"50% 50%", prefix:true});
16319 _registerComplexSpecialProp("transformStyle", {prefix:true});
16320 _registerComplexSpecialProp("backfaceVisibility", {prefix:true});
16321 _registerComplexSpecialProp("userSelect", {prefix:true});
16322 _registerComplexSpecialProp("margin", {parser:_getEdgeParser("marginTop,marginRight,marginBottom,marginLeft")});
16323 _registerComplexSpecialProp("padding", {parser:_getEdgeParser("paddingTop,paddingRight,paddingBottom,paddingLeft")});
16324 _registerComplexSpecialProp("clip", {defaultValue:"rect(0px,0px,0px,0px)", parser:function(t, e, p, cssp, pt, plugin){
16326 if (_ieVers < 9) { //IE8 and earlier don't report a "clip" value in the currentStyle - instead, the values are split apart into clipTop, clipRight, clipBottom, and clipLeft. Also, in IE7 and earlier, the values inside rect() are space-delimited, not comma-delimited.
16327 cs = t.currentStyle;
16328 delim = _ieVers < 8 ? " " : ",";
16329 b = "rect(" + cs.clipTop + delim + cs.clipRight + delim + cs.clipBottom + delim + cs.clipLeft + ")";
16330 e = this.format(e).split(",").join(delim);
16332 b = this.format(_getStyle(t, this.p, _cs, false, this.dflt));
16333 e = this.format(e);
16335 return this.parseComplex(t.style, b, e, pt, plugin);
16337 _registerComplexSpecialProp("textShadow", {defaultValue:"0px 0px 0px #999", color:true, multi:true});
16338 _registerComplexSpecialProp("autoRound,strictUnits", {parser:function(t, e, p, cssp, pt) {return pt;}}); //just so that we can ignore these properties (not tween them)
16339 _registerComplexSpecialProp("border", {defaultValue:"0px solid #000", parser:function(t, e, p, cssp, pt, plugin) {
16340 return this.parseComplex(t.style, this.format(_getStyle(t, "borderTopWidth", _cs, false, "0px") + " " + _getStyle(t, "borderTopStyle", _cs, false, "solid") + " " + _getStyle(t, "borderTopColor", _cs, false, "#000")), this.format(e), pt, plugin);
16341 }, color:true, formatter:function(v) {
16342 var a = v.split(" ");
16343 return a[0] + " " + (a[1] || "solid") + " " + (v.match(_colorExp) || ["#000"])[0];
16345 _registerComplexSpecialProp("borderWidth", {parser:_getEdgeParser("borderTopWidth,borderRightWidth,borderBottomWidth,borderLeftWidth")}); //Firefox doesn't pick up on borderWidth set in style sheets (only inline).
16346 _registerComplexSpecialProp("float,cssFloat,styleFloat", {parser:function(t, e, p, cssp, pt, plugin) {
16348 prop = ("cssFloat" in s) ? "cssFloat" : "styleFloat";
16349 return new CSSPropTween(s, prop, 0, 0, pt, -1, p, false, 0, s[prop], e);
16353 var _setIEOpacityRatio = function(v) {
16354 var t = this.t, //refers to the element's style property
16355 filters = t.filter || _getStyle(this.data, "filter"),
16356 val = (this.s + this.c * v) | 0,
16358 if (val === 100) { //for older versions of IE that need to use a filter to apply opacity, we should remove the filter if opacity hits 1 in order to improve performance, but make sure there isn't a transform (matrix) or gradient in the filters.
16359 if (filters.indexOf("atrix(") === -1 && filters.indexOf("radient(") === -1 && filters.indexOf("oader(") === -1) {
16360 t.removeAttribute("filter");
16361 skip = (!_getStyle(this.data, "filter")); //if a class is applied that has an alpha filter, it will take effect (we don't want that), so re-apply our alpha filter in that case. We must first remove it and then check.
16363 t.filter = filters.replace(_alphaFilterExp, "");
16369 t.filter = filters = filters || ("alpha(opacity=" + val + ")"); //works around bug in IE7/8 that prevents changes to "visibility" from being applied properly if the filter is changed to a different alpha on the same frame.
16371 if (filters.indexOf("pacity") === -1) { //only used if browser doesn't support the standard opacity style property (IE 7 and 8). We omit the "O" to avoid case-sensitivity issues
16372 if (val !== 0 || !this.xn1) { //bugs in IE7/8 won't render the filter properly if opacity is ADDED on the same frame/render as "visibility" changes (this.xn1 is 1 if this tween is an "autoAlpha" tween)
16373 t.filter = filters + " alpha(opacity=" + val + ")"; //we round the value because otherwise, bugs in IE7/8 can prevent "visibility" changes from being applied properly.
16376 t.filter = filters.replace(_opacityExp, "opacity=" + val);
16380 _registerComplexSpecialProp("opacity,alpha,autoAlpha", {defaultValue:"1", parser:function(t, e, p, cssp, pt, plugin) {
16381 var b = parseFloat(_getStyle(t, "opacity", _cs, false, "1")),
16383 isAutoAlpha = (p === "autoAlpha");
16384 if (typeof(e) === "string" && e.charAt(1) === "=") {
16385 e = ((e.charAt(0) === "-") ? -1 : 1) * parseFloat(e.substr(2)) + b;
16387 if (isAutoAlpha && b === 1 && _getStyle(t, "visibility", _cs) === "hidden" && e !== 0) { //if visibility is initially set to "hidden", we should interpret that as intent to make opacity 0 (a convenience)
16390 if (_supportsOpacity) {
16391 pt = new CSSPropTween(style, "opacity", b, e - b, pt);
16393 pt = new CSSPropTween(style, "opacity", b * 100, (e - b) * 100, pt);
16394 pt.xn1 = isAutoAlpha ? 1 : 0; //we need to record whether or not this is an autoAlpha so that in the setRatio(), we know to duplicate the setting of the alpha in order to work around a bug in IE7 and IE8 that prevents changes to "visibility" from taking effect if the filter is changed to a different alpha(opacity) at the same time. Setting it to the SAME value first, then the new value works around the IE7/8 bug.
16395 style.zoom = 1; //helps correct an IE issue.
16397 pt.b = "alpha(opacity=" + pt.s + ")";
16398 pt.e = "alpha(opacity=" + (pt.s + pt.c) + ")";
16400 pt.plugin = plugin;
16401 pt.setRatio = _setIEOpacityRatio;
16403 if (isAutoAlpha) { //we have to create the "visibility" PropTween after the opacity one in the linked list so that they run in the order that works properly in IE8 and earlier
16404 pt = new CSSPropTween(style, "visibility", 0, 0, pt, -1, null, false, 0, ((b !== 0) ? "inherit" : "hidden"), ((e === 0) ? "hidden" : "inherit"));
16405 pt.xs0 = "inherit";
16406 cssp._overwriteProps.push(pt.n);
16407 cssp._overwriteProps.push(p);
16413 var _removeProp = function(s, p) {
16415 if (s.removeProperty) {
16416 if (p.substr(0,2) === "ms") { //Microsoft browsers don't conform to the standard of capping the first prefix character, so we adjust so that when we prefix the caps with a dash, it's correct (otherwise it'd be "ms-transform" instead of "-ms-transform" for IE9, for example)
16417 p = "M" + p.substr(1);
16419 s.removeProperty(p.replace(_capsExp, "-$1").toLowerCase());
16420 } else { //note: old versions of IE use "removeAttribute()" instead of "removeProperty()"
16421 s.removeAttribute(p);
16425 _setClassNameRatio = function(v) {
16426 this.t._gsClassPT = this;
16427 if (v === 1 || v === 0) {
16428 this.t.setAttribute("class", (v === 0) ? this.b : this.e);
16429 var mpt = this.data, //first MiniPropTween
16433 _removeProp(s, mpt.p);
16439 if (v === 1 && this.t._gsClassPT === this) {
16440 this.t._gsClassPT = null;
16442 } else if (this.t.getAttribute("class") !== this.e) {
16443 this.t.setAttribute("class", this.e);
16446 _registerComplexSpecialProp("className", {parser:function(t, e, p, cssp, pt, plugin, vars) {
16447 var b = t.getAttribute("class") || "", //don't use t.className because it doesn't work consistently on SVG elements; getAttribute("class") and setAttribute("class", value") is more reliable.
16448 cssText = t.style.cssText,
16449 difData, bs, cnpt, cnptLookup, mpt;
16450 pt = cssp._classNamePT = new CSSPropTween(t, p, 0, 0, pt, 2);
16451 pt.setRatio = _setClassNameRatio;
16453 _hasPriority = true;
16455 bs = _getAllStyles(t, _cs);
16456 //if there's a className tween already operating on the target, force it to its end so that the necessary inline styles are removed and the class name is applied before we determine the end state (we don't want inline styles interfering that were there just for class-specific values)
16457 cnpt = t._gsClassPT;
16460 mpt = cnpt.data; //first MiniPropTween which stores the inline styles - we need to force these so that the inline styles don't contaminate things. Otherwise, there's a small chance that a tween could start and the inline values match the destination values and they never get cleaned.
16462 cnptLookup[mpt.p] = 1;
16468 pt.e = (e.charAt(1) !== "=") ? e : b.replace(new RegExp("\\s*\\b" + e.substr(2) + "\\b"), "") + ((e.charAt(0) === "+") ? " " + e.substr(2) : "");
16469 if (cssp._tween._duration) { //if it's a zero-duration tween, there's no need to tween anything or parse the data. In fact, if we switch classes temporarily (which we must do for proper parsing) and the class has a transition applied, it could cause a quick flash to the end state and back again initially in some browsers.
16470 t.setAttribute("class", pt.e);
16471 difData = _cssDif(t, bs, _getAllStyles(t), vars, cnptLookup);
16472 t.setAttribute("class", b);
16473 pt.data = difData.firstMPT;
16474 t.style.cssText = cssText; //we recorded cssText before we swapped classes and ran _getAllStyles() because in cases when a className tween is overwritten, we remove all the related tweening properties from that class change (otherwise class-specific stuff can't override properties we've directly set on the target's style object due to specificity).
16475 pt = pt.xfirst = cssp.parse(t, difData.difs, pt, plugin); //we record the CSSPropTween as the xfirst so that we can handle overwriting propertly (if "className" gets overwritten, we must kill all the properties associated with the className part of the tween, so we can loop through from xfirst to the pt itself)
16481 var _setClearPropsRatio = function(v) {
16482 if (v === 1 || v === 0) if (this.data._totalTime === this.data._totalDuration && this.data.data !== "isFromStart") { //this.data refers to the tween. Only clear at the END of the tween (remember, from() tweens make the ratio go from 1 to 0, so we can't just check that and if the tween is the zero-duration one that's created internally to render the starting values in a from() tween, ignore that because otherwise, for example, from(...{height:100, clearProps:"height", delay:1}) would wipe the height at the beginning of the tween and after 1 second, it'd kick back in).
16483 var s = this.t.style,
16484 transformParse = _specialProps.transform.parse,
16485 a, p, i, clearTransform;
16486 if (this.e === "all") {
16488 clearTransform = true;
16490 a = this.e.split(",");
16494 if (_specialProps[p]) {
16495 if (_specialProps[p].parse === transformParse) {
16496 clearTransform = true;
16498 p = (p === "transformOrigin") ? _transformOriginProp : _specialProps[p].p; //ensures that special properties use the proper browser-specific property name, like "scaleX" might be "-webkit-transform" or "boxShadow" might be "-moz-box-shadow"
16504 if (clearTransform) {
16505 _removeProp(s, _transformProp);
16506 if (this.t._gsTransform) {
16507 delete this.t._gsTransform;
16513 _registerComplexSpecialProp("clearProps", {parser:function(t, e, p, cssp, pt) {
16514 pt = new CSSPropTween(t, p, 0, 0, pt, 2);
16515 pt.setRatio = _setClearPropsRatio;
16518 pt.data = cssp._tween;
16519 _hasPriority = true;
16523 p = "bezier,throwProps,physicsProps,physics2D".split(",");
16526 _registerPluginProp(p[i]);
16536 p = CSSPlugin.prototype;
16539 //gets called when the tween renders for the first time. This kicks everything off, recording start/end values, etc.
16540 p._onInitTween = function(target, vars, tween) {
16541 if (!target.nodeType) { //css is only for dom elements
16544 this._target = target;
16545 this._tween = tween;
16547 _autoRound = vars.autoRound;
16548 _hasPriority = false;
16549 _suffixMap = vars.suffixMap || CSSPlugin.suffixMap;
16550 _cs = _getComputedStyle(target, "");
16551 _overwriteProps = this._overwriteProps;
16552 var style = target.style,
16553 v, pt, pt2, first, last, next, zIndex, tpt, threeD;
16554 if (_reqSafariFix) if (style.zIndex === "") {
16555 v = _getStyle(target, "zIndex", _cs);
16556 if (v === "auto" || v === "") {
16557 //corrects a bug in [non-Android] Safari that prevents it from repainting elements in their new positions if they don't have a zIndex set. We also can't just apply this inside _parseTransform() because anything that's moved in any way (like using "left" or "top" instead of transforms like "x" and "y") can be affected, so it is best to ensure that anything that's tweening has a z-index. Setting "WebkitPerspective" to a non-zero value worked too except that on iOS Safari things would flicker randomly. Plus zIndex is less memory-intensive.
16558 this._addLazySet(style, "zIndex", 0);
16562 if (typeof(vars) === "string") {
16563 first = style.cssText;
16564 v = _getAllStyles(target, _cs);
16565 style.cssText = first + ";" + vars;
16566 v = _cssDif(target, v, _getAllStyles(target)).difs;
16567 if (!_supportsOpacity && _opacityValExp.test(vars)) {
16568 v.opacity = parseFloat( RegExp.$1 );
16571 style.cssText = first;
16573 this._firstPT = pt = this.parse(target, vars, null);
16575 if (this._transformType) {
16576 threeD = (this._transformType === 3);
16577 if (!_transformProp) {
16578 style.zoom = 1; //helps correct an IE issue.
16579 } else if (_isSafari) {
16580 _reqSafariFix = true;
16581 //if zIndex isn't set, iOS Safari doesn't repaint things correctly sometimes (seemingly at random).
16582 if (style.zIndex === "") {
16583 zIndex = _getStyle(target, "zIndex", _cs);
16584 if (zIndex === "auto" || zIndex === "") {
16585 this._addLazySet(style, "zIndex", 0);
16588 //Setting WebkitBackfaceVisibility corrects 3 bugs:
16589 // 1) [non-Android] Safari skips rendering changes to "top" and "left" that are made on the same frame/render as a transform update.
16590 // 2) iOS Safari sometimes neglects to repaint elements in their new positions. Setting "WebkitPerspective" to a non-zero value worked too except that on iOS Safari things would flicker randomly.
16591 // 3) Safari sometimes displayed odd artifacts when tweening the transform (or WebkitTransform) property, like ghosts of the edges of the element remained. Definitely a browser bug.
16592 //Note: we allow the user to override the auto-setting by defining WebkitBackfaceVisibility in the vars of the tween.
16593 if (_isSafariLT6) {
16594 this._addLazySet(style, "WebkitBackfaceVisibility", this._vars.WebkitBackfaceVisibility || (threeD ? "visible" : "hidden"));
16598 while (pt2 && pt2._next) {
16601 tpt = new CSSPropTween(target, "transform", 0, 0, null, 2);
16602 this._linkCSSP(tpt, null, pt2);
16603 tpt.setRatio = (threeD && _supports3D) ? _set3DTransformRatio : _transformProp ? _set2DTransformRatio : _setIETransformRatio;
16604 tpt.data = this._transform || _getTransform(target, _cs, true);
16605 _overwriteProps.pop(); //we don't want to force the overwrite of all "transform" tweens of the target - we only care about individual transform properties like scaleX, rotation, etc. The CSSPropTween constructor automatically adds the property to _overwriteProps which is why we need to pop() here.
16608 if (_hasPriority) {
16609 //reorders the linked list in order of pr (priority)
16613 while (pt2 && pt2.pr > pt.pr) {
16616 if ((pt._prev = pt2 ? pt2._prev : last)) {
16617 pt._prev._next = pt;
16621 if ((pt._next = pt2)) {
16628 this._firstPT = first;
16634 p.parse = function(target, vars, pt, plugin) {
16635 var style = target.style,
16636 p, sp, bn, en, bs, es, bsfx, esfx, isStr, rel;
16638 es = vars[p]; //ending value string
16639 sp = _specialProps[p]; //SpecialProp lookup.
16641 pt = sp.parse(target, es, p, this, pt, plugin, vars);
16644 bs = _getStyle(target, p, _cs) + "";
16645 isStr = (typeof(es) === "string");
16646 if (p === "color" || p === "fill" || p === "stroke" || p.indexOf("Color") !== -1 || (isStr && _rgbhslExp.test(es))) { //Opera uses background: to define color sometimes in addition to backgroundColor:
16648 es = _parseColor(es);
16649 es = ((es.length > 3) ? "rgba(" : "rgb(") + es.join(",") + ")";
16651 pt = _parseComplex(style, p, bs, es, true, "transparent", pt, 0, plugin);
16653 } else if (isStr && (es.indexOf(" ") !== -1 || es.indexOf(",") !== -1)) {
16654 pt = _parseComplex(style, p, bs, es, true, null, pt, 0, plugin);
16657 bn = parseFloat(bs);
16658 bsfx = (bn || bn === 0) ? bs.substr((bn + "").length) : ""; //remember, bs could be non-numeric like "normal" for fontWeight, so we should default to a blank suffix in that case.
16660 if (bs === "" || bs === "auto") {
16661 if (p === "width" || p === "height") {
16662 bn = _getDimension(target, p, _cs);
16664 } else if (p === "left" || p === "top") {
16665 bn = _calculateOffset(target, p, _cs);
16668 bn = (p !== "opacity") ? 0 : 1;
16673 rel = (isStr && es.charAt(1) === "=");
16675 en = parseInt(es.charAt(0) + "1", 10);
16677 en *= parseFloat(es);
16678 esfx = es.replace(_suffixExp, "");
16680 en = parseFloat(es);
16681 esfx = isStr ? es.substr((en + "").length) || "" : "";
16685 esfx = (p in _suffixMap) ? _suffixMap[p] : bsfx; //populate the end suffix, prioritizing the map, then if none is found, use the beginning suffix.
16688 es = (en || en === 0) ? (rel ? en + bn : en) + esfx : vars[p]; //ensures that any += or -= prefixes are taken care of. Record the end value before normalizing the suffix because we always want to end the tween on exactly what they intended even if it doesn't match the beginning value's suffix.
16690 //if the beginning/ending suffixes don't match, normalize them...
16691 if (bsfx !== esfx) if (esfx !== "") if (en || en === 0) if (bn) { //note: if the beginning value (bn) is 0, we don't need to convert units!
16692 bn = _convertToPixels(target, p, bn, bsfx);
16693 if (esfx === "%") {
16694 bn /= _convertToPixels(target, p, 100, "%") / 100;
16695 if (vars.strictUnits !== true) { //some browsers report only "px" values instead of allowing "%" with getComputedStyle(), so we assume that if we're tweening to a %, we should start there too unless strictUnits:true is defined. This approach is particularly useful for responsive designs that use from() tweens.
16699 } else if (esfx === "em") {
16700 bn /= _convertToPixels(target, p, 1, "em");
16702 //otherwise convert to pixels.
16703 } else if (esfx !== "px") {
16704 en = _convertToPixels(target, p, en, esfx);
16705 esfx = "px"; //we don't use bsfx after this, so we don't need to set it to px too.
16707 if (rel) if (en || en === 0) {
16708 es = (en + bn) + esfx; //the changes we made affect relative calculations, so adjust the end value here.
16716 if ((bn || bn === 0) && (en || en === 0)) { //faster than isNaN(). Also, previously we required en !== bn but that doesn't really gain much performance and it prevents _parseToProxy() from working properly if beginning and ending values match but need to get tweened by an external plugin anyway. For example, a bezier tween where the target starts at left:0 and has these points: [{left:50},{left:0}] wouldn't work properly because when parsing the last point, it'd match the first (current) one and a non-tweening CSSPropTween would be recorded when we actually need a normal tween (type:0) so that things get updated during the tween properly.
16717 pt = new CSSPropTween(style, p, bn, en - bn, pt, 0, p, (_autoRound !== false && (esfx === "px" || p === "zIndex")), 0, bs, es);
16719 //DEBUG: _log("tween "+p+" from "+pt.b+" ("+bn+esfx+") to "+pt.e+" with suffix: "+pt.xs0);
16720 } else if (style[p] === undefined || !es && (es + "" === "NaN" || es == null)) {
16721 _log("invalid " + p + " tween value: " + vars[p]);
16723 pt = new CSSPropTween(style, p, en || bn || 0, 0, pt, -1, p, false, 0, bs, es);
16724 pt.xs0 = (es === "none" && (p === "display" || p.indexOf("Style") !== -1)) ? bs : es; //intermediate value should typically be set immediately (end value) except for "display" or things like borderTopStyle, borderBottomStyle, etc. which should use the beginning value during the tween.
16725 //DEBUG: _log("non-tweening value "+p+": "+pt.xs0);
16729 if (plugin) if (pt && !pt.plugin) {
16730 pt.plugin = plugin;
16737 //gets called every time the tween updates, passing the new ratio (typically a value between 0 and 1, but not always (for example, if an Elastic.easeOut is used, the value can jump above 1 mid-tween). It will always start and 0 and end at 1.
16738 p.setRatio = function(v) {
16739 var pt = this._firstPT,
16743 //at the end of the tween, we set the values to exactly what we received in order to make sure non-tweening values (like "position" or "float" or whatever) are set and so that if the beginning/ending suffixes (units) didn't match and we normalized to px, the value that the user passed in is used here. We check to see if the tween is at its beginning in case it's a from() tween in which case the ratio will actually go from 1 to 0 over the course of the tween (backwards).
16744 if (v === 1 && (this._tween._time === this._tween._duration || this._tween._time === 0)) {
16746 if (pt.type !== 2) {
16754 } else if (v || !(this._tween._time === this._tween._duration || this._tween._time === 0) || this._tween._rawPrevTime === -0.000001) {
16756 val = pt.c * v + pt.s;
16758 val = Math.round(val);
16759 } else if (val < min) if (val > -min) {
16763 pt.t[pt.p] = val + pt.xs0;
16764 } else if (pt.type === 1) { //complex value (one that typically has multiple numbers inside a string, like "rect(5px,10px,20px,25px)"
16767 pt.t[pt.p] = pt.xs0 + val + pt.xs1 + pt.xn1 + pt.xs2;
16768 } else if (i === 3) {
16769 pt.t[pt.p] = pt.xs0 + val + pt.xs1 + pt.xn1 + pt.xs2 + pt.xn2 + pt.xs3;
16770 } else if (i === 4) {
16771 pt.t[pt.p] = pt.xs0 + val + pt.xs1 + pt.xn1 + pt.xs2 + pt.xn2 + pt.xs3 + pt.xn3 + pt.xs4;
16772 } else if (i === 5) {
16773 pt.t[pt.p] = pt.xs0 + val + pt.xs1 + pt.xn1 + pt.xs2 + pt.xn2 + pt.xs3 + pt.xn3 + pt.xs4 + pt.xn4 + pt.xs5;
16775 str = pt.xs0 + val + pt.xs1;
16776 for (i = 1; i < pt.l; i++) {
16777 str += pt["xn"+i] + pt["xs"+(i+1)];
16782 } else if (pt.type === -1) { //non-tweening value
16783 pt.t[pt.p] = pt.xs0;
16785 } else if (pt.setRatio) { //custom setRatio() for things like SpecialProps, external plugins, etc.
16791 //if the tween is reversed all the way back to the beginning, we need to restore the original values which may have different units (like % instead of px or em or whatever).
16794 if (pt.type !== 2) {
16806 * Forces rendering of the target's transforms (rotation, scale, etc.) whenever the CSSPlugin's setRatio() is called.
16807 * Basically, this tells the CSSPlugin to create a CSSPropTween (type 2) after instantiation that runs last in the linked
16808 * list and calls the appropriate (3D or 2D) rendering function. We separate this into its own method so that we can call
16809 * it from other plugins like BezierPlugin if, for example, it needs to apply an autoRotation and this CSSPlugin
16810 * doesn't have any transform-related properties of its own. You can call this method as many times as you
16811 * want and it won't create duplicate CSSPropTweens.
16813 * @param {boolean} threeD if true, it should apply 3D tweens (otherwise, just 2D ones are fine and typically faster)
16815 p._enableTransforms = function(threeD) {
16816 this._transformType = (threeD || this._transformType === 3) ? 3 : 2;
16817 this._transform = this._transform || _getTransform(this._target, _cs, true); //ensures that the element has a _gsTransform property with the appropriate values.
16820 var lazySet = function(v) {
16821 this.t[this.p] = this.e;
16822 this.data._linkCSSP(this, this._next, null, true); //we purposefully keep this._next even though it'd make sense to null it, but this is a performance optimization, as this happens during the while (pt) {} loop in setRatio() at the bottom of which it sets pt = pt._next, so if we null it, the linked list will be broken in that loop.
16824 /** @private Gives us a way to set a value on the first render (and only the first render). **/
16825 p._addLazySet = function(t, p, v) {
16826 var pt = this._firstPT = new CSSPropTween(t, p, 0, 0, this._firstPT, 2);
16828 pt.setRatio = lazySet;
16833 p._linkCSSP = function(pt, next, prev, remove) {
16839 pt._next._prev = pt._prev;
16842 pt._prev._next = pt._next;
16843 } else if (this._firstPT === pt) {
16844 this._firstPT = pt._next;
16845 remove = true; //just to prevent resetting this._firstPT 5 lines down in case pt._next is null. (optimized for speed)
16849 } else if (!remove && this._firstPT === null) {
16850 this._firstPT = pt;
16858 //we need to make sure that if alpha or autoAlpha is killed, opacity is too. And autoAlpha affects the "visibility" property.
16859 p._kill = function(lookup) {
16862 if (lookup.autoAlpha || lookup.alpha) {
16864 for (p in lookup) { //copy the lookup so that we're not changing the original which may be passed elsewhere.
16865 copy[p] = lookup[p];
16868 if (copy.autoAlpha) {
16869 copy.visibility = 1;
16872 if (lookup.className && (pt = this._classNamePT)) { //for className tweens, we need to kill any associated CSSPropTweens too; a linked list starts at the className's "xfirst".
16873 xfirst = pt.xfirst;
16874 if (xfirst && xfirst._prev) {
16875 this._linkCSSP(xfirst._prev, pt._next, xfirst._prev._prev); //break off the prev
16876 } else if (xfirst === this._firstPT) {
16877 this._firstPT = pt._next;
16880 this._linkCSSP(pt._next, pt._next._next, xfirst._prev);
16882 this._classNamePT = null;
16884 return TweenPlugin.prototype._kill.call(this, copy);
16889 //used by cascadeTo() for gathering all the style properties of each child element into an array for comparison.
16890 var _getChildStyles = function(e, props, targets) {
16891 var children, i, child, type;
16895 _getChildStyles(e[i], props, targets);
16899 children = e.childNodes;
16900 i = children.length;
16902 child = children[i];
16905 props.push(_getAllStyles(child));
16907 targets.push(child);
16910 if ((type === 1 || type === 9 || type === 11) && child.childNodes.length) {
16911 _getChildStyles(child, props, targets);
16917 * Typically only useful for className tweens that may affect child elements, this method creates a TweenLite
16918 * and then compares the style properties of all the target's child elements at the tween's start and end, and
16919 * if any are different, it also creates tweens for those and returns an array containing ALL of the resulting
16920 * tweens (so that you can easily add() them to a TimelineLite, for example). The reason this functionality is
16921 * wrapped into a separate static method of CSSPlugin instead of being integrated into all regular className tweens
16922 * is because it creates entirely new tweens that may have completely different targets than the original tween,
16923 * so if they were all lumped into the original tween instance, it would be inconsistent with the rest of the API
16924 * and it would create other problems. For example:
16925 * - If I create a tween of elementA, that tween instance may suddenly change its target to include 50 other elements (unintuitive if I specifically defined the target I wanted)
16926 * - We can't just create new independent tweens because otherwise, what happens if the original/parent tween is reversed or pause or dropped into a TimelineLite for tight control? You'd expect that tween's behavior to affect all the others.
16927 * - Analyzing every style property of every child before and after the tween is an expensive operation when there are many children, so this behavior shouldn't be imposed on all className tweens by default, especially since it's probably rare that this extra functionality is needed.
16929 * @param {Object} target object to be tweened
16930 * @param {number} Duration in seconds (or frames for frames-based tweens)
16931 * @param {Object} Object containing the end values, like {className:"newClass", ease:Linear.easeNone}
16932 * @return {Array} An array of TweenLite instances
16934 CSSPlugin.cascadeTo = function(target, duration, vars) {
16935 var tween = TweenLite.to(target, duration, vars),
16940 _reservedProps = TweenLite._internals.reservedProps,
16942 target = tween._targets || tween.target;
16943 _getChildStyles(target, b, targets);
16944 tween.render(duration, true);
16945 _getChildStyles(target, e);
16946 tween.render(0, true);
16947 tween._enabled(true);
16948 i = targets.length;
16950 difs = _cssDif(targets[i], b[i], e[i]);
16951 if (difs.firstMPT) {
16954 if (_reservedProps[p]) {
16958 results.push( TweenLite.to(targets[i], duration, difs) );
16964 TweenPlugin.activate([CSSPlugin]);
16980 * ----------------------------------------------------------------
16982 * ----------------------------------------------------------------
16986 var RoundPropsPlugin = window._gsDefine.plugin({
16987 propName: "roundProps",
16991 //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.
16992 init: function(target, value, tween) {
16993 this._tween = tween;
16998 p = RoundPropsPlugin.prototype;
17000 p._onInitAllProps = function() {
17001 var tween = this._tween,
17002 rp = (tween.vars.roundProps instanceof Array) ? tween.vars.roundProps : tween.vars.roundProps.split(","),
17005 rpt = tween._propLookup.roundProps,
17013 pt = tween._firstPT;
17015 next = pt._next; //record here, because it may get removed
17017 pt.t._roundProps(lookup, true);
17018 } else if (pt.n === prop) {
17019 this._add(pt.t, prop, pt.s, pt.c);
17020 //remove from linked list
17022 next._prev = pt._prev;
17025 pt._prev._next = next;
17026 } else if (tween._firstPT === pt) {
17027 tween._firstPT = next;
17029 pt._next = pt._prev = null;
17030 tween._propLookup[prop] = rpt;
17038 p._add = function(target, p, s, c) {
17039 this._addTween(target, p, s, s + c, p, true);
17040 this._overwriteProps.push(p);
17055 * ----------------------------------------------------------------
17057 * ----------------------------------------------------------------
17059 window._gsDefine.plugin({
17064 //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.
17065 init: function(target, value, tween) {
17067 if (typeof(target.setAttribute) !== "function") {
17070 this._target = target;
17072 this._start = {}; // we record start and end values exactly as they are in case they're strings (not numbers) - we need to be able to revert to them cleanly.
17075 this._start[p] = this._proxy[p] = start = target.getAttribute(p);
17076 end = this._addTween(this._proxy, p, parseFloat(start), value[p], p);
17077 this._end[p] = end ? end.s + end.c : value[p];
17078 this._overwriteProps.push(p);
17083 //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.)
17084 set: function(ratio) {
17085 this._super.setRatio.call(this, ratio);
17086 var props = this._overwriteProps,
17088 lookup = (ratio === 1) ? this._end : ratio ? this._proxy : this._start,
17092 this._target.setAttribute(p, lookup[p] + "");
17108 * ----------------------------------------------------------------
17109 * DirectionalRotationPlugin
17110 * ----------------------------------------------------------------
17112 window._gsDefine.plugin({
17113 propName: "directionalRotation",
17117 //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.
17118 init: function(target, value, tween) {
17119 if (typeof(value) !== "object") {
17120 value = {rotation:value};
17123 var cap = (value.useRadians === true) ? Math.PI * 2 : 360,
17125 p, v, start, end, dif, split;
17127 if (p !== "useRadians") {
17128 split = (value[p] + "").split("_");
17130 start = parseFloat( (typeof(target[p]) !== "function") ? target[p] : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]() );
17131 end = this.finals[p] = (typeof(v) === "string" && v.charAt(1) === "=") ? start + parseInt(v.charAt(0) + "1", 10) * Number(v.substr(2)) : Number(v) || 0;
17133 if (split.length) {
17134 v = split.join("_");
17135 if (v.indexOf("short") !== -1) {
17137 if (dif !== dif % (cap / 2)) {
17138 dif = (dif < 0) ? dif + cap : dif - cap;
17141 if (v.indexOf("_cw") !== -1 && dif < 0) {
17142 dif = ((dif + cap * 9999999999) % cap) - ((dif / cap) | 0) * cap;
17143 } else if (v.indexOf("ccw") !== -1 && dif > 0) {
17144 dif = ((dif - cap * 9999999999) % cap) - ((dif / cap) | 0) * cap;
17147 if (dif > min || dif < -min) {
17148 this._addTween(target, p, start, start + dif, p);
17149 this._overwriteProps.push(p);
17156 //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.)
17157 set: function(ratio) {
17160 this._super.setRatio.call(this, ratio);
17162 pt = this._firstPT;
17165 pt.t[pt.p](this.finals[pt.p]);
17167 pt.t[pt.p] = this.finals[pt.p];
17174 })._autoCSS = true;
17187 * ----------------------------------------------------------------
17189 * ----------------------------------------------------------------
17191 window._gsDefine("easing.Back", ["easing.Ease"], function(Ease) {
17193 var w = (window.GreenSockGlobals || window),
17194 gs = w.com.greensock,
17195 _2PI = Math.PI * 2,
17196 _HALF_PI = Math.PI / 2,
17197 _class = gs._class,
17198 _create = function(n, f) {
17199 var C = _class("easing." + n, function(){}, true),
17200 p = C.prototype = new Ease();
17205 _easeReg = Ease.register || function(){}, //put an empty function in place just as a safety measure in case someone loads an OLD version of TweenLite.js where Ease.register doesn't exist.
17206 _wrap = function(name, EaseOut, EaseIn, EaseInOut, aliases) {
17207 var C = _class("easing."+name, {
17208 easeOut:new EaseOut(),
17209 easeIn:new EaseIn(),
17210 easeInOut:new EaseInOut()
17215 EasePoint = function(time, value, next) {
17221 this.c = next.v - value;
17222 this.gap = next.t - time;
17227 _createBack = function(n, f) {
17228 var C = _class("easing." + n, function(overshoot) {
17229 this._p1 = (overshoot || overshoot === 0) ? overshoot : 1.70158;
17230 this._p2 = this._p1 * 1.525;
17232 p = C.prototype = new Ease();
17235 p.config = function(overshoot) {
17236 return new C(overshoot);
17241 Back = _wrap("Back",
17242 _createBack("BackOut", function(p) {
17243 return ((p = p - 1) * p * ((this._p1 + 1) * p + this._p1) + 1);
17245 _createBack("BackIn", function(p) {
17246 return p * p * ((this._p1 + 1) * p - this._p1);
17248 _createBack("BackInOut", function(p) {
17249 return ((p *= 2) < 1) ? 0.5 * p * p * ((this._p2 + 1) * p - this._p2) : 0.5 * ((p -= 2) * p * ((this._p2 + 1) * p + this._p2) + 2);
17255 SlowMo = _class("easing.SlowMo", function(linearRatio, power, yoyoMode) {
17256 power = (power || power === 0) ? power : 0.7;
17257 if (linearRatio == null) {
17259 } else if (linearRatio > 1) {
17262 this._p = (linearRatio !== 1) ? power : 0;
17263 this._p1 = (1 - linearRatio) / 2;
17264 this._p2 = linearRatio;
17265 this._p3 = this._p1 + this._p2;
17266 this._calcEnd = (yoyoMode === true);
17268 p = SlowMo.prototype = new Ease(),
17269 SteppedEase, RoughEase, _createElastic;
17271 p.constructor = SlowMo;
17272 p.getRatio = function(p) {
17273 var r = p + (0.5 - p) * this._p;
17274 if (p < this._p1) {
17275 return this._calcEnd ? 1 - ((p = 1 - (p / this._p1)) * p) : r - ((p = 1 - (p / this._p1)) * p * p * p * r);
17276 } else if (p > this._p3) {
17277 return this._calcEnd ? 1 - (p = (p - this._p3) / this._p1) * p : r + ((p - r) * (p = (p - this._p3) / this._p1) * p * p * p);
17279 return this._calcEnd ? 1 : r;
17281 SlowMo.ease = new SlowMo(0.7, 0.7);
17283 p.config = SlowMo.config = function(linearRatio, power, yoyoMode) {
17284 return new SlowMo(linearRatio, power, yoyoMode);
17289 SteppedEase = _class("easing.SteppedEase", function(steps) {
17290 steps = steps || 1;
17291 this._p1 = 1 / steps;
17292 this._p2 = steps + 1;
17294 p = SteppedEase.prototype = new Ease();
17295 p.constructor = SteppedEase;
17296 p.getRatio = function(p) {
17299 } else if (p >= 1) {
17302 return ((this._p2 * p) >> 0) * this._p1;
17304 p.config = SteppedEase.config = function(steps) {
17305 return new SteppedEase(steps);
17310 RoughEase = _class("easing.RoughEase", function(vars) {
17312 var taper = vars.taper || "none",
17315 points = (vars.points || 20) | 0,
17317 randomize = (vars.randomize !== false),
17318 clamp = (vars.clamp === true),
17319 template = (vars.template instanceof Ease) ? vars.template : null,
17320 strength = (typeof(vars.strength) === "number") ? vars.strength * 0.4 : 0.4,
17321 x, y, bump, invX, obj, pnt;
17323 x = randomize ? Math.random() : (1 / points) * i;
17324 y = template ? template.getRatio(x) : x;
17325 if (taper === "none") {
17327 } else if (taper === "out") {
17329 bump = invX * invX * strength;
17330 } else if (taper === "in") {
17331 bump = x * x * strength;
17332 } else if (x < 0.5) { //"both" (start)
17334 bump = invX * invX * 0.5 * strength;
17335 } else { //"both" (end)
17336 invX = (1 - x) * 2;
17337 bump = invX * invX * 0.5 * strength;
17340 y += (Math.random() * bump) - (bump * 0.5);
17341 } else if (i % 2) {
17349 } else if (y < 0) {
17353 a[cnt++] = {x:x, y:y};
17355 a.sort(function(a, b) {
17359 pnt = new EasePoint(1, 1, null);
17363 pnt = new EasePoint(obj.x, obj.y, pnt);
17366 this._prev = new EasePoint(0, 0, (pnt.t !== 0) ? pnt : pnt.next);
17368 p = RoughEase.prototype = new Ease();
17369 p.constructor = RoughEase;
17370 p.getRatio = function(p) {
17371 var pnt = this._prev;
17373 while (pnt.next && p >= pnt.t) {
17378 while (pnt.prev && p <= pnt.t) {
17383 return (pnt.v + ((p - pnt.t) / pnt.gap) * pnt.c);
17385 p.config = function(vars) {
17386 return new RoughEase(vars);
17388 RoughEase.ease = new RoughEase();
17393 _create("BounceOut", function(p) {
17394 if (p < 1 / 2.75) {
17395 return 7.5625 * p * p;
17396 } else if (p < 2 / 2.75) {
17397 return 7.5625 * (p -= 1.5 / 2.75) * p + 0.75;
17398 } else if (p < 2.5 / 2.75) {
17399 return 7.5625 * (p -= 2.25 / 2.75) * p + 0.9375;
17401 return 7.5625 * (p -= 2.625 / 2.75) * p + 0.984375;
17403 _create("BounceIn", function(p) {
17404 if ((p = 1 - p) < 1 / 2.75) {
17405 return 1 - (7.5625 * p * p);
17406 } else if (p < 2 / 2.75) {
17407 return 1 - (7.5625 * (p -= 1.5 / 2.75) * p + 0.75);
17408 } else if (p < 2.5 / 2.75) {
17409 return 1 - (7.5625 * (p -= 2.25 / 2.75) * p + 0.9375);
17411 return 1 - (7.5625 * (p -= 2.625 / 2.75) * p + 0.984375);
17413 _create("BounceInOut", function(p) {
17414 var invert = (p < 0.5);
17420 if (p < 1 / 2.75) {
17421 p = 7.5625 * p * p;
17422 } else if (p < 2 / 2.75) {
17423 p = 7.5625 * (p -= 1.5 / 2.75) * p + 0.75;
17424 } else if (p < 2.5 / 2.75) {
17425 p = 7.5625 * (p -= 2.25 / 2.75) * p + 0.9375;
17427 p = 7.5625 * (p -= 2.625 / 2.75) * p + 0.984375;
17429 return invert ? (1 - p) * 0.5 : p * 0.5 + 0.5;
17436 _create("CircOut", function(p) {
17437 return Math.sqrt(1 - (p = p - 1) * p);
17439 _create("CircIn", function(p) {
17440 return -(Math.sqrt(1 - (p * p)) - 1);
17442 _create("CircInOut", function(p) {
17443 return ((p*=2) < 1) ? -0.5 * (Math.sqrt(1 - p * p) - 1) : 0.5 * (Math.sqrt(1 - (p -= 2) * p) + 1);
17449 _createElastic = function(n, f, def) {
17450 var C = _class("easing." + n, function(amplitude, period) {
17451 this._p1 = amplitude || 1;
17452 this._p2 = period || def;
17453 this._p3 = this._p2 / _2PI * (Math.asin(1 / this._p1) || 0);
17455 p = C.prototype = new Ease();
17458 p.config = function(amplitude, period) {
17459 return new C(amplitude, period);
17464 _createElastic("ElasticOut", function(p) {
17465 return this._p1 * Math.pow(2, -10 * p) * Math.sin( (p - this._p3) * _2PI / this._p2 ) + 1;
17467 _createElastic("ElasticIn", function(p) {
17468 return -(this._p1 * Math.pow(2, 10 * (p -= 1)) * Math.sin( (p - this._p3) * _2PI / this._p2 ));
17470 _createElastic("ElasticInOut", function(p) {
17471 return ((p *= 2) < 1) ? -0.5 * (this._p1 * Math.pow(2, 10 * (p -= 1)) * Math.sin( (p - this._p3) * _2PI / this._p2)) : this._p1 * Math.pow(2, -10 *(p -= 1)) * Math.sin( (p - this._p3) * _2PI / this._p2 ) *0.5 + 1;
17478 _create("ExpoOut", function(p) {
17479 return 1 - Math.pow(2, -10 * p);
17481 _create("ExpoIn", function(p) {
17482 return Math.pow(2, 10 * (p - 1)) - 0.001;
17484 _create("ExpoInOut", function(p) {
17485 return ((p *= 2) < 1) ? 0.5 * Math.pow(2, 10 * (p - 1)) : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
17492 _create("SineOut", function(p) {
17493 return Math.sin(p * _HALF_PI);
17495 _create("SineIn", function(p) {
17496 return -Math.cos(p * _HALF_PI) + 1;
17498 _create("SineInOut", function(p) {
17499 return -0.5 * (Math.cos(Math.PI * p) - 1);
17503 _class("easing.EaseLookup", {
17505 return Ease.map[s];
17509 //register the non-standard eases
17510 _easeReg(w.SlowMo, "SlowMo", "ease,");
17511 _easeReg(RoughEase, "RoughEase", "ease,");
17512 _easeReg(SteppedEase, "SteppedEase", "ease,");
17532 * ----------------------------------------------------------------
17533 * Base classes like TweenLite, SimpleTimeline, Ease, Ticker, etc.
17534 * ----------------------------------------------------------------
17536 (function(window) {
17539 var _globals = window.GreenSockGlobals || window;
17540 if (_globals.TweenLite) {
17541 return; //in case the core set of classes is already loaded, don't instantiate twice.
17543 var _namespace = function(ns) {
17544 var a = ns.split("."),
17546 for (i = 0; i < a.length; i++) {
17547 p[a[i]] = p = p[a[i]] || {};
17551 gs = _namespace("com.greensock"),
17552 _tinyNum = 0.0000000001,
17554 _emptyFunc = function() {},
17555 _isArray = (function() { //works around issues in iframe environments where the Array global isn't shared, thus if the object originates in a different window/iframe, "(obj instanceof Array)" will evaluate false. We added some speed optimizations to avoid Object.prototype.toString.call() unless it's absolutely necessary because it's VERY slow (like 20x slower)
17556 var toString = Object.prototype.toString,
17557 array = toString.call([]);
17558 return function(obj) {
17559 return obj != null && (obj instanceof Array || (typeof(obj) === "object" && !!obj.push && toString.call(obj) === array));
17562 a, i, p, _ticker, _tickerActive,
17567 * Defines a GreenSock class, optionally with an array of dependencies that must be instantiated first and passed into the definition.
17568 * This allows users to load GreenSock JS files in any order even if they have interdependencies (like CSSPlugin extends TweenPlugin which is
17569 * inside TweenLite.js, but if CSSPlugin is loaded first, it should wait to run its code until TweenLite.js loads and instantiates TweenPlugin
17570 * and then pass TweenPlugin to CSSPlugin's definition). This is all done automatically and internally.
17572 * Every definition will be added to a "com.greensock" global object (typically window, but if a window.GreenSockGlobals object is found,
17573 * it will go there as of v1.7). For example, TweenLite will be found at window.com.greensock.TweenLite and since it's a global class that should be available anywhere,
17574 * it is ALSO referenced at window.TweenLite. However some classes aren't considered global, like the base com.greensock.core.Animation class, so
17575 * those will only be at the package like window.com.greensock.core.Animation. Again, if you define a GreenSockGlobals object on the window, everything
17576 * gets tucked neatly inside there instead of on the window directly. This allows you to do advanced things like load multiple versions of GreenSock
17577 * files and put them into distinct objects (imagine a banner ad uses a newer version but the main site uses an older one). In that case, you could
17578 * sandbox the banner one like:
17581 * var gs = window.GreenSockGlobals = {}; //the newer version we're about to load could now be referenced in a "gs" object, like gs.TweenLite.to(...). Use whatever alias you want as long as it's unique, "gs" or "banner" or whatever.
17583 * <script src="js/greensock/v1.7/TweenMax.js"></script>
17585 * window.GreenSockGlobals = null; //reset it back to null so that the next load of TweenMax affects the window and we can reference things directly like TweenLite.to(...)
17587 * <script src="js/greensock/v1.6/TweenMax.js"></script>
17589 * gs.TweenLite.to(...); //would use v1.7
17590 * TweenLite.to(...); //would use v1.6
17593 * @param {!string} ns The namespace of the class definition, leaving off "com.greensock." as that's assumed. For example, "TweenLite" or "plugins.CSSPlugin" or "easing.Back".
17594 * @param {!Array.<string>} dependencies An array of dependencies (described as their namespaces minus "com.greensock." prefix). For example ["TweenLite","plugins.TweenPlugin","core.Animation"]
17595 * @param {!function():Object} func The function that should be called and passed the resolved dependencies which will return the actual class for this definition.
17596 * @param {boolean=} global If true, the class will be added to the global scope (typically window unless you define a window.GreenSockGlobals object)
17598 Definition = function(ns, dependencies, func, global) {
17599 this.sc = (_defLookup[ns]) ? _defLookup[ns].sc : []; //subclasses
17600 _defLookup[ns] = this;
17601 this.gsClass = null;
17604 this.check = function(init) {
17605 var i = dependencies.length,
17609 if ((cur = _defLookup[dependencies[i]] || new Definition(dependencies[i], [])).gsClass) {
17610 _classes[i] = cur.gsClass;
17616 if (missing === 0 && func) {
17617 a = ("com.greensock." + ns).split(".");
17619 cl = _namespace(a.join("."))[n] = this.gsClass = func.apply(func, _classes);
17621 //exports to multiple environments
17623 _globals[n] = cl; //provides a way to avoid global namespace pollution. By default, the main classes like TweenLite, Power1, Strong, etc. are added to window unless a GreenSockGlobals is defined. So if you want to have things added to a custom object instead, just do something like window.GreenSockGlobals = {} before loading any GreenSock files. You can even set up an alias like window.GreenSockGlobals = windows.gs = {} so that you can access everything like gs.TweenLite. Also remember that ALL classes are added to the window.com.greensock object (in their respective packages, like com.greensock.easing.Power1, com.greensock.TweenLite, etc.)
17624 if (typeof(define) === "function" && define.amd){ //AMD
17625 define((window.GreenSockAMDPath ? window.GreenSockAMDPath + "/" : "") + ns.split(".").join("/"), [], function() { return cl; });
17626 } else if (typeof(module) !== "undefined" && module.exports){ //node
17627 module.exports = cl;
17630 for (i = 0; i < this.sc.length; i++) {
17631 this.sc[i].check();
17638 //used to create Definition instances (which basically registers a class that has dependencies).
17639 _gsDefine = window._gsDefine = function(ns, dependencies, func, global) {
17640 return new Definition(ns, dependencies, func, global);
17643 //a quick way to create a class that doesn't have any dependencies. Returns the class, but first registers it in the GreenSock namespace so that other classes can grab it (other classes might be dependent on the class).
17644 _class = gs._class = function(ns, func, global) {
17645 func = func || function() {};
17646 _gsDefine(ns, [], function(){ return func; }, global);
17650 _gsDefine.globals = _globals;
17655 * ----------------------------------------------------------------
17657 * ----------------------------------------------------------------
17659 var _baseParams = [0, 0, 1, 1],
17661 Ease = _class("easing.Ease", function(func, extraParams, type, power) {
17663 this._type = type || 0;
17664 this._power = power || 0;
17665 this._params = extraParams ? _baseParams.concat(extraParams) : _baseParams;
17667 _easeMap = Ease.map = {},
17668 _easeReg = Ease.register = function(ease, names, types, create) {
17669 var na = names.split(","),
17671 ta = (types || "easeIn,easeOut,easeInOut").split(","),
17675 e = create ? _class("easing."+name, null, true) : gs.easing[name] || {};
17679 _easeMap[name + "." + type] = _easeMap[type + name] = e[type] = ease.getRatio ? ease : ease[type] || new ease();
17684 p = Ease.prototype;
17685 p._calcEnd = false;
17686 p.getRatio = function(p) {
17688 this._params[0] = p;
17689 return this._func.apply(null, this._params);
17691 var t = this._type,
17693 r = (t === 1) ? 1 - p : (t === 2) ? p : (p < 0.5) ? p * 2 : (1 - p) * 2;
17696 } else if (pw === 2) {
17698 } else if (pw === 3) {
17700 } else if (pw === 4) {
17701 r *= r * r * r * r;
17703 return (t === 1) ? 1 - r : (t === 2) ? r : (p < 0.5) ? r / 2 : 1 - (r / 2);
17706 //create all the standard eases like Linear, Quad, Cubic, Quart, Quint, Strong, Power0, Power1, Power2, Power3, and Power4 (each with easeIn, easeOut, and easeInOut)
17707 a = ["Linear","Quad","Cubic","Quart","Quint,Strong"];
17710 p = a[i]+",Power"+i;
17711 _easeReg(new Ease(null,null,1,i), p, "easeOut", true);
17712 _easeReg(new Ease(null,null,2,i), p, "easeIn" + ((i === 0) ? ",easeNone" : ""));
17713 _easeReg(new Ease(null,null,3,i), p, "easeInOut");
17715 _easeMap.linear = gs.easing.Linear.easeIn;
17716 _easeMap.swing = gs.easing.Quad.easeInOut; //for jQuery folks
17720 * ----------------------------------------------------------------
17722 * ----------------------------------------------------------------
17724 var EventDispatcher = _class("events.EventDispatcher", function(target) {
17725 this._listeners = {};
17726 this._eventTarget = target || this;
17728 p = EventDispatcher.prototype;
17730 p.addEventListener = function(type, callback, scope, useParam, priority) {
17731 priority = priority || 0;
17732 var list = this._listeners[type],
17735 if (list == null) {
17736 this._listeners[type] = list = [];
17740 listener = list[i];
17741 if (listener.c === callback && listener.s === scope) {
17743 } else if (index === 0 && listener.pr < priority) {
17747 list.splice(index, 0, {c:callback, s:scope, up:useParam, pr:priority});
17748 if (this === _ticker && !_tickerActive) {
17753 p.removeEventListener = function(type, callback) {
17754 var list = this._listeners[type], i;
17758 if (list[i].c === callback) {
17766 p.dispatchEvent = function(type) {
17767 var list = this._listeners[type],
17771 t = this._eventTarget;
17773 listener = list[i];
17775 listener.c.call(listener.s || t, {type:type, target:t});
17777 listener.c.call(listener.s || t);
17785 * ----------------------------------------------------------------
17787 * ----------------------------------------------------------------
17789 var _reqAnimFrame = window.requestAnimationFrame,
17790 _cancelAnimFrame = window.cancelAnimationFrame,
17791 _getTime = Date.now || function() {return new Date().getTime();},
17792 _lastUpdate = _getTime();
17794 //now try to determine the requestAnimationFrame and cancelAnimationFrame functions and if none are found, we'll use a setTimeout()/clearTimeout() polyfill.
17795 a = ["ms","moz","webkit","o"];
17797 while (--i > -1 && !_reqAnimFrame) {
17798 _reqAnimFrame = window[a[i] + "RequestAnimationFrame"];
17799 _cancelAnimFrame = window[a[i] + "CancelAnimationFrame"] || window[a[i] + "CancelRequestAnimationFrame"];
17802 _class("Ticker", function(fps, useRAF) {
17804 _startTime = _getTime(),
17805 _useRAF = (useRAF !== false && _reqAnimFrame),
17806 _lagThreshold = 500,
17808 _fps, _req, _id, _gap, _nextTime,
17809 _tick = function(manual) {
17810 var elapsed = _getTime() - _lastUpdate,
17812 if (elapsed > _lagThreshold) {
17813 _startTime += elapsed - _adjustedLag;
17815 _lastUpdate += elapsed;
17816 _self.time = (_lastUpdate - _startTime) / 1000;
17817 overlap = _self.time - _nextTime;
17818 if (!_fps || overlap > 0 || manual === true) {
17820 _nextTime += overlap + (overlap >= _gap ? 0.004 : _gap - overlap);
17823 if (manual !== true) { //make sure the request is made before we dispatch the "tick" event so that timing is maintained. Otherwise, if processing the "tick" requires a bunch of time (like 15ms) and we're using a setTimeout() that's based on 16.7ms, it'd technically take 31.7ms between frames otherwise.
17827 _self.dispatchEvent("tick");
17831 EventDispatcher.call(_self);
17832 _self.time = _self.frame = 0;
17833 _self.tick = function() {
17837 _self.lagSmoothing = function(threshold, adjustedLag) {
17838 _lagThreshold = threshold || (1 / _tinyNum); //zero should be interpreted as basically unlimited
17839 _adjustedLag = Math.min(adjustedLag, _lagThreshold, 0);
17842 _self.sleep = function() {
17846 if (!_useRAF || !_cancelAnimFrame) {
17849 _cancelAnimFrame(_id);
17853 if (_self === _ticker) {
17854 _tickerActive = false;
17858 _self.wake = function() {
17859 if (_id !== null) {
17861 } else if (_self.frame > 10) { //don't trigger lagSmoothing if we're just waking up, and make sure that at least 10 frames have elapsed because of the iOS bug that we work around below with the 1.5-second setTimout().
17862 _lastUpdate = _getTime() - _lagThreshold + 5;
17864 _req = (_fps === 0) ? _emptyFunc : (!_useRAF || !_reqAnimFrame) ? function(f) { return setTimeout(f, ((_nextTime - _self.time) * 1000 + 1) | 0); } : _reqAnimFrame;
17865 if (_self === _ticker) {
17866 _tickerActive = true;
17871 _self.fps = function(value) {
17872 if (!arguments.length) {
17876 _gap = 1 / (_fps || 60);
17877 _nextTime = this.time + _gap;
17881 _self.useRAF = function(value) {
17882 if (!arguments.length) {
17891 //a bug in iOS 6 Safari occasionally prevents the requestAnimationFrame from working initially, so we use a 1.5-second timeout that automatically falls back to setTimeout() if it senses this condition.
17892 setTimeout(function() {
17893 if (_useRAF && (!_id || _self.frame < 5)) {
17894 _self.useRAF(false);
17899 p = gs.Ticker.prototype = new gs.events.EventDispatcher();
17900 p.constructor = gs.Ticker;
17904 * ----------------------------------------------------------------
17906 * ----------------------------------------------------------------
17908 var Animation = _class("core.Animation", function(duration, vars) {
17909 this.vars = vars = vars || {};
17910 this._duration = this._totalDuration = duration || 0;
17911 this._delay = Number(vars.delay) || 0;
17912 this._timeScale = 1;
17913 this._active = (vars.immediateRender === true);
17914 this.data = vars.data;
17915 this._reversed = (vars.reversed === true);
17917 if (!_rootTimeline) {
17920 if (!_tickerActive) { //some browsers (like iOS 6 Safari) shut down JavaScript execution when the tab is disabled and they [occasionally] neglect to start up requestAnimationFrame again when returning - this code ensures that the engine starts up again properly.
17924 var tl = this.vars.useFrames ? _rootFramesTimeline : _rootTimeline;
17925 tl.add(this, tl._time);
17927 if (this.vars.paused) {
17932 _ticker = Animation.ticker = new gs.Ticker();
17933 p = Animation.prototype;
17934 p._dirty = p._gc = p._initted = p._paused = false;
17935 p._totalTime = p._time = 0;
17936 p._rawPrevTime = -1;
17937 p._next = p._last = p._onUpdate = p._timeline = p.timeline = null;
17941 //some browsers (like iOS) occasionally drop the requestAnimationFrame event when the user switches to a different tab and then comes back again, so we use a 2-second setTimeout() to sense if/when that condition occurs and then wake() the ticker.
17942 var _checkTimeout = function() {
17943 if (_tickerActive && _getTime() - _lastUpdate > 2000) {
17946 setTimeout(_checkTimeout, 2000);
17951 p.play = function(from, suppressEvents) {
17952 if (from != null) {
17953 this.seek(from, suppressEvents);
17955 return this.reversed(false).paused(false);
17958 p.pause = function(atTime, suppressEvents) {
17959 if (atTime != null) {
17960 this.seek(atTime, suppressEvents);
17962 return this.paused(true);
17965 p.resume = function(from, suppressEvents) {
17966 if (from != null) {
17967 this.seek(from, suppressEvents);
17969 return this.paused(false);
17972 p.seek = function(time, suppressEvents) {
17973 return this.totalTime(Number(time), suppressEvents !== false);
17976 p.restart = function(includeDelay, suppressEvents) {
17977 return this.reversed(false).paused(false).totalTime(includeDelay ? -this._delay : 0, (suppressEvents !== false), true);
17980 p.reverse = function(from, suppressEvents) {
17981 if (from != null) {
17982 this.seek((from || this.totalDuration()), suppressEvents);
17984 return this.reversed(true).paused(false);
17987 p.render = function(time, suppressEvents, force) {
17988 //stub - we override this method in subclasses.
17991 p.invalidate = function() {
17995 p.isActive = function() {
17996 var tl = this._timeline, //the 2 root timelines won't have a _timeline; they're always active.
17997 startTime = this._startTime,
17999 return (!tl || (!this._gc && !this._paused && tl.isActive() && (rawTime = tl.rawTime()) >= startTime && rawTime < startTime + this.totalDuration() / this._timeScale));
18002 p._enabled = function (enabled, ignoreTimeline) {
18003 if (!_tickerActive) {
18006 this._gc = !enabled;
18007 this._active = this.isActive();
18008 if (ignoreTimeline !== true) {
18009 if (enabled && !this.timeline) {
18010 this._timeline.add(this, this._startTime - this._delay);
18011 } else if (!enabled && this.timeline) {
18012 this._timeline._remove(this, true);
18019 p._kill = function(vars, target) {
18020 return this._enabled(false, false);
18023 p.kill = function(vars, target) {
18024 this._kill(vars, target);
18028 p._uncache = function(includeSelf) {
18029 var tween = includeSelf ? this : this.timeline;
18031 tween._dirty = true;
18032 tween = tween.timeline;
18037 p._swapSelfInParams = function(params) {
18038 var i = params.length,
18039 copy = params.concat();
18041 if (params[i] === "{self}") {
18048 //----Animation getters/setters --------------------------------------------------------
18050 p.eventCallback = function(type, callback, params, scope) {
18051 if ((type || "").substr(0,2) === "on") {
18053 if (arguments.length === 1) {
18056 if (callback == null) {
18059 v[type] = callback;
18060 v[type + "Params"] = (_isArray(params) && params.join("").indexOf("{self}") !== -1) ? this._swapSelfInParams(params) : params;
18061 v[type + "Scope"] = scope;
18063 if (type === "onUpdate") {
18064 this._onUpdate = callback;
18070 p.delay = function(value) {
18071 if (!arguments.length) {
18072 return this._delay;
18074 if (this._timeline.smoothChildTiming) {
18075 this.startTime( this._startTime + value - this._delay );
18077 this._delay = value;
18081 p.duration = function(value) {
18082 if (!arguments.length) {
18083 this._dirty = false;
18084 return this._duration;
18086 this._duration = this._totalDuration = value;
18087 this._uncache(true); //true in case it's a TweenMax or TimelineMax that has a repeat - we'll need to refresh the totalDuration.
18088 if (this._timeline.smoothChildTiming) if (this._time > 0) if (this._time < this._duration) if (value !== 0) {
18089 this.totalTime(this._totalTime * (value / this._duration), true);
18094 p.totalDuration = function(value) {
18095 this._dirty = false;
18096 return (!arguments.length) ? this._totalDuration : this.duration(value);
18099 p.time = function(value, suppressEvents) {
18100 if (!arguments.length) {
18104 this.totalDuration();
18106 return this.totalTime((value > this._duration) ? this._duration : value, suppressEvents);
18109 p.totalTime = function(time, suppressEvents, uncapped) {
18110 if (!_tickerActive) {
18113 if (!arguments.length) {
18114 return this._totalTime;
18116 if (this._timeline) {
18117 if (time < 0 && !uncapped) {
18118 time += this.totalDuration();
18120 if (this._timeline.smoothChildTiming) {
18122 this.totalDuration();
18124 var totalDuration = this._totalDuration,
18125 tl = this._timeline;
18126 if (time > totalDuration && !uncapped) {
18127 time = totalDuration;
18129 this._startTime = (this._paused ? this._pauseTime : tl._time) - ((!this._reversed ? time : totalDuration - time) / this._timeScale);
18130 if (!tl._dirty) { //for performance improvement. If the parent's cache is already dirty, it already took care of marking the ancestors as dirty too, so skip the function call here.
18131 this._uncache(false);
18133 //in case any of the ancestor timelines had completed but should now be enabled, we should reset their totalTime() which will also ensure that they're lined up properly and enabled. Skip for animations that are on the root (wasteful). Example: a TimelineLite.exportRoot() is performed when there's a paused tween on the root, the export will not complete until that tween is unpaused, but imagine a child gets restarted later, after all [unpaused] tweens have completed. The startTime of that child would get pushed out, but one of the ancestors may have completed.
18134 if (tl._timeline) {
18135 while (tl._timeline) {
18136 if (tl._timeline._time !== (tl._startTime + tl._totalTime) / tl._timeScale) {
18137 tl.totalTime(tl._totalTime, true);
18144 this._enabled(true, false);
18146 if (this._totalTime !== time || this._duration === 0) {
18147 this.render(time, suppressEvents, false);
18148 if (_lazyTweens.length) { //in case rendering caused any tweens to lazy-init, we should render them because typically when someone calls seek() or time() or progress(), they expect an immediate render.
18156 p.progress = p.totalProgress = function(value, suppressEvents) {
18157 return (!arguments.length) ? this._time / this.duration() : this.totalTime(this.duration() * value, suppressEvents);
18160 p.startTime = function(value) {
18161 if (!arguments.length) {
18162 return this._startTime;
18164 if (value !== this._startTime) {
18165 this._startTime = value;
18166 if (this.timeline) if (this.timeline._sortChildren) {
18167 this.timeline.add(this, value - this._delay); //ensures that any necessary re-sequencing of Animations in the timeline occurs to make sure the rendering order is correct.
18173 p.timeScale = function(value) {
18174 if (!arguments.length) {
18175 return this._timeScale;
18177 value = value || _tinyNum; //can't allow zero because it'll throw the math off
18178 if (this._timeline && this._timeline.smoothChildTiming) {
18179 var pauseTime = this._pauseTime,
18180 t = (pauseTime || pauseTime === 0) ? pauseTime : this._timeline.totalTime();
18181 this._startTime = t - ((t - this._startTime) * this._timeScale / value);
18183 this._timeScale = value;
18184 return this._uncache(false);
18187 p.reversed = function(value) {
18188 if (!arguments.length) {
18189 return this._reversed;
18191 if (value != this._reversed) {
18192 this._reversed = value;
18193 this.totalTime(((this._timeline && !this._timeline.smoothChildTiming) ? this.totalDuration() - this._totalTime : this._totalTime), true);
18198 p.paused = function(value) {
18199 if (!arguments.length) {
18200 return this._paused;
18202 if (value != this._paused) if (this._timeline) {
18203 if (!_tickerActive && !value) {
18206 var tl = this._timeline,
18207 raw = tl.rawTime(),
18208 elapsed = raw - this._pauseTime;
18209 if (!value && tl.smoothChildTiming) {
18210 this._startTime += elapsed;
18211 this._uncache(false);
18213 this._pauseTime = value ? raw : null;
18214 this._paused = value;
18215 this._active = this.isActive();
18216 if (!value && elapsed !== 0 && this._initted && this.duration()) {
18217 this.render((tl.smoothChildTiming ? this._totalTime : (raw - this._startTime) / this._timeScale), true, true); //in case the target's properties changed via some other tween or manual update by the user, we should force a render.
18220 if (this._gc && !value) {
18221 this._enabled(true, false);
18228 * ----------------------------------------------------------------
18230 * ----------------------------------------------------------------
18232 var SimpleTimeline = _class("core.SimpleTimeline", function(vars) {
18233 Animation.call(this, 0, vars);
18234 this.autoRemoveChildren = this.smoothChildTiming = true;
18237 p = SimpleTimeline.prototype = new Animation();
18238 p.constructor = SimpleTimeline;
18239 p.kill()._gc = false;
18240 p._first = p._last = null;
18241 p._sortChildren = false;
18243 p.add = p.insert = function(child, position, align, stagger) {
18245 child._startTime = Number(position || 0) + child._delay;
18246 if (child._paused) if (this !== child._timeline) { //we only adjust the _pauseTime if it wasn't in this timeline already. Remember, sometimes a tween will be inserted again into the same timeline when its startTime is changed so that the tweens in the TimelineLite/Max are re-ordered properly in the linked list (so everything renders in the proper order).
18247 child._pauseTime = child._startTime + ((this.rawTime() - child._startTime) / child._timeScale);
18249 if (child.timeline) {
18250 child.timeline._remove(child, true); //removes from existing timeline so that it can be properly added to this one.
18252 child.timeline = child._timeline = this;
18254 child._enabled(true, true);
18256 prevTween = this._last;
18257 if (this._sortChildren) {
18258 st = child._startTime;
18259 while (prevTween && prevTween._startTime > st) {
18260 prevTween = prevTween._prev;
18264 child._next = prevTween._next;
18265 prevTween._next = child;
18267 child._next = this._first;
18268 this._first = child;
18271 child._next._prev = child;
18273 this._last = child;
18275 child._prev = prevTween;
18276 if (this._timeline) {
18277 this._uncache(true);
18282 p._remove = function(tween, skipDisable) {
18283 if (tween.timeline === this) {
18284 if (!skipDisable) {
18285 tween._enabled(false, true);
18287 tween.timeline = null;
18290 tween._prev._next = tween._next;
18291 } else if (this._first === tween) {
18292 this._first = tween._next;
18295 tween._next._prev = tween._prev;
18296 } else if (this._last === tween) {
18297 this._last = tween._prev;
18300 if (this._timeline) {
18301 this._uncache(true);
18307 p.render = function(time, suppressEvents, force) {
18308 var tween = this._first,
18310 this._totalTime = this._time = this._rawPrevTime = time;
18312 next = tween._next; //record it here because the value could change after rendering...
18313 if (tween._active || (time >= tween._startTime && !tween._paused)) {
18314 if (!tween._reversed) {
18315 tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
18317 tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
18324 p.rawTime = function() {
18325 if (!_tickerActive) {
18328 return this._totalTime;
18332 * ----------------------------------------------------------------
18334 * ----------------------------------------------------------------
18336 var TweenLite = _class("TweenLite", function(target, duration, vars) {
18337 Animation.call(this, duration, vars);
18338 this.render = TweenLite.prototype.render; //speed optimization (avoid prototype lookup on this "hot" method)
18340 if (target == null) {
18341 throw "Cannot tween a null target.";
18344 this.target = target = (typeof(target) !== "string") ? target : TweenLite.selector(target) || target;
18346 var isSelector = (target.jquery || (target.length && target !== window && target[0] && (target[0] === window || (target[0].nodeType && target[0].style && !target.nodeType)))),
18347 overwrite = this.vars.overwrite,
18350 this._overwrite = overwrite = (overwrite == null) ? _overwriteLookup[TweenLite.defaultOverwrite] : (typeof(overwrite) === "number") ? overwrite >> 0 : _overwriteLookup[overwrite];
18352 if ((isSelector || target instanceof Array || (target.push && _isArray(target))) && typeof(target[0]) !== "number") {
18353 this._targets = targets = _slice.call(target, 0);
18354 this._propLookup = [];
18355 this._siblings = [];
18356 for (i = 0; i < targets.length; i++) {
18359 targets.splice(i--, 1);
18361 } else if (typeof(targ) === "string") {
18362 targ = targets[i--] = TweenLite.selector(targ); //in case it's an array of strings
18363 if (typeof(targ) === "string") {
18364 targets.splice(i+1, 1); //to avoid an endless loop (can't imagine why the selector would return a string, but just in case)
18367 } else if (targ.length && targ !== window && targ[0] && (targ[0] === window || (targ[0].nodeType && targ[0].style && !targ.nodeType))) { //in case the user is passing in an array of selector objects (like jQuery objects), we need to check one more level and pull things out if necessary. Also note that <select> elements pass all the criteria regarding length and the first child having style, so we must also check to ensure the target isn't an HTML node itself.
18368 targets.splice(i--, 1);
18369 this._targets = targets = targets.concat(_slice.call(targ, 0));
18372 this._siblings[i] = _register(targ, this, false);
18373 if (overwrite === 1) if (this._siblings[i].length > 1) {
18374 _applyOverwrite(targ, this, null, 1, this._siblings[i]);
18379 this._propLookup = {};
18380 this._siblings = _register(target, this, false);
18381 if (overwrite === 1) if (this._siblings.length > 1) {
18382 _applyOverwrite(target, this, null, 1, this._siblings);
18385 if (this.vars.immediateRender || (duration === 0 && this._delay === 0 && this.vars.immediateRender !== false)) {
18386 this._time = -_tinyNum; //forces a render without having to set the render() "force" parameter to true because we want to allow lazying by default (using the "force" parameter always forces an immediate full render)
18387 this.render(-this._delay);
18390 _isSelector = function(v) {
18391 return (v.length && v !== window && v[0] && (v[0] === window || (v[0].nodeType && v[0].style && !v.nodeType))); //we cannot check "nodeType" if the target is window from within an iframe, otherwise it will trigger a security error in some browsers like Firefox.
18393 _autoCSS = function(vars, target) {
18397 if (!_reservedProps[p] && (!(p in target) || p === "transform" || p === "x" || p === "y" || p === "width" || p === "height" || p === "className" || p === "border") && (!_plugins[p] || (_plugins[p] && _plugins[p]._autoCSS))) { //note: <img> elements contain read-only "x" and "y" properties. We should also prioritize editing css width/height rather than the element's properties.
18405 p = TweenLite.prototype = new Animation();
18406 p.constructor = TweenLite;
18407 p.kill()._gc = false;
18409 //----TweenLite defaults, overwrite management, and root updates ----------------------------------------------------
18412 p._firstPT = p._targets = p._overwrittenProps = p._startAt = null;
18413 p._notifyPluginsOfEnabled = p._lazy = false;
18415 TweenLite.version = "1.12.1";
18416 TweenLite.defaultEase = p._ease = new Ease(null, null, 1, 1);
18417 TweenLite.defaultOverwrite = "auto";
18418 TweenLite.ticker = _ticker;
18419 TweenLite.autoSleep = true;
18420 TweenLite.lagSmoothing = function(threshold, adjustedLag) {
18421 _ticker.lagSmoothing(threshold, adjustedLag);
18423 TweenLite.selector = window.$ || window.jQuery || function(e) { if (window.$) { TweenLite.selector = window.$; return window.$(e); } return window.document ? window.document.getElementById((e.charAt(0) === "#") ? e.substr(1) : e) : e; };
18425 var _lazyTweens = [],
18427 _internals = TweenLite._internals = {isArray:_isArray, isSelector:_isSelector, lazyTweens:_lazyTweens}, //gives us a way to expose certain private values to other GreenSock classes without contaminating tha main TweenLite object.
18428 _plugins = TweenLite._plugins = {},
18429 _tweenLookup = _internals.tweenLookup = {},
18430 _tweenLookupNum = 0,
18431 _reservedProps = _internals.reservedProps = {ease:1, delay:1, overwrite:1, onComplete:1, onCompleteParams:1, onCompleteScope:1, useFrames:1, runBackwards:1, startAt:1, onUpdate:1, onUpdateParams:1, onUpdateScope:1, onStart:1, onStartParams:1, onStartScope:1, onReverseComplete:1, onReverseCompleteParams:1, onReverseCompleteScope:1, onRepeat:1, onRepeatParams:1, onRepeatScope:1, easeParams:1, yoyo:1, immediateRender:1, repeat:1, repeatDelay:1, data:1, paused:1, reversed:1, autoCSS:1, lazy:1},
18432 _overwriteLookup = {none:0, all:1, auto:2, concurrent:3, allOnStart:4, preexisting:5, "true":1, "false":0},
18433 _rootFramesTimeline = Animation._rootFramesTimeline = new SimpleTimeline(),
18434 _rootTimeline = Animation._rootTimeline = new SimpleTimeline(),
18435 _lazyRender = function() {
18436 var i = _lazyTweens.length;
18439 a = _lazyTweens[i];
18440 if (a && a._lazy !== false) {
18441 a.render(a._lazy, false, true);
18445 _lazyTweens.length = 0;
18448 _rootTimeline._startTime = _ticker.time;
18449 _rootFramesTimeline._startTime = _ticker.frame;
18450 _rootTimeline._active = _rootFramesTimeline._active = true;
18451 setTimeout(_lazyRender, 1); //on some mobile devices, there isn't a "tick" before code runs which means any lazy renders wouldn't run before the next official "tick".
18453 Animation._updateRoot = TweenLite.render = function() {
18455 if (_lazyTweens.length) { //if code is run outside of the requestAnimationFrame loop, there may be tweens queued AFTER the engine refreshed, so we need to ensure any pending renders occur before we refresh again.
18458 _rootTimeline.render((_ticker.time - _rootTimeline._startTime) * _rootTimeline._timeScale, false, false);
18459 _rootFramesTimeline.render((_ticker.frame - _rootFramesTimeline._startTime) * _rootFramesTimeline._timeScale, false, false);
18460 if (_lazyTweens.length) {
18463 if (!(_ticker.frame % 120)) { //dump garbage every 120 frames...
18464 for (p in _tweenLookup) {
18465 a = _tweenLookup[p].tweens;
18472 if (a.length === 0) {
18473 delete _tweenLookup[p];
18476 //if there are no more tweens in the root timelines, or if they're all paused, make the _timer sleep to reduce load on the CPU slightly
18477 p = _rootTimeline._first;
18478 if (!p || p._paused) if (TweenLite.autoSleep && !_rootFramesTimeline._first && _ticker._listeners.tick.length === 1) {
18479 while (p && p._paused) {
18489 _ticker.addEventListener("tick", Animation._updateRoot);
18491 var _register = function(target, tween, scrub) {
18492 var id = target._gsTweenID, a, i;
18493 if (!_tweenLookup[id || (target._gsTweenID = id = "t" + (_tweenLookupNum++))]) {
18494 _tweenLookup[id] = {target:target, tweens:[]};
18497 a = _tweenLookup[id].tweens;
18498 a[(i = a.length)] = tween;
18501 if (a[i] === tween) {
18507 return _tweenLookup[id].tweens;
18510 _applyOverwrite = function(target, tween, props, mode, siblings) {
18511 var i, changed, curTween, l;
18512 if (mode === 1 || mode >= 4) {
18513 l = siblings.length;
18514 for (i = 0; i < l; i++) {
18515 if ((curTween = siblings[i]) !== tween) {
18516 if (!curTween._gc) if (curTween._enabled(false, false)) {
18519 } else if (mode === 5) {
18525 //NOTE: Add 0.0000000001 to overcome floating point errors that can cause the startTime to be VERY slightly off (when a tween's time() is set for example)
18526 var startTime = tween._startTime + _tinyNum,
18529 zeroDur = (tween._duration === 0),
18531 i = siblings.length;
18533 if ((curTween = siblings[i]) === tween || curTween._gc || curTween._paused) {
18535 } else if (curTween._timeline !== tween._timeline) {
18536 globalStart = globalStart || _checkOverlap(tween, 0, zeroDur);
18537 if (_checkOverlap(curTween, globalStart, zeroDur) === 0) {
18538 overlaps[oCount++] = curTween;
18540 } else if (curTween._startTime <= startTime) if (curTween._startTime + curTween.totalDuration() / curTween._timeScale > startTime) if (!((zeroDur || !curTween._initted) && startTime - curTween._startTime <= 0.0000000002)) {
18541 overlaps[oCount++] = curTween;
18547 curTween = overlaps[i];
18548 if (mode === 2) if (curTween._kill(props, target)) {
18551 if (mode !== 2 || (!curTween._firstPT && curTween._initted)) {
18552 if (curTween._enabled(false, false)) { //if all property tweens have been overwritten, kill the tween.
18560 _checkOverlap = function(tween, reference, zeroDur) {
18561 var tl = tween._timeline,
18562 ts = tl._timeScale,
18563 t = tween._startTime;
18564 while (tl._timeline) {
18565 t += tl._startTime;
18566 ts *= tl._timeScale;
18573 return (t > reference) ? t - reference : ((zeroDur && t === reference) || (!tween._initted && t - reference < 2 * _tinyNum)) ? _tinyNum : ((t += tween.totalDuration() / tween._timeScale / ts) > reference + _tinyNum) ? 0 : t - reference - _tinyNum;
18577 //---- TweenLite instance methods -----------------------------------------------------------------------------
18579 p._init = function() {
18581 op = this._overwrittenProps,
18582 dur = this._duration,
18583 immediate = !!v.immediateRender,
18585 i, initPlugins, pt, p, startVars;
18587 if (this._startAt) {
18588 this._startAt.render(-1, true); //if we've run a startAt previously (when the tween instantiated), we should revert it so that the values re-instantiate correctly particularly for relative tweens. Without this, a TweenLite.fromTo(obj, 1, {x:"+=100"}, {x:"-=100"}), for example, would actually jump to +=200 because the startAt would run twice, doubling the relative change.
18589 this._startAt.kill();
18592 for (p in v.startAt) { //copy the properties/values into a new object to avoid collisions, like var to = {x:0}, from = {x:500}; timeline.fromTo(e, 1, from, to).fromTo(e, 1, to, from);
18593 startVars[p] = v.startAt[p];
18595 startVars.overwrite = false;
18596 startVars.immediateRender = true;
18597 startVars.lazy = (immediate && v.lazy !== false);
18598 startVars.startAt = startVars.delay = null; //no nesting of startAt objects allowed (otherwise it could cause an infinite loop).
18599 this._startAt = TweenLite.to(this.target, 0, startVars);
18601 if (this._time > 0) {
18602 this._startAt = null; //tweens that render immediately (like most from() and fromTo() tweens) shouldn't revert when their parent timeline's playhead goes backward past the startTime because the initial render could have happened anytime and it shouldn't be directly correlated to this tween's startTime. Imagine setting up a complex animation where the beginning states of various objects are rendered immediately but the tween doesn't happen for quite some time - if we revert to the starting values as soon as the playhead goes backward past the tween's startTime, it will throw things off visually. Reversion should only happen in TimelineLite/Max instances where immediateRender was false (which is the default in the convenience methods like from()).
18603 } else if (dur !== 0) {
18604 return; //we skip initialization here so that overwriting doesn't occur until the tween actually begins. Otherwise, if you create several immediateRender:true tweens of the same target/properties to drop into a TimelineLite or TimelineMax, the last one created would overwrite the first ones because they didn't get placed into the timeline yet before the first render occurs and kicks in overwriting.
18607 } else if (v.runBackwards && dur !== 0) {
18608 //from() tweens must be handled uniquely: their beginning values must be rendered but we don't want overwriting to occur yet (when time is still 0). Wait until the tween actually begins before doing all the routines like overwriting. At that time, we should render at the END of the tween to ensure that things initialize correctly (remember, from() tweens go backwards)
18609 if (this._startAt) {
18610 this._startAt.render(-1, true);
18611 this._startAt.kill();
18612 this._startAt = null;
18615 for (p in v) { //copy props into a new object and skip any reserved props, otherwise onComplete or onUpdate or onStart could fire. We should, however, permit autoCSS to go through.
18616 if (!_reservedProps[p] || p === "autoCSS") {
18621 pt.data = "isFromStart"; //we tag the tween with as "isFromStart" so that if [inside a plugin] we need to only do something at the very END of a tween, we have a way of identifying this tween as merely the one that's setting the beginning values for a "from()" tween. For example, clearProps in CSSPlugin should only get applied at the very END of a tween and without this tag, from(...{height:100, clearProps:"height", delay:1}) would wipe the height at the beginning of the tween and after 1 second, it'd kick back in.
18622 pt.lazy = (immediate && v.lazy !== false);
18623 pt.immediateRender = immediate; //zero-duration tweens render immediately by default, but if we're not specifically instructed to render this tween immediately, we should skip this and merely _init() to record the starting values (rendering them immediately would push them to completion which is wasteful in that case - we'd have to render(-1) immediately after)
18624 this._startAt = TweenLite.to(this.target, 0, pt);
18626 this._startAt._init(); //ensures that the initial values are recorded
18627 this._startAt._enabled(false); //no need to have the tween render on the next cycle. Disable it because we'll always manually control the renders of the _startAt tween.
18628 } else if (this._time === 0) {
18634 this._ease = TweenLite.defaultEase;
18635 } else if (ease instanceof Ease) {
18636 this._ease = (v.easeParams instanceof Array) ? ease.config.apply(ease, v.easeParams) : ease;
18638 this._ease = (typeof(ease) === "function") ? new Ease(ease, v.easeParams) : _easeMap[ease] || TweenLite.defaultEase;
18640 this._easeType = this._ease._type;
18641 this._easePower = this._ease._power;
18642 this._firstPT = null;
18644 if (this._targets) {
18645 i = this._targets.length;
18647 if ( this._initProps( this._targets[i], (this._propLookup[i] = {}), this._siblings[i], (op ? op[i] : null)) ) {
18648 initPlugins = true;
18652 initPlugins = this._initProps(this.target, this._propLookup, this._siblings, op);
18656 TweenLite._onPluginEvent("_onInitAllProps", this); //reorders the array in order of priority. Uses a static TweenPlugin method in order to minimize file size in TweenLite
18658 if (op) if (!this._firstPT) if (typeof(this.target) !== "function") { //if all tweening properties have been overwritten, kill the tween. If the target is a function, it's probably a delayedCall so let it live.
18659 this._enabled(false, false);
18661 if (v.runBackwards) {
18662 pt = this._firstPT;
18669 this._onUpdate = v.onUpdate;
18670 this._initted = true;
18673 p._initProps = function(target, propLookup, siblings, overwrittenProps) {
18674 var p, i, initPlugins, plugin, pt, v;
18675 if (target == null) {
18679 if (_lazyLookup[target._gsTweenID]) {
18680 _lazyRender(); //if other tweens of the same target have recently initted but haven't rendered yet, we've got to force the render so that the starting values are correct (imagine populating a timeline with a bunch of sequential tweens and then jumping to the end)
18683 if (!this.vars.css) if (target.style) if (target !== window && target.nodeType) if (_plugins.css) if (this.vars.autoCSS !== false) { //it's so common to use TweenLite/Max to animate the css of DOM elements, we assume that if the target is a DOM element, that's what is intended (a convenience so that users don't have to wrap things in css:{}, although we still recommend it for a slight performance boost and better specificity). Note: we cannot check "nodeType" on the window inside an iframe.
18684 _autoCSS(this.vars, target);
18686 for (p in this.vars) {
18688 if (_reservedProps[p]) {
18689 if (v) if ((v instanceof Array) || (v.push && _isArray(v))) if (v.join("").indexOf("{self}") !== -1) {
18690 this.vars[p] = v = this._swapSelfInParams(v, this);
18693 } else if (_plugins[p] && (plugin = new _plugins[p]())._onInitTween(target, this.vars[p], this)) {
18695 //t - target [object]
18696 //p - property [string]
18697 //s - start [number]
18698 //c - change [number]
18699 //f - isFunction [boolean]
18700 //n - name [string]
18701 //pg - isPlugin [boolean]
18702 //pr - priority [number]
18703 this._firstPT = pt = {_next:this._firstPT, t:plugin, p:"setRatio", s:0, c:1, f:true, n:p, pg:true, pr:plugin._priority};
18704 i = plugin._overwriteProps.length;
18706 propLookup[plugin._overwriteProps[i]] = this._firstPT;
18708 if (plugin._priority || plugin._onInitAllProps) {
18709 initPlugins = true;
18711 if (plugin._onDisable || plugin._onEnable) {
18712 this._notifyPluginsOfEnabled = true;
18716 this._firstPT = propLookup[p] = pt = {_next:this._firstPT, t:target, p:p, f:(typeof(target[p]) === "function"), n:p, pg:false, pr:0};
18717 pt.s = (!pt.f) ? parseFloat(target[p]) : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]();
18718 pt.c = (typeof(v) === "string" && v.charAt(1) === "=") ? parseInt(v.charAt(0) + "1", 10) * Number(v.substr(2)) : (Number(v) - pt.s) || 0;
18720 if (pt) if (pt._next) {
18721 pt._next._prev = pt;
18725 if (overwrittenProps) if (this._kill(overwrittenProps, target)) { //another tween may have tried to overwrite properties of this tween before init() was called (like if two tweens start at the same time, the one created second will run first)
18726 return this._initProps(target, propLookup, siblings, overwrittenProps);
18728 if (this._overwrite > 1) if (this._firstPT) if (siblings.length > 1) if (_applyOverwrite(target, this, propLookup, this._overwrite, siblings)) {
18729 this._kill(propLookup, target);
18730 return this._initProps(target, propLookup, siblings, overwrittenProps);
18732 if (this._firstPT) if ((this.vars.lazy !== false && this._duration) || (this.vars.lazy && !this._duration)) { //zero duration tweens don't lazy render by default; everything else does.
18733 _lazyLookup[target._gsTweenID] = true;
18735 return initPlugins;
18738 p.render = function(time, suppressEvents, force) {
18739 var prevTime = this._time,
18740 duration = this._duration,
18741 prevRawPrevTime = this._rawPrevTime,
18742 isComplete, callback, pt, rawPrevTime;
18743 if (time >= duration) {
18744 this._totalTime = this._time = duration;
18745 this.ratio = this._ease._calcEnd ? this._ease.getRatio(1) : 1;
18746 if (!this._reversed ) {
18748 callback = "onComplete";
18750 if (duration === 0) if (this._initted || !this.vars.lazy || force) { //zero-duration tweens are tricky because we must discern the momentum/direction of time in order to determine whether the starting values should be rendered or the ending values. If the "playhead" of its timeline goes past the zero-duration tween in the forward direction or lands directly on it, the end values should be rendered, but if the timeline's "playhead" moves past it in the backward direction (from a postitive time to a negative time), the starting values must be rendered.
18751 if (this._startTime === this._timeline._duration) { //if a zero-duration tween is at the VERY end of a timeline and that timeline renders at its end, it will typically add a tiny bit of cushion to the render time to prevent rounding errors from getting in the way of tweens rendering their VERY end. If we then reverse() that timeline, the zero-duration tween will trigger its onReverseComplete even though technically the playhead didn't pass over it again. It's a very specific edge case we must accommodate.
18754 if (time === 0 || prevRawPrevTime < 0 || prevRawPrevTime === _tinyNum) if (prevRawPrevTime !== time) {
18756 if (prevRawPrevTime > _tinyNum) {
18757 callback = "onReverseComplete";
18760 this._rawPrevTime = rawPrevTime = (!suppressEvents || time || prevRawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
18763 } else if (time < 0.0000001) { //to work around occasional floating point math artifacts, round super small values to 0.
18764 this._totalTime = this._time = 0;
18765 this.ratio = this._ease._calcEnd ? this._ease.getRatio(0) : 0;
18766 if (prevTime !== 0 || (duration === 0 && prevRawPrevTime > 0 && prevRawPrevTime !== _tinyNum)) {
18767 callback = "onReverseComplete";
18768 isComplete = this._reversed;
18771 this._active = false;
18772 if (duration === 0) if (this._initted || !this.vars.lazy || force) { //zero-duration tweens are tricky because we must discern the momentum/direction of time in order to determine whether the starting values should be rendered or the ending values. If the "playhead" of its timeline goes past the zero-duration tween in the forward direction or lands directly on it, the end values should be rendered, but if the timeline's "playhead" moves past it in the backward direction (from a postitive time to a negative time), the starting values must be rendered.
18773 if (prevRawPrevTime >= 0) {
18776 this._rawPrevTime = rawPrevTime = (!suppressEvents || time || prevRawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
18778 } else if (!this._initted) { //if we render the very beginning (time == 0) of a fromTo(), we must force the render (normal tweens wouldn't need to render at a time of 0 when the prevTime was also 0). This is also mandatory to make sure overwriting kicks in immediately.
18782 this._totalTime = this._time = time;
18784 if (this._easeType) {
18785 var r = time / duration, type = this._easeType, pow = this._easePower;
18786 if (type === 1 || (type === 3 && r >= 0.5)) {
18794 } else if (pow === 2) {
18796 } else if (pow === 3) {
18798 } else if (pow === 4) {
18799 r *= r * r * r * r;
18803 this.ratio = 1 - r;
18804 } else if (type === 2) {
18806 } else if (time / duration < 0.5) {
18807 this.ratio = r / 2;
18809 this.ratio = 1 - (r / 2);
18813 this.ratio = this._ease.getRatio(time / duration);
18817 if (this._time === prevTime && !force) {
18819 } else if (!this._initted) {
18821 if (!this._initted || this._gc) { //immediateRender tweens typically won't initialize until the playhead advances (_time is greater than 0) in order to ensure that overwriting occurs properly. Also, if all of the tweening properties have been overwritten (which would cause _gc to be true, as set in _init()), we shouldn't continue otherwise an onStart callback could be called for example.
18823 } else if (!force && this._firstPT && ((this.vars.lazy !== false && this._duration) || (this.vars.lazy && !this._duration))) {
18824 this._time = this._totalTime = prevTime;
18825 this._rawPrevTime = prevRawPrevTime;
18826 _lazyTweens.push(this);
18830 //_ease is initially set to defaultEase, so now that init() has run, _ease is set properly and we need to recalculate the ratio. Overall this is faster than using conditional logic earlier in the method to avoid having to set ratio twice because we only init() once but renderTime() gets called VERY frequently.
18831 if (this._time && !isComplete) {
18832 this.ratio = this._ease.getRatio(this._time / duration);
18833 } else if (isComplete && this._ease._calcEnd) {
18834 this.ratio = this._ease.getRatio((this._time === 0) ? 0 : 1);
18837 if (this._lazy !== false) { //in case a lazy render is pending, we should flush it because the new render is occuring now (imagine a lazy tween instantiating and then immediately the user calls tween.seek(tween.duration()), skipping to the end - the end render would be forced, and then if we didn't flush the lazy render, it'd fire AFTER the seek(), rendering it at the wrong time.
18838 this._lazy = false;
18840 if (!this._active) if (!this._paused && this._time !== prevTime && time >= 0) {
18841 this._active = true; //so that if the user renders a tween (as opposed to the timeline rendering it), the timeline is forced to re-render and align it with the proper time/frame on the next rendering cycle. Maybe the tween already finished but the user manually re-renders it as halfway done.
18843 if (prevTime === 0) {
18844 if (this._startAt) {
18846 this._startAt.render(time, suppressEvents, force);
18847 } else if (!callback) {
18848 callback = "_dummyGS"; //if no callback is defined, use a dummy value just so that the condition at the end evaluates as true because _startAt should render AFTER the normal render loop when the time is negative. We could handle this in a more intuitive way, of course, but the render loop is the MOST important thing to optimize, so this technique allows us to avoid adding extra conditional logic in a high-frequency area.
18851 if (this.vars.onStart) if (this._time !== 0 || duration === 0) if (!suppressEvents) {
18852 this.vars.onStart.apply(this.vars.onStartScope || this, this.vars.onStartParams || _blankArray);
18856 pt = this._firstPT;
18859 pt.t[pt.p](pt.c * this.ratio + pt.s);
18861 pt.t[pt.p] = pt.c * this.ratio + pt.s;
18866 if (this._onUpdate) {
18867 if (time < 0) if (this._startAt && this._startTime) { //if the tween is positioned at the VERY beginning (_startTime 0) of its parent timeline, it's illegal for the playhead to go back further, so we should not render the recorded startAt values.
18868 this._startAt.render(time, suppressEvents, force); //note: for performance reasons, we tuck this conditional logic inside less traveled areas (most tweens don't have an onUpdate). We'd just have it at the end before the onComplete, but the values should be updated before any onUpdate is called, so we ALSO put it here and then if it's not called, we do so later near the onComplete.
18870 if (!suppressEvents) if (this._time !== prevTime || isComplete) {
18871 this._onUpdate.apply(this.vars.onUpdateScope || this, this.vars.onUpdateParams || _blankArray);
18875 if (callback) if (!this._gc) { //check _gc because there's a chance that kill() could be called in an onUpdate
18876 if (time < 0 && this._startAt && !this._onUpdate && this._startTime) { //if the tween is positioned at the VERY beginning (_startTime 0) of its parent timeline, it's illegal for the playhead to go back further, so we should not render the recorded startAt values.
18877 this._startAt.render(time, suppressEvents, force);
18880 if (this._timeline.autoRemoveChildren) {
18881 this._enabled(false, false);
18883 this._active = false;
18885 if (!suppressEvents && this.vars[callback]) {
18886 this.vars[callback].apply(this.vars[callback + "Scope"] || this, this.vars[callback + "Params"] || _blankArray);
18888 if (duration === 0 && this._rawPrevTime === _tinyNum && rawPrevTime !== _tinyNum) { //the onComplete or onReverseComplete could trigger movement of the playhead and for zero-duration tweens (which must discern direction) that land directly back on their start time, we don't want to fire again on the next render. Think of several addPause()'s in a timeline that forces the playhead to a certain spot, but what if it's already paused and another tween is tweening the "time" of the timeline? Each time it moves [forward] past that spot, it would move back, and since suppressEvents is true, it'd reset _rawPrevTime to _tinyNum so that when it begins again, the callback would fire (so ultimately it could bounce back and forth during that tween). Again, this is a very uncommon scenario, but possible nonetheless.
18889 this._rawPrevTime = 0;
18895 p._kill = function(vars, target) {
18896 if (vars === "all") {
18899 if (vars == null) if (target == null || target === this.target) {
18900 this._lazy = false;
18901 return this._enabled(false, false);
18903 target = (typeof(target) !== "string") ? (target || this._targets || this.target) : TweenLite.selector(target) || target;
18904 var i, overwrittenProps, p, pt, propLookup, changed, killProps, record;
18905 if ((_isArray(target) || _isSelector(target)) && typeof(target[0]) !== "number") {
18908 if (this._kill(vars, target[i])) {
18913 if (this._targets) {
18914 i = this._targets.length;
18916 if (target === this._targets[i]) {
18917 propLookup = this._propLookup[i] || {};
18918 this._overwrittenProps = this._overwrittenProps || [];
18919 overwrittenProps = this._overwrittenProps[i] = vars ? this._overwrittenProps[i] || {} : "all";
18923 } else if (target !== this.target) {
18926 propLookup = this._propLookup;
18927 overwrittenProps = this._overwrittenProps = vars ? this._overwrittenProps || {} : "all";
18931 killProps = vars || propLookup;
18932 record = (vars !== overwrittenProps && overwrittenProps !== "all" && vars !== propLookup && (typeof(vars) !== "object" || !vars._tempKill)); //_tempKill is a super-secret way to delete a particular tweening property but NOT have it remembered as an official overwritten property (like in BezierPlugin)
18933 for (p in killProps) {
18934 if ((pt = propLookup[p])) {
18935 if (pt.pg && pt.t._kill(killProps)) {
18936 changed = true; //some plugins need to be notified so they can perform cleanup tasks first
18938 if (!pt.pg || pt.t._overwriteProps.length === 0) {
18940 pt._prev._next = pt._next;
18941 } else if (pt === this._firstPT) {
18942 this._firstPT = pt._next;
18945 pt._next._prev = pt._prev;
18947 pt._next = pt._prev = null;
18949 delete propLookup[p];
18952 overwrittenProps[p] = 1;
18955 if (!this._firstPT && this._initted) { //if all tweening properties are killed, kill the tween. Without this line, if there's a tween with multiple targets and then you killTweensOf() each target individually, the tween would technically still remain active and fire its onComplete even though there aren't any more properties tweening.
18956 this._enabled(false, false);
18963 p.invalidate = function() {
18964 if (this._notifyPluginsOfEnabled) {
18965 TweenLite._onPluginEvent("_onDisable", this);
18967 this._firstPT = null;
18968 this._overwrittenProps = null;
18969 this._onUpdate = null;
18970 this._startAt = null;
18971 this._initted = this._active = this._notifyPluginsOfEnabled = this._lazy = false;
18972 this._propLookup = (this._targets) ? {} : [];
18976 p._enabled = function(enabled, ignoreTimeline) {
18977 if (!_tickerActive) {
18980 if (enabled && this._gc) {
18981 var targets = this._targets,
18984 i = targets.length;
18986 this._siblings[i] = _register(targets[i], this, true);
18989 this._siblings = _register(this.target, this, true);
18992 Animation.prototype._enabled.call(this, enabled, ignoreTimeline);
18993 if (this._notifyPluginsOfEnabled) if (this._firstPT) {
18994 return TweenLite._onPluginEvent((enabled ? "_onEnable" : "_onDisable"), this);
19000 //----TweenLite static methods -----------------------------------------------------
19002 TweenLite.to = function(target, duration, vars) {
19003 return new TweenLite(target, duration, vars);
19006 TweenLite.from = function(target, duration, vars) {
19007 vars.runBackwards = true;
19008 vars.immediateRender = (vars.immediateRender != false);
19009 return new TweenLite(target, duration, vars);
19012 TweenLite.fromTo = function(target, duration, fromVars, toVars) {
19013 toVars.startAt = fromVars;
19014 toVars.immediateRender = (toVars.immediateRender != false && fromVars.immediateRender != false);
19015 return new TweenLite(target, duration, toVars);
19018 TweenLite.delayedCall = function(delay, callback, params, scope, useFrames) {
19019 return new TweenLite(callback, 0, {delay:delay, onComplete:callback, onCompleteParams:params, onCompleteScope:scope, onReverseComplete:callback, onReverseCompleteParams:params, onReverseCompleteScope:scope, immediateRender:false, useFrames:useFrames, overwrite:0});
19022 TweenLite.set = function(target, vars) {
19023 return new TweenLite(target, 0, vars);
19026 TweenLite.getTweensOf = function(target, onlyActive) {
19027 if (target == null) { return []; }
19028 target = (typeof(target) !== "string") ? target : TweenLite.selector(target) || target;
19030 if ((_isArray(target) || _isSelector(target)) && typeof(target[0]) !== "number") {
19034 a = a.concat(TweenLite.getTweensOf(target[i], onlyActive));
19037 //now get rid of any duplicates (tweens of arrays of objects could cause duplicates)
19048 a = _register(target).concat();
19051 if (a[i]._gc || (onlyActive && !a[i].isActive())) {
19059 TweenLite.killTweensOf = TweenLite.killDelayedCallsTo = function(target, onlyActive, vars) {
19060 if (typeof(onlyActive) === "object") {
19061 vars = onlyActive; //for backwards compatibility (before "onlyActive" parameter was inserted)
19062 onlyActive = false;
19064 var a = TweenLite.getTweensOf(target, onlyActive),
19067 a[i]._kill(vars, target);
19074 * ----------------------------------------------------------------
19075 * TweenPlugin (could easily be split out as a separate file/class, but included for ease of use (so that people don't need to include another <script> call before loading plugins which is easy to forget)
19076 * ----------------------------------------------------------------
19078 var TweenPlugin = _class("plugins.TweenPlugin", function(props, priority) {
19079 this._overwriteProps = (props || "").split(",");
19080 this._propName = this._overwriteProps[0];
19081 this._priority = priority || 0;
19082 this._super = TweenPlugin.prototype;
19085 p = TweenPlugin.prototype;
19086 TweenPlugin.version = "1.10.1";
19087 TweenPlugin.API = 2;
19090 p._addTween = function(target, prop, start, end, overwriteProp, round) {
19092 if (end != null && (c = (typeof(end) === "number" || end.charAt(1) !== "=") ? Number(end) - start : parseInt(end.charAt(0) + "1", 10) * Number(end.substr(2)))) {
19093 this._firstPT = pt = {_next:this._firstPT, t:target, p:prop, s:start, c:c, f:(typeof(target[prop]) === "function"), n:overwriteProp || prop, r:round};
19095 pt._next._prev = pt;
19101 p.setRatio = function(v) {
19102 var pt = this._firstPT,
19106 val = pt.c * v + pt.s;
19108 val = Math.round(val);
19109 } else if (val < min) if (val > -min) { //prevents issues with converting very small numbers to strings in the browser
19121 p._kill = function(lookup) {
19122 var a = this._overwriteProps,
19123 pt = this._firstPT,
19125 if (lookup[this._propName] != null) {
19126 this._overwriteProps = [];
19130 if (lookup[a[i]] != null) {
19136 if (lookup[pt.n] != null) {
19138 pt._next._prev = pt._prev;
19141 pt._prev._next = pt._next;
19143 } else if (this._firstPT === pt) {
19144 this._firstPT = pt._next;
19152 p._roundProps = function(lookup, value) {
19153 var pt = this._firstPT;
19155 if (lookup[this._propName] || (pt.n != null && lookup[ pt.n.split(this._propName + "_").join("") ])) { //some properties that are very plugin-specific add a prefix named after the _propName plus an underscore, so we need to ignore that extra stuff here.
19162 TweenLite._onPluginEvent = function(type, tween) {
19163 var pt = tween._firstPT,
19164 changed, pt2, first, last, next;
19165 if (type === "_onInitAllProps") {
19166 //sorts the PropTween linked list in order of priority because some plugins need to render earlier/later than others, like MotionBlurPlugin applies its effects after all x/y/alpha tweens have rendered on each frame.
19170 while (pt2 && pt2.pr > pt.pr) {
19173 if ((pt._prev = pt2 ? pt2._prev : last)) {
19174 pt._prev._next = pt;
19178 if ((pt._next = pt2)) {
19185 pt = tween._firstPT = first;
19188 if (pt.pg) if (typeof(pt.t[type]) === "function") if (pt.t[type]()) {
19196 TweenPlugin.activate = function(plugins) {
19197 var i = plugins.length;
19199 if (plugins[i].API === TweenPlugin.API) {
19200 _plugins[(new plugins[i]())._propName] = plugins[i];
19206 //provides a more concise way to define plugins that have no dependencies besides TweenPlugin and TweenLite, wrapping common boilerplate stuff into one function (added in 1.9.0). You don't NEED to use this to define a plugin - the old way still works and can be useful in certain (rare) situations.
19207 _gsDefine.plugin = function(config) {
19208 if (!config || !config.propName || !config.init || !config.API) { throw "illegal plugin definition."; }
19209 var propName = config.propName,
19210 priority = config.priority || 0,
19211 overwriteProps = config.overwriteProps,
19212 map = {init:"_onInitTween", set:"setRatio", kill:"_kill", round:"_roundProps", initAll:"_onInitAllProps"},
19213 Plugin = _class("plugins." + propName.charAt(0).toUpperCase() + propName.substr(1) + "Plugin",
19215 TweenPlugin.call(this, propName, priority);
19216 this._overwriteProps = overwriteProps || [];
19217 }, (config.global === true)),
19218 p = Plugin.prototype = new TweenPlugin(propName),
19220 p.constructor = Plugin;
19221 Plugin.API = config.API;
19222 for (prop in map) {
19223 if (typeof(config[prop]) === "function") {
19224 p[map[prop]] = config[prop];
19227 Plugin.version = config.version;
19228 TweenPlugin.activate([Plugin]);
19233 //now run through all the dependencies discovered and if any are missing, log that to the console as a warning. This is why it's best to have TweenLite load last - it can check all the dependencies for you.
19234 a = window._gsQueue;
19236 for (i = 0; i < a.length; i++) {
19239 for (p in _defLookup) {
19240 if (!_defLookup[p].func) {
19241 //window.console.log("GSAP encountered missing dependency: com.greensock." + p);
19246 _tickerActive = false; //ensures that the first official animation forces a ticker.tick() to update the time when it is instantiated
19250 angular.module('b2b.att.collapse', ['b2b.att.transition'])
19252 // The collapsible directive indicates a block of html that will expand and collapse
19253 .directive('b2bCollapse', ['$transition', function($transition) {
19254 // CSS transitions don't work with height: auto, so we have to manually change the height to a
19255 // specific value and then once the animation completes, we can reset the height to auto.
19256 // Unfortunately if you do this while the CSS transitions are specified (i.e. in the CSS class
19257 // "collapse") then you trigger a change to height 0 in between.
19258 // The fix is to remove the "collapse" CSS class while changing the height back to auto - phew!
19263 marginBottom: null,
19265 paddingBottom: null,
19277 var fixUpHeight = function(scope, element, height) {
19278 // We remove the collapse CSS class to prevent a transition when we change to height: auto
19279 element.removeClass('b2bCollapse');
19280 element.css({height: height});
19281 //adjusting for any margin or padding
19282 if (height === 0) {
19283 element.css(props.closed);
19285 element.css(props.open);
19287 // It appears that reading offsetWidth makes the browser realise that we have changed the
19288 // height already :-/
19289 var x = element[0].offsetWidth;
19290 element.addClass('b2bCollapse');
19294 link: function(scope, element, attrs) {
19296 var initialAnimSkip = true;
19297 scope.$watch(function() {
19298 return element[0].scrollHeight;
19299 }, function(value) {
19300 //The listener is called when scrollHeight changes
19301 //It actually does on 2 scenarios:
19302 // 1. Parent is set to display none
19303 // 2. angular bindings inside are resolved
19304 //When we have a change of scrollHeight we are setting again the correct height if the group is opened
19305 if (element[0].scrollHeight !== 0) {
19306 if (!isCollapsed) {
19307 if (initialAnimSkip) {
19308 fixUpHeight(scope, element, element[0].scrollHeight + 'px');
19310 fixUpHeight(scope, element, 'auto');
19311 element.css({overflow: 'visible'});
19317 scope.$watch(attrs.b2bCollapse, function(value) {
19326 var currentTransition;
19327 var doTransition = function(change) {
19328 if (currentTransition) {
19329 currentTransition.cancel();
19331 currentTransition = $transition(element, change);
19332 currentTransition.then(
19334 currentTransition = undefined;
19337 currentTransition = undefined;
19340 return currentTransition;
19343 var expand = function() {
19344 scope.postTransition = true;
19345 if (initialAnimSkip) {
19346 initialAnimSkip = false;
19347 if (!isCollapsed) {
19348 fixUpHeight(scope, element, 'auto');
19351 //doTransition({ height : element[0].scrollHeight + 'px' })
19352 doTransition(angular.extend({height: element[0].scrollHeight + 'px'}, props.open))
19354 // This check ensures that we don't accidentally update the height if the user has closed
19355 // the group while the animation was still running
19356 if (!isCollapsed) {
19357 fixUpHeight(scope, element, 'auto');
19361 isCollapsed = false;
19364 var collapse = function() {
19365 isCollapsed = true;
19366 if (initialAnimSkip) {
19367 initialAnimSkip = false;
19368 fixUpHeight(scope, element, 0);
19370 fixUpHeight(scope, element, element[0].scrollHeight + 'px');
19371 doTransition(angular.extend({height: 0}, props.closed)).then(function() {
19372 scope.postTransition = false;
19374 element.css({overflow: 'hidden'});
19380 angular.module('b2b.att.position', [])
19382 .factory('$position', ['$document', '$window', function ($document, $window) {
19383 function getStyle(el, cssprop) {
19384 if (el.currentStyle) { //IE
19385 return el.currentStyle[cssprop];
19386 } else if ($window.getComputedStyle) {
19387 return $window.getComputedStyle(el)[cssprop];
19389 // finally try and get inline style
19390 return el.style[cssprop];
19394 * Checks if a given element is statically positioned
19395 * @param element - raw DOM element
19397 function isStaticPositioned(element) {
19398 return (getStyle(element, "position") || 'static') === 'static';
19402 * returns the closest, non-statically positioned parentOffset of a given element
19405 var parentOffsetEl = function (element) {
19406 var docDomEl = $document[0];
19407 var offsetParent = element.offsetParent || docDomEl;
19408 while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent)) {
19409 offsetParent = offsetParent.offsetParent;
19411 return offsetParent || docDomEl;
19416 * Provides read-only equivalent of jQuery's position function:
19417 * http://api.jquery.com/position/
19419 position: function (element) {
19420 var elBCR = this.offset(element);
19421 var offsetParentBCR = {
19425 var offsetParentEl = parentOffsetEl(element[0]);
19426 if (offsetParentEl !== $document[0]) {
19427 offsetParentBCR = this.offset(angular.element(offsetParentEl));
19428 offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
19429 offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
19433 width: element.prop('offsetWidth'),
19434 height: element.prop('offsetHeight'),
19435 top: elBCR.top - offsetParentBCR.top,
19436 left: elBCR.left - offsetParentBCR.left
19441 * Provides read-only equivalent of jQuery's offset function:
19442 * http://api.jquery.com/offset/
19444 offset: function (element) {
19445 var boundingClientRect = element[0].getBoundingClientRect();
19447 width: element.prop('offsetWidth'),
19448 height: element.prop('offsetHeight'),
19449 top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
19450 left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)
19455 * Provides functionality to check whether an element is in view port.
19457 isElementInViewport: function (element) {
19459 var rect = element[0].getBoundingClientRect();
19463 rect.bottom <= ($window.innerHeight || $document[0].documentElement.clientHeight) &&
19464 rect.right <= ($window.innerWidth || $document[0].documentElement.clientWidth)
19473 .factory('$isElement', [function () {
19474 var isElement = function (currentElem, targetElem, alternateElem) {
19475 if (currentElem[0] === targetElem[0]) {
19477 } else if (currentElem[0] === alternateElem[0]) {
19480 return isElement((currentElem.parent()[0] && currentElem.parent()) || targetElem, targetElem, alternateElem);
19487 .directive('attPosition', ['$position', function ($position) {
19490 link: function (scope, elem, attr) {
19491 scope.$watchCollection(function () {
19492 return $position.position(elem);
19493 }, function (value) {
19494 scope[attr.attPosition] = value;
19500 angular.module('b2b.att.transition', [])
19502 .factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
19504 var $transition = function(element, trigger, options) {
19505 options = options || {};
19506 var deferred = $q.defer();
19507 var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
19509 var transitionEndHandler = function() {
19510 $rootScope.$apply(function() {
19511 element.unbind(endEventName, transitionEndHandler);
19512 deferred.resolve(element);
19516 if (endEventName) {
19517 element.bind(endEventName, transitionEndHandler);
19520 // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
19521 $timeout(function() {
19522 if ( angular.isString(trigger) ) {
19523 element.addClass(trigger);
19524 } else if ( angular.isFunction(trigger) ) {
19526 } else if ( angular.isObject(trigger) ) {
19527 element.css(trigger);
19529 //If browser does not support transitions, instantly resolve
19530 if ( !endEventName ) {
19531 deferred.resolve(element);
19535 // Add our custom cancel function to the promise that is returned
19536 // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
19537 // i.e. it will therefore never raise a transitionEnd event for that transition
19538 deferred.promise.cancel = function() {
19539 if ( endEventName ) {
19540 element.unbind(endEventName, transitionEndHandler);
19542 deferred.reject('Transition cancelled');
19545 return deferred.promise;
19548 // Work out the name of the transitionEnd event
19549 var transElement = document.createElement('trans');
19550 var transitionEndEventNames = {
19551 'WebkitTransition': 'webkitTransitionEnd',
19552 'MozTransition': 'transitionend',
19553 'OTransition': 'oTransitionEnd',
19554 'transition': 'transitionend'
19556 var animationEndEventNames = {
19557 'WebkitTransition': 'webkitAnimationEnd',
19558 'MozTransition': 'animationend',
19559 'OTransition': 'oAnimationEnd',
19560 'transition': 'animationend'
19562 function findEndEventName(endEventNames) {
19563 for (var name in endEventNames){
19564 if (transElement.style[name] !== undefined) {
19565 return endEventNames[name];
19569 $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
19570 $transition.animationEndEventName = findEndEventName(animationEndEventNames);
19571 return $transition;
19574 .factory('$scrollTo', ['$window', function($window) {
19575 var $scrollTo = function(offsetLeft, offsetTop, duration) {
19576 TweenMax.to($window, duration || 1, {scrollTo: {y: offsetTop, x: offsetLeft}, ease: Power4.easeOut});
19580 .factory('animation', function(){
19583 .factory('$progressBar', function(){
19585 //Provides a function to pass in code for closure purposes
19586 var loadingAnimationCreator = function(onUpdateCallback){
19588 //Use closure to setup some resuable code
19589 var loadingAnimation = function(callback, duration){
19590 TweenMax.to({}, duration, {
19591 onUpdateParams: ["{self}"],
19592 onUpdate: onUpdateCallback,
19593 onComplete: callback
19596 //Returns a function that takes a callback function and a duration for the animation
19597 return (function(){
19598 return loadingAnimation;
19602 return loadingAnimationCreator;
19604 .factory('$height', function(){
19605 var heightAnimation = function(element,duration,height,alpha){
19606 TweenMax.to(element,
19608 {height:height, autoAlpha:alpha},
19611 return heightAnimation;
19613 angular.module('b2b.att.utilities', ['ngSanitize'])
19614 .constant('b2bUtilitiesConfig', {
19621 enableSearch: false,
19623 circularTraversal: false
19625 .constant('b2bWhenScrollEndsConstants', {
19630 // All breakpoints ranges from >= min and < max
19631 .constant('b2bAwdBreakpoints', {
19647 .filter('groupBy', function ($timeout) {
19648 //Custom GroupBy Filter for treeNav, returns key string and value.childarray as set of grouped elements
19649 return function (data, key) {
19650 if (!key) return data;
19651 var outputPropertyName = '__groupBy__' + key;
19652 if (!data[outputPropertyName]) {
19654 for (var i = 0; i < data.length; i++) {
19655 if (!result[data[i][key]])
19656 result[data[i][key]] = {};
19657 if (!result[data[i][key]].childArray) {
19658 result[data[i][key]].childArray = [];
19660 result[data[i][key]].childArray.push(data[i]);
19661 if (data[i].activeGrp && data[i].activeGrp == true) {
19662 result[data[i][key]].showGroup = true;
19665 Object.defineProperty(result, 'length', {enumerable: false,value: Object.keys(result).length});
19666 Object.defineProperty(data, outputPropertyName, {enumerable: false,configurable: true,writable:false,value:result});
19667 $timeout(function(){delete data[outputPropertyName];},0,false);
19669 return data[outputPropertyName];
19672 .filter('searchObjectPropertiesFilter', [function() {
19673 return function(items, searchText, attrs) {
19678 searchText = searchText.toLowerCase();
19679 angular.forEach(items, function(item) {
19680 angular.forEach(attrs, function(attr) {
19681 if (item.hasOwnProperty(attr) && (item[attr].toLowerCase().indexOf(searchText) != -1)) {
19682 filtered.push(item);
19690 .filter('unsafe',[ '$sce', function ($sce) {
19691 return function(val){
19692 return $sce.trustAsHtml(val);
19695 .filter('b2bHighlight', function () {
19696 function escapeRegexp(queryToEscape) {
19697 return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
19699 return function (matchItem, query, className) {
19700 return query && matchItem ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<span class=\"' + className + '\">$&</span>') : matchItem;
19704 Copyright © 2013 Matt Diamond
19705 https://github.com/cwilso/AudioRecorder/blob/master/js/recorderjs/recorder.js
19707 .factory('b2bRecorder', function() {
19709 var Recorder = function(source, cfg) {
19710 var WORKER_PATH = 'recorderWorker.js';
19711 var config = cfg || {};
19712 var bufferLen = config.bufferLen || 4096;
19713 this.context = source.context;
19714 if(!this.context.createScriptProcessor) {
19715 this.node = this.context.createJavacriptProcessor(bufferLen, 2, 2);
19717 this.node = this.context.createScriptProcessor(bufferLen, 2, 2);
19719 var workerCode = 'function init(a){sampleRate=a.sampleRate}function record(a){recBuffersL.push(a[0]),recBuffersR.push(a[1]),recLength+=a[0].length}function exportWAV(a){var b=mergeBuffers(recBuffersL,recLength),c=mergeBuffers(recBuffersR,recLength),d=interleave(b,c),e=encodeWAV(d),f=new Blob([e],{type:a});this.postMessage(f)}function exportMonoWAV(a){var b=mergeBuffers(recBuffersL,recLength),c=encodeWAV(b,!0),d=new Blob([c],{type:a});this.postMessage(d)}function getBuffers(){var a=[];a.push(mergeBuffers(recBuffersL,recLength)),a.push(mergeBuffers(recBuffersR,recLength)),this.postMessage(a)}function clear(){recLength=0,recBuffersL=[],recBuffersR=[]}function mergeBuffers(a,b){for(var c=new Float32Array(b),d=0,e=0;e<a.length;e++)c.set(a[e],d),d+=a[e].length;return c}function interleave(a,b){for(var c=a.length+b.length,d=new Float32Array(c),e=0,f=0;e<c;)d[e++]=a[f],d[e++]=b[f],f++;return d}function floatTo16BitPCM(a,b,c){for(var d=0;d<c.length;d++,b+=2){var e=Math.max(-1,Math.min(1,c[d]));a.setInt16(b,e<0?32768*e:32767*e,!0)}}function writeString(a,b,c){for(var d=0;d<c.length;d++)a.setUint8(b+d,c.charCodeAt(d))}function encodeWAV(a,b){var c=new ArrayBuffer(44+2*a.length),d=new DataView(c);return writeString(d,0,"RIFF"),d.setUint32(4,32+2*a.length,!0),writeString(d,8,"WAVE"),writeString(d,12,"fmt "),d.setUint32(16,16,!0),d.setUint16(20,1,!0),d.setUint16(22,b?1:2,!0),d.setUint32(24,sampleRate,!0),d.setUint32(28,4*sampleRate,!0),d.setUint16(32,4,!0),d.setUint16(34,16,!0),writeString(d,36,"data"),d.setUint32(40,2*a.length,!0),floatTo16BitPCM(d,44,a),d}var recLength=0,recBuffersL=[],recBuffersR=[],sampleRate;this.onmessage=function(a){switch(a.data.command){case"init":init(a.data.config);break;case"record":record(a.data.buffer);break;case"exportWAV":exportWAV(a.data.type);break;case"exportMonoWAV":exportMonoWAV(a.data.type);break;case"getBuffers":getBuffers();break;case"clear":clear()}};';
19720 var blob = new Blob([workerCode]);
19722 var worker = new Worker(window.URL.createObjectURL(blob)); //TODO: Use a blob instead
19723 worker.postMessage({
19726 sampleRate: this.context.sampleRate
19729 var recording = false,
19732 this.node.onaudioprocess = function(e) {
19733 if (!recording) return;
19734 worker.postMessage({
19737 e.inputBuffer.getChannelData(0),
19738 e.inputBuffer.getChannelData(1)
19743 this.configure = function(cfg) {
19744 for (var prop in cfg) {//TODO: look into using angular.extend() here
19745 if (cfg.hasOwnProperty(prop)) {
19746 config[prop] = cfg[prop];
19751 this.record = function() {
19755 this.stop = function() {
19759 this.clear = function() {
19760 worker.postMessage({ command: 'clear' });
19761 window.URL.revokeObjectURL(blob);
19764 this.getBuffers = function(cb) {
19765 currCallback = cb || config.callback;
19766 worker.postMessage({ command: 'getBuffers' });
19769 this.exportWAV = function(cb, type) {
19770 currCallback = cb || config.callback;
19771 type = type || config.type || 'audio/wav';
19772 if (!currCallback) throw new Error('[b2bRecorder]: Callback not set!');
19773 worker.postMessage({
19774 command: 'exportWAV',
19779 this.exportMonoWAV = function(cb, type) {
19780 currCallback = cb || config.callback;
19781 type = type || config.type || 'audio/wav';
19782 if (!currCallback) throw new Error('[b2bRecorder]: Callback not set!');
19783 worker.postMessage({
19784 command: 'exportMonoWAV',
19789 worker.onmessage = function(e) {
19791 currCallback(blob);
19794 source.connect(this.node);
19795 this.node.connect(this.context.destination); // if the script node is not connected to an output the "onaudioprocess" event is not triggerd in Chrome
19802 .factory('b2bViewport', function() {
19803 /* Source: https://gist.github.com/bjankord/2399828 */
19804 var _viewportWidth = function() {
19806 var webkit = (!(window.webkitConvertPointFromNodeToPage == null));
19810 var vpwtest = document.createElement( "div" );
19811 // Sets test div to width 100%, !important overrides any other misc. box model styles that may be set in the CSS
19812 vpwtest.style.cssText = "width:100% !important; margin:0 !important; padding:0 !important; border:none !important;";
19813 document.documentElement.insertBefore( vpwtest, document.documentElement.firstChild );
19814 vpw = vpwtest.offsetWidth;
19815 document.documentElement.removeChild( vpwtest );
19818 else if ( window.innerWidth === undefined ) {
19819 vpw = document.documentElement.clientWidth;
19823 vpw = window.innerWidth;
19829 viewportWidth: _viewportWidth
19832 .directive('b2bWhenScrollEnds', function(b2bWhenScrollEndsConstants, $log) {
19835 link: function (scope, element, attrs) {
19837 * Exposed Attributes:
19838 * threshold - integer - number of pixels before scrollbar hits end that callback is called
19839 * width - integer - override for element's width (px)
19840 * height - integer - override for element's height (px)
19841 * axis - string - x/y for scroll bar axis
19843 var threshold = parseInt(attrs.threshold, 10) || b2bWhenScrollEndsConstants.threshold;
19845 if (!attrs.axis || attrs.axis === '') {
19846 $log.warn('axis attribute must be defined for b2bWhenScrollEnds.');
19850 if (attrs.axis === 'x') {
19851 visibleWidth = parseInt(attrs.width, 10) || b2bWhenScrollEndsConstants.width;
19852 if (element.css('width')) {
19853 visibleWidth = element.css('width').split('px')[0];
19856 element[0].addEventListener('scroll', function() {
19857 var scrollableWidth = element.prop('scrollWidth');
19858 if (scrollableWidth === undefined) {
19859 scrollableWidth = 1;
19861 var hiddenContentWidth = scrollableWidth - visibleWidth;
19863 if (hiddenContentWidth - element[0].scrollLeft <= threshold) {
19864 /* Scroll almost at bottom, load more rows */
19865 scope.$apply(attrs.b2bWhenScrollEnds);
19868 } else if (attrs.axis === 'y') {
19869 visibleHeight = parseInt(attrs.height, 10) || b2bWhenScrollEndsConstants.height;
19870 if (element.css('width')) {
19871 visibleHeight = element.css('height').split('px')[0];
19874 element[0].addEventListener('scroll', function() {
19875 var scrollableHeight = element.prop('scrollHeight');
19876 if (scrollableHeight === undefined) {
19877 scrollableHeight = 1;
19879 var hiddenContentHeight = scrollableHeight - visibleHeight;
19881 if (hiddenContentHeight - element[0].scrollTop <= threshold) {
19882 /* Scroll almost at bottom, load more rows */
19883 scope.$apply(attrs.b2bWhenScrollEnds);
19891 .factory('$windowBind', ['$window', '$timeout', function($window, $timeout) {
19892 var win = angular.element($window);
19893 var _scroll = function (flag, callbackFunc, scope) {
19894 scope.$watch(flag, function (val) {
19895 $timeout(function () {
19897 win.bind('scroll', callbackFunc);
19899 win.unbind('scroll', callbackFunc);
19905 var throttle = function(type, name, obj) {
19906 obj = obj || window;
19907 var running = false;
19908 var func = function() {
19909 if (running) { return; }
19911 requestAnimationFrame(function() {
19912 obj.dispatchEvent(new CustomEvent(name));
19916 obj.addEventListener(type, func);
19919 var _resize = function(callbackFunc, scope) {
19920 throttle("resize", "optimizedResize");
19921 window.addEventListener("optimizedResize", function(event) {
19923 //win.bind(event, callbackFunc);
19924 if (!scope.$$phase) {
19930 var _click = function (flag, callbackFunc, scope) {
19931 scope.$watch(flag, function (val) {
19932 $timeout(function () {
19934 win.bind('click', callbackFunc);
19936 win.unbind('click', callbackFunc);
19942 var _event = function (event, flag, callbackFunc, scope, timeoutFlag, timeoutValue) {
19944 if (!(timeoutValue)) {
19947 scope.$watch(flag, function (newVal, oldVal) {
19948 if (newVal !== oldVal) {
19949 $timeout(function () {
19951 win.bind(event, callbackFunc);
19953 win.unbind(event, callbackFunc);
19959 scope.$watch(flag, function (newVal, oldVal) {
19960 if (newVal !== oldVal) {
19962 win.bind(event, callbackFunc);
19964 win.unbind(event, callbackFunc);
19979 .factory('keymap', function () {
20001 MAP: { 91 : "COMMAND", 8 : "BACKSPACE" , 9 : "TAB" , 13 : "ENTER" , 16 : "SHIFT" , 17 : "CTRL" , 18 : "ALT" , 19 : "PAUSEBREAK" , 20 : "CAPSLOCK" , 27 : "ESC" , 32 : "SPACE" , 33 : "PAGE_UP", 34 : "PAGE_DOWN" , 35 : "END" , 36 : "HOME" , 37 : "LEFT" , 38 : "UP" , 39 : "RIGHT" , 40 : "DOWN" , 43 : "+" , 44 : "PRINTSCREEN" , 45 : "INSERT" , 46 : "DELETE", 48 : "0" , 49 : "1" , 50 : "2" , 51 : "3" , 52 : "4" , 53 : "5" , 54 : "6" , 55 : "7" , 56 : "8" , 57 : "9" , 59 : ";", 61 : "=" , 65 : "A" , 66 : "B" , 67 : "C" , 68 : "D" , 69 : "E" , 70 : "F" , 71 : "G" , 72 : "H" , 73 : "I" , 74 : "J" , 75 : "K" , 76 : "L", 77 : "M" , 78 : "N" , 79 : "O" , 80 : "P" , 81 : "Q" , 82 : "R" , 83 : "S" , 84 : "T" , 85 : "U" , 86 : "V" , 87 : "W" , 88 : "X" , 89 : "Y" , 90 : "Z", 96 : "0" , 97 : "1" , 98 : "2" , 99 : "3" , 100 : "4" , 101 : "5" , 102 : "6" , 103 : "7" , 104 : "8" , 105 : "9", 106 : "*" , 107 : "+" , 109 : "-" , 110 : "." , 111 : "/", 112 : "F1" , 113 : "F2" , 114 : "F3" , 115 : "F4" , 116 : "F5" , 117 : "F6" , 118 : "F7" , 119 : "F8" , 120 : "F9" , 121 : "F10" , 122 : "F11" , 123 : "F12", 144 : "NUMLOCK" , 145 : "SCROLLLOCK" , 186 : ";" , 187 : "=" , 188 : "," , 189 : "-" , 190 : "." , 191 : "/" , 192 : "`" , 219 : "[" , 220 : "\\" , 221 : "]" , 222 : "'"
20003 isControl: function (e) {
20006 case this.KEY.COMMAND:
20007 case this.KEY.SHIFT:
20008 case this.KEY.CTRL:
20020 isFunctionKey: function (k) {
20021 k = k.keyCode ? k.keyCode : k;
20022 return k >= 112 && k <= 123;
20024 isVerticalMovement: function (k) {
20025 return ~[this.KEY.UP, this.KEY.DOWN].indexOf(k);
20027 isHorizontalMovement: function (k) {
20028 return ~[this.KEY.LEFT, this.KEY.RIGHT, this.KEY.BACKSPACE, this.KEY.DELETE].indexOf(k);
20030 isAllowedKey: function (k) {
20031 return (~[this.KEY.SPACE, this.KEY.ESC, this.KEY.ENTER].indexOf(k)) || this.isHorizontalMovement(k) || this.isVerticalMovement(k);
20033 isNumericKey: function (e) {
20035 if ((k >= 48 && k <= 57) || (k >= 96 && k <= 105)) {
20041 isAlphaNumericKey: function (e) {
20043 if ((k >= 48 && k <= 57) || (k >= 96 && k <= 105) || (k >= 65 && k <= 90)) {
20052 .factory('$isElement', [function () {
20053 var isElement = function (currentElem, targetElem, alternateElem) {
20054 if (currentElem[0] === targetElem[0]) {
20056 } else if (currentElem[0] === alternateElem[0]) {
20059 return isElement((currentElem.parent()[0] && currentElem.parent()) || targetElem, targetElem, alternateElem);
20066 .factory('events', function () {
20067 var _stopPropagation = function (evt) {
20068 if (evt.stopPropagation) {
20069 evt.stopPropagation();
20071 evt.returnValue = false;
20074 var _preventDefault = function (evt) {
20075 if (evt.preventDefault) {
20076 evt.preventDefault();
20078 evt.returnValue = false;
20082 stopPropagation: _stopPropagation,
20083 preventDefault: _preventDefault
20088 .factory('isHighContrast', function () {
20089 var _isHighContrast = function (idval)
20093 var objDiv, objImage, strColor, strWidth, strReady;
20094 var strImageID = idval; // ID of image on the page
20096 // Create a test div
20097 objDiv = document.createElement('div');
20099 //Set its color style to something unusual
20100 objDiv.style.color = 'rgb(31, 41, 59)';
20102 // Attach to body so we can inspect it
20103 document.body.appendChild(objDiv);
20105 // Read computed color value
20106 strColor = document.defaultView ? document.defaultView.getComputedStyle(objDiv, null).color : objDiv.currentStyle.color;
20107 strColor = strColor.replace(/ /g, '');
20109 // Delete the test DIV
20110 document.body.removeChild(objDiv);
20112 // Check if we get the color back that we set. If not, we're in
20113 // high contrast mode.
20114 if (strColor !== 'rgb(31,41,59)') {
20121 return _isHighContrast;
20124 .run(['isHighContrast', '$document', function (isHighContrast, $document) {
20125 var html = $document.find('html').eq(0);
20126 if (isHighContrast()) {
20127 html.addClass('ds2-no-colors');
20129 html.removeClass('ds2-no-colors');
20133 .factory('$documentBind', ['$document', '$timeout', function ($document, $timeout) {
20134 var _click = function (flag, callbackFunc, scope) {
20135 scope.$watch(flag, function (val) {
20136 $timeout(function () {
20138 $document.bind('click', callbackFunc);
20140 $document.unbind('click', callbackFunc);
20146 var _touch = function (flag, callbackFunc, scope) {
20147 scope.$watch(flag, function (val) {
20148 $timeout(function () {
20150 $document.bind('touchstart', callbackFunc);
20152 $document.unbind('touchstart', callbackFunc);
20158 var _scroll = function (flag, callbackFunc, scope) {
20159 scope.$watch(flag, function (val) {
20160 $timeout(function () {
20162 $document.bind('scroll', callbackFunc);
20164 $document.unbind('scroll', callbackFunc);
20170 var _event = function (event, flag, callbackFunc, scope, timeoutFlag, timeoutValue) {
20172 if (!(timeoutValue)) {
20175 scope.$watch(flag, function (newVal, oldVal) {
20176 if (newVal !== oldVal) {
20177 $timeout(function () {
20179 $document.bind(event, callbackFunc);
20181 $document.unbind(event, callbackFunc);
20187 scope.$watch(flag, function (newVal, oldVal) {
20188 if (newVal !== oldVal) {
20190 $document.bind(event, callbackFunc);
20192 $document.unbind(event, callbackFunc);
20207 .directive('b2bOnlyNums', function (keymap) {
20210 require: 'ngModel',
20211 link: function (scope, elm, attrs, ctrl) {
20212 var maxChars = attrs.b2bOnlyNums ? attrs.b2bOnlyNums : 4;
20213 elm.on('keydown', function (event) {
20214 if ((event.which >= 48 && event.which <= 57) || (event.which >= 96 && event.which <= 105)) {
20215 // check for maximum characters allowed
20216 if (elm.val().length < maxChars){
20219 event.preventDefault();
20222 } else if ([8, 9, 13, 27, 37, 38, 39, 40].indexOf(event.which) > -1) {
20223 // to allow backspace, tab, enter, escape, arrows
20225 } else if (event.altKey || event.ctrlKey) {
20226 // to allow alter, control, and shift keys
20230 event.preventDefault();
20235 var validateString = function (value) {
20236 if (angular.isUndefined(value) || value === null || value === '') {
20237 return ctrl.$modelValue;
20241 ctrl.$parsers.unshift(validateString);
20246 .directive('b2bKeyupClick', [ function () {
20249 link: function (scope, elem, attr) {
20251 attr.$observe('b2bKeyupClick', function (value) {
20253 keyCode = value.split(',');
20256 elem.bind('keydown keyup', function (ev) {
20257 var keyCodeCondition = function () {
20259 if (!(ev.keyCode)) {
20261 ev.keyCode = ev.which;
20262 } else if (ev.charCode) {
20263 ev.keyCode = ev.charCode;
20266 if ((ev.keyCode && keyCode.indexOf(ev.keyCode.toString()) > -1)) {
20271 if (ev.type === 'keydown' && keyCodeCondition()) {
20272 ev.preventDefault();
20274 else if (ev.type === 'keyup' && keyCodeCondition()) {
20282 .factory('b2bDOMHelper', function() {
20284 var _isTabable = function(node) {
20285 var element = angular.element(node);
20286 var tagName = element[0].tagName.toUpperCase();
20288 if (isHidden(element)) {
20291 if (element.attr('tabindex') !== undefined) {
20292 return (parseInt(element.attr('tabindex'), 10) >= 0);
20294 if (tagName === 'A' || tagName === 'AREA' || tagName === 'BUTTON' || tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT') {
20295 if (tagName === 'A' || tagName === 'AREA') {
20296 // anchors/areas without href are not focusable
20297 return (element[0].href !== '');
20299 return !(element[0].disabled);
20304 function isValidChild(child) {
20305 return child.nodeType == 1 && child.nodeName != 'SCRIPT' && child.nodeName != 'STYLE';
20308 function isHidden(obj) {
20309 var elem = angular.element(obj);
20310 var elemStyle = undefined;
20311 if(obj instanceof HTMLElement){
20312 elemStyle = window.getComputedStyle(obj);
20315 elemStyle = window.getComputedStyle(obj[0]);
20317 return elem.hasClass('ng-hide') || elem.css('display') === 'none' || elemStyle.display === 'none' || elemStyle.visibility === 'hidden' || elem.css('visibility') === 'hidden';
20320 function hasValidParent(obj) {
20321 return (isValidChild(obj) && obj.parentElement.nodeName !== 'BODY');
20324 function traverse(obj, fromTop) {
20325 var obj = obj || document.getElementsByTagName('body')[0];
20326 if (isValidChild(obj) && _isTabable(obj)) {
20329 // If object is hidden, skip it's children
20330 if (isValidChild(obj) && isHidden(obj)) {
20333 // If object is hidden, skip it's children
20334 if (angular.element(obj).hasClass('ng-hide')) {
20337 if (obj.hasChildNodes()) {
20340 child = obj.firstChild;
20342 child = obj.lastChild;
20345 var res = traverse(child, fromTop);
20351 child = child.nextSibling;
20353 child = child.previousSibling;
20363 var _previousElement = function(el, isFocusable){
20366 if (el.hasOwnProperty('length')) {
20370 var parent = elem.parentElement;
20371 var previousElem = undefined;
20374 if (hasValidParent(elem)) {
20375 var siblings = angular.element(parent).children();
20376 if (siblings.length > 0) {
20377 // Good practice to splice out the elem from siblings if there, saving some time.
20378 // We allow for a quick check for jumping to parent first before removing.
20379 if (siblings[0] === elem) {
20380 // If we are looking at immidiate parent and elem is first child, we need to go higher
20381 var e = _previousElement(angular.element(elem).parent(), isFocusable);
20382 if (_isTabable(e)) {
20386 // I need to filter myself and any nodes next to me from the siblings
20387 var indexOfElem = Array.prototype.indexOf.call(siblings, elem);
20388 siblings = Array.prototype.filter.call(siblings, function(item, itemIndex) {
20389 if (!angular.equals(elem, item) && itemIndex < indexOfElem) {
20394 // We need to search backwards
20395 for (var i = 0; i <= siblings.length-1; i++) {//for (var i = siblings.length-1; i >= 0; i--) {
20396 var ret = traverse(siblings[i], false);
20397 if (ret !== undefined) {
20402 var e = _previousElement(angular.element(elem).parent(), isFocusable);
20403 if (_isTabable(e)) {
20409 var siblings = angular.element(parent).children();
20410 if (siblings.length > 1) {
20411 // Since indexOf is on Array.prototype and parent.children is a NodeList, we have to use call()
20412 var index = Array.prototype.indexOf.call(siblings, elem);
20413 previousElem = siblings[index-1];
20416 return previousElem;
20419 var _lastTabableElement = function(el) {
20420 /* This will return the first tabable element from the parent el */
20422 if (el.hasOwnProperty('length')) {
20426 return traverse(elem, false);
20429 var _firstTabableElement = function(el) {
20430 /* This will return the first tabable element from the parent el */
20432 if (el.hasOwnProperty('length')) {
20436 return traverse(elem, true);
20439 var _isInDOM = function(obj) {
20440 return document.documentElement.contains(obj);
20444 firstTabableElement: _firstTabableElement,
20445 lastTabableElement: _lastTabableElement,
20446 previousElement: _previousElement,
20448 isTabable: _isTabable,
20453 .factory('windowOrientation', ['$window', function ($window) {
20454 var _isPotrait = function () {
20455 if ($window.innerHeight > $window.innerWidth) {
20461 var _isLandscape = function () {
20462 if ($window.innerHeight < $window.innerWidth) {
20470 isPotrait: _isPotrait,
20471 isLandscape: _isLandscape
20474 .directive('b2bNextElement', function() {
20478 link: function (scope, elem, attr, ctrls) {
20480 var keys = attr.b2bNextElement.split(',');
20482 elem.bind('keydown', function (e) {
20483 var nextElement = elem.next();
20484 if(e.keyCode == 39 || e.keyCode == 40){ // if e.keyCode in keys
20485 if(nextElement.length) {
20486 e.preventDefault();
20487 nextElement[0].focus();
20495 .directive('b2bAccessibilityClick', [function () {
20498 link: function (scope, elem, attr, ctrl) {
20500 attr.$observe('b2bAccessibilityClick', function (value) {
20502 keyCode = value.split(',');
20505 elem.bind('keydown keypress', function (ev) {
20506 var keyCodeCondition = function () {
20508 if (!(ev.keyCode)) {
20510 ev.keyCode = ev.which;
20511 } else if (ev.charCode) {
20512 ev.keyCode = ev.charCode;
20515 if ((ev.keyCode && keyCode.indexOf(ev.keyCode.toString()) > -1)) {
20520 if (keyCode.length > 0 && keyCodeCondition()) {
20522 ev.preventDefault();
20529 .directive('b2bReset', ['$compile', function ($compile) {
20532 require: ['?ngModel', 'b2bReset'],
20533 controller: ['$scope', function ($scope) {
20534 var resetButton = angular.element('<button type="button" class="reset-field" tabindex="-1" aria-label="Click to reset" aria-hidden="true" role="button"></button>');
20536 this.getResetButton = function () {
20537 return resetButton;
20540 link: function (scope, element, attrs, ctrls) {
20542 var ngModelCtrl = ctrls[0];
20543 var ctrl = ctrls[1];
20545 var resetButton = ctrl.getResetButton();
20548 resetButton.on('click', function () {
20549 element[0].value = '';
20552 if (attrs.b2bReset) {
20553 ngModelCtrl.$setViewValue(attrs.b2bReset);
20555 ngModelCtrl.$setViewValue('');
20557 element[0].value = ngModelCtrl.$viewValue;
20558 ngModelCtrl.$render();
20561 element[0].focus();
20562 element[0].select();
20565 var addResetButton = function () {
20566 element.after(resetButton);
20567 element.unbind('focus input', addResetButton);
20570 element.bind('focus input', addResetButton);
20575 .directive('b2bPrevElement', ['b2bDOMHelper', 'keymap', function (b2bDOMHelper, keymap) {
20579 link: function (scope, elem, attr) {
20581 elem.bind('keydown', function (e) {
20582 if(e.keyCode == 37 || e.keyCode == 38){
20583 var prev = b2bDOMHelper.previousElement(elem, false);
20584 if(prev !== undefined) {
20585 e.preventDefault();
20594 * @param {integer} delay - Timeout before first and last focusable elements are found
20595 * @param {boolean} trigger - A variable on scope that will trigger refinding first/last focusable elements
20597 .directive('b2bTrapFocusInsideElement', ['$timeout', 'b2bDOMHelper', 'keymap', 'events', function ($timeout, b2bDOMHelper, keymap, events) {
20601 link: function (scope, elem, attr) {
20603 var delay = parseInt(attr.delay, 10) || 10;
20605 /* Before opening modal, find the focused element */
20606 var firstTabableElement = undefined,
20607 lastTabableElement = undefined;
20610 $timeout(function () {
20611 firstTabableElement = b2bDOMHelper.firstTabableElement(elem);
20612 lastTabableElement = b2bDOMHelper.lastTabableElement(elem);
20613 angular.element(firstTabableElement).bind('keydown', firstTabableElementKeyhandler);
20614 angular.element(lastTabableElement).bind('keydown', lastTabableElementKeyhandler);
20618 if (attr.trigger !== undefined) {
20619 scope.$watch('trigger', function() {
20620 if (scope.trigger) {
20626 var firstTabableElementKeyhandler = function(e) {
20628 e.keyCode = e.which;
20630 if (e.keyCode === keymap.KEY.TAB && e.shiftKey) {
20631 if (attr.trapFocusInsideElement === 'true') {
20632 var temp = b2bDOMHelper.lastTabableElement(elem);
20633 if (lastTabableElement !== temp) {
20634 // Unbind keydown handler on lastTabableElement
20635 angular.element(lastTabableElement).unbind('keydown', lastTabableElementKeyhandler);
20636 lastTabableElement = temp;
20637 angular.element(lastTabableElement).bind('keydown', lastTabableElementKeyhandler);
20640 lastTabableElement.focus();
20641 events.preventDefault(e);
20642 events.stopPropagation(e);
20646 var lastTabableElementKeyhandler = function(e) {
20648 e.keyCode = e.which;
20650 if (e.keyCode === keymap.KEY.TAB && !e.shiftKey) {
20651 if (attr.trapFocusInsideElement === 'true') {
20652 var temp = b2bDOMHelper.firstTabableElement(elem);
20653 if (firstTabableElement !== temp) {
20654 // Unbind keydown handler on firstTabableElement
20655 angular.element(firstTabableElement).unbind('keydown', firstTabableElementKeyhandler);
20656 firstTabableElement = temp;
20657 angular.element(firstTabableElement).bind('keydown', firstTabableElementKeyhandler);
20660 firstTabableElement.focus();
20661 events.preventDefault(e);
20662 events.stopPropagation(e);
20671 .factory('trapFocusInElement', ['$document', '$isElement', 'b2bDOMHelper', 'keymap', '$interval', function ($document, $isElement, b2bDOMHelper, keymap, $interval) {
20672 var elementStack = [];
20673 var stackHead = undefined;
20674 var stopInterval = undefined;
20675 var intervalRequired = false;
20676 var interval = 1000;
20677 var firstTabableElement, lastTabableElement;
20679 var trapKeyboardFocusInFirstElement = function (e) {
20681 e.keyCode = e.which;
20684 if (e.shiftKey === true && e.keyCode === keymap.KEY.TAB) {
20685 lastTabableElement[0].focus();
20686 e.preventDefault(e);
20687 e.stopPropagation(e);
20692 var trapKeyboardFocusInLastElement = function (e) {
20694 e.keyCode = e.which;
20697 if (e.shiftKey === false && e.keyCode === keymap.KEY.TAB) {
20698 firstTabableElement[0].focus();
20699 e.preventDefault(e);
20700 e.stopPropagation(e);
20704 var trapFocusInElement = function (flag, firstTabableElementParam, lastTabableElementParam) {
20705 var bodyElements = $document.find('body').children();
20707 firstTabableElement = firstTabableElementParam ? firstTabableElementParam : angular.element(b2bDOMHelper.firstTabableElement(stackHead));
20708 lastTabableElement = lastTabableElementParam ? lastTabableElementParam : angular.element(b2bDOMHelper.lastTabableElement(stackHead));
20711 for (var i = 0; i < bodyElements.length; i++) {
20712 if (bodyElements[i] !== stackHead[0]) {
20713 bodyElements.eq(i).attr('aria-hidden', true);
20716 firstTabableElement.bind('keydown', trapKeyboardFocusInFirstElement);
20717 lastTabableElement.bind('keydown', trapKeyboardFocusInLastElement);
20719 for (var j = 0; j < bodyElements.length; j++) {
20720 if (bodyElements[j] !== stackHead[0]) {
20721 bodyElements.eq(j).removeAttr('aria-hidden');
20724 firstTabableElement.unbind('keydown', trapKeyboardFocusInFirstElement);
20725 lastTabableElement.unbind('keydown', trapKeyboardFocusInLastElement);
20728 if (intervalRequired && flag) {
20729 stopInterval = $interval(function () {
20730 var firstTabableElementTemp = angular.element(b2bDOMHelper.firstTabableElement(stackHead));
20731 var lastTabableElementTemp = angular.element(b2bDOMHelper.lastTabableElement(stackHead));
20732 if (firstTabableElementTemp[0] !== firstTabableElement[0] || lastTabableElementTemp[0] !== lastTabableElement[0]) {
20733 $interval.cancel(stopInterval);
20734 stopInterval = undefined;
20735 trapFocusInElement(false, firstTabableElement, lastTabableElement);
20736 trapFocusInElement(true, firstTabableElementTemp, lastTabableElementTemp);
20740 if (stopInterval) {
20741 $interval.cancel(stopInterval);
20742 stopInterval = undefined;
20746 var toggleTrapFocusInElement = function (flag, element, intervalRequiredParam, intervalParam) {
20747 intervalRequired = intervalRequiredParam ? intervalRequiredParam : intervalRequired;
20748 interval = intervalParam ? intervalParam : interval;
20749 if (angular.isDefined(flag) && angular.isDefined(element)) {
20750 if (flag && angular.isUndefined(stackHead)) {
20751 stackHead = element;
20752 trapFocusInElement(flag);
20755 trapFocusInElement(false);
20756 elementStack.push(stackHead);
20757 stackHead = element;
20758 trapFocusInElement(true);
20760 if (angular.isDefined(stackHead) && (stackHead[0] === element[0])) {
20761 trapFocusInElement(false);
20762 stackHead = elementStack.pop();
20763 if (angular.isDefined(stackHead)) {
20764 trapFocusInElement(true);
20770 if (angular.isDefined(stackHead)) {
20771 trapFocusInElement(false, firstTabableElement, lastTabableElement);
20772 trapFocusInElement(true);
20777 return toggleTrapFocusInElement;
20779 .factory('draggedElement', function(){
20780 var draggedElement;
20782 setElement: function(data){
20783 draggedElement = data;
20785 getElement: function(){
20786 return draggedElement;
20791 .directive('draggable', ['draggedElement',function (draggedElement) {
20792 return function(scope, element) {
20794 element[0].draggable = true;
20796 element.bind('dragstart', function(e) {
20797 draggedElement.setElement(this.parentElement.parentElement);
20798 e.dataTransfer.effectAllowed = 'move';
20799 e.dataTransfer.setDragImage(this.parentElement.parentElement, 0, 0);
20800 this.parentElement.parentElement.classList.add('b2-drag-element');
20804 element.bind('dragend', function(e) {
20805 draggedElement.setElement(e);
20806 this.parentElement.parentElement.classList.remove('b2-drag-element');
20812 .directive('droppable', ['draggedElement',function (draggedElement) {
20819 link: function(scope, element, attr) {
20820 if(attr.droppable === 'true') {
20821 element.bind('dragover', function(e) {
20822 e.dataTransfer.dropEffect = 'move';
20823 this.classList.add('b2b-drag-over')
20824 if (e.preventDefault) e.preventDefault(); // allows us to drop
20828 element.bind('dragstart', function(e) {
20829 if(!e.target.parentElement.classList.contains('b2b-draggable')) {
20830 e.preventDefault();
20835 element.bind('dragenter', function(e) {
20836 if(e.target.getAttribute('droppable') ==='true') {
20841 element.bind('dragleave', function(e) {
20842 this.classList.remove('b2b-drag-over');
20846 element.bind('drop', function(e) {
20847 var ele = draggedElement.getElement();
20848 if (e.stopPropagation) e.stopPropagation();
20849 if (e.preventDefault) e.preventDefault();
20850 this.classList.remove('b2b-drag-over');
20852 if(ele && ele.hasAttribute('data-index')){
20853 var element = scope.rowData[parseInt(ele.getAttribute('data-index'))];
20854 if(element !== undefined) {
20855 scope.rowData.splice(parseInt(ele.getAttribute('data-index')), 1);
20856 scope.rowData.splice(parseInt(e.currentTarget.getAttribute('data-index')), 0 , element);
20867 .directive('b2bSetNextFocusOn', ['b2bDOMHelper', '$timeout', function(b2bDOMHelper, $timeout) {
20871 link: function (scope, elem, attr) {
20872 elem.bind('click', function(){
20873 var firstFocusableElement = undefined;
20874 var containerElem = undefined;
20875 var containerArray = [];
20876 var timeout = parseInt(attr.setNextFocusTimeout, 0) | 100;
20877 var index = parseInt(attr.b2bSetNextFocusIndex, 0) | 0;
20880 *Fix for IE7 and lower
20881 *polyfill src: https://github.com/HubSpot/pace/issues/102
20883 if (!document.querySelectorAll) {
20884 document.querySelectorAll = function (selectors) {
20885 var style = document.createElement('style'), elements = [], element;
20886 document.documentElement.firstChild.appendChild(style);
20887 document._qsa = [];
20889 style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}';
20890 window.scrollBy(0, 0);
20891 style.parentNode.removeChild(style);
20893 while (document._qsa.length) {
20894 element = document._qsa.shift();
20895 element.style.removeAttribute('x-qsa');
20896 elements.push(element);
20898 document._qsa = null;
20903 if (attr.b2bSetNextFocusOn === '') {
20906 containerArray = attr.b2bSetNextFocusOn.split(' ');
20908 $timeout(function(){
20910 do { // cycles thru containerArray until finds a match in DOM to set focus to
20911 containerElem = document.querySelectorAll(containerArray[i])[index];
20913 } while ( (!containerElem) && (i < containerArray.length) );
20915 if (!angular.isDefined(firstFocusableElement)) {
20916 firstFocusableElement = b2bDOMHelper.firstTabableElement(containerElem);
20918 firstFocusableElement.focus();
20927 .directive('b2bInputAllow', [function() {
20930 require: 'ngModel',
20931 link: function (scope, elem, attr, ctrl) {
20932 var regexExpression = null;
20933 attr.$observe('b2bInputAllow', function (value) {
20935 regexExpression = new RegExp(value);
20938 var isValid = function(str) {
20939 if (regexExpression !== null) {
20940 return regexExpression.test(str);
20944 elem.bind('keypress', function($event) {
20945 var charcode = String.fromCharCode($event.which || $event.keyCode);
20946 if (!isValid(charcode)) {
20947 $event.preventDefault();
20948 $event.stopPropagation();
20951 elem.bind('input', function (evt) {
20952 var inputString = ctrl.$viewValue;
20953 if (isValid(inputString)) {
20954 ctrl.$setViewValue(inputString);
20963 .directive('b2bInputDeny', [function() {
20966 require: 'ngModel',
20967 link: function (scope, elem, attr, ctrl) {
20968 var regexExpression = null;
20969 attr.$observe('b2bInputDeny', function (value) {
20971 regexExpression = new RegExp(value, 'g');
20974 elem.bind('input', function () {
20975 var inputString = ctrl.$viewValue && ctrl.$viewValue.replace(regexExpression, '');
20976 if (inputString !== ctrl.$viewValue) {
20977 ctrl.$setViewValue(inputString);
20986 .directive('b2bDragonInput', [function() {
20989 require: 'ngModel',
20990 link: function (scope, elem, attr, ctrl) {
20991 elem.on('focus keyup', function(){
20992 elem.triggerHandler('change');
20998 .directive('b2bKey', ['b2bUtilitiesConfig', '$timeout', 'keymap', function (b2bUtilitiesConfig, $timeout, keymap) {
21001 controller: ['$scope', '$element', '$attrs', function ($scope, $element,attr) {
21002 this.childElements = [];
21003 this.disableNodes = {};
21004 this.enableSearch = attr.enableSearch !== undefined ? true : b2bUtilitiesConfig.enableSearch;
21005 this.circularTraversal = attr.circularTraversal !== undefined ? true : b2bUtilitiesConfig.circularTraversal;
21007 if (this.enableSearch) {
21008 this.searchKeys = [];
21010 var searchString = '';
21012 var selfCtrl = this;
21014 this.childElementsList = [];
21016 this.b2bKeyID = "";
21018 if (angular.isDefined(attr.b2bKey)) {
21019 this.b2bKeyID = attr.b2bKey;
21022 this.calculateChildElementsList = function () {
21023 return $element[0].querySelectorAll("[b2b-key-item='" + this.b2bKeyID + "']:not([disabled])");
21026 this.resetChildElementsList = function () {
21027 return $timeout(function () {
21028 selfCtrl.childElementsList = selfCtrl.calculateChildElementsList();
21032 this.resetChildElementsList();
21034 $scope.$on('b2b-key-reset-child-elements-list', function () {
21035 selfCtrl.resetChildElementsList();
21039 this.registerElement = function (childElement, searchKey) {
21040 this.childElements.push(childElement);
21041 if (this.enableSearch) {
21042 this.searchKeys.push(searchKey);
21044 var count = this.childElements.length - 1;
21045 this.maxLength = count + 1;
21048 this.toggleDisable = function (count, state) {
21049 this.disableNodes[count] = state;
21051 this.searchElement = function (searchExp) {
21052 var regex = new RegExp("\\b" + searchExp, "gi");
21053 var position = this.searchKeys.regexIndexOf(regex, this.counter + 1, true);
21054 if (position > -1) {
21055 this.counter = position;
21056 this.moveFocus(this.counter);
21059 this.startTimer = function (time) {
21060 if (searchString === '') {
21061 $timeout(function () {
21066 this.resetCounter = function (count) {
21067 this.counter = count;
21069 this.moveNext = function (count) {
21070 this.counter = (this.counter + count) < this.maxLength ? this.counter + count : (this.circularTraversal ? 0 : this.counter);
21071 if (this.disableNodes[this.counter]) {
21072 if ((this.counter + count) < this.maxLength) {
21073 this.moveNext(count);
21076 this.moveFocus(this.counter);
21079 this.movePrev = function (count) {
21080 this.counter = (this.counter - count) > -1 ? this.counter - count : (this.circularTraversal ? this.maxLength-1 : this.counter);
21081 if (this.disableNodes[this.counter]) {
21082 if ((this.counter - count) > -1) {
21083 this.movePrev(count);
21086 this.moveFocus(this.counter);
21089 this.moveFocus = function (index) {
21090 this.childElements[index][0].focus();
21093 this.keyDownHandler = function (ev, count) {
21094 if (angular.isDefined(count) && !isNaN(count) && count !== this.counter) {
21095 this.resetCounter(count);
21099 ev.keyCode = ev.which;
21100 } else if (ev.charCode) {
21101 ev.keyCode = ev.charCode;
21105 if (this.prev && this.prev.indexOf(ev.keyCode.toString()) > -1) {
21107 ev.preventDefault();
21108 ev.stopPropagation();
21109 } else if (this.next && this.next.indexOf(ev.keyCode.toString()) > -1) {
21111 ev.preventDefault();
21112 ev.stopPropagation();
21113 } else if (this.up && this.up.indexOf(ev.keyCode.toString()) > -1) {
21114 if (this.type === 'table') {
21115 this.movePrev(this.columns);
21116 ev.preventDefault();
21117 ev.stopPropagation();
21119 } else if (this.down && this.down.indexOf(ev.keyCode.toString()) > -1) {
21120 if (this.type === 'table') {
21121 this.moveNext(this.columns);
21122 ev.preventDefault();
21123 ev.stopPropagation();
21125 } else if (ev.keyCode === keymap.KEY.HOME) {
21126 var firstIndex = 0;
21127 while (this.disableNodes[firstIndex] !== false) {
21130 var count = this.counter - firstIndex;
21131 this.movePrev(count);
21132 ev.preventDefault();
21133 ev.stopPropagation();
21134 } else if (ev.keyCode === keymap.KEY.END) {
21135 var lastIndex = this.childElements.length - 1;
21136 while (this.disableNodes[lastIndex] !== false) {
21139 var count = lastIndex - this.counter;
21140 this.moveNext(count);
21141 ev.preventDefault();
21142 ev.stopPropagation();
21143 } else if (ev.keyCode >= 48 && ev.keyCode <= 105) {
21144 if (this.enableSearch) {
21145 this.startTimer(b2bUtilitiesConfig.searchTimer);
21146 searchString = searchString + (keymap.MAP[ev.keyCode] || '');
21147 this.searchElement(searchString);
21148 ev.preventDefault();
21149 ev.stopPropagation();
21155 link: function (scope, elem, attr, ctrl) {
21156 ctrl.prev = attr.prev ? attr.prev.split(',') : b2bUtilitiesConfig.prev.split(',');
21157 ctrl.next = attr.next ? attr.next.split(',') : b2bUtilitiesConfig.next.split(',');
21158 ctrl.type = attr.type ? attr.type : b2bUtilitiesConfig.type;
21159 if (ctrl.type === 'table') {
21160 ctrl.up = attr.up ? attr.up.split(',') : b2bUtilitiesConfig.up.split(',');
21161 ctrl.down = attr.down ? attr.down.split(',') : b2bUtilitiesConfig.down.split(',');
21162 ctrl.columns = attr.columns ? parseInt(attr.columns, 10) : b2bUtilitiesConfig.columns;
21165 elem.bind('keydown', function (ev) {
21166 ctrl.keyDownHandler(ev);
21172 .directive('b2bKeyItem', [function () {
21175 link: function (scope, elem, attr, ctrl) {
21176 var parentCtrl = (elem.parent() && elem.parent().controller('b2bKey')) || undefined;
21177 if (angular.isDefined(parentCtrl)) {
21178 var count = parentCtrl.registerElement(elem, attr.searchKey);
21179 elem.bind('keydown', function (ev) {
21180 parentCtrl.keyDownHandler(ev, count);
21182 scope.$watch(attr.b2bKeyItem, function (value) {
21183 value = value === undefined ? true : value;
21184 parentCtrl.toggleDisable(count, !value);
21186 scope.$on('$destroy', function () {
21187 parentCtrl.toggleDisable(count, true);
21194 .directive('b2bElementFocus', [function () {
21197 link: function (scope, elem, attr, ctrl) {
21198 scope.$watch(attr.b2bElementFocus, function (value) {
21199 if (value === true) {
21208 .directive('b2bAppendElement', ['$compile', function ($compile) {
21211 link: function (scope, elem, attr, ctrl) {
21212 var parameters = attr.b2bAppendElement.split(':');
21213 if (parameters.length === 1) {
21214 elem.append(scope.$eval(parameters[0]));
21215 } else if (parameters.length === 2) {
21216 if (parameters[1] === 'compile') {
21217 var element = angular.element('<span>' + scope.$eval(parameters[0]) + '</span>');
21218 elem.append($compile(element)(scope));
21226 .directive('b2bKeyItemRefreshInNgRepeat', [function () {
21229 require: '^^b2bKey',
21230 link: function (scope, elem, attr, parentCtrl) {
21231 if (angular.isDefined(parentCtrl)) {
21233 var attrToObserve = 'attrToObserve';
21235 if (attr.b2bKeyItemRefreshInNgRepeat) {
21236 attrToObserve = 'b2bKeyItemRefreshInNgRepeat';
21239 attr.$observe(attrToObserve, function (newVal, oldVal) {
21240 if (newVal && newVal !== oldVal) {
21241 parentCtrl.resetChildElementsList();
21249 .constant('b2bMaskConfig', {
21255 clearOnBlur: false,
21256 clearOnBlurPlaceholder: false,
21258 eventsToHandle: ['input', 'keyup', 'click', 'focus'],
21259 addDefaultPlaceholder: true,
21260 allowInvalidValue: true
21263 * @param {boolean} modelViewValue - If this is set to true, then the model value bound with ng-model will be the same as the $viewValue meaning it will contain any static mask characters present in the mask definition. This will not set the model value to a $viewValue that is considered invalid.
21264 * @param {String} maskPlaceholder - Allows customizing the mask placeholder when a user has focused the input element and while typing in their value
21265 * @param {String} maskPlaceholderChar - Allows customizing the mask placeholder character. The default mask placeholder is _.
21266 * @param {boolean} addDefaultPlaceholder - The default placeholder is constructed from the ui-mask definition so a mask of 999-9999 would have a default placeholder of ___-____; unless you have overridden the default placeholder character.
21268 .directive('b2bMask', ['b2bMaskConfig', function(b2bMaskConfig) {
21270 require: 'ngModel',
21272 link: function(scope, element, attrs, ctrl) {
21273 var maskProcessed = false, eventsBound = false,
21274 maskCaretMap, maskPatterns, maskPlaceholder, maskComponents,
21275 // Minimum required length of the value to be considered valid
21277 value, valueMasked, isValid,
21278 // Vars for initializing/uninitializing
21279 originalPlaceholder = attrs.placeholder,
21280 originalMaxlength = attrs.maxlength,
21281 // Vars used exclusively in eventHandler()
21282 oldValue, oldValueUnmasked, oldCaretPosition, oldSelectionLength,
21283 // Used for communicating if a backspace operation should be allowed between
21284 // keydownHandler and eventHandler
21287 var options = b2bMaskConfig;
21289 function isFocused (elem) {
21290 return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
21293 var originalIsEmpty = ctrl.$isEmpty;
21294 ctrl.$isEmpty = function(value) {
21295 if (maskProcessed) {
21296 return originalIsEmpty(unmaskValue(value || ''));
21298 return originalIsEmpty(value);
21302 function initialize(maskAttr) {
21303 if (!angular.isDefined(maskAttr)) {
21304 return uninitialize();
21306 processRawMask(maskAttr);
21307 if (!maskProcessed) {
21308 return uninitialize();
21310 initializeElement();
21311 bindEventListeners();
21315 function initPlaceholder(placeholderAttr) {
21316 if ( ! placeholderAttr) {
21319 maskPlaceholder = placeholderAttr;
21320 /* If the mask is processed, then we need to update the value
21321 but don't set the value if there is nothing entered into the element
21322 and there is a placeholder attribute on the element because that
21323 will only set the value as the blank maskPlaceholder
21324 and override the placeholder on the element */
21325 if (maskProcessed && !(element.val().length === 0 && angular.isDefined(attrs.placeholder))) {
21326 element.val(maskValue(unmaskValue(element.val())));
21330 function initPlaceholderChar() {
21331 return initialize(attrs.uiMask);
21334 var modelViewValue = false;
21336 attrs.$observe('modelViewValue', function(val) {
21337 if (val === 'true') {
21338 modelViewValue = true;
21342 attrs.$observe('allowInvalidValue', function(val) {
21343 linkOptions.allowInvalidValue = val === ''? true : !!val;
21344 formatter(ctrl.$modelValue);
21347 function formatter(fromModelValue) {
21348 if (!maskProcessed) {
21349 return fromModelValue;
21351 value = unmaskValue(fromModelValue || '');
21352 isValid = validateValue(value);
21353 ctrl.$setValidity('mask', isValid);
21355 if (!value.length) return undefined;
21356 if (isValid || linkOptions.allowInvalidValue) {
21357 return maskValue(value);
21363 function parser(fromViewValue) {
21364 if (!maskProcessed) {
21365 return fromViewValue;
21367 value = unmaskValue(fromViewValue || '');
21368 isValid = validateValue(value);
21369 /* We have to set viewValue manually as the reformatting of the input
21370 value performed by eventHandler() doesn't happen until after
21371 this parser is called, which causes what the user sees in the input
21372 to be out-of-sync with what the ctrl's $viewValue is set to. */
21373 ctrl.$viewValue = value.length ? maskValue(value) : '';
21374 ctrl.$setValidity('mask', isValid);
21376 if (isValid || linkOptions.allowInvalidValue) {
21377 return modelViewValue ? ctrl.$viewValue : value;
21381 var linkOptions = {};
21384 if (attrs.b2bMaskOptions) {
21385 linkOptions = scope.$eval('[' + attrs.b2bMaskOptions + ']');
21386 if (angular.isObject(linkOptions[0])) {
21387 // we can't use angular.copy nor angular.extend, they lack the power to do a deep merge
21388 linkOptions = (function(original, current) {
21389 for (var i in original) {
21390 if (Object.prototype.hasOwnProperty.call(original, i)) {
21391 if (current[i] === undefined) {
21392 current[i] = angular.copy(original[i]);
21394 if (angular.isObject(current[i]) && !angular.isArray(current[i])) {
21395 current[i] = angular.extend({}, original[i], current[i]);
21401 })(options, linkOptions[0]);
21403 linkOptions = options; //gotta be a better way to do this..
21406 linkOptions = options;
21409 attrs.$observe('b2bMask', initialize);
21410 if (angular.isDefined(attrs.maskPlaceholder)) {
21411 attrs.$observe('maskPlaceholder', initPlaceholder);
21414 attrs.$observe('placeholder', initPlaceholder);
21416 if (angular.isDefined(attrs.maskPlaceholderChar)) {
21417 attrs.$observe('maskPlaceholderChar', initPlaceholderChar);
21420 ctrl.$formatters.unshift(formatter);
21421 ctrl.$parsers.unshift(parser);
21423 function uninitialize() {
21424 maskProcessed = false;
21425 unbindEventListeners();
21427 if (angular.isDefined(originalPlaceholder)) {
21428 element.attr('placeholder', originalPlaceholder);
21430 element.removeAttr('placeholder');
21433 if (angular.isDefined(originalMaxlength)) {
21434 element.attr('maxlength', originalMaxlength);
21436 element.removeAttr('maxlength');
21439 element.val(ctrl.$modelValue);
21440 ctrl.$viewValue = ctrl.$modelValue;
21444 function initializeElement() {
21445 value = oldValueUnmasked = unmaskValue(ctrl.$modelValue || '');
21446 valueMasked = oldValue = maskValue(value);
21447 isValid = validateValue(value);
21448 if (attrs.maxlength) { // Double maxlength to allow pasting new val at end of mask
21449 element.attr('maxlength', maskCaretMap[maskCaretMap.length - 1] * 2);
21451 if ( ! originalPlaceholder && linkOptions.addDefaultPlaceholder) {
21452 element.attr('placeholder', maskPlaceholder);
21454 var viewValue = ctrl.$modelValue;
21455 var idx = ctrl.$formatters.length;
21457 viewValue = ctrl.$formatters[idx](viewValue);
21459 ctrl.$viewValue = viewValue || '';
21463 function bindEventListeners() {
21467 element.bind('blur', blurHandler);
21468 element.bind('mousedown mouseup', mouseDownUpHandler);
21469 element.bind('keydown', keydownHandler);
21470 element.bind(linkOptions.eventsToHandle.join(' '), eventHandler);
21471 eventsBound = true;
21474 function unbindEventListeners() {
21475 if (!eventsBound) {
21478 element.unbind('blur', blurHandler);
21479 element.unbind('mousedown', mouseDownUpHandler);
21480 element.unbind('mouseup', mouseDownUpHandler);
21481 element.unbind('keydown', keydownHandler);
21482 element.unbind('input', eventHandler);
21483 element.unbind('keyup', eventHandler);
21484 element.unbind('click', eventHandler);
21485 element.unbind('focus', eventHandler);
21486 eventsBound = false;
21489 function validateValue(value) {
21490 // Zero-length value validity is ngRequired's determination
21491 return value.length ? value.length >= minRequiredLength : true;
21494 function unmaskValue(value) {
21495 var valueUnmasked = '',
21496 input = element[0],
21497 maskPatternsCopy = maskPatterns.slice(),
21498 selectionStart = oldCaretPosition,
21499 selectionEnd = selectionStart + getSelectionLength(input),
21500 valueOffset, valueDelta, tempValue = '';
21501 // Preprocess by stripping mask components from value
21502 value = value.toString();
21504 valueDelta = value.length - maskPlaceholder.length;
21505 angular.forEach(maskComponents, function(component) {
21506 var position = component.position;
21507 //Only try and replace the component if the component position is not within the selected range
21508 //If component was in selected range then it was removed with the user input so no need to try and remove that component
21509 if (!(position >= selectionStart && position < selectionEnd)) {
21510 if (position >= selectionStart) {
21511 position += valueDelta;
21513 if (value.substring(position, position + component.value.length) === component.value) {
21514 tempValue += value.slice(valueOffset, position);// + value.slice(position + component.value.length);
21515 valueOffset = position + component.value.length;
21519 value = tempValue + value.slice(valueOffset);
21520 angular.forEach(value.split(''), function(chr) {
21521 if (maskPatternsCopy.length && maskPatternsCopy[0].test(chr)) {
21522 valueUnmasked += chr;
21523 maskPatternsCopy.shift();
21527 return valueUnmasked;
21530 function maskValue(unmaskedValue) {
21531 var valueMasked = '',
21532 maskCaretMapCopy = maskCaretMap.slice();
21534 angular.forEach(maskPlaceholder.split(''), function(chr, i) {
21535 if (unmaskedValue.length && i === maskCaretMapCopy[0]) {
21536 valueMasked += unmaskedValue.charAt(0) || '_';
21537 unmaskedValue = unmaskedValue.substr(1);
21538 maskCaretMapCopy.shift();
21541 valueMasked += chr;
21544 return valueMasked;
21547 function getPlaceholderChar(i) {
21548 var placeholder = angular.isDefined(attrs.uiMaskPlaceholder) ? attrs.uiMaskPlaceholder : attrs.placeholder,
21549 defaultPlaceholderChar;
21551 if (angular.isDefined(placeholder) && placeholder[i]) {
21552 return placeholder[i];
21554 defaultPlaceholderChar = angular.isDefined(attrs.uiMaskPlaceholderChar) && attrs.uiMaskPlaceholderChar ? attrs.uiMaskPlaceholderChar : '_';
21555 return (defaultPlaceholderChar.toLowerCase() === 'space') ? ' ' : defaultPlaceholderChar[0];
21559 /* Generate array of mask components that will be stripped from a masked value
21560 before processing to prevent mask components from being added to the unmasked value.
21561 E.g., a mask pattern of '+7 9999' won't have the 7 bleed into the unmasked value. */
21562 function getMaskComponents() {
21563 var maskPlaceholderChars = maskPlaceholder.split(''),
21564 maskPlaceholderCopy, components;
21566 /* maskCaretMap can have bad values if the input has the ui-mask attribute implemented as an obversable property, e.g. the demo page */
21567 if (maskCaretMap && !isNaN(maskCaretMap[0])) {
21568 /* Instead of trying to manipulate the RegEx based on the placeholder characters
21569 we can simply replace the placeholder characters based on the already built
21570 maskCaretMap to underscores and leave the original working RegEx to get the proper
21572 angular.forEach(maskCaretMap, function(value) {
21573 maskPlaceholderChars[value] = '_';
21576 maskPlaceholderCopy = maskPlaceholderChars.join('');
21577 components = maskPlaceholderCopy.replace(/[_]+/g, '_').split('_');
21578 components = components.filter(function(s) {
21582 /* need a string search offset in cases where the mask contains multiple identical components
21583 E.g., a mask of 99.99.99-999.99 */
21585 return components.map(function(c) {
21586 var componentPosition = maskPlaceholderCopy.indexOf(c, offset);
21587 offset = componentPosition + 1;
21590 position: componentPosition
21595 function processRawMask(mask) {
21596 var characterCount = 0;
21600 maskPlaceholder = '';
21602 if (angular.isString(mask)) {
21603 minRequiredLength = 0;
21605 var isOptional = false,
21606 numberOfOptionalCharacters = 0,
21607 splitMask = mask.split('');
21609 var inEscape = false;
21610 angular.forEach(splitMask, function(chr, i) {
21613 maskPlaceholder += chr;
21616 else if (linkOptions.escChar === chr) {
21619 else if (linkOptions.maskDefinitions[chr]) {
21620 maskCaretMap.push(characterCount);
21622 maskPlaceholder += getPlaceholderChar(i - numberOfOptionalCharacters);
21623 maskPatterns.push(linkOptions.maskDefinitions[chr]);
21627 minRequiredLength++;
21630 isOptional = false;
21632 else if (chr === '?') {
21634 numberOfOptionalCharacters++;
21637 maskPlaceholder += chr;
21642 // Caret position immediately following last position is valid.
21643 maskCaretMap.push(maskCaretMap.slice().pop() + 1);
21645 maskComponents = getMaskComponents();
21646 maskProcessed = maskCaretMap.length > 1 ? true : false;
21649 var prevValue = element.val();
21650 function blurHandler() {
21651 if (linkOptions.clearOnBlur || ((linkOptions.clearOnBlurPlaceholder) && (value.length === 0) && attrs.placeholder)) {
21652 oldCaretPosition = 0;
21653 oldSelectionLength = 0;
21654 if (!isValid || value.length === 0) {
21657 scope.$apply(function() {
21658 //only $setViewValue when not $pristine to avoid changing $pristine state.
21659 if (!ctrl.$pristine) {
21660 ctrl.$setViewValue('');
21665 //Check for different value and trigger change.
21666 if (value !== prevValue) {
21667 var currentVal = element.val();
21668 var isTemporarilyEmpty = value === '' && currentVal && angular.isDefined(attrs.uiMaskPlaceholderChar) && attrs.uiMaskPlaceholderChar === 'space';
21669 if(isTemporarilyEmpty) {
21672 triggerChangeEvent(element[0]);
21673 if(isTemporarilyEmpty) {
21674 element.val(currentVal);
21680 function triggerChangeEvent(element) {
21682 if (angular.isFunction(window.Event) && !element.fireEvent) {
21683 // modern browsers and Edge
21685 change = new Event('change', {
21691 //this is for certain mobile browsers that have the Event object
21692 //but don't support the Event constructor
21693 change = document.createEvent('HTMLEvents');
21694 change.initEvent('change', false, true);
21696 element.dispatchEvent(change);
21698 } else if ('createEvent' in document) {
21700 change = document.createEvent('HTMLEvents');
21701 change.initEvent('change', false, true);
21702 element.dispatchEvent(change);
21704 else if (element.fireEvent) {
21706 element.fireEvent('onchange');
21710 function mouseDownUpHandler(e) {
21711 if (e.type === 'mousedown') {
21712 element.bind('mouseout', mouseoutHandler);
21714 element.unbind('mouseout', mouseoutHandler);
21718 element.bind('mousedown mouseup', mouseDownUpHandler);
21720 function mouseoutHandler() {
21721 oldSelectionLength = getSelectionLength(this);
21722 element.unbind('mouseout', mouseoutHandler);
21725 function keydownHandler(e) {
21726 var isKeyBackspace = e.which === 8,
21727 caretPos = getCaretPosition(this) - 1 || 0, //value in keydown is pre change so bump caret position back to simulate post change
21728 isCtrlZ = e.which === 90 && e.ctrlKey; //ctrl+z pressed
21730 if (isKeyBackspace) {
21731 while(caretPos >= 0) {
21732 if (isValidCaretPosition(caretPos)) {
21733 //re-adjust the caret position.
21734 //Increment to account for the initial decrement to simulate post change caret position
21735 setCaretPosition(this, caretPos + 1);
21740 preventBackspace = caretPos === -1;
21744 // prevent IE bug - value should be returned to initial state
21746 e.preventDefault();
21750 function eventHandler(e) {
21752 // Allows more efficient minification
21753 var eventWhich = e.which,
21754 eventType = e.type;
21756 // Prevent shift and ctrl from mucking with old values
21757 if (eventWhich === 16 || eventWhich === 91) {
21761 var val = element.val(),
21764 valAltered = false,
21765 valUnmasked = unmaskValue(val),
21766 valUnmaskedOld = oldValueUnmasked,
21767 caretPos = getCaretPosition(this) || 0,
21768 caretPosOld = oldCaretPosition || 0,
21769 caretPosDelta = caretPos - caretPosOld,
21770 caretPosMin = maskCaretMap[0],
21771 caretPosMax = maskCaretMap[valUnmasked.length] || maskCaretMap.slice().shift(),
21772 selectionLenOld = oldSelectionLength || 0,
21773 isSelected = getSelectionLength(this) > 0,
21774 wasSelected = selectionLenOld > 0,
21775 // Case: Typing a character to overwrite a selection
21776 isAddition = (val.length > valOld.length) || (selectionLenOld && val.length > valOld.length - selectionLenOld),
21777 // Case: Delete and backspace behave identically on a selection
21778 isDeletion = (val.length < valOld.length) || (selectionLenOld && val.length === valOld.length - selectionLenOld),
21779 isSelection = (eventWhich >= 37 && eventWhich <= 40) && e.shiftKey, // Arrow key codes
21781 isKeyLeftArrow = eventWhich === 37,
21782 // Necessary due to "input" event not providing a key code
21783 isKeyBackspace = eventWhich === 8 || (eventType !== 'keyup' && isDeletion && (caretPosDelta === -1)),
21784 isKeyDelete = eventWhich === 46 || (eventType !== 'keyup' && isDeletion && (caretPosDelta === 0) && !wasSelected),
21785 // Handles cases where caret is moved and placed in front of invalid maskCaretMap position. Logic below
21786 // ensures that, on click or leftward caret placement, caret is moved leftward until directly right of
21787 // non-mask character. Also applied to click since users are (arguably) more likely to backspace
21788 // a character when clicking within a filled input.
21789 caretBumpBack = (isKeyLeftArrow || isKeyBackspace || eventType === 'click') && caretPos > caretPosMin;
21791 oldSelectionLength = getSelectionLength(this);
21793 // These events don't require any action
21794 if (isSelection || (isSelected && (eventType === 'click' || eventType === 'keyup' || eventType === 'focus'))) {
21798 if (isKeyBackspace && preventBackspace) {
21799 element.val(maskPlaceholder);
21800 // This shouldn't be needed but for some reason after aggressive backspacing the ctrl $viewValue is incorrect.
21801 // This keeps the $viewValue updated and correct.
21802 scope.$apply(function () {
21803 ctrl.$setViewValue(''); // $setViewValue should be run in angular context, otherwise the changes will be invisible to angular and user code.
21805 setCaretPosition(this, caretPosOld);
21809 // User attempted to delete but raw value was unaffected--correct this grievous offense
21810 if ((eventType === 'input') && isDeletion && !wasSelected && valUnmasked === valUnmaskedOld) {
21811 while (isKeyBackspace && caretPos > caretPosMin && !isValidCaretPosition(caretPos)) {
21814 while (isKeyDelete && caretPos < caretPosMax && maskCaretMap.indexOf(caretPos) === -1) {
21817 var charIndex = maskCaretMap.indexOf(caretPos);
21818 // Strip out non-mask character that user would have deleted if mask hadn't been in the way.
21819 valUnmasked = valUnmasked.substring(0, charIndex) + valUnmasked.substring(charIndex + 1);
21821 // If value has not changed, don't want to call $setViewValue, may be caused by IE raising input event due to placeholder
21822 if (valUnmasked !== valUnmaskedOld)
21827 valMasked = maskValue(valUnmasked);
21829 oldValue = valMasked;
21830 oldValueUnmasked = valUnmasked;
21832 //additional check to fix the problem where the viewValue is out of sync with the value of the element.
21833 //better fix for commit 2a83b5fb8312e71d220a497545f999fc82503bd9 (I think)
21834 if (!valAltered && val.length > valMasked.length)
21837 element.val(valMasked);
21839 //we need this check. What could happen if you don't have it is that you'll set the model value without the user
21840 //actually doing anything. Meaning, things like pristine and touched will be set.
21842 scope.$apply(function () {
21843 ctrl.$setViewValue(valMasked); // $setViewValue should be run in angular context, otherwise the changes will be invisible to angular and user code.
21847 // Caret Repositioning
21848 // Ensure that typing always places caret ahead of typed character in cases where the first char of
21849 // the input is a mask char and the caret is placed at the 0 position.
21850 if (isAddition && (caretPos <= caretPosMin)) {
21851 caretPos = caretPosMin + 1;
21854 if (caretBumpBack) {
21858 // Make sure caret is within min and max position limits
21859 caretPos = caretPos > caretPosMax ? caretPosMax : caretPos < caretPosMin ? caretPosMin : caretPos;
21861 // Scoot the caret back or forth until it's in a non-mask position and within min/max position limits
21862 while (!isValidCaretPosition(caretPos) && caretPos > caretPosMin && caretPos < caretPosMax) {
21863 caretPos += caretBumpBack ? -1 : 1;
21866 if ((caretBumpBack && caretPos < caretPosMax) || (isAddition && !isValidCaretPosition(caretPosOld))) {
21869 oldCaretPosition = caretPos;
21870 setCaretPosition(this, caretPos);
21873 function isValidCaretPosition(pos) {
21874 return maskCaretMap.indexOf(pos) > -1;
21877 function getCaretPosition(input) {
21880 if (input.selectionStart !== undefined) {
21881 return input.selectionStart;
21882 } else if (document.selection) {
21883 if (isFocused(element[0])) {
21886 var selection = document.selection.createRange();
21887 selection.moveStart('character', input.value ? -input.value.length : 0);
21888 return selection.text.length;
21894 function setCaretPosition(input, pos) {
21897 if (input.offsetWidth === 0 || input.offsetHeight === 0) {
21898 return; // Input's hidden
21900 if (input.setSelectionRange) {
21901 if (isFocused(element[0])) {
21903 input.setSelectionRange(pos, pos);
21906 else if (input.createTextRange) {
21908 var range = input.createTextRange();
21909 range.collapse(true);
21910 range.moveEnd('character', pos);
21911 range.moveStart('character', pos);
21916 function getSelectionLength(input) {
21919 if (input.selectionStart !== undefined) {
21920 return (input.selectionEnd - input.selectionStart);
21922 if (window.getSelection) {
21923 return (window.getSelection().toString().length);
21925 if (document.selection) {
21926 return (document.selection.createRange().text.length);
21933 .filter('b2bMultiSepartorHighlight', function($sce) {
21934 return function(text, searchText, searchSeperator) {
21935 var splitText = function(string) {
21936 if(angular.isDefined(searchSeperator)){
21937 if (string.indexOf(searchSeperator) > -1) {
21938 return string.split(searchSeperator);
21947 var newText = splitText(text);
21948 var newPhrase = splitText(searchText);
21949 if (angular.isArray(newPhrase)) {
21950 for (var i = 0; i < newText.length; i++) {
21952 text = newText[i].replace(new RegExp('(' + newPhrase[i] + ')', 'gi'),
21953 '<span class="b2b-search-hightlight">$1</span>');
21955 text = text + searchSeperator + ' ' + (newPhrase[i] ? newText[i].replace(new RegExp('(' + newPhrase[i] + ')', 'gi'),
21956 '<span class="b2b-search-hightlight">$1</span>') : newText[i]);
21960 text = text.replace(new RegExp('(' + searchText + ')', 'gi'),
21961 '<span class="b2b-search-hightlight">$1</span>');
21964 return $sce.trustAsHtml(text)
21968 .factory('b2bUserAgent', [function() {
21969 var _isMobile = function() {
21970 if(/Android/i.test(navigator.userAgent)){
21971 return /Mobile/i.test(navigator.userAgent);
21973 return /Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
21977 var _notMobile = function() {
21978 if(/Android/i.test(navigator.userAgent)){
21979 return !/Mobile/i.test(navigator.userAgent);
21981 return !/Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
21985 var _isIE = function() {
21986 return /msie|trident/i.test(navigator.userAgent);
21988 var _isFF = function() {
21989 return /Firefox/.test(navigator.userAgent);
21991 var _isChrome = function() {
21992 return /Chrome/.test(navigator.userAgent);
21994 var _isSafari = function() {
21995 return /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
21999 isMobile: _isMobile,
22000 notMobile: _notMobile,
22003 isChrome: _isChrome,
22004 isSafari: _isSafari
22007 .run(['$document', 'b2bUserAgent', function($document, b2bUserAgent) {
22008 var html = $document.find('html').eq(0);
22009 if (b2bUserAgent.isIE()) {
22010 html.addClass('isIE');
22012 html.removeClass('isIE');
22018 String.prototype.toSnakeCase = function () {
22019 return this.replace(/([A-Z])/g, function ($1) {
22020 return "-" + $1.toLowerCase();
22023 var concat = function (character, times) {
22024 character = character || '';
22025 times = (!isNaN(times) && times) || 0;
22026 var finalChar = '';
22027 for (var i = 0; i < times; i++) {
22028 finalChar += character;
22033 // direction: true for left and false for right
22034 var pad = function (actualString, width, character, direction) {
22035 actualString = actualString || '';
22036 width = (!isNaN(width) && width) || 0;
22037 character = character || '';
22038 if (width > actualString.length) {
22040 return concat(character, (width - actualString.length)) + actualString;
22042 return actualString + concat(character, (width - actualString.length));
22045 return actualString;
22048 String.prototype.lPad = function (width, character) {
22049 return pad(this, width, character, true);
22052 String.prototype.rPad = function (width, character) {
22053 return pad(this, width, character, false);
22056 if (!Array.prototype.indexOf) {
22057 Array.prototype.indexOf = function (val) {
22058 for (var index = 0; index < this.length; index++) {
22059 if (this[index] === val) {
22067 if (!Array.prototype.regexIndexOf) {
22068 Object.defineProperty(Array.prototype, 'regexIndexOf', {
22070 value: function (regex, startIndex, loop) {
22071 startIndex = startIndex && startIndex > -1 ? startIndex : 0;
22072 for (var index = startIndex; index < this.length; index++) {
22073 if (this[index].toString().match(regex)) {
22078 for (var index = 0; index < startIndex; index++) {
22079 if (this[index].toString().match(regex)) {
22089 angular.module("b2bTemplate/audioPlayer/audioPlayer.html", []).run(["$templateCache", function($templateCache) {
22090 $templateCache.put("b2bTemplate/audioPlayer/audioPlayer.html",
22091 "<div class=\"b2b-audio\">\n" +
22092 " <audio preload=\"auto\">\n" +
22093 " <source ng-src=\"{{audio.mp3 | trustedAudioUrl}}\" type=\"audio/mp3\"></source>\n" +
22094 " <i>Your browser does not support the audio element.</i>\n" +
22097 " <div audio-play-pause-icon class=\"controls-wrapper\" ng-click=\"toggleAudio()\" tabindex=\"0\" b2b-accessibility-click=\"13,32\" role=\"button\" aria-label=\"{{isPlayInProgress?'pause':'play'}}\">\n" +
22098 " <i class=\"icoControls-pointer\" ng-show='!isPlayInProgress'></i>\n" +
22099 " <i class=\"icoControls-pause\" ng-show='isPlayInProgress'></i>\n" +
22102 " <div class=\"seek-bar-container-wrapper\">\n" +
22103 " <b2b-seek-bar min=\"0\" max=\"{{audio.duration}}\" step=\"1\" skip-interval=\"{{audio.timeShiftInSeconds}}\" no-aria-label ng-model=\"audio.currentTime\" on-drag-init=\"isAudioDragging=true\" on-drag-end=\"setAudioPosition(audio.currentTime)\"></b2b-seek-bar>\n" +
22104 " <div class=\"timing-container\">\n" +
22105 " <span class=\"timing-container-left\">{{timeFormatter(audio.currentTime)}}</span>\n" +
22106 " <span class=\"timing-container-right\">{{timeFormatter(audio.duration)}}</span>\n" +
22107 " <div class=\"timing-container-spacer\"></div>\n" +
22111 " <b2b-flyout>\n" +
22112 " <div tabindex=\"0\" b2b-accessibility-click=\"13,32\" role=\"button\" aria-label=\"Volume pop-over. Volume set at {{audio.currentVolume}}%\" class=\"controls-wrapper audio-volume-control\" b2b-flyout-toggler>\n" +
22113 " <i class=\"icoControls-mutespeakers\" ng-show=\"audio.currentVolume === 0\"></i>\n" +
22114 " <i class=\"icoControls-volumedown\" ng-show=\"audio.currentVolume > 0 && audio.currentVolume <= 50\"></i>\n" +
22115 " <i class=\"icoControls-volumeup\" ng-show=\"audio.currentVolume > 50\"></i>\n" +
22118 " <b2b-flyout-content horizontal-placement=\"center\" flyout-style=\"width:70px; height:190px;\" vertical-placement=\"above\">\n" +
22119 " <div class=\"b2b-audio-popover text-center\">\n" +
22120 " <span>Max</span>\n" +
22121 " <b2b-seek-bar min=\"0\" max=\"100\" step=\"1\" skip-interval=\"10\" vertical data-ng-model=\"audio.currentVolume\" class='volume-popover'></b2b-seek-bar>\n" +
22122 " <div class=\"min-label\">Min</div>\n" +
22124 " </b2b-flyout-content>\n" +
22125 " </b2b-flyout>\n" +
22129 angular.module("b2bTemplate/audioRecorder/audioRecorder.html", []).run(["$templateCache", function($templateCache) {
22130 $templateCache.put("b2bTemplate/audioRecorder/audioRecorder.html",
22131 "<div class=\"b2b-audio-recorder row\">\n" +
22132 " <div class=\"b2b-elapsed-time span11\">\n" +
22133 " <div ng-if=\"isRecording\">\n" +
22134 " <span style=\"padding-right: 25px;\">{{config.whileRecordingMessage}}</span>\n" +
22135 " <span>{{timeFormatter(elapsedTime)}}</span>\n" +
22137 " <span ng-if=\"!isRecording\">{{config.startRecordingMessage}}</span>\n" +
22139 " <div class=\"b2b-controls\" title=\"{{isRecording ? 'Stop' : 'REC'}}\" b2b-accessibility-click=\"13,32\" ng-click=\"toggleRecording()\" role=\"button\">\n" +
22140 " <i ng-if=\"isRecording\" class=\"icoControls-stop\" ></i>\n" +
22141 " <i ng-if=\"!isRecording\" class=\"icoControls-record\"></i>\n" +
22146 angular.module("b2bTemplate/backToTop/backToTop.html", []).run(["$templateCache", function($templateCache) {
22147 $templateCache.put("b2bTemplate/backToTop/backToTop.html",
22148 "<button class=\"btn-arrow b2b-backtotop-button\" type=\"button\" aria-label=\"Back to top\">\n" +
22149 " <div class=\"btn-secondary b2b-top-btn\">\n" +
22150 " <i class=\"icoControls-upPRIMARY\" role=\"img\"></i>\n" +
22156 angular.module("b2bTemplate/boardstrip/b2bAddBoard.html", []).run(["$templateCache", function($templateCache) {
22157 $templateCache.put("b2bTemplate/boardstrip/b2bAddBoard.html",
22158 "<div tabindex=\"0\" role=\"menuitem\" b2b-accessibility-click=\"13,32\" ng-click=\"addBoard()\" aria-label=\"Add Board\" class=\"boardstrip-item--add\">\n" +
22159 " <div class=\"centered\"><i aria-hidden=\"true\" class=\"icoControls-add-maximize\"></i> Add board</div>\n" +
22163 angular.module("b2bTemplate/boardstrip/b2bBoard.html", []).run(["$templateCache", function($templateCache) {
22164 $templateCache.put("b2bTemplate/boardstrip/b2bBoard.html",
22165 "<li b2b-board-navigation tabindex=\"-1\" role=\"menuitem\" aria-label=\"{{boardLabel}} {{getCurrentIndex()===boardIndex?'selected':''}}\" b2b-accessibility-click=\"13,32\" ng-click=\"selectBoard(boardIndex)\" class=\"board-item\" ng-class=\"{'selected': getCurrentIndex()===boardIndex}\">\n" +
22166 " <div ng-transclude></div>\n" +
22167 " <div class=\"board-caret\" ng-if=\"getCurrentIndex()===boardIndex\">\n" +
22168 " <div class=\"board-caret-indicator\"></div>\n" +
22169 " <div class=\"board-caret-arrow-up\"></div>\n" +
22174 angular.module("b2bTemplate/boardstrip/b2bBoardstrip.html", []).run(["$templateCache", function($templateCache) {
22175 $templateCache.put("b2bTemplate/boardstrip/b2bBoardstrip.html",
22176 "<div class=\"b2b-boardstrip\">\n" +
22177 " <div class=\"boardstrip-reel\" role=\"menu\">\n" +
22178 " <div class=\"prev-items\">\n" +
22179 " <!-- <i tabindex=\"{{isPrevBoard() ? 0 : -1}}\" ng-class=\"{'disabled': !isPrevBoard()}\" role=\"menuitem\" aria-label=\"See previous boards\" b2b-accessibility-click=\"13,32\" ng-click=\"prevBoard()\" class=\"arrow icoControls-left\"></i> -->\n" +
22180 " <button class=\"btn-arrow arrow\" b2b-accessibility-click=\"13,32\" ng-click=\"prevBoard()\" ng-disabled=\"!isPrevBoard()\">\n" +
22181 " <div class=\"btn btn-small btn-alt\"><i class=\"icon-primary-left\"></i>\n" +
22183 " <span class=\"offscreen-text\">Previous boards</span>\n" +
22186 " <div b2b-add-board on-add-board=\"onAddBoard()\"></div>\n" +
22187 " <div class=\"board-viewport\"><ul role=\"menu\" class=\"boardstrip-container\" ng-transclude></ul></div>\n" +
22188 " <div class=\"next-items\">\n" +
22189 " <!-- <i tabindex=\"{{isNextBoard() ? 0 : -1}}\" ng-class=\"{'disabled': !isNextBoard()}\" role=\"menuitem\" aria-label=\"See next boards\" b2b-accessibility-click=\"13,32\" ng-click=\"nextBoard()\" class=\"arrow icoControls-right\"></i> -->\n" +
22190 " <button class=\"btn-arrow arrow\" b2b-accessibility-click=\"13,32\" ng-click=\"nextBoard()\" ng-disabled=\"!isNextBoard()\">\n" +
22191 " <div class=\"btn btn-small btn-alt\"><i class=\"icon-primary-right\"></i>\n" +
22193 " <span class=\"offscreen-text\">Next boards</span>\n" +
22201 angular.module("b2bTemplate/calendar/datepicker-popup.html", []).run(["$templateCache", function($templateCache) {
22202 $templateCache.put("b2bTemplate/calendar/datepicker-popup.html",
22203 "<div id=\"datepicker\" class=\"datepicker dropdown-menu\" ng-class=\"{'datepicker-dropdown datepicker-orient-top': !inline, 'datepicker-orient-left': !inline && orientation === 'left', 'datepicker-orient-right': !inline && orientation === 'right'}\" ng-style=\"{position: inline && 'relative', 'z-index': inline && '0', top: !inline && position.top + 'px' || 0, left: !inline && position.left + 'px'}\" style=\"display: block;\" aria-hidden=\"false\" role=\"dialog\" tabindex=\"-1\" b2b-key type=\"table\" columns=\"7\">\n" +
22204 " <div class=\"datepicker-days\" style=\"display: block;\">\n" +
22205 " <div ng-repeat=\"header in headers\" class=\"text-left\" style=\"width: 100%;\" b2b-append-element=\"header\"></div>\n" +
22206 " <table class=\"table-condensed\">\n" +
22209 " <th id=\"prev\" class=\"prev\" tabindex=\"0\" b2b-accessibility-click=\"13,32\" aria-label=\"Previous Month\" role=\"button\" b2b-element-focus=\"!disablePrev && getFocus\" ng-style=\"{visibility: visibilityPrev}\" ng-click=\"!disablePrev && move(-1,$event)\"><i class=\"icon-primary-left\" aria-hidden=\"true\"></i></th>\n" +
22210 " <th id=\"month\" tabindex=\"-1\" aria-label=\"{{title}}\" class=\"datepicker-switch\" colspan=\"{{rows[0].length - 2}}\">{{title}}</th>\n" +
22211 " <th id=\"next\" class=\"next\" tabindex=\"0\" b2b-accessibility-click=\"13,32\" b2b-element-focus=\"disablePrev && getFocus\" aria-label=\"Next Month\" role=\"button\" ng-style=\"{visibility: visibilityNext}\" ng-click=\"!disableNext && move(1,$event)\"><i class=\"icon-primary-right\" aria-hidden=\"true\"></i></th>\n" +
22213 " <tr ng-show=\"labels.length > 0\">\n" +
22214 " <th id=\"{{label.post}}\" class=\"dow\" ng-repeat=\"label in labels\" aria-hidden=\"true\"><span aria-hidden=\"true\">{{label.pre}}</span></th>\n" +
22218 " <tr ng-repeat=\"row in rows\">\n" +
22219 " <td headers=\"{{dt.header}}\" b2b-key-item=\"dt.focusable\" b2b-accessibility-click=\"13\" b2b-element-focus=\"disablePrev && disableNext && getFocus && (dt.selected || dt.firstFocus || dt.fromDate || dt.dateRange)\" tabindex=\"{{(!(dt.focusable && (dt.selected || dt.firstFocus || dt.fromDate || currFocus)) && -1) || 0}}\" aria-hidden=\"{{(!dt.focusable && true) || false}}\" role=\"{{(dt.focusable && 'gridcell') || ''}}\" aria-label=\"{{(dt.focusable && dt.date | date : 'EEEE, MMMM d') || ''}}\" aria-selected=\"{{(dt.focusable && (dt.selected || dt.dateRange) && true) || false}}\" ng-repeat=\"dt in row\" class=\"day magic\" ng-class=\"{'active': dt.focusable && dt.selected && !dt.dateRange, 'start-date': dt.focusable && dt.fromDate && dt.dateRange, 'between-date': dt.focusable && !dt.fromDate && !dt.selected && dt.dateRange, 'end-date': dt.focusable && dt.selected && dt.dateRange, 'old': dt.oldMonth, 'new': dt.nextMonth, 'disabled': dt.disabled, 'due-date': dt.dueDate, 'late-fee': dt.pastDue}\" ng-focus=\"currFocus=true; trapFocus();\" ng-blur=\"currFocus=false; trapFocus();\" title=\"{{(dt.focusable && dt.pastDue && legendMessage) || ''}}\" ng-click=\"!ngDisabled && !dt.disabled && select(dt.date)\">\n" +
22220 " <div aria-hidden=\"true\" tabindex=\"-1\" class=\"show-date\" ng-if=\"!(dt.oldMonth || dt.nextMonth)\">{{dt.label}}</div>{{(dt.focusable && dt.date | date : 'EEEE, MMMM d yyyy') || ''}}{{(dt.focusable && dt.dueDate && '. Bill-due-date.. ') || ''}}{{(dt.focusable && dt.pastDue && legendMessage) || ''}}</td>\n" +
22224 " <tr ng-repeat=\"footer in footers\">\n" +
22225 " <th colspan=\"7\" class=\"text-left\" style=\"width: 278px;\" b2b-append-element=\"footer\"></th>\n" +
22233 angular.module("b2bTemplate/calendar/datepicker.html", []).run(["$templateCache", function($templateCache) {
22234 $templateCache.put("b2bTemplate/calendar/datepicker.html",
22236 " <span class=\"icon-primary-calendar span12\" ng-class=\"{'disabled': ngDisabled}\" ng-transclude></span>\n" +
22240 angular.module("b2bTemplate/coachmark/coachmark.html", []).run(["$templateCache", function($templateCache) {
22241 $templateCache.put("b2bTemplate/coachmark/coachmark.html",
22242 "<div class=\"b2b-coachmark-container\" tabindex=\"-1\" role=\"dialog\" aria-label=\"{{currentCoachmark.contentHeader}} {{currentCoachmark.content}} {{currentCoachmark.offscreenText}}\" ng-style=\"{'top':coackmarkElPos.top,'left':coackmarkElPos.left}\" aria-describeby=\"{{currentCoachmark}}\">\n" +
22243 " <i class=\"b2b-coachmark-caret\"></i>\n" +
22244 " <div class=\"b2b-coachmark-header\">\n" +
22245 " <div class=\"b2b-coachmark-countlabel\"><span ng-if=\"coachmarkIndex !== 0\">{{coachmarkIndex}} of {{(coachmarks.length-1)}}<span></div>\n" +
22246 " <div class=\"corner-button\">\n" +
22247 " <button type=\"button\" ng-focus=\"closeButtonFocus()\" class=\"close\" title=\"close\" aria-label=\"Close\" ng-click=\"closeCoachmark()\"></button>\n" +
22250 " <div class=\"b2b-coachmark-content\"> \n" +
22251 " <i class=\"icon-misc-dimmer\"></i>\n" +
22252 " <div class=\"b2b-coachmark-content-header\"><span class=\"offscreen-text\">{{currentCoachmark.offscreenText}}</span>{{currentCoachmark.contentHeader}}</div>\n" +
22253 " <div class=\"b2b-coachmark-description\">{{currentCoachmark.content}}</div>\n" +
22254 " <div class=\"b2b-coachmark-btn-group\">\n" +
22255 " <a class=\"b2b-coachmark-link\" href=\"javascript:void(0)\" ng-if=\"currentCoachmark.linkLabel !== '' && currentCoachmark.linkLabel !== undefined\" ng-click=\"actionCoachmark(currentCoachmark.linkLabel)\">{{currentCoachmark.linkLabel}}</a>\n" +
22256 " <button class=\"btn btn-alt\" ng-if=\"currentCoachmark.buttonLabel !== '' && currentCoachmark.buttonLabel !== undefined\" ng-click=\"actionCoachmark(currentCoachmark.buttonLabel)\">{{currentCoachmark.buttonLabel}}</button>\n" +
22262 angular.module("b2bTemplate/dropdowns/b2bDropdownDesktop.html", []).run(["$templateCache", function($templateCache) {
22263 $templateCache.put("b2bTemplate/dropdowns/b2bDropdownDesktop.html",
22264 "<span b2b-key prev=\"38\" next=\"40\" enable-search ng-class=\"{'large': (dropdownSize === 'large'), 'disabled': (disabled), 'selectWrap': (isInputDropdown), 'selectorModule': (dropdownType === 'menu'), 'linkSelectorModule': (dropdownType === 'link-menu')}\">\n" +
22265 " <input b2b-dropdown-toggle b2b-dropdown-validation ng-disabled=\"disabled\" type=\"text\" id=\"{{dropdownId}}\" name=\"{{dropdownName}}\" class=\"awd-select isWrapped\" ng-required=\"dropdownRequired\" ng-model=\"currentSelected.text\" role=\"combobox\" aria-owns=\"listbox{{$id}}\" aria-expanded=\"{{toggleFlag}}\" ng-click=\"toggleDropdown()\" ng-blur=\"setBlur();\" ng-class=\"{'active': toggleFlag, 'closed': !toggleFlag, 'large': (dropdownSize === 'large')}\" style=\"width:100%;\" value=\"{{currentSelected.text}}\" ng-show=\"isInputDropdown\" aria-describedby=\"{{dropdownDescribedBy}}\" readonly=\"readonly\">\n" +
22266 " <button type=\"button\" b2b-dropdown-toggle ng-disabled=\"disabled\" class=\"selectModule\" aria-label=\"{{labelText}} {{currentSelected.label}}\" aria-expanded=\"{{toggleFlag}}\" aria-haspopup=\"true\" ng-click=\"toggleDropdown()\" ng-blur=\"setBlur()\" ng-class=\"{'active opened': toggleFlag, 'closed': !toggleFlag, 'large': (dropdownSize === 'large')}\" ng-bind-html=\"currentSelected.text\" ng-show=\"!isInputDropdown\"></button>\n" +
22267 " <div ng-class=\"{'selectWrapper': (isInputDropdown), 'moduleWrapper': (!isInputDropdown)}\">\n" +
22268 " <ul id=\"listbox{{$id}}\" role=\"{{isInputDropdown?'listbox':'menu'}}\" ng-class=\"{'awd-select-list': (isInputDropdown), 'awd-module-list': (!isInputDropdown)}\" tabindex=\"-1\" ng-show=\"toggleFlag\" aria-label=\"Choose options\"></ul>\n" +
22269 " <ul class=\"module-optinalcta\" ng-if=\"toggleFlag && optionalCta\" tabindex=\"-1\" aria-hidden=\"false\">\n" +
22270 " <li class=\"module-list-item\" tabindex=\"-1\" role=\"menuitem\" value=\"\" aria-label=\"Optinal CTA\" aria-selected=\"true\">\n" +
22271 " <span class=\"module-data\" b2b-append-element=\"optionalCta\"></span>\n" +
22275 "<i class=\"icon-primary-down\" aria-hidden=\"true\"></i>\n" +
22279 angular.module("b2bTemplate/dropdowns/b2bDropdownGroupDesktop.html", []).run(["$templateCache", function($templateCache) {
22280 $templateCache.put("b2bTemplate/dropdowns/b2bDropdownGroupDesktop.html",
22281 "<li b2b-dropdown-group-desktop class=\"optgroup-wrapper\">{{groupHeader}}\n" +
22282 " <ul class=\"optgroup\" role=\"group\" aria-label=\"{{groupHeader}}\"></ul>\n" +
22286 angular.module("b2bTemplate/dropdowns/b2bDropdownListDesktop.html", []).run(["$templateCache", function($templateCache) {
22287 $templateCache.put("b2bTemplate/dropdowns/b2bDropdownListDesktop.html",
22288 "<li b2b-dropdown-list-desktop b2b-key-item b2b-accessibility-click=\"13\" aria-selected=\"{{currentSelected.value === dropdownListValue}}\" data-hover=\"{{dropdown.highlightedValue === dropdownListValue}}\" ng-class=\"{'awd-select-list-item': (isInputDropdown), 'module-list-item': (!isInputDropdown)}\" tabindex=\"0\" role=\"{{isInputDropdown?'option':'menuitem'}}\" ng-click=\"selectDropdownItem()\" ng-focus=\"highlightDropdown()\"></li>");
22291 angular.module("b2bTemplate/fileUpload/fileUpload.html", []).run(["$templateCache", function($templateCache) {
22292 $templateCache.put("b2bTemplate/fileUpload/fileUpload.html",
22293 "<label class=\"b2b-file-container\">\n" +
22294 " <span class=\"b2b-upload-link\" ng-transclude></span>\n" +
22295 " <input type=\"file\" b2b-file-change>\n" +
22299 angular.module("b2bTemplate/flyout/flyout.html", []).run(["$templateCache", function($templateCache) {
22300 $templateCache.put("b2bTemplate/flyout/flyout.html",
22301 "<span class=\"b2b-flyout\" b2b-flyout-trap-focus-inside>\n" +
22302 " <span ng-transclude></span>\n" +
22306 angular.module("b2bTemplate/flyout/flyoutContent.html", []).run(["$templateCache", function($templateCache) {
22307 $templateCache.put("b2bTemplate/flyout/flyoutContent.html",
22308 "<div class=\"b2b-flyout-container\" aria-live=\"polite\" aria-atomic=\"false\" ng-class=\"{'b2b-flyout-left':horizontalPlacement==='left',\n" +
22309 " 'b2b-flyout-center':horizontalPlacement==='center', \n" +
22310 " 'b2b-flyout-right':horizontalPlacement==='right',\n" +
22311 " 'b2b-flyout-centerLeft':horizontalPlacement==='centerLeft',\n" +
22312 " 'b2b-flyout-centerRight':horizontalPlacement==='centerRight', \n" +
22313 " 'b2b-flyout-above':verticalPlacement==='above', \n" +
22314 " 'b2b-flyout-below':verticalPlacement==='below',\n" +
22315 " 'open-flyout': openFlyout,\n" +
22316 " 'b2b-close-flyout': !openFlyout}\">\n" +
22317 " <i class=\"b2b-flyout-caret\" ng-class=\"{'open-flyout': openFlyout, \n" +
22318 " 'b2b-flyout-caret-above':verticalPlacement==='above',\n" +
22319 " 'b2b-flyout-caret-below':verticalPlacement==='below'}\"></i>\n" +
22320 " <span ng-transclude></span>\n" +
22324 angular.module("b2bTemplate/footer/footer_column_switch_tpl.html", []).run(["$templateCache", function($templateCache) {
22325 $templateCache.put("b2bTemplate/footer/footer_column_switch_tpl.html",
22326 "<div class=\"footer-columns \" ng-class=\"{'five-column':footerColumns===5, 'four-column':footerColumns===4, 'three-column':footerColumns===3}\" ng-repeat=\"item in footerLinkItems\">\n" +
22327 " <h3 class=\"b2b-footer-header\">{{item.categoryName}}</h3>\n" +
22329 " <li ng-repeat=\"i in item.values\">\n" +
22330 " <a href=\"{{i.href}}\" title=\"{{item.categoryName}} - {{i.displayLink}}\">{{i.displayLink}}</a> \n" +
22336 "<div ng-transclude></div>\n" +
22340 angular.module("b2bTemplate/horizontalTable/horizontalTable.html", []).run(["$templateCache", function($templateCache) {
22341 $templateCache.put("b2bTemplate/horizontalTable/horizontalTable.html",
22342 "<div class=\"b2b-horizontal-table\">\n" +
22343 " <div class=\"b2b-horizontal-table-arrows row span12\">\n" +
22344 " <div class=\"span4 b2b-prev-link\">\n" +
22345 " <a href=\"javascript:void(0)\" ng-click=\"moveViewportLeft()\" ddh-accessibility-click=\"13,32\" ng-if=\"!disableLeft\">Previous</a>\n" +
22346 " <span ng-if=\"disableLeft\" class=\"b2b-disabled-text\">Previous</span>\n" +
22349 " <span class=\"span5 b2b-horizontal-table-column-info\" aria-live=\"polite\" tabindex=\"-1\">\n" +
22350 " Showing {{countDisplayText}} {{getColumnSet()[0]+1}}-{{getColumnSet()[1]+1}} of {{numOfCols}} columns\n" +
22353 " <div ng-if=\"legendContent\" class=\"span2 b2b-horizontal-table-legend\">\n" +
22354 " | <b2b-flyout>\n" +
22355 " <div tabindex=\"0\" role=\"button\" aria-haspopup=\"true\" b2b-flyout-toggler b2b-accessibility-click=\"13,32\" aria-expanded=\"{{flyoutOpened ? 'true' : 'false'}}\">\n" +
22357 " <i class=\"icoControls-down\" role=\"img\"></i>\n" +
22359 " <b2b-flyout-content horizontal-placement=\"center\" vertical-placement=\"below\">\n" +
22360 " <div ng-bind-html=\"legendContent\"></div>\n" +
22361 " </b2b-flyout-content>\n" +
22362 " </b2b-flyout>\n" +
22365 " <div class=\"span3 text-right b2b-next-link\">\n" +
22366 " <a href=\"javascript:void(0)\" ng-click=\"moveViewportRight()\" ddh-accessibility-click=\"13,32\" ng-if=\"!disableRight\">Next</a>\n" +
22367 " <span ng-if=\"disableRight\" class=\"b2b-disabled-text\">Next</span>\n" +
22370 " <div class=\"b2b-horizontal-table-inner-container\">\n" +
22371 " <span ng-transclude></span>\n" +
22376 angular.module("b2bTemplate/hourPicker/b2bHourpicker.html", []).run(["$templateCache", function($templateCache) {
22377 $templateCache.put("b2bTemplate/hourPicker/b2bHourpicker.html",
22378 "<div class=\"hp-container\">\n" +
22379 " <div class=\"hp-selected\">\n" +
22380 " <div b2b-hourpicker-value=\"$index\" days=\"value.days\" start-time=\"value.startTime\" start-meridiem=\"value.startMeridiem\" end-time=\"value.endTime\" end-meridiem=\"value.endMeridiem\" ng-repeat=\"value in finalHourpickerValues\"></div>\n" +
22382 " <div b2b-hourpicker-panel></div>\n" +
22386 angular.module("b2bTemplate/hourPicker/b2bHourpickerPanel.html", []).run(["$templateCache", function($templateCache) {
22387 $templateCache.put("b2bTemplate/hourPicker/b2bHourpickerPanel.html",
22388 "<form name=\"{{'hourpickerForm' + $id}}\">\n" +
22389 " <div class=\"hp-checkbox\" role=\"group\">\n" +
22390 " <label class=\"checkbox\" for=\"checkbox_{{dayOption.title}}_{{$id}}\" ng-repeat=\"dayOption in hourpicker.dayOptions\">\n" +
22391 " <input type=\"checkbox\" id=\"checkbox_{{dayOption.title}}_{{$id}}\" name=\"{{'hourpickerDays' + $id}}\" ng-model=\"hourpickerPanelValue.days[$index].value\" ng-disabled=\"dayOption.disabled\" /><i class=\"skin\"></i><span>{{dayOption.label}}</span>\n" +
22394 " <div class=\"row hp-dropdowns\">\n" +
22395 " <div class=\"span4\">\n" +
22396 " <label for=\"{{'hourpickerStartTime' + $id}}\">From</label>\n" +
22397 " <select id=\"{{'hourpickerStartTime' + $parent.$id}}\" b2b-dropdown type=\"\" ng-model=\"hourpickerPanelValue.startTime\">\n" +
22398 " <option b2b-dropdown-list value=\"\">From</option>\n" +
22399 " <option b2b-dropdown-list option-repeat=\"startTimeOption in hourpicker.startTimeOptions\" value=\"{{startTimeOption}}\">{{startTimeOption}}</option>\n" +
22402 " <div class=\"span8 radio-buttons\" role=\"radiogroup\" aria-label=\"Select AM or PM\">\n" +
22403 " <label class=\"radio\" for=\"hourpickerStartMeridiem_AM_{{$id}}\">\n" +
22404 " <input type=\"radio\" id=\"hourpickerStartMeridiem_AM_{{$id}}\" name=\"{{'hourpickerStartMeridiem' + $id}}\" value=\"am\" ng-model=\"hourpickerPanelValue.startMeridiem\" /><i class=\"skin\"></i><span>AM</span>\n" +
22406 " <label class=\"radio\" for=\"hourpickerStartMeridiem_PM_{{$id}}\">\n" +
22407 " <input type=\"radio\" id=\"hourpickerStartMeridiem_PM_{{$id}}\" name=\"{{'hourpickerStartMeridiem' + $id}}\" value=\"pm\" ng-model=\"hourpickerPanelValue.startMeridiem\" /><i class=\"skin\"></i><span>PM</span>\n" +
22411 " <div class=\"row hp-dropdowns\">\n" +
22412 " <div class=\"span4\">\n" +
22413 " <label for=\"{{'hourpickerEndTime' + $id}}\">To</label>\n" +
22414 " <select id=\"{{'hourpickerEndTime' + $parent.$id}}\" b2b-dropdown ng-model=\"hourpickerPanelValue.endTime\">\n" +
22415 " <option b2b-dropdown-list value=\"\">To</option>\n" +
22416 " <option b2b-dropdown-list option-repeat=\"endTimeOption in hourpicker.endTimeOptions\" value=\"{{endTimeOption}}\">{{endTimeOption}}</option>\n" +
22419 " <div class=\"span8 radio-buttons\" role=\"radiogroup\" aria-label=\"Select AM or PM\">\n" +
22420 " <label class=\"radio\" for=\"hourpickerEndMeridiem_AM_{{$id}}\">\n" +
22421 " <input type=\"radio\" id=\"hourpickerEndMeridiem_AM_{{$id}}\" name=\"{{'hourpickerEndMeridiem' + $id}}\" value=\"am\" ng-model=\"hourpickerPanelValue.endMeridiem\" /><i class=\"skin\"></i><span>AM</span>\n" +
22423 " <label class=\"radio\" for=\"hourpickerEndMeridiem_PM_{{$id}}\">\n" +
22424 " <input type=\"radio\" id=\"hourpickerEndMeridiem_PM_{{$id}}\" name=\"{{'hourpickerEndMeridiem' + $id}}\" value=\"pm\" ng-model=\"hourpickerPanelValue.endMeridiem\" /><i class=\"skin\"></i><span>PM</span>\n" +
22428 " <div class=\"row hp-buttons\">\n" +
22429 " <div class=\"span12\">\n" +
22430 " <div style=\"float:right\">\n" +
22431 " <button class=\"btn btn-secondary btn-small\" ng-click=\"resetHourpickerPanelValue()\">Clear</button>\n" +
22432 " <button class=\"btn btn-alt btn-small\" ng-disabled = \"disableAddBtn\" ng-click=\"updateHourpickerValue()\">{{hourpicker.editMode > -1 ? 'Update' : 'Add'}}</button>\n" +
22439 angular.module("b2bTemplate/hourPicker/b2bHourpickerValue.html", []).run(["$templateCache", function($templateCache) {
22440 $templateCache.put("b2bTemplate/hourPicker/b2bHourpickerValue.html",
22441 "<div class=\"selected-days\">\n" +
22442 " <span class=\"day\">{{hourpickerValue.days}} {{hourpickerValue.startTime}} {{hourpickerValue.startMeridiem}} - {{hourpickerValue.endTime}} {{hourpickerValue.endMeridiem}}</span>\n" +
22443 " <span style=\"float:right\">\n" +
22444 " <i class=\"icon-misc-pen\" role=\"button\" aria-label=\"Edit {{hourpickerValue.days}} {{hourpickerValue.startTime}} {{hourpickerValue.startMeridiem}} - {{hourpickerValue.endTime}} {{hourpickerValue.endMeridiem}}\" title=\"Edit\" tabindex=\"0\" b2b-accessibility-click=\"13,32\" ng-click=\"editHourpickerValue(hourpickerValue.index)\"></i>\n" +
22445 " <i class=\"icon-misc-trash\" role=\"button\" aria-label=\"Delete {{hourpickerValue.days}} {{hourpickerValue.startTime}} {{hourpickerValue.startMeridiem}} - {{hourpickerValue.endTime}} {{hourpickerValue.endMeridiem}}\" title=\"Delete\" tabindex=\"0\" b2b-accessibility-click=\"13,32\" ng-click=\"deleteHourpickerValue(hourpickerValue.index)\"></i>\n" +
22447 " <div style=\"clear:both\"></div>\n" +
22451 angular.module("b2bTemplate/leftNavigation/leftNavigation.html", []).run(["$templateCache", function($templateCache) {
22452 $templateCache.put("b2bTemplate/leftNavigation/leftNavigation.html",
22453 "<div class=\"b2b-nav-menu\">\n" +
22454 " <div class=\"b2b-subnav-container\">\n" +
22455 " <ul class=\"b2b-subnav-content\">\n" +
22456 " <li ng-repeat=\"menu in menuData\" ng-click=\"toggleNav($index)\"><a ng-class=\"{'expand': idx==$index}\" aria-label=\"{{menu.name}}\" title=\" \" aria-expanded=\"{{(idx==$index)?true:false;}}\" href=\"javascript:void(0);\">{{menu.name}}<i class=\"b2b-icon-primary-plus-minus\" ng-class=\"idx==$index ? 'icon-primary-expanded' : 'icon-primary-collapsed'\"></i></a>\n" +
22457 " <ul ng-class=\"{expand: idx==$index}\">\n" +
22458 " <li ng-repeat=\"menuItem in menu.menuItems\" ng-click=\"liveLink($event, $index, $parent.$index)\"><a aria-hidden=\"{{!(idx==$parent.$index)}}\" aria-label=\"{{menuItem.name}}\" title=\" \" href=\"{{menuItem.href}}\" tabindex=\"{{(idx==$parent.$index)?0:-1;}}\" ng-class=\"{active: itemIdx==$index && navIdx==$parent.$index}\">{{menuItem.name}}</a></li>\n" +
22466 angular.module("b2bTemplate/listbox/listbox.html", []).run(["$templateCache", function($templateCache) {
22467 $templateCache.put("b2bTemplate/listbox/listbox.html",
22468 "<div class=\"b2b-list-box\" tabindex=\"0\" role=\"listbox\" ng-transclude>\n" +
22472 angular.module("b2bTemplate/modalsAndAlerts/b2b-backdrop.html", []).run(["$templateCache", function($templateCache) {
22473 $templateCache.put("b2bTemplate/modalsAndAlerts/b2b-backdrop.html",
22474 "<div class=\"b2b-modal-backdrop fade in hidden-by-modal\" aria-hidden=\"true\" tabindex=\"-1\"></div>");
22477 angular.module("b2bTemplate/modalsAndAlerts/b2b-window.html", []).run(["$templateCache", function($templateCache) {
22478 $templateCache.put("b2bTemplate/modalsAndAlerts/b2b-window.html",
22479 "<div class=\"modalwrapper active {{windowClass}}\" ng-class=\"{'modal-landscape': isModalLandscape}\" role=\"{{isNotifDialog?'alertdialog':'dialog'}}\" tabindex=\"-1\" aria-labelledby=\"{{title}}\" aria-describedby=\"{{content}}\">\n" +
22480 " <div class=\"modal fade in {{sizeClass}}\" ng-transclude></div>\n" +
22484 angular.module("b2bTemplate/monthSelector/monthSelector-popup.html", []).run(["$templateCache", function($templateCache) {
22485 $templateCache.put("b2bTemplate/monthSelector/monthSelector-popup.html",
22486 "<div id=\"monthselector{{$id}}\" class=\"datepicker dropdown-menu monthselector\" \n" +
22487 " ng-class=\"{'datepicker-dropdown datepicker-orient-top': !inline, 'datepicker-orient-left': !inline && orientation === 'left', 'datepicker-orient-right': !inline && orientation === 'right'}\" \n" +
22488 " ng-style=\"{position: inline && 'relative', 'z-index': inline && '0', top: !inline && position.top + 'px' || 0, left: !inline && position.left + 'px'}\" \n" +
22489 " style=\"display: block;\" aria-hidden=\"false\" role=\"dialog presentation\" tabindex=\"-1\">\n" +
22490 " <div class=\"datepicker-days\" style=\"display: block;\">\n" +
22491 " <span class=\"offscreen-text\" aria-live=\"polite\" aria-atomic=\"true\">{{title}} displaying</span>\n" +
22492 " <table class=\"table-condensed\" role=\"grid\">\n" +
22494 " <tr ng-repeat=\"header in headers\">\n" +
22495 " <th colspan=\"7\" class=\"text-left\" style=\"width: 278px;\" b2b-append-element=\"header\"></th>\n" +
22498 " <th id=\"prev{{$id}}\" role=\"button\" tabindex=\"0\" b2b-accessibility-click=\"13\" aria-label=\"Previous Year\" ng-class=\"{'prev': !disablePrev}\" ng-style=\"{visibility: visibilityPrev}\" ng-click=\"!disablePrev && move(-1,$event)\"><i class=\"icon-primary-left\"></i></th>\n" +
22499 " <th id=\"year{{$id}}\" role=\"heading\" tabindex=\"-1\" aria-label=\"{{title}}\" class=\"datepicker-switch b2b-monthSelector-label\" colspan=\"{{rows[0].length - 2}}\">{{title}}</th>\n" +
22500 " <th id=\"next{{$id}}\" role=\"button\" tabindex=\"0\" b2b-accessibility-click=\"13\" aria-label=\"Next Year\" ng-class=\"{'next': !disableNext}\" ng-style=\"{visibility: visibilityNext}\" ng-click=\"!disableNext && move(1,$event)\"><i class=\"icon-primary-right\"></i></th>\n" +
22502 " <tr ng-show=\"labels.length > 0\">\n" +
22503 " <th id=\"{{label.post}}\" class=\"dow\" ng-repeat=\"label in labels\" aria-hidden=\"true\"><span aria-hidden=\"true\">{{label.pre}}</span></th>\n" +
22506 " <tbody b2b-key type=\"table\" columns=\"4\" >\n" +
22507 " <tr ng-repeat=\"row in rows\">\n" +
22508 " <td headers=\"{{dt.header}}\" b2b-key-item=\"dt.focusable\" b2b-accessibility-click=\"13,32\" tabindex=\"{{(!(dt.focusable && (dt.selected || dt.firstFocus || currFocus)) && -1) || 0}}\" aria-hidden=\"{{(!dt.focusable && true) || false}}\" role=\"{{(dt.focusable && 'gridcell') || ''}}\" aria-label=\"{{(dt.focusable && dt.date | date : 'MMMM, y') || ''}}\" aria-selected=\"{{(dt.focusable && (dt.selected || dt.dateRange) && true) || false}}\" ng-repeat=\"dt in row\" class=\"day magic\" ng-class=\"{'active': dt.focusable && dt.selected && !dt.dateRange, 'start-date': dt.focusable && dt.fromDate && dt.dateRange, 'between-date': dt.focusable && !dt.fromDate && !dt.selected && dt.dateRange, 'end-date': dt.focusable && dt.selected && dt.dateRange, 'old': dt.oldMonth, 'new': dt.nextMonth, 'disabled': dt.disabled, 'due-date': dt.dueDate, 'late-fee': dt.pastDue}\" ng-focus=\"currFocus=true\" ng-blur=\"currFocus=false\" title=\"{{(dt.focusable && dt.pastDue && legendMessage) || ''}}\" b2b-element-focus=\"inline && dt.focusable && dt.selected && !dt.dateRange\" ng-click=\"!ngDisabled && !dt.disabled && select(dt.date)\">\n" +
22509 " <div aria-hidden=\"true\" tabindex=\"-1\" class=\"show-date\" ng-if=\"!(dt.oldMonth || dt.nextMonth)\">{{dt.label | limitTo: 3}}</div>\n" +
22514 " <tr ng-repeat=\"footer in footers\">\n" +
22515 " <th colspan=\"7\" class=\"text-left\" b2b-append-element=\"footer\"></th>\n" +
22523 angular.module("b2bTemplate/monthSelector/monthSelector.html", []).run(["$templateCache", function($templateCache) {
22524 $templateCache.put("b2bTemplate/monthSelector/monthSelector.html",
22526 " <span class=\"icon-primary-calendar span12\" ng-class=\"{'disabled': ngDisabled}\" ng-transclude></span>\n" +
22530 angular.module("b2bTemplate/monthSelector/monthSelectorLink.html", []).run(["$templateCache", function($templateCache) {
22531 $templateCache.put("b2bTemplate/monthSelector/monthSelectorLink.html",
22533 " <span class=\"span12\" ng-transclude></span>\n" +
22537 angular.module("b2bTemplate/pagination/b2b-pagination.html", []).run(["$templateCache", function($templateCache) {
22538 $templateCache.put("b2bTemplate/pagination/b2b-pagination.html",
22539 "<div class=\"b2b-pager\">\n" +
22540 " <div ng-if=\"notMobile && totalPages > 1\">\n" +
22541 " <a tabindex=\"{{currentPage <= 1 ? -1 : 0 }}\" class=\"b2b-pager__item--prev\" b2b-accessibility-click=\"13,32\" title=\"Previous Page\" ng-click=\"prev($event)\" ng-if=\"totalPages > 10\" ng-class=\"currentPage <= 1 ? 'b2b-pager__item--prev-disabled': '' \">\n" +
22542 " <i class=\"icon-primary-left\"></i>\n" +
22544 " <a droppable=\"{{droppableAttribute}}\" tabindex=\"{{currentPage === 1 ? -1 : 0}}\" ng-class=\"{'b2b-pager__item--noclick': currentPage === 1}\" class=\"b2b-pager__item b2b-pager__item--link\" title=\"Page 1{{checkSelectedPage(page) ? ' selected' : '' }}\" ng-if=\"totalPages > 10 && currentPage > 6\" b2b-accessibility-click=\"13,32\" ng-click=\"selectPage(1, $event)\" aria-atomic=\"true\" aria-live=\"polite\">\n" +
22545 " 1<span class=\"offscreen-text\" ng-if=\"currentPage === 1\"> is selected</span>\n" +
22548 " <span class=\"b2b-pager__item\" ng-if=\"totalPages > 10 && currentPage > 6\">...</span>\n" +
22550 " <a droppable=\"{{droppableAttribute}}\" tabindex=\"{{checkSelectedPage(page) ? -1 : 0}}\" href=\"javascript:void(0);\" class=\"b2b-pager__item b2b-pager__item--link\" title=\"Page {{page}}\" b2b-element-focus=\"isFocused(page)\" ng-repeat=\"page in pages\" ng-class=\"{'b2b-pager__item--active': checkSelectedPage(page), 'b2b-pager__item--noclick': checkSelectedPage(page),'b2b-pager__item--droppable': droppableAttribute == true}\" b2b-accessibility-click=\"13,32\" ng-click=\"!checkSelectedPage(page) && selectPage(page, $event)\" aria-atomic=\"true\" aria-live=\"polite\">\n" +
22551 " {{page}}<span class=\"offscreen-text\" ng-if=\"checkSelectedPage(page)\"> is selected</span>\n" +
22554 " <span class=\"b2b-pager__item\" ng-if=\"totalPages > 10 && currentPage <= (totalPages - boundary)\">...</span>\n" +
22556 " <a droppable=\"{{droppableAttribute}}\" tabindex=\"{{currentPage === totalPages ? -1 : 0}}\" href=\"javascript:void(0);\" ng-class=\"{'b2b-pager__item--noclick': currentPage === totalPages}\" class=\"b2b-pager__item b2b-pager__item--link\" title=\"Page {{totalPages}}\" ng-if=\"totalPages > 10 && currentPage <= (totalPages - boundary)\" b2b-accessibility-click=\"13,32\" ng-click=\"selectPage(totalPages, $event)\" aria-atomic=\"true\" aria-live=\"polite\">\n" +
22557 " {{totalPages}}<span class=\"offscreen-text\" ng-if=\"checkSelectedPage(page)\"> is selected</span>\n" +
22561 " <a tabindex=\"{{currentPage >= totalPages ? -1 : 0 }}\" href=\"javascript:void(0);\" class=\"b2b-pager__item--next\" b2b-accessibility-click=\"13,32\" title=\"Next Page\" ng-click=\"next($event)\" ng-if=\"totalPages > 10\" ng-class=\"currentPage >= totalPages ? 'b2b-pager__item--next-disabled' :'' \">\n" +
22562 " <i class=\"icon-primary-right\"></i>\n" +
22565 " <div class=\"fieldLabel b2b-go-to-page\" ng-class=\"{'b2b-go-to-page-inline' : inputClass !== undefined }\" ng-show=\"totalPages > 20\"> \n" +
22566 " <label for=\"{{inputId}}\">Go to Page:</label>\n" +
22567 " <input id=\"{{inputId}}\" class=\"b2b-pager__item--input\" type=\"number\" ng-blur=\"onfocusOut($event)\" ng-focus=\"onfocusIn($event)\" ng-keypress=\"gotoKeyClick($event)\" b2b-only-nums=\"4\" ng-model=\"$parent.gotoPage\" />\n" +
22568 " <button class=\"btn-arrow\" ng-click=\"gotoBtnClick()\" ng-disabled=\"$parent.gotoPage !== 0 || $parent.gotoPage !== undefined\" aria-label=\"Go to\">\n" +
22569 " <div class=\"btn btn-small btn-secondary\">\n" +
22570 " <i class=\"icon-primary-right\"></i>\n" +
22575 " <div ng-if=\"isMobile && totalPages > 1\" ng-swipe-right=\"prev()\" ng-swipe-left=\"next()\" ng-class=\"isMobile ? 'b2b-mobile-view': '' \">\n" +
22576 " <a droppable=\"{{droppableAttribute}}\" tabindex=\"{{checkSelectedPage(page) ? -1 : 0}}\" href=\"javascript:void(0);\" class=\"b2b-pager__item b2b-pager__item--link\" title=\"Page {{page}}\" b2b-element-focus=\"isFocused(page)\" ng-repeat=\"page in pages\" ng-class=\"{'b2b-pager__item--active': checkSelectedPage(page), fade1: ($index == 0 && currentPage > meanVal+1), fade2: ($index == 1 && currentPage > meanVal+1), fadesl: ($index == pages.length-2 && currentPage < totalPages - meanVal), fadel: ($last && currentPage < totalPages - meanVal), 'b2b-pager__item--noclick': checkSelectedPage(page)}\" b2b-accessibility-click=\"13,32\" ng-click=\"selectPage(page, $event)\">{{page}}</a>\n" +
22582 angular.module("b2bTemplate/paneSelector/paneSelector.html", []).run(["$templateCache", function($templateCache) {
22583 $templateCache.put("b2bTemplate/paneSelector/paneSelector.html",
22584 "<div class=\"panes\" ng-transclude></div>");
22587 angular.module("b2bTemplate/paneSelector/paneSelectorPane.html", []).run(["$templateCache", function($templateCache) {
22588 $templateCache.put("b2bTemplate/paneSelector/paneSelectorPane.html",
22589 "<div class=\"pane-block\" ng-transclude></div>");
22592 angular.module("b2bTemplate/profileCard/profileCard-addUser.html", []).run(["$templateCache", function($templateCache) {
22593 $templateCache.put("b2bTemplate/profileCard/profileCard-addUser.html",
22594 "<div class=\"span3 b2b-profile-card b2b-add-user\">\n" +
22595 " <div class=\"atcenter\">\n" +
22596 " <div class=\"circle\"><i class=\"icon-primary-add-maximize\"></i></div>\n" +
22597 " <div>Create new user</div>\n" +
22602 angular.module("b2bTemplate/profileCard/profileCard.html", []).run(["$templateCache", function($templateCache) {
22603 $templateCache.put("b2bTemplate/profileCard/profileCard.html",
22604 "<div class=\"span3 b2b-profile-card\">\n" +
22605 " <div class=\"top-block\">\n" +
22606 " <div class=\"profile-image\">\n" +
22607 " <img ng-if=\"image\" profile-name=\"{{profile.name}}\" ng-src=\"{{profile.img}}\" alt=\"{{profile.name}}\">\n" +
22608 " <span ng-if=\"!image\" class=\"default-img\">{{initials}}</span>\n" +
22610 " <h4 class=\"name\">{{profile.name}}</h4>\n" +
22612 " <p class=\"status\">\n" +
22613 " <span class=\"status-icon\" ng-class=\"{'status-green':colorIcon==='green','status-red':colorIcon==='red','status-blue':colorIcon==='blue','status-yellow':colorIcon==='yellow'}\"> \n" +
22615 " <span>{{profile.state}}<span ng-if=\"badge\" class=\"status-badge\">Admin</span></span>\n" +
22619 " <div class=\"bottom-block\">\n" +
22620 " <div class=\"profile-details\">\n" +
22621 " <label>Username</label>\n" +
22622 " <div ng-if=\"shouldClip(profile.userName)\" ng-mouseover=\"showUserNameTooltip = true;\" ng-mouseleave=\"showUserNameTooltip = false;\">\n" +
22623 " <div ng-if=\"shouldClip(profile.userName)\" class=\"tooltip\" b2b-tooltip show-tooltip=\"showUserNameTooltip\">\n" +
22624 " {{profile.userName.slice(0, 25)+'...'}}\n" +
22625 " <div class=\"arrow\"></div>\n" +
22626 " <div class=\"tooltip-wrapper\" role=\"tooltip\" aria-live=\"polite\" aria-atomic=\"false\" style=\"z-index:1111\">\n" +
22627 " <div class=\"tooltip-size-control\">\n" +
22628 " <div class=\"helpertext\" tabindex=\"-1\" role=\"tooltip\">\n" +
22629 " {{profile.userName}}\n" +
22635 " <div ng-if=\"!shouldClip(profile.userName)\">\n" +
22636 " {{profile.userName}}\n" +
22638 " <label>Email</label>\n" +
22639 " <div ng-if=\"shouldClip(profile.email)\" ng-mouseover=\"showEmailTooltip = true;\" ng-mouseleave=\"showEmailTooltip = false;\">\n" +
22640 " <div ng-if=\"shouldClip(profile.email)\" class=\"tooltip\" data-placement=\"bottom\" b2b-tooltip show-tooltip=\"showEmailTooltip\">\n" +
22641 " {{profile.email.slice(0, 25)+'...'}}\n" +
22642 " <div class=\"arrow\"></div>\n" +
22643 " <div class=\"tooltip-wrapper\" role=\"tooltip\" aria-live=\"polite\" aria-atomic=\"false\" style=\"z-index:1111\">\n" +
22644 " <div class=\"tooltip-size-control\">\n" +
22645 " <div class=\"helpertext\" tabindex=\"-1\" role=\"tooltip\">\n" +
22646 " {{profile.email}}\n" +
22652 " <div ng-if=\"!shouldClip(profile.email)\">\n" +
22653 " {{profile.email}}\n" +
22655 " <label>Role</label>\n" +
22656 " <div ng-if=\"shouldClip(profile.role)\" ng-mouseover=\"showRoleTooltip = true;\" ng-mouseleave=\"showRoleTooltip = false;\">\n" +
22657 " <div ng-if=\"shouldClip(profile.role)\" class=\"tooltip\" b2b-tooltip show-tooltip=\"showRoleTooltip\">\n" +
22658 " {{profile.role.slice(0, 25)+'...'}}\n" +
22659 " <div class=\"tooltip-wrapper\" role=\"tooltip\" aria-live=\"polite\" aria-atomic=\"false\" style=\"z-index:1111\">\n" +
22660 " <div class=\"tooltip-size-control\">\n" +
22661 " <div class=\"helpertext\" tabindex=\"-1\" role=\"tooltip\">\n" +
22662 " {{profile.role}}\n" +
22668 " <div ng-if=\"!shouldClip(profile.role)\">\n" +
22669 " {{profile.role}}\n" +
22671 " <label>Last login</label>\n" +
22672 " <div ng-if=\"shouldClip(profile.lastLogin)\" ng-mouseover=\"showLastLoginTooltip = true;\" ng-mouseleave=\"showLastLoginTooltip = false;\">\n" +
22673 " <div ng-if=\"shouldClip(profile.lastLogin)\" class=\"tooltip\" b2b-tooltip show-tooltip=\"showLastLoginTooltip\">\n" +
22674 " {{profile.lastLogin.slice(0, 25)+'...'}}\n" +
22675 " <div class=\"tooltip-wrapper\" role=\"tooltip\" aria-live=\"polite\" aria-atomic=\"false\" style=\"z-index:1111\">\n" +
22676 " <div class=\"tooltip-size-control\">\n" +
22677 " <div class=\"helpertext\" tabindex=\"-1\" role=\"tooltip\">\n" +
22678 " {{profile.lastLogin}}\n" +
22684 " <div ng-if=\"!shouldClip(profile.lastLogin)\">\n" +
22685 " {{profile.lastLogin}}\n" +
22692 angular.module("b2bTemplate/searchField/searchField.html", []).run(["$templateCache", function($templateCache) {
22693 $templateCache.put("b2bTemplate/searchField/searchField.html",
22694 "<div class=\"search-bar\">\n" +
22695 " <div class='input-container' ng-blur=\"blurInput()\">\n" +
22696 " <input type=\"text\" class=\"innershadow b2b-search-input-field\" id=\"{{configObj.labelId}}\" b2b-search-input ng-model=\"inputModel\" ng-disabled=\"disabled\" b2b-reset ng-keydown=\"selectionIndex($event)\" placeholder=\"{{configObj.ghostText}}\" style=\"width:100%\" maxlength=\"{{configObj.maxLength}}\" title=\"{{inputModel}}\" aria-label=\"{{inputModel.length>0?inputModel:configObj.ghostText}}\" />\n" +
22697 " <button class=\"btn-search\" ng-disabled=\"disabled\" ng-click=\"startSearch()\" b2b-accessibility-click=\"13,32\" aria-label=\"Search\" ng-focus=\"showListFlag = false\" type=\"button\"><i class=\"icoControls-magnifyingglass\"></i></button>\n" +
22699 " <div class=\"search-suggestion-wrapper\" ng-if=\"filterList.length >=0\" ng-show=\"showListFlag\">\n" +
22700 " <ul class=\"search-suggestion-list\" role=\"listbox\"> \n" +
22701 " <li class=\"no-result\" ng-if=\"filterList.length == 0 && configObj.display\">{{configObj.resultText}}</li>\n" +
22702 " <li class=\"search-suggestion-item\" ng-if=\"filterList.length\" ng-repeat=\"item in filterList | limitTo:configObj.noOfItemsDisplay track by $index\" ng-bind-html=\"item.title | b2bMultiSepartorHighlight:inputModel:configObj.searchSeperator\" ng-class=\"{active:isActive($index,filterList.length)}\" ng-click=\"selectItem($index,item.title)\">\n" +
22703 " {{item.title}} \n" +
22710 angular.module("b2bTemplate/seekBar/seekBar.html", []).run(["$templateCache", function($templateCache) {
22711 $templateCache.put("b2bTemplate/seekBar/seekBar.html",
22712 "<div class=\"b2b-seek-bar-container\" ng-class=\"{vertical:verticalSeekBar}\">\n" +
22713 " <div class=\"b2b-seek-bar-track-container\">\n" +
22714 " <div class=\"b2b-seek-bar-track\"></div>\n" +
22715 " <div class=\"b2b-seek-bar-track-fill\"></div>\n" +
22717 " <div class=\"b2b-seek-bar-knob-container\" role=\"menu\" >\n" +
22718 " <div class=\"b2b-seek-bar-knob\" tabindex=\"0\" role=\"menuitem\"></div>\n" +
22723 angular.module("b2bTemplate/slider/slider.html", []).run(["$templateCache", function($templateCache) {
22724 $templateCache.put("b2bTemplate/slider/slider.html",
22725 "<div class=\"b2b-slider-container\" ng-class=\"{'vertical':verticalSlider}\">\n" +
22726 " <div class=\"slider-track-container\">\n" +
22727 " <div class=\"slider-track\"></div>\n" +
22728 " <div class=\"slider-track-fill\" ng-style=\"{backgroundColor:trackFillColor}\"></div>\n" +
22730 " <div class=\"slider-knob-container\" ng-class=\"{'slider-knob-hidden':hideKnob}\">\n" +
22731 " <div class=\"slider-knob\" role=\"slider\" aria-valuemin=\"{{min}}\" aria-valuemax=\"{{max}}\" aria-labelledby=\"{{labelId}}\" aria-valuenow=\"{{currentModelValue}}\" aria-valuetext=\"{{currentModelValue}}{{postAriaLabel}}\" aria-orientation=\"{{verticalSlider ? 'vertical' : 'horizontal'}}\"></div>\n" +
22736 angular.module("b2bTemplate/spinButton/spinButton.html", []).run(["$templateCache", function($templateCache) {
22737 $templateCache.put("b2bTemplate/spinButton/spinButton.html",
22738 "<div class=\"btn-group btn-spinbutton-toggle\" ng-class=\"{'disabled': disabledFlag}\">\n" +
22739 " <button type=\"button\" tabindex=\"{{isMobile?'0':'-1'}}\" aria-hidden=\"{{notMobile}}\" class=\"btn btn-secondary btn-prev icon-primary-subtractminimize\" ng-click=\"minus();focusInputSpinButton($event)\" aria-label=\"Remove {{step}}\" aria-controls=\"{{spinButtonId}}\" ng-class=\"{'disabled': isMinusDisabled()}\" ng-disabled=\"isMinusDisabled()\" aria-disabled=\"{{isMinusDisabled()}}\" role=\"spinbutton\"></button>\n" +
22740 " <input class=\"btn pull-left\" id=\"{{spinButtonId}}\" type=\"tel\" b2b-only-nums=\"3\" maxlength=\"3\" min={{min}} max={{max}} data-max-value=\"{{max}}\" ng-model=\"inputValue[inputModelKey]\" value=\"{{inputValue[inputModelKey]}}\" aria-live=\"polite\" aria-valuenow=\"{{inputValue[inputModelKey]}}\" aria-valuemin=\"{{min}}\" aria-valuemax=\"{{max}}\" ng-disabled=\"disabledFlag\">\n" +
22741 " <button type=\"button\" tabindex=\"{{isMobile?'0':'-1'}}\" aria-hidden=\"{{notMobile}}\" class=\"btn btn-secondary btn-next icon-primary-add-maximize\" ng-click=\"plus();focusInputSpinButton($event)\" aria-label=\"Add {{step}}\" aria-controls=\"{{spinButtonId}}\" ng-class=\"{'disabled': isPlusDisabled()}\" ng-disabled=\"isPlusDisabled()\" aria-disabled=\"{{isPlusDisabled()}}\" role=\"spinbutton\"></button>\n" +
22745 angular.module("b2bTemplate/statusTracker/statusTracker.html", []).run(["$templateCache", function($templateCache) {
22746 $templateCache.put("b2bTemplate/statusTracker/statusTracker.html",
22747 "<div class=\"b2b-status-tracker row\">\n" +
22748 " <button tabindex=\"0\" ng-disabled=\"currentViewIndex === 0\" ng-if=\"statuses.length > b2bStatusTrackerConfig.maxViewItems\" class=\"btn-arrow\" type=\"button\" aria-label=\"Previous status\" ng-click=\"previousStatus()\">\n" +
22749 " <div class=\"btn btn-small btn-secondary\">\n" +
22750 " <i class=\"icon-primary-left\"></i>\n" +
22753 " <div ng-repeat=\"status in statuses\" class=\"b2b-status-tracker-step {{ status.state }}\" ng-show=\"isInViewport($index)\">\n" +
22754 " <p class=\"b2b-status-tracker-heading\">{{status.heading}}</p>\n" +
22755 " <div class=\"progress\">\n" +
22756 " <div class=\"progress-bar\">\n" +
22757 " <span class=\"hidden-spoken\">\n" +
22758 " {{ removeCamelCase(status.state) }}\n" +
22762 " <div class=\"b2b-status-tracker-estimate {{status.state}}\">\n" +
22763 " <i ng-show=\"status.estimate !== '' && status.estimate !== undefined\" ng-class=\"b2bStatusTrackerConfig.icons[status.state]\"></i>\n" +
22765 " <span ng-bind-html=\"status.estimate\"></span>\n" +
22768 " <div class=\"b2b-status-tracker-description\" ng-bind-html=\"status.description\"> \n" +
22771 " <button tabindex=\"0\" ng-disabled=\"currentViewIndex + b2bStatusTrackerConfig.maxViewItems === statuses.length\" ng-if=\"statuses.length > b2bStatusTrackerConfig.maxViewItems\" class=\"btn-arrow\" type=\"button\" aria-label=\"Next status\" ng-click=\"nextStatus()\">\n" +
22772 " <div class=\"btn btn-small btn-secondary\">\n" +
22773 " <i class=\"icon-primary-right\"></i>\n" +
22779 angular.module("b2bTemplate/stepTracker/stepTracker.html", []).run(["$templateCache", function($templateCache) {
22780 $templateCache.put("b2bTemplate/stepTracker/stepTracker.html",
22781 "<div class=\"b2b-step-tracker\">\n" +
22782 " <button class=\"btn-arrow b2b-left-arrow\" ng-click=\"previousStatus()\" ng-disabled=\"currentViewIndex === 0\" ng-if=\"stepsItemsObject.length > b2bStepTrackerConfig.maxViewItems\" aria-label=\"Previous step\">\n" +
22783 " <div class=\"btn btn-left btn-small btn-secondary\"><i class=\"icon-primary-left\"></i></div>\n" +
22785 " <button class=\"btn-arrow b2b-right-arrow\" ng-click=\"nextStatus()\" ng-disabled=\"currentViewIndex + b2bStepTrackerConfig.maxViewItems === stepsItemsObject.length\" ng-if=\"stepsItemsObject.length > b2bStepTrackerConfig.maxViewItems\" aria-label=\"Next step\">\n" +
22786 " <div class=\"btn btn-small btn-right btn-secondary\"><i class=\"icon-primary-right\"></i></div>\n" +
22788 " <ul class=\"b2b-steps\">\n" +
22789 " <li ng-class=\"{'b2b-step-done':$index < currentIndex - 1 ,'b2b-step-on':$index == currentIndex - 1}\" \n" +
22790 " ng-repeat=\"stepsItem in stepsItemsObject\" ng-show=\"isInViewport($index)\">\n" +
22791 " <p class=\"b2b-step-text\" data-sm-text=\"{{stepsItem.dataMobile}}\" data-large-text=\"{{stepsItem.dataDesktop}}\">{{stepsItem.text}}</p>\n" +
22792 " <span class=\"hidden-spoken\">\n" +
22793 " {{($index < currentIndex - 1)? 'Complete. '+stepsItem.text+' '+stepsItem.dataMobile:''}} \n" +
22794 " {{($index == currentIndex - 1)? 'In Progress. '+stepsItem.text+' '+stepsItem.dataMobile:''}} \n" +
22795 " {{($index > currentIndex - 1)? 'Incomplete. '+stepsItem.text+' '+stepsItem.dataMobile:''}}\n" +
22802 angular.module("b2bTemplate/switches/switches-spanish.html", []).run(["$templateCache", function($templateCache) {
22803 $templateCache.put("b2bTemplate/switches/switches-spanish.html",
22804 "<div class=\"switch-overlay\" aria-hidden=\"true\">\n" +
22805 " <span class=\"btn-slider-on\"><span class=\"hidden-spoken\">seleccione para hacer </span><i class=\"activo\">activo</i></span>\n" +
22806 " <span class=\"switch-handle\"></span>\n" +
22807 " <span class=\"btn-slider-off\"><span class=\"hidden-spoken\">seleccione para hacer </span><i class=\"inactivo\">inactivo</i></span>\n" +
22811 angular.module("b2bTemplate/switches/switches.html", []).run(["$templateCache", function($templateCache) {
22812 $templateCache.put("b2bTemplate/switches/switches.html",
22813 "<div class=\"switch-overlay\" aria-hidden=\"true\">\n" +
22814 " <span class=\"btn-slider-on\">On</span>\n" +
22815 " <span class=\"switch-handle\"></span>\n" +
22816 " <span class=\"btn-slider-off\">Off</span>\n" +
22820 angular.module("b2bTemplate/tableMessages/tableMessage.html", []).run(["$templateCache", function($templateCache) {
22821 $templateCache.put("b2bTemplate/tableMessages/tableMessage.html",
22822 "<div class=\"b2b-table-message\">\n" +
22823 " <div class=\"b2b-message\" ng-if=\"msgType === 'noMatchingResults'\">\n" +
22824 " <div class=\"b2b-magnify-glass\"></div>\n" +
22826 " <div ng-transclude></div>\n" +
22829 " <div class=\"b2b-message\" ng-if=\"msgType == 'infoCouldNotLoad'\">\n" +
22830 " <div class=\"icon-primary-alert b2b-alert\" aria-label=\"Oops! The information could not load at this time. Please click link to refresh the page.\"></div>\n" +
22831 " <div>Oops!</div>\n" +
22832 " <div>The information could not load at this time.</div>\n" +
22833 " <div>Please <a href=\"javascript:void(0)\" title=\"Refresh the page\" ng-click=\"refreshAction($event)\">refresh the page</a>\n" +
22836 " <div class=\"b2b-message\" ng-if=\"msgType == 'magnifySearch'\">\n" +
22837 " <div class=\"b2b-magnify-glass\"></div>\n" +
22839 " <p class=\"b2b-message-title\">Please input values to\n" +
22840 " <br/> begin your search.</p>\n" +
22843 " <div class=\"b2b-message\" ng-if=\"msgType === 'loadingTable'\">\n" +
22844 " <div class=\"icon-primary-spinner-ddh b2b-loading-dots\"></div>\n" +
22845 " <div ng-transclude></div>\n" +
22851 angular.module("b2bTemplate/tableScrollbar/tableScrollbar.html", []).run(["$templateCache", function($templateCache) {
22852 $templateCache.put("b2bTemplate/tableScrollbar/tableScrollbar.html",
22853 "<div class=\"b2b-table-scrollbar\">\n" +
22854 " <div class=\"b2b-scrollbar-arrows\">\n" +
22855 " <button class=\"btn-arrow b2b-scrollbar-arrow-left\" type=\"button\" ng-attr-aria-label=\"{{disableLeft ? 'Scroll Left Disabled' : 'Scroll Left'}}\" ng-click=\"scrollLeft()\" ng-disabled=\"disableLeft\"><div class=\"btn btn-alt\"><i class=\"icon-primary-left\"></i></div></button>\n" +
22856 " <button class=\"btn-arrow b2b-scrollbar-arrow-right\" ng-attr-aria-label=\"{{disableRight ? 'Scroll Right Disabled' : 'Scroll Right'}}\" ng-click=\"scrollRight()\" ng-disabled=\"disableRight\" type=\"button\"><div class=\"btn btn-alt\"><i class=\"icon-primary-right\"></i></div></button>\n" +
22858 " <div class=\"b2b-table-inner-container\">\n" +
22859 " <span ng-transclude></span>\n" +
22864 angular.module("b2bTemplate/tables/b2bTable.html", []).run(["$templateCache", function($templateCache) {
22865 $templateCache.put("b2bTemplate/tables/b2bTable.html",
22866 "<table ng-class=\"{'striped': responsive, 'complex-table': responsive && active}\" ng-transclude></table>");
22869 angular.module("b2bTemplate/tables/b2bTableBody.html", []).run(["$templateCache", function($templateCache) {
22870 $templateCache.put("b2bTemplate/tables/b2bTableBody.html",
22871 "<td ng-hide=\"isHidden()\" ng-transclude></td>");
22874 angular.module("b2bTemplate/tables/b2bTableHeaderSortable.html", []).run(["$templateCache", function($templateCache) {
22875 $templateCache.put("b2bTemplate/tables/b2bTableHeaderSortable.html",
22876 "<th scope=\"col\" role=\"columnheader\" aria-sort=\"{{sortPattern !== 'null' && 'none' || sortPattern}}\" aria-label=\"{{headerName}} {{sortable !== 'false' && ': activate to sort' || ' '}} {{sortPattern !== 'null' && '' || sortPattern}}\" tabindex=\"{{sortable !== 'false' && '0' || '-1'}}\" b2b-accessibility-click=\"13,32\" ng-click=\"(sortable !== 'false') && sort();\" ng-hide=\"isHidden()\">\n" +
22877 " <span ng-transclude></span>\n" +
22878 " <i ng-class=\"{'icoArrows-sort-arrow active': sortPattern === 'ascending', 'icoArrows-sort-arrow active down': sortPattern === 'descending'}\"></i>\n" +
22882 angular.module("b2bTemplate/tables/b2bTableHeaderUnsortable.html", []).run(["$templateCache", function($templateCache) {
22883 $templateCache.put("b2bTemplate/tables/b2bTableHeaderUnsortable.html",
22884 "<th scope=\"col\" ng-hide=\"isHidden()\" ng-transclude></th>");
22887 angular.module("b2bTemplate/tabs/b2bTab.html", []).run(["$templateCache", function($templateCache) {
22888 $templateCache.put("b2bTemplate/tabs/b2bTab.html",
22889 "<li role=\"tab\" aria-selected=\"{{isTabActive()}}\" aria-controls=\"{{tabPanelId}}\" class=\"tab\" \n" +
22890 " ng-class=\"{'active': isTabActive()}\" ng-click=\"clickTab()\" ng-hide=\"tabItem.disabled\">\n" +
22891 " <a href=\"javascript:void(0)\" tabindex=\"{{(isTabActive() && tabItem.disabled)?-1:0}}\"\n" +
22892 " ng-disabled=\"tabItem.disabled\" aria-expanded=\"{{(isTabActive() && !tabItem.disabled)}}\" \n" +
22893 " b2b-accessibility-click=\"13,32\" ng-transclude></a>\n" +
22894 " <span class=\"hidden-spoken\" ng-if=\"isTabActive()\">Active tab</span>\n" +
22898 angular.module("b2bTemplate/tabs/b2bTabset.html", []).run(["$templateCache", function($templateCache) {
22899 $templateCache.put("b2bTemplate/tabs/b2bTabset.html",
22900 "<ul class=\"tabs promo-tabs\" role=\"tablist\" ng-transclude></ul>");
22903 angular.module("b2bTemplate/treeNav/groupedTree.html", []).run(["$templateCache", function($templateCache) {
22904 $templateCache.put("b2bTemplate/treeNav/groupedTree.html",
22905 "<ul role=\"group\">\n" +
22906 " <li aria-expanded=\"{{(member.active?true:false)}}\" role=\"treeitem\" aria-label=\"{{key}}\" ng-repeat='(key,value) in collection | groupBy : \"grpChild\"' b2b-tree-link><a class=\"grp\" ng-class=\"{'active': value.showGroup == true}\" tabindex=\"-1\" href='javascript:void(0);'>{{(key)?key:''}}<span class=\"b2b-tree-node-icon\"><i ng-class=\"{'icon-primary-expanded': value.showGroup == true, 'icon-primary-collapsed': value.showGroup == undefined || value.showGroup == false }\"></i></span></a>\n" +
22907 " <ul role=\"group\">\n" +
22908 " <b2b-member ng-repeat='member in value.childArray' member='member' time-delay='{{timeDelay}}' group-it='groupIt'></b2b-member>\n" +
22914 angular.module("b2bTemplate/treeNav/treeMember.html", []).run(["$templateCache", function($templateCache) {
22915 $templateCache.put("b2bTemplate/treeNav/treeMember.html",
22916 "<li role=\"treeitem\" aria-expanded=\"{{(member.active?true:false)}}\" aria-label=\"{{member.tooltipContent ? member.tooltipContent : member.name}}\" aria-describedby=\"description_{{$id}}\" ng-class=\"{'bg':member.selected}\" b2b-tree-link>\n" +
22917 " <a tabindex=\"-1\" title=\"{{member.tooltipContent ? member.tooltipContent : member.name}}\" href=\"javascript:void(0)\" ng-class=\"{'active':member.active,'b2b-locked-node':member.locked}\"> <span class=\"b2b-tree-node-name\">{{member.name}}</span>\n" +
22918 " <span class=\"{{!member.child?'end':''}} b2b-tree-node-icon\">\n" +
22919 " <i class=\"b2b-tree-expandCollapse-icon\" ng-class=\"{'icon-primary-expanded':member.active}\"></i>\n" +
22921 " <div id=\"description_{{$id}}\" class=\"offscreen-text\">\n" +
22922 " {{member.descriptionText}}\n" +
22924 " <div class=\"b2b-tree-tooltip\" ng-if=\"member.showTooltip\">\n" +
22925 " <span class=\"b2b-tree-arrow-left\"></span>\n" +
22926 " <div class=\"b2b-tree-tooltip-content\">\n" +
22927 " {{member.tooltipContent}}\n" +
22934 angular.module("b2bTemplate/treeNav/ungroupedTree.html", []).run(["$templateCache", function($templateCache) {
22935 $templateCache.put("b2bTemplate/treeNav/ungroupedTree.html",
22936 "<ul role=\"{{setRole}}\"><b2b-member ng-repeat='member in collection' member='member' group-it='groupIt'></b2b-member></ul>");
22939 angular.module("b2bTemplate/treeNodeCheckbox/groupedTree.html", []).run(["$templateCache", function($templateCache) {
22940 $templateCache.put("b2bTemplate/treeNodeCheckbox/groupedTree.html",
22941 "<ul role=\"group\">\n" +
22942 " <li aria-expanded=\"{{(member.active?true:false)}}\" role=\"treeitem\" aria-label=\"{{key}}\" ng-repeat='(key,value) in collection | groupBy : \"grpChild\"' b2b-tree-node-link><a class=\"grp\" ng-class=\"{'active': value.showGroup == true}\" tabindex=\"-1\" href='javascript:void(0);'>\n" +
22943 " <span class=\"ng-hide\">\n" +
22944 " <label class=\"checkbox\">\n" +
22945 " <input type=\"checkbox\" tabindex=\"-1\" class=\"treeCheckBox grpTreeCheckbox\" style=\"margin-top:2px;\"/><i class=\"skin\"></i><span> {{(key)?key:''}}</span>\n" +
22949 " {{(key)?key:''}} \n" +
22951 " <span class=\"nodeIcon\"><i class=\"expandCollapseIcon\" ng-class=\"{'icon-primary-expanded': value.showGroup == true, 'icon-primary-collapsed': value.showGroup == undefined || value.showGroup == false }\"></i></span></a>\n" +
22952 " <ul role=\"group\">\n" +
22953 " <b2b-tree-member ng-repeat='member in value.childArray' member='member' group-it='groupIt'></b2b-tree-member>\n" +
22959 angular.module("b2bTemplate/treeNodeCheckbox/treeMember.html", []).run(["$templateCache", function($templateCache) {
22960 $templateCache.put("b2bTemplate/treeNodeCheckbox/treeMember.html",
22961 "<li role=\"treeitem\" aria-expanded=\"{{(member.active?true:false)}}\" aria-label=\"{{member.name}}\" ng-class=\"{'bg':member.selected}\" b2b-tree-node-link>\n" +
22962 " <a tabindex=\"-1\" title=\"{{member.name}}\" href=\"javascript:void(0)\" ng-class=\"{'active':member.active}\">\n" +
22963 " <span ng-show=\"member.displayCheckbox\">\n" +
22964 " <label class=\"checkbox\">\n" +
22965 " <input type=\"checkbox\" tabindex=\"-1\" ng-model=\"member.isSelected\" ng-class=\"{'treeCheckBox': (member.displayCheckbox !== undefined)}\" style=\"margin-top:2px;\"/><i class=\"skin\"></i><span> {{member.name}}</span>\n" +
22968 " <span ng-show=\"!member.displayCheckbox\">\n" +
22969 " {{member.name}} \n" +
22971 " <span class=\"nodeIcon {{!member.child?'end':''}}\">\n" +
22972 " <i class=\"expandCollapseIcon\" ng-class=\"{'icon-primary-expanded':member.active}\"></i>\n" +
22978 angular.module("b2bTemplate/treeNodeCheckbox/ungroupedTree.html", []).run(["$templateCache", function($templateCache) {
22979 $templateCache.put("b2bTemplate/treeNodeCheckbox/ungroupedTree.html",
22980 "<ul role=\"{{setRole}}\"><b2b-tree-member ng-repeat='member in collection' member='member' group-it='groupIt'></b2b-tree-member></ul>");