1 /*! b2b-angular-library - v1.0.1 - Last updated: 2017-03-02. 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.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.headings','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.selectorModule','b2b.att.separators','b2b.att.slider','b2b.att.spinButton','b2b.att.staticRouteTemplate','b2b.att.statusTracker','b2b.att.stepTracker','b2b.att.switches','b2b.att.tableMessages','b2b.att.tables','b2b.att.tableScrollbar','b2b.att.tabs','b2b.att.tagBadges','b2b.att.textArea','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 Navigation.att:breadcrumbs
1013 * <file src="src/breadcrumbs/docs/readme.md" />
1015 <ul class="breadcrumb">
1016 <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>
1019 <example module="b2b.att">
1020 <file src="src/breadcrumbs/docs/demo.html" />
1021 <file src="src/breadcrumbs/docs/demo.js" />
1024 angular.module('b2b.att.breadcrumbs',[])
1027 * @name Buttons, links & UI controls.att:buttonGroups
1030 * <file src="src/buttonGroups/docs/readme.md" />
1033 <h2>Radio Aproach</h2>
1034 <div class="btn-group" b2b-key prev="37,38" next="39,40" circular-traversal role="radiogroup">
1035 <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>
1036 <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>
1037 <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>
1040 <h2>Checkbox Aproach</h2>
1041 <span b2b-button-group class="btn-group btn-fullwidth" role="group" max-select="3" ng-model="checkModel1">
1042 <button type="button" class="btn btn-secondary" ng-model="checkModel1.Button1" b2b-btn-checkbox>Button1</button>
1043 <button type="button" class="btn btn-secondary" ng-model="checkModel1.Button2" b2b-btn-checkbox>Button2</button>
1044 <button type="button" class="btn btn-secondary" ng-model="checkModel1.Button3" b2b-btn-checkbox>Button3</button>
1045 <button type="button" class="btn btn-secondary" ng-model="checkModel1.Button4" b2b-btn-checkbox>Button4</button>
1046 <button type="button" class="btn btn-secondary" ng-model="checkModel1.Button5" b2b-btn-checkbox>Button5</button>
1050 * <section id="code">
1051 <example module="b2b.att">
1052 <file src="src/buttonGroups/docs/demo.html" />
1053 <file src="src/buttonGroups/docs/demo.js" />
1058 angular.module('b2b.att.buttonGroups', ['b2b.att.utilities'])
1059 .constant('buttonConfig', {
1060 activeClass: 'active',
1061 toggleEvent: 'click'
1063 .directive('b2bBtnRadio', ['buttonConfig', function (buttonConfig) {
1064 var activeClass = buttonConfig.activeClass || 'active';
1065 var toggleEvent = buttonConfig.toggleEvent || 'click';
1069 link: function (scope, element, attrs, ngModelCtrl) {
1070 var notMobile = !/Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
1073 element.bind('focus', function () {
1074 scope.$apply(function () {
1075 ngModelCtrl.$setViewValue(scope.$eval(attrs.b2bBtnRadio));
1076 ngModelCtrl.$render();
1081 element.attr('role', 'radio');
1084 ngModelCtrl.$render = function () {
1085 element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.b2bBtnRadio)));
1086 if (angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.b2bBtnRadio))) {
1087 element.attr("aria-checked", true);
1089 element.attr("aria-checked", false);
1094 element.bind(toggleEvent, function () {
1095 if (!element.hasClass(activeClass)) {
1096 scope.$apply(function () {
1097 ngModelCtrl.$setViewValue(scope.$eval(attrs.b2bBtnRadio));
1098 ngModelCtrl.$render();
1105 .directive('b2bBtnCheckbox', ['buttonConfig', function (buttonConfig) {
1106 var activeClass = buttonConfig.activeClass || 'active';
1107 var toggleEvent = buttonConfig.toggleEvent || 'click';
1110 require: ['ngModel', '^^b2bButtonGroup'],
1111 link: function (scope, element, attrs, ctrls) {
1113 var ngModelCtrl = ctrls[0];
1114 var parentCtrl = ctrls[1];
1116 element.attr('role', 'checkbox');
1117 element.attr('aria-describedby', parentCtrl.getStateDescriptionElemId());
1119 function getTrueValue() {
1120 var trueValue = scope.$eval(attrs.b2bBtnCheckboxTrue);
1121 return angular.isDefined(trueValue) ? trueValue : true;
1124 function getFalseValue() {
1125 var falseValue = scope.$eval(attrs.b2bBtnCheckboxFalse);
1126 return angular.isDefined(falseValue) ? falseValue : false;
1130 ngModelCtrl.$render = function () {
1131 element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
1132 if ((angular.equals(ngModelCtrl.$modelValue, getTrueValue()))) {
1133 element.attr("aria-checked", true);
1135 element.attr("aria-checked", false);
1140 element.bind(toggleEvent, function () {
1141 scope.$apply(function () {
1142 ngModelCtrl.$setViewValue(element.hasClass(activeClass) ? getFalseValue() : getTrueValue());
1143 ngModelCtrl.$render();
1149 .directive('b2bButtonGroup', ['$timeout', '$compile', function ($timeout, $compile) {
1154 ngModelButtonState: '=ngModel'
1156 controller: ['$scope', '$element', function ($scope, $element) {
1159 var stateDescriptionElem = angular.element('<span id="b2b_button_group_' + $scope.$id + '" class="hide" aria-hidden="true">{{nSel}} of {{maxSelect}} options selected.</span>');
1160 $compile(stateDescriptionElem)($scope);
1161 $element.after(stateDescriptionElem);
1163 this.getStateDescriptionElemId = function () {
1164 return stateDescriptionElem.attr('id');
1167 link: function (scope, element) {
1170 var executeFxn = function () {
1172 angular.forEach(scope.ngModelButtonState, function (value, key) {
1173 if (value === true) {
1178 if (scope.nSel >= scope.maxSelect) {
1179 angular.forEach(element.children(), function (chd) {
1180 if (chd.className.indexOf('active') < 0) {
1181 chd.disabled = true;
1182 chd.setAttribute('aria-disabled', true);
1186 angular.forEach(element.children(), function (chd) {
1187 chd.disabled = false;
1188 chd.setAttribute('aria-disabled', false);
1194 $timeout(function () {
1197 element.bind('click', executeFxn);
1203 * @name Buttons, links & UI controls.att:buttons
1208 * <file src="src/buttons/docs/readme.md" />
1212 <button class="btn" type="button">Button</button> button.btn (button shape only)
1213 <button aria-label="Custom aria label" class="btn" type="button">Button</button> button.btn (button shape only) with custom aria label
1214 <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
1215 <a b2b-keyup-click="32" href="javascript:void(0)" class="btn" role="button">Button</a> a.btn (button shape only)
1216 <button class="btn btn-primary">Button</button> .btn-primary
1217 <a b2b-keyup-click="32" href="javascript:void(0)" class="btn btn-primary" role="button">Button</a> a.btn-primary
1220 <button class="btn btn-secondary">Button</button> .btn-secondary
1221 <a b2b-keyup-click="32" href="javascript:void(0)" class="btn btn-secondary" role="button">Button</a> a.btn-secondary
1222 <button class="btn btn-alt">Button</button> .btn-alt
1223 <a b2b-keyup-click="32" href="javascript:void(0)" class="btn btn-alt" role="button">Button</a> a.btn-alt
1224 <button class="btn btn-specialty">Button</button> .btn-specialty
1225 <a b2b-keyup-click="32" href="javascript:void(0)" class="btn btn-specialty" role="button">Button</a> a.btn-specialty
1226 <button class="btn btn-specialty" disabled="">Button</button> disabled="disabled"
1227 <a b2b-keyup-click="32" aria-disabled="true" href="javascript:void(0)" class="btn btn-primary disabled" role="button">Button</a> a.disabled
1230 <button class="btn btn-secondary">Button</button> .btn is default and 46px height
1231 <button class="btn btn-secondary btn-medium">Button</button> .btn-medium is 42px
1232 <button class="btn btn-secondary btn-small">Button</button> .btn-small is 36px
1234 .row-nowrap 2 up buttons
1235 <div class="row-nowrap">
1236 <button class="btn btn-secondary btn-fullwidth" type="button">Cancel</button>
1237 <button class="btn btn-primary btn-fullwidth" type="button">Continue</button>
1240 .row 2 up buttons (desktop) stacked (mobile) (different order)
1241 <div class="row cta-button-group">
1242 <button class="span btn btn-secondary btn-fullwidth hidden-phone" type="button">Cancel</button>
1243 <button class="span btn btn-primary btn-fullwidth" type="button">Continue</button>
1244 <button class="span btn btn-secondary btn-fullwidth visible-phone" type="button">Cancel</button>
1248 * <section id="code">
1249 <b>HTML + AngularJS</b>
1250 * <example module="b2b.att">
1251 * <file src="src/buttons/docs/demo.html" />
1252 <file src="src/buttons/docs/demo.js" />
1257 angular.module('b2b.att.buttons', ['b2b.att.utilities']);
1260 * @name Forms.att:calendar
1263 * <file src="src/calendar/docs/readme.md" />
1265 * <input type="text" ng-model="dt" b2b-datepicker>
1269 <b>HTML + AngularJS</b>
1270 <example module="b2b.att">
1271 <file src="src/calendar/docs/demo.html" />
1272 <file src="src/calendar/docs/demo.js" />
1276 angular.module('b2b.att.calendar', ['b2b.att.position', 'b2b.att.utilities'])
1278 .constant('b2bDatepickerConfig', {
1279 dateFormat: 'MM/dd/yyyy',
1281 monthFormat: 'MMMM',
1283 dayHeaderFormat: 'EEEE',
1284 dayTitleFormat: 'MMMM yyyy',
1285 disableWeekend: false,
1286 disableSunday: false,
1288 onSelectClose: null,
1295 legendMessage: null,
1296 calendarDisabled: false,
1298 orientation: 'left',
1300 helperText: 'The date you selected is $date. In case of mobile double tap to open calendar. Select a date to close the calendar.',
1301 datepickerEvalAttributes: ['dateFormat', 'dayFormat', 'monthFormat', 'yearFormat', 'dayHeaderFormat', 'dayTitleFormat', 'disableWeekend', 'disableSunday', 'startingDay', 'collapseWait', 'orientation'],
1302 datepickerWatchAttributes: ['min', 'max', 'due', 'from', 'legendIcon', 'legendMessage', 'ngDisabled'],
1303 datepickerFunctionAttributes: ['disableDates', 'onSelectClose']
1306 .factory('b2bDatepickerService', ['b2bDatepickerConfig', 'dateFilter', function (b2bDatepickerConfig, dateFilter) {
1307 var setAttributes = function (attr, elem) {
1308 if (angular.isDefined(attr) && attr !== null && angular.isDefined(elem) && elem !== null) {
1309 var attributes = b2bDatepickerConfig.datepickerEvalAttributes.concat(b2bDatepickerConfig.datepickerWatchAttributes, b2bDatepickerConfig.datepickerFunctionAttributes);
1310 for (var key in attr) {
1311 var val = attr[key];
1312 if (attributes.indexOf(key) !== -1 && angular.isDefined(val)) {
1313 elem.attr(key.toSnakeCase(), key);
1319 var bindScope = function (attr, scope) {
1320 if (angular.isDefined(attr) && attr !== null && angular.isDefined(scope) && scope !== null) {
1321 var evalFunction = function (key, val) {
1322 scope[key] = scope.$parent.$eval(val);
1325 var watchFunction = function (key, val) {
1326 scope.$parent.$watch(val, function (value) {
1329 scope.$watch(key, function (value) {
1330 scope.$parent[val] = value;
1334 var evalAttributes = b2bDatepickerConfig.datepickerEvalAttributes;
1335 var watchAttributes = b2bDatepickerConfig.datepickerWatchAttributes;
1336 for (var key in attr) {
1337 var val = attr[key];
1338 if (evalAttributes.indexOf(key) !== -1 && angular.isDefined(val)) {
1339 evalFunction(key, val);
1340 } else if (watchAttributes.indexOf(key) !== -1 && angular.isDefined(val)) {
1341 watchFunction(key, val);
1348 setAttributes: setAttributes,
1349 bindScope: bindScope
1353 .controller('b2bDatepickerController', ['$scope', '$attrs', 'dateFilter', '$element', '$position', 'b2bDatepickerConfig', function ($scope, $attrs, dateFilter, $element, $position, dtConfig) {
1355 date: getValue($attrs.dateFormat, dtConfig.dateFormat),
1356 day: getValue($attrs.dayFormat, dtConfig.dayFormat),
1357 month: getValue($attrs.monthFormat, dtConfig.monthFormat),
1358 year: getValue($attrs.yearFormat, dtConfig.yearFormat),
1359 dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat),
1360 dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat),
1361 disableWeekend: getValue($attrs.disableWeekend, dtConfig.disableWeekend),
1362 disableSunday: getValue($attrs.disableSunday, dtConfig.disableSunday),
1363 disableDates: getValue($attrs.disableDates, dtConfig.disableDates)
1365 startingDay = getValue($attrs.startingDay, dtConfig.startingDay);
1367 $scope.minDate = dtConfig.minDate ? $scope.resetTime(dtConfig.minDate) : null;
1368 $scope.maxDate = dtConfig.maxDate ? $scope.resetTime(dtConfig.maxDate) : null;
1369 $scope.dueDate = dtConfig.dueDate ? $scope.resetTime(dtConfig.dueDate) : null;
1370 $scope.fromDate = dtConfig.fromDate ? $scope.resetTime(dtConfig.fromDate) : null;
1371 $scope.legendIcon = dtConfig.legendIcon ? dtConfig.legendIcon : null;
1372 $scope.legendMessage = dtConfig.legendMessage ? dtConfig.legendMessage : null;
1373 $scope.ngDisabled = dtConfig.calendarDisabled ? dtConfig.calendarDisabled : null;
1374 $scope.collapseWait = getValue($attrs.collapseWait, dtConfig.collapseWait);
1375 $scope.orientation = getValue($attrs.orientation, dtConfig.orientation);
1376 $scope.onSelectClose = getValue($attrs.onSelectClose, dtConfig.onSelectClose);
1378 $scope.inline = $attrs.inline === 'true' ? true : dtConfig.inline;
1380 function getValue(value, defaultValue) {
1381 return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue;
1384 function getDaysInMonth(year, month) {
1385 return new Date(year, month, 0).getDate();
1388 function getDates(startDate, n) {
1389 var dates = new Array(n);
1390 var current = startDate,
1393 dates[i++] = new Date(current);
1394 current.setDate(current.getDate() + 1);
1399 this.updatePosition = function (b2bDatepickerPopupTemplate) {
1400 $scope.position = $position.offset($element);
1401 $scope.position.top = $scope.position.top + $element.find('input').prop('offsetHeight');
1402 if ($scope.orientation === 'right') {
1403 $scope.position.left = $scope.position.left - (((b2bDatepickerPopupTemplate && b2bDatepickerPopupTemplate.prop('offsetWidth')) || 290) - $element.find('input').prop('offsetWidth'));
1407 function isSelected(dt) {
1408 if (dt && angular.isDate($scope.currentDate) && compare(dt, $scope.currentDate) === 0) {
1414 function isFromDate(dt) {
1415 if (dt && angular.isDate($scope.fromDate) && compare(dt, $scope.fromDate) === 0) {
1421 function isDateRange(dt) {
1422 if (dt && $scope.fromDate && angular.isDate($scope.currentDate) && (compare(dt, $scope.fromDate) >= 0) && (compare(dt, $scope.currentDate) <= 0)) {
1424 } else if (dt && $scope.fromDate && compare(dt, $scope.fromDate) === 0) {
1430 function isOld(date, currentMonthDate) {
1431 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())) {
1438 function isNew(date, currentMonthDate) {
1439 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())) {
1446 function isPastDue(dt) {
1447 if ($scope.dueDate) {
1448 return (dt > $scope.dueDate);
1453 function isDueDate(dt) {
1454 if ($scope.dueDate) {
1455 return (dt.getTime() === $scope.dueDate.getTime());
1460 var isDisabled = function (date, currentMonthDate) {
1461 if ($attrs.from && !angular.isDate($scope.fromDate)) {
1464 if (format.disableWeekend === true && (dateFilter(date, format.dayHeader) === "Saturday" || dateFilter(date, format.dayHeader) === "Sunday")) {
1467 if (format.disableSunday === true && (dateFilter(date, format.dayHeader) === "Sunday")) {
1470 if (isOld(date, currentMonthDate) || isNew(date, currentMonthDate)) {
1473 return (($scope.minDate && compare(date, $scope.minDate) < 0) || ($scope.maxDate && compare(date, $scope.maxDate) > 0) || (format.disableDates && format.disableDates({
1478 var compare = function (date1, date2) {
1479 return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()));
1482 function isMinDateAvailable(startDate, endDate) {
1483 if (($scope.minDate && $scope.minDate.getTime() >= startDate.getTime()) && ($scope.minDate.getTime() <= endDate.getTime())) {
1484 $scope.disablePrev = true;
1485 $scope.visibilityPrev = "hidden";
1487 $scope.disablePrev = false;
1488 $scope.visibilityPrev = "visible";
1492 function isMaxDateAvailable(startDate, endDate) {
1493 if (($scope.maxDate && $scope.maxDate.getTime() >= startDate.getTime()) && ($scope.maxDate.getTime() <= endDate.getTime())) {
1494 $scope.disableNext = true;
1495 $scope.visibilityNext = "hidden";
1497 $scope.disableNext = false;
1498 $scope.visibilityNext = "visible";
1502 function getLabel(label) {
1505 pre: label.substr(0, 1).toUpperCase(),
1513 function makeDate(date, dayFormat, dayHeaderFormat, isSelected, isFromDate, isDateRange, isOld, isNew, isDisabled, dueDate, pastDue) {
1516 label: dateFilter(date, dayFormat),
1517 header: dateFilter(date, dayHeaderFormat),
1518 selected: !!isSelected,
1519 fromDate: !!isFromDate,
1520 dateRange: !!isDateRange,
1523 disabled: !!isDisabled,
1526 focusable: !((isDisabled && !(isSelected || isDateRange)) || (isOld || isNew))
1533 getVisibleDates: function (date) {
1534 var year = date.getFullYear(),
1535 month = date.getMonth(),
1536 firstDayOfMonth = new Date(year, month, 1),
1537 lastDayOfMonth = new Date(year, month + 1, 0);
1538 var difference = startingDay - firstDayOfMonth.getDay(),
1539 numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : -difference,
1540 firstDate = new Date(firstDayOfMonth),
1543 if (numDisplayedFromPreviousMonth > 0) {
1544 firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
1545 numDates += numDisplayedFromPreviousMonth; // Previous
1547 numDates += getDaysInMonth(year, month + 1); // Current
1548 numDates += (7 - numDates % 7) % 7; // Next
1550 var days = getDates(firstDate, numDates),
1551 labels = new Array(7);
1552 for (var i = 0; i < numDates; i++) {
1553 var dt = new Date(days[i]);
1554 days[i] = makeDate(dt,
1562 isDisabled(dt, date),
1566 for (var j = 0; j < 7; j++) {
1567 labels[j] = getLabel(dateFilter(days[j].date, format.dayHeader));
1569 isMinDateAvailable(firstDayOfMonth, lastDayOfMonth);
1570 isMaxDateAvailable(firstDayOfMonth, lastDayOfMonth);
1573 title: dateFilter(date, format.dayTitle),
1585 .directive('b2bDatepickerPopup', ['$parse', '$log', '$timeout', '$document', '$documentBind', '$isElement', '$templateCache', '$compile', 'trapFocusInElement', '$position', '$window', function ($parse, $log, $timeout, $document, $documentBind, $isElement, $templateCache, $compile, trapFocusInElement, $position, $window) {
1590 templateUrl: function (elem, attr) {
1591 if (attr.inline === 'true') {
1592 return 'b2bTemplate/calendar/datepicker-popup.html';
1594 return 'b2bTemplate/calendar/datepicker.html';
1598 require: ['b2bDatepickerPopup', 'ngModel', '?^b2bDatepickerGroup'],
1599 controller: 'b2bDatepickerController',
1600 link: function (scope, element, attrs, ctrls) {
1601 var datepickerCtrl = ctrls[0],
1603 b2bDatepickerGroupCtrl = ctrls[2];
1604 var b2bDatepickerPopupTemplate;
1607 $log.error("ng-model is required.");
1608 return; // do nothing if no ng-model
1611 // Configuration parameters
1614 scope.isOpen = false;
1619 if (b2bDatepickerGroupCtrl) {
1620 b2bDatepickerGroupCtrl.registerDatepickerScope(scope);
1623 element.find('button').bind('click', function () {
1624 element.find('input')[0].click();
1627 element.find('input').bind('click', function () {
1628 if (!scope.ngDisabled) {
1629 scope.isOpen = !scope.isOpen;
1630 toggleCalendar(scope.isOpen);
1632 datepickerCtrl.updatePosition(b2bDatepickerPopupTemplate);
1633 $timeout(function () {
1634 angular.element(element[0].querySelector('.datepicker-input')).scrollTop=0;
1638 var toggleCalendar = function (flag) {
1639 if (!scope.inline) {
1641 b2bDatepickerPopupTemplate = angular.element($templateCache.get('b2bTemplate/calendar/datepicker-popup.html'));
1642 b2bDatepickerPopupTemplate = $compile(b2bDatepickerPopupTemplate)(scope);
1643 $document.find('body').append(b2bDatepickerPopupTemplate);
1644 b2bDatepickerPopupTemplate.bind('keydown', keyPress);
1645 $timeout(function () {
1646 scope.getFocus = true;
1647 trapFocusInElement(flag, b2bDatepickerPopupTemplate);
1649 $timeout(function () {
1650 scope.getFocus = false;
1655 b2bDatepickerPopupTemplate.unbind('keydown', keyPress);
1656 b2bDatepickerPopupTemplate.remove();
1657 element.find('button')[0].focus();
1658 scope.getFocus = false;
1659 trapFocusInElement(flag, b2bDatepickerPopupTemplate);
1664 var outsideClick = function (e) {
1665 var isElement = $isElement(angular.element(e.target), element, $document);
1666 var isb2bDatepickerPopupTemplate = $isElement(angular.element(e.target), b2bDatepickerPopupTemplate, $document);
1667 if (!(isElement || isb2bDatepickerPopupTemplate)) {
1668 scope.isOpen = false;
1669 toggleCalendar(scope.isOpen);
1674 var keyPress = function (ev) {
1677 ev.keyCode = ev.which;
1678 } else if (ev.charCode) {
1679 ev.keyCode = ev.charCode;
1683 if (ev.keyCode === 27) {
1684 scope.isOpen = false;
1685 toggleCalendar(scope.isOpen);
1686 ev.preventDefault();
1687 ev.stopPropagation();
1688 } else if (ev.keyCode === 33) {
1689 !scope.disablePrev && scope.move(-1);
1690 $timeout(function () {
1691 scope.getFocus = true;
1693 $timeout(function () {
1694 scope.getFocus = false;
1698 ev.preventDefault();
1699 ev.stopPropagation();
1700 } else if (ev.keyCode === 34) {
1701 !scope.disableNext && scope.move(1);
1702 $timeout(function () {
1703 scope.getFocus = true;
1705 $timeout(function () {
1706 scope.getFocus = false;
1710 ev.preventDefault();
1711 ev.stopPropagation();
1717 $documentBind.click('isOpen', outsideClick, scope);
1719 var modalContainer = angular.element(document.querySelector('.modalwrapper'));
1720 var modalBodyContainer = angular.element(document.querySelector('.modal-body'));
1721 if (modalContainer) {
1722 modalContainer.bind('scroll', function () {
1723 if (b2bDatepickerPopupTemplate) {
1724 datepickerCtrl.updatePosition(b2bDatepickerPopupTemplate);
1729 if (modalBodyContainer) {
1730 modalBodyContainer.bind('scroll', function () {
1731 if (b2bDatepickerPopupTemplate) {
1732 datepickerCtrl.updatePosition(b2bDatepickerPopupTemplate);
1733 var datepickerTextfield = $position.offset(element.find('input'));
1734 var modalBodyPosition = $position.offset(modalBodyContainer);
1736 if (((datepickerTextfield.top + datepickerTextfield.height) < modalBodyPosition.top || datepickerTextfield.top > (modalBodyPosition.top + modalBodyPosition.height)) && scope.isOpen) {
1737 scope.isOpen = false;
1738 toggleCalendar(scope.isOpen);
1744 var window = angular.element($window);
1745 window.bind('resize', function () {
1746 if (b2bDatepickerPopupTemplate) {
1747 datepickerCtrl.updatePosition(b2bDatepickerPopupTemplate);
1752 scope.$on('$destroy', function () {
1754 scope.isOpen = false;
1755 toggleCalendar(scope.isOpen);
1759 scope.resetTime = function (date) {
1760 if (typeof date === 'string') {
1761 date = date + 'T12:00:00';
1764 if (!isNaN(new Date(date))) {
1765 dt = new Date(date);
1769 return new Date(dt.getFullYear(), dt.getMonth(), dt.getDate());
1773 scope.$parent.$watch($parse(attrs.min), function (value) {
1774 scope.minDate = value ? scope.resetTime(value) : null;
1779 scope.$parent.$watch($parse(attrs.max), function (value) {
1780 scope.maxDate = value ? scope.resetTime(value) : null;
1785 scope.$parent.$watch($parse(attrs.due), function (value) {
1786 scope.dueDate = value ? scope.resetTime(value) : null;
1791 scope.$parent.$watch($parse(attrs.from), function (value) {
1792 scope.fromDate = value ? scope.resetTime(value) : null;
1797 if (attrs.legendIcon) {
1798 scope.$parent.$watch(attrs.legendIcon, function (value) {
1799 scope.legendIcon = value ? value : null;
1803 if (attrs.legendMessage) {
1804 scope.$parent.$watch(attrs.legendMessage, function (value) {
1805 scope.legendMessage = value ? value : null;
1809 if (attrs.ngDisabled) {
1810 scope.$parent.$watch(attrs.ngDisabled, function (value) {
1811 scope.ngDisabled = value ? value : null;
1815 // Split array into smaller arrays
1816 function split(arr, size) {
1818 while (arr.length > 0) {
1819 arrays.push(arr.splice(0, size));
1824 function refill(date) {
1825 if (angular.isDate(date) && !isNaN(date)) {
1826 selected = new Date(date);
1829 selected = new Date();
1834 var currentMode = datepickerCtrl.modes[mode],
1835 data = currentMode.getVisibleDates(selected);
1836 scope.rows = split(data.objects, currentMode.split);
1838 var startFlag = false;
1839 var firstSelected = false;
1840 for (var i = 0; i < scope.rows.length; i++) {
1841 for (var j = 0; j < scope.rows[i].length; j++) {
1843 if (scope.rows[i][j].label === "1" && !firstSelected) {
1844 firstSelected = true;
1845 var firstDay = scope.rows[i][j];
1848 if (scope.rows[i][j].selected === true) {
1858 firstDay.firstFocus = true;
1861 scope.labels = data.labels || [];
1862 scope.title = data.title;
1864 datepickerCtrl.updatePosition(b2bDatepickerPopupTemplate);
1868 scope.select = function (date) {
1869 var dt = new Date(date.getFullYear(), date.getMonth(), date.getDate());
1870 if (!scope.onSelectClose || (scope.onSelectClose && scope.onSelectClose({
1873 scope.currentDate = dt;
1874 if (angular.isNumber(scope.collapseWait)) {
1875 $timeout(function () {
1876 scope.isOpen = false;
1877 toggleCalendar(scope.isOpen);
1878 }, scope.collapseWait);
1880 scope.isOpen = false;
1881 toggleCalendar(scope.isOpen);
1886 scope.move = function (direction,$event) {
1887 var step = datepickerCtrl.modes[mode].step;
1888 selected.setDate(1);
1889 selected.setMonth(selected.getMonth() + direction * (step.months || 0));
1890 selected.setFullYear(selected.getFullYear() + direction * (step.years || 0));
1893 $timeout(function () {
1894 trapFocusInElement();
1897 $event.preventDefault();
1898 $event.stopPropagation();
1901 scope.trapFocus = function () {
1902 $timeout(function () {
1903 trapFocusInElement();
1907 scope.$watch('currentDate', function (value) {
1908 if (angular.isDefined(value) && value !== null) {
1913 ngModel.$setViewValue(value);
1916 ngModel.$render = function () {
1917 scope.currentDate = ngModel.$viewValue;
1920 var stringToDate = function (value) {
1921 if (!isNaN(new Date(value))) {
1922 value = new Date(value);
1926 ngModel.$formatters.unshift(stringToDate);
1931 .directive('b2bDatepicker', ['$compile', '$log', 'b2bDatepickerConfig', 'b2bDatepickerService', function ($compile, $log, b2bDatepickerConfig, b2bDatepickerService) {
1939 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
1940 var dateFormatString = angular.isDefined(attr.dateFormat) ? scope.$parent.$eval(attr.dateFormat) : b2bDatepickerConfig.dateFormat;
1941 var helperText = angular.isDefined(attr.helperText) ? scope.$parent.$eval(attr.helperText) : b2bDatepickerConfig.helperText;
1942 helperText = helperText.replace('$date', '{{dt | date : \'' + dateFormatString + '\'}}');
1945 if (elem.prop('nodeName') !== 'INPUT') {
1949 var calendarIcon = '<i class="icon-primary-calendar" aria-hidden="true"></i>'
1950 var selectedDateMessage = '<button id="' + attr.btnId + '" type="button" class="span12 faux-input" ng-disabled="ngDisabled"><span class="hidden-spoken">' + helperText + '</span></button>';
1952 elem.removeAttr('b2b-datepicker');
1953 elem.removeAttr('ng-model');
1954 elem.removeAttr('ng-disabled');
1955 elem.addClass('datepicker-input');
1956 elem.attr('ng-model', 'dt');
1957 elem.attr('aria-describedby', 'datepicker');
1958 elem.attr('aria-hidden', 'true');
1959 elem.attr('tabindex', '-1');
1960 elem.attr('readonly', 'true');
1961 elem.attr('ng-disabled', 'ngDisabled');
1962 elem.attr('b2b-format-date', dateFormatString);
1964 var wrapperElement = angular.element('<div></div>');
1965 wrapperElement.attr('b2b-datepicker-popup', '');
1966 wrapperElement.attr('ng-model', 'dt');
1968 wrapperElement.attr('inline', inline);
1971 b2bDatepickerService.setAttributes(attr, wrapperElement);
1972 b2bDatepickerService.bindScope(attr, scope);
1974 wrapperElement.html('');
1975 wrapperElement.append(calendarIcon);
1976 wrapperElement.append(selectedDateMessage);
1977 wrapperElement.append(elem.prop('outerHTML'));
1979 var elm = wrapperElement.prop('outerHTML');
1980 elm = $compile(elm)(scope);
1981 elem.replaceWith(elm);
1983 link: function (scope, elem, attr, ctrl) {
1985 $log.error("ng-model is required.");
1986 return; // do nothing if no ng-model
1989 scope.$watch('dt', function (value) {
1990 ctrl.$setViewValue(value);
1992 ctrl.$render = function () {
1993 scope.dt = ctrl.$viewValue;
1999 .directive('b2bDatepickerGroup', [function () {
2002 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
2003 this.$$headers = [];
2004 this.$$footers = [];
2005 this.registerDatepickerScope = function (datepickerScope) {
2006 datepickerScope.headers = this.$$headers;
2007 datepickerScope.footers = this.$$footers;
2010 link: function (scope, elem, attr, ctrl) {}
2014 .directive('b2bFormatDate', ['dateFilter', function (dateFilter) {
2018 link: function (scope, elem, attr, ctrl) {
2019 var b2bFormatDate = "";
2020 attr.$observe('b2bFormatDate', function (value) {
2021 b2bFormatDate = value;
2023 var dateToString = function (value) {
2024 if (!isNaN(new Date(value))) {
2025 return dateFilter(new Date(value), b2bFormatDate);
2029 ctrl.$formatters.unshift(dateToString);
2034 .directive('b2bDatepickerHeader', [function () {
2037 require: '^b2bDatepickerGroup',
2041 compile: function (elem, attr, transclude) {
2042 return function link(scope, elem, attr, ctrl) {
2044 ctrl.$$headers.push(transclude(scope, function () {}));
2052 .directive('b2bDatepickerFooter', [function () {
2055 require: '^b2bDatepickerGroup',
2059 compile: function (elem, attr, transclude) {
2060 return function link(scope, elem, attr, ctrl) {
2062 ctrl.$$footers.push(transclude(scope, function () {}));
2071 * @name Forms.att:checkboxes
2074 * <file src="src/checkboxes/docs/readme.md" />
2078 <example module="b2b.att">
2079 <file src="src/checkboxes/docs/demo.html" />
2080 <file src="src/checkboxes/docs/demo.js" />
2083 angular.module('b2b.att.checkboxes', ['b2b.att.utilities'])
2084 .directive('b2bSelectGroup', [function (){
2091 link: function (scope, elem, attr, ctrl) {
2092 elem.bind('change', function () {
2093 var isChecked = elem.prop('checked');
2094 angular.forEach(scope.checkboxes, function (item) {
2095 item.isSelected = isChecked;
2099 scope.$watch('checkboxes', function () {
2101 if(scope.checkboxes === undefined) {
2104 angular.forEach(scope.checkboxes, function (item) {
2105 if (item.isSelected) {
2109 elem.prop('indeterminate', false);
2110 if ( scope.checkboxes !==undefined && setBoxes === scope.checkboxes.length && scope.checkboxes.length > 0) {
2111 ctrl.$setViewValue(true);
2112 elem.removeClass('indeterminate');
2113 } else if (setBoxes === 0) {
2114 ctrl.$setViewValue(false);
2115 elem.removeClass('indeterminate');
2117 ctrl.$setViewValue(false);
2118 elem.addClass('indeterminate');
2119 elem.prop('indeterminate', true);
2128 * @name Misc.att:coachmark
2131 * <file src="src/coachmark/docs/readme.md" />
2135 <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>
2139 <b>HTML + AngularJS</b>
2140 <example module="b2b.att">
2141 <file src="src/coachmark/docs/demo.html" />
2142 <file src="src/coachmark/docs/demo.js" />
2147 angular.module('b2b.att.coachmark', ['b2b.att.utilities','b2b.att.position'])
2149 .directive('b2bCoachmark', ['$document', '$compile', '$position', '$timeout', function($document, $compile, $position, $timeout) {
2154 coachmarkIndex: '=',
2155 startCoachmarkCallback: '&',
2156 endCoachmarkCallback: '&',
2157 actionCoachmarkCallback: '&'
2159 link: function (scope, element, attrs, ctrl) {
2160 var coachmarkItems = scope.coachmarks;
2161 var body = $document.find('body').eq(0);
2162 var coackmarkJqContainer;
2163 var coackmarkContainer;
2164 var coachMarkElement;
2165 var backdropjqLiteEl;
2166 var coachmarkHighlight;
2167 var initaitedCoachmark = false;
2168 scope.coackmarkElPos ={
2173 scope.currentCoachmark = {};
2176 var coachmarkBackdrop = function(){
2177 backdropjqLiteEl = angular.element('<div class="b2b-modal-backdrop fade in hidden-by-modal"></div>');
2178 body.append(backdropjqLiteEl);
2180 backdropjqLiteEl.bind('click', function() {
2181 scope.closeCoachmark();
2187 scope.closeButtonFocus = function(){
2188 if(document.getElementsByClassName('b2b-coachmark-header').length >0){
2189 document.getElementsByClassName('b2b-coachmark-header')[0].scrollLeft = 0;
2190 document.getElementsByClassName('b2b-coachmark-header')[0].scrollTop = 0;
2194 scope.actionCoachmark = function(action){
2195 scope.actionCoachmarkCallback({
2200 scope.closeCoachmark = function(){
2201 initaitedCoachmark = false;
2202 backdropjqLiteEl.remove();
2203 coackmarkContainer.remove();
2204 coachmarkHighlight.remove();
2205 if(coachMarkElement !== undefined && coachMarkElement !==""){
2206 coachMarkElement.removeClass('b2b-coachmark-label')
2208 if (angular.isFunction(scope.endCoachmarkCallback)){
2209 scope.endCoachmarkCallback();
2214 function showCoachmark(targetElement) {
2215 scope.currentCoachmark = targetElement;
2216 if(coachMarkElement !== undefined && coachMarkElement !==""){
2217 coachMarkElement.removeClass('b2b-coachmark-label')
2218 coackmarkContainer.remove();
2219 coachmarkHighlight.remove();
2221 coachMarkElement = angular.element(document.querySelector(targetElement.elementId));
2222 coachMarkElement.addClass('b2b-coachmark-label');
2223 var elementPosition = $position.offset(coachMarkElement);
2225 coachmarkHighlight = angular.element('<div class="b2b-coachmark-highlight"></div><div class="b2b-coachmark-highlight b2b-coachmark-highlight-mask"></div>');
2226 coachmarkHighlight.css({
2227 'width': (elementPosition.width + 20) +'px',
2228 'top': (elementPosition.top -10) + 'px',
2229 'left': (elementPosition.left - 10) + 'px',
2230 'height': (elementPosition.height + 20) +'px'
2232 body.append(coachmarkHighlight);
2234 scope.coackmarkElPos.top = (elementPosition.top + elementPosition.height + 32) + 'px';
2235 scope.coackmarkElPos.left = (elementPosition.left - 158 + elementPosition.width / 2 ) + 'px';
2236 coackmarkJqContainer = angular.element('<div b2b-coachmark-container b2b-trap-focus-inside-element="true"></div>');
2237 coackmarkContainer = $compile(coackmarkJqContainer)(scope);
2238 body.append(coackmarkContainer);
2240 $timeout(function () {
2241 var currentCoachmarkContainer = document.getElementsByClassName('b2b-coachmark-container')[0];
2242 currentCoachmarkContainer.focus();
2243 var coachmarkHeight = window.getComputedStyle(currentCoachmarkContainer).height.split('px')[0];
2244 var newOffsetHeight = (Math.round(elementPosition.top) - Math.round(coachmarkHeight));
2246 // We need a slight offset to show the lightboxed item
2247 TweenLite.to(window, 2, {scrollTo:{x: (scope.coackmarkElPos.left.split('px')[0]-100), y: newOffsetHeight}});
2251 element.bind('click', function (e) {
2252 initaitedCoachmark = true;
2253 if(scope.coachmarkIndex === -1 || scope.coachmarkIndex >= coachmarkItems.length ){
2254 scope.coachmarkIndex = 0;
2256 scope.$watch('coachmarkIndex', function () {
2257 if(initaitedCoachmark === true){
2258 if(scope.coachmarkIndex === -1 || scope.coachmarkIndex >= coachmarkItems.length ){
2259 scope.closeCoachmark();
2261 showCoachmark(coachmarkItems[scope.coachmarkIndex]);
2265 coachmarkBackdrop();
2266 showCoachmark(coachmarkItems[scope.coachmarkIndex]);
2267 if (angular.isFunction(scope.startCoachmarkCallback)){
2268 scope.startCoachmarkCallback();
2270 $document.bind('keydown', function (evt) {
2271 if (evt.which === 27 && initaitedCoachmark) {
2272 scope.closeCoachmark();
2281 .directive('b2bCoachmarkContainer', ['$document', '$position', function($document, $position) {
2286 templateUrl: 'b2bTemplate/coachmark/coachmark.html',
2287 link: function (scope, element, attrs, ctrl) {
2295 * @name Template.att:Configuration Section
2298 * <file src="src/configurationSection/docs/readme.md" />
2301 * <section id="code">
2302 <b>HTML + AngularJS</b>
2303 <example module="b2b.att">
2304 <file src="src/configurationSection/docs/demo.html" />
2305 <file src="src/configurationSection/docs/demo.js" />
2310 angular.module('b2b.att.configurationSection', [])
2314 * @name Template.att:Directory Listing
2317 * <file src="src/directoryListingTemplate/docs/readme.md" />
2320 * <section id="code">
2321 <b>HTML + AngularJS</b>
2322 <example module="b2b.att">
2323 <file src="src/directoryListingTemplate/docs/demo.html" />
2324 <file src="src/directoryListingTemplate/docs/demo.js" />
2329 angular.module('b2b.att.directoryListingTemplate', [])
2333 * @name Forms.att:dropdowns
2336 * <file src="src/dropdowns/docs/readme.md" />
2341 <example module="b2b.att">
2342 <file src="src/dropdowns/docs/demo.html" />
2343 <file src="src/dropdowns/docs/demo.js" />
2347 angular.module('b2b.att.dropdowns', ['b2b.att.utilities', 'b2b.att.position', 'ngSanitize'])
2349 .constant('b2bDropdownConfig', {
2352 menuKeyword: 'menu',
2353 linkMenuKeyword: 'link-menu',
2354 largeKeyword: 'large',
2355 smallKeyword: 'small'
2358 .directive("b2bDropdown", ['$timeout', '$compile', '$templateCache', 'b2bUserAgent', 'b2bDropdownConfig', '$position', function ($timeout, $compile, $templateCache, b2bUserAgent, b2bDropdownConfig, $position) {
2363 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
2364 scope.isInputDropdown = true;
2365 scope.placeHoldertext = attr.placeholderText;
2367 if (attr.type.indexOf(b2bDropdownConfig.menuKeyword) > -1 || attr.type.indexOf(b2bDropdownConfig.linkMenuKeyword) > -1) {
2368 scope.isInputDropdown = false;
2369 if (attr.type.indexOf(b2bDropdownConfig.linkMenuKeyword) > -1) {
2370 scope.dropdownType = b2bDropdownConfig.linkMenuKeyword;
2371 } else if (attr.type.indexOf(b2bDropdownConfig.menuKeyword) > -1) {
2372 scope.dropdownType = b2bDropdownConfig.menuKeyword;
2375 if (attr.type.indexOf(b2bDropdownConfig.largeKeyword) > -1) {
2376 scope.dropdownSize = b2bDropdownConfig.largeKeyword;
2377 } else if (attr.type.indexOf(b2bDropdownConfig.smallKeyword) > -1) {
2378 scope.dropdownSize = b2bDropdownConfig.smallKeyword;
2382 scope.labelText = attr.labelText;
2384 scope.setBlur = function () {
2388 if ((scope.isInputDropdown && b2bUserAgent.notMobile()) || (!scope.isInputDropdown)) {
2389 var formCtrl = elem.controller('form');
2390 scope.setNgModelController = function (name, ngModelCtrl) {
2391 if (name && formCtrl && ngModelCtrl) {
2392 formCtrl[name] = ngModelCtrl;
2395 scope.setOptionalCta = function (optionalCta) {
2396 scope.optionalCta = optionalCta;
2398 var innerHtml = angular.element('<div></div>').append(elem.html());
2399 innerHtml = ($compile(innerHtml)(scope)).html();
2400 var template = angular.element($templateCache.get('b2bTemplate/dropdowns/b2bDropdownDesktop.html'));
2401 template.find('ul').eq(0).append(innerHtml);
2402 template = $compile(template)(scope);
2403 elem.replaceWith(template);
2404 } else if (scope.isInputDropdown && b2bUserAgent.isMobile()) {
2407 'filter': 'alpha(opacity=0)'
2409 elem.addClass('awd-select isWrapped');
2410 elem.wrap('<span class="selectWrap"></span>');
2411 var cover = angular.element('<span aria-hidden="true"><i class="icon-primary-down" aria-hidden="true"></i></span>');
2412 elem.parent().append(cover);
2413 elem.parent().append('<i class="icon-primary-down" aria-hidden="true"></i>');
2414 var set = function () {
2415 var sel = elem[0] ? elem[0] : elem;
2416 var selectedText = "";
2417 var selIndex = sel.selectedIndex;
2418 if (typeof selIndex !== 'undefined') {
2419 selectedText = sel.options[selIndex].text;
2421 cover.text(selectedText).append('<i class="icon-primary-down" aria-hidden="true"></i>');
2423 var update = function (value) {
2428 scope.$watch(attr.ngModel, function (newVal, oldVal) {
2432 elem.bind('keyup', function (ev) {
2433 if (ev.keyCode === keymap.KEY.TAB || ev.keyCode === keymap.KEY.ESC) {
2440 link: function (scope, elem, attr, ctrl) {
2441 if ((scope.isInputDropdown && b2bUserAgent.notMobile()) || (!scope.isInputDropdown)) {
2442 scope.updateModel = function () {
2446 ctrl.$setViewValue(scope.currentSelected.value);
2447 if (scope.dropdownRequired && scope.currentSelected.value === '') {
2448 scope.setRequired(false);
2450 scope.setRequired(true);
2453 if (scope.dropdownType === b2bDropdownConfig.linkMenuKeyword) {
2454 $timeout(function () {
2455 scope.appendCaretPositionStyle();
2459 ctrl.$render = function () {
2460 // if(ctrl.$dirty || ctrl.$pristine) {
2461 $timeout(function () {
2463 if ((angular.isUndefined(ctrl.$viewValue) || ctrl.$viewValue == '') && (angular.isUndefined(scope.placeHoldertext) || scope.placeHoldertext == '')) {
2464 scope.dropdownLists[ctrl.$viewValue] && scope.dropdownLists[ctrl.$viewValue][0].updateDropdownValue();
2465 } else if ((angular.isUndefined(scope.placeHoldertext) || scope.placeHoldertext == '') && ctrl.$viewValue !== '' ) {
2466 scope.dropdownLists[ctrl.$viewValue] && scope.dropdownLists[ctrl.$viewValue][0].updateDropdownValue();
2467 } else if ((angular.isUndefined(ctrl.$viewValue) || ctrl.$viewValue == '') && scope.placeHoldertext !== '' ) {
2468 scope.currentSelected.text = scope.placeHoldertext;
2470 scope.dropdownLists[ctrl.$viewValue] && scope.dropdownLists[ctrl.$viewValue][0].updateDropdownValue();
2476 scope.disabled = false;
2477 scope.dropdownName = attr.name;
2478 scope.dropdownId = attr.id;
2479 scope.labelId = attr.ariaLabelledby;
2480 scope.dropdownDescribedBy = attr.ariaDescribedby;
2481 if (attr.required) {
2482 scope.dropdownRequired = true;
2484 scope.dropdownRequired = false;
2486 elem.removeAttr('name');
2487 elem.removeAttr('id');
2488 scope.$parent.$watch(attr.ngDisabled, function (val) {
2489 scope.disabled = val;
2496 .directive("b2bDropdownToggle", ['$document', '$documentBind', '$isElement', 'b2bDropdownConfig', 'keymap', 'b2bUtilitiesConfig', '$timeout', '$position', function ($document, $documentBind, $isElement, b2bDropdownConfig, keymap, b2bUtilitiesConfig, $timeout, $position) {
2499 require: '?^b2bKey',
2500 link: function (scope, elem, attr, ctrl) {
2501 scope.appendCaretPositionStyle = function () {
2502 while (document.querySelector('style.b2bDropdownCaret')) {
2503 document.querySelector('style.b2bDropdownCaret').remove();
2505 var caretPosition = $position.position(elem).width - 26;
2506 if (scope.dropdownType === b2bDropdownConfig.linkMenuKeyword) {
2507 var template = angular.element('<style class="b2bDropdownCaret" type="text/css">.linkSelectorModule .active+.moduleWrapper:before {left: ' + caretPosition + 'px;}</style>');
2508 $document.find('head').append(template);
2512 if (scope.isInputDropdown && (scope.labelText !== undefined)) {
2513 elem.attr('aria-label', scope.labelText);
2516 scope.toggleFlag = false;
2517 scope.dropdownLists = {};
2518 scope.dropdownListValues = [];
2522 scope.currentSelected = {
2528 var searchString = '';
2529 var searchElement = function (searchExp) {
2530 var regex = new RegExp("\\b" + searchExp, "gi");
2531 var position = scope.dropdownListValues.regexIndexOf(regex, scope.currentSelected.index + 1, true);
2532 if (position > -1) {
2537 var startTimer = function (time) {
2538 if (searchString === '') {
2539 $timeout(function () {
2544 scope.toggleDropdown = function (toggleFlag) {
2545 if (!scope.disabled) {
2546 if (angular.isDefined(toggleFlag)) {
2547 scope.toggleFlag = toggleFlag;
2549 scope.toggleFlag = !scope.toggleFlag;
2551 if (!scope.toggleFlag) {
2552 if (scope.isInputDropdown) {
2553 elem.parent().find('input')[0].focus();
2555 elem.parent().find('button')[0].focus();
2558 scope.dropdown.highlightedValue = scope.currentSelected.value;
2559 if (ctrl && ctrl.enableSearch) {
2560 if (angular.isDefined(scope.dropdownLists[scope.currentSelected.value])) {
2561 ctrl.resetCounter(scope.dropdownLists[scope.currentSelected.value][2]);
2564 if (scope.dropdownType === b2bDropdownConfig.linkMenuKeyword) {
2565 scope.appendCaretPositionStyle();
2571 elem.bind('keydown', function (ev) {
2574 ev.keyCode = ev.which;
2575 } else if (ev.charCode) {
2576 ev.keyCode = ev.charCode;
2579 if (!scope.toggleFlag) {
2581 var currentIndex = scope.currentSelected.index;
2582 if (ev.altKey === true && ev.keyCode === keymap.KEY.DOWN) {
2583 scope.toggleDropdown(true);
2584 ev.preventDefault();
2585 ev.stopPropagation();
2586 } else if (b2bDropdownConfig.prev.split(',').indexOf(ev.keyCode.toString()) > -1) {
2587 angular.isDefined(scope.dropdownListValues[currentIndex - 1]) ? scope.dropdownLists[scope.dropdownListValues[currentIndex - 1]][0].updateDropdownValue() : angular.noop();
2588 ev.preventDefault();
2589 ev.stopPropagation();
2590 } else if (b2bDropdownConfig.next.split(',').indexOf(ev.keyCode.toString()) > -1) {
2591 angular.isDefined(scope.dropdownListValues[currentIndex + 1]) ? scope.dropdownLists[scope.dropdownListValues[currentIndex + 1]][0].updateDropdownValue() : angular.noop();
2592 ev.preventDefault();
2593 ev.stopPropagation();
2594 } else if (ev.keyCode >= 48 && ev.keyCode <= 105) {
2595 startTimer(b2bUtilitiesConfig.searchTimer);
2596 searchString = searchString + (keymap.MAP[ev.keyCode] || '');
2597 var position = searchElement(searchString);
2598 angular.isDefined(scope.dropdownListValues[position]) ? scope.dropdownLists[scope.dropdownListValues[position]][0].updateDropdownValue() : angular.noop();
2599 ev.preventDefault();
2600 ev.stopPropagation();
2604 if (ev.altKey === true && ev.keyCode === keymap.KEY.UP) {
2605 scope.toggleDropdown(false);
2606 ev.preventDefault();
2607 ev.stopPropagation();
2608 } else if (ev.keyCode === keymap.KEY.TAB || ev.keyCode === keymap.KEY.ESC) {
2609 scope.toggleDropdown(false);
2610 ev.preventDefault();
2611 ev.stopPropagation();
2614 scope.$apply(); // TODO: Move this into each block to avoid expensive digest cycles
2616 var outsideClick = function (e) {
2617 var isElement = $isElement(angular.element(e.target), elem.parent(), $document);
2619 scope.toggleDropdown(false);
2623 $documentBind.click('toggleFlag', outsideClick, scope);
2628 .directive("b2bDropdownGroup", ['$compile', '$templateCache', 'b2bUserAgent', function ($compile, $templateCache, b2bUserAgent) {
2631 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
2632 if ((scope.isInputDropdown && b2bUserAgent.notMobile()) || (!scope.isInputDropdown)) {
2633 var innerHtml = angular.element('<div></div>').append(elem.html());
2634 innerHtml = ($compile(innerHtml)(scope)).html();
2635 var template = angular.element($templateCache.get('b2bTemplate/dropdowns/b2bDropdownGroupDesktop.html'));
2636 template.attr('ng-repeat', attr.optGroupRepeat);
2637 template.attr('label', elem.attr('label'));
2638 template.find('ul').append(innerHtml);
2639 elem.replaceWith(template);
2640 } else if (scope.isInputDropdown && b2bUserAgent.isMobile()) {
2641 var template = angular.element(elem.prop('outerHTML'));
2642 template.attr('ng-repeat', attr.optGroupRepeat);
2643 template.removeAttr('b2b-dropdown-group');
2644 template.removeAttr('opt-group-repeat');
2645 template = $compile(template)(scope);
2646 elem.replaceWith(template);
2652 .directive("b2bDropdownGroupDesktop", [function () {
2656 link: function (scope, elem, attr, ctrl) {
2657 scope.groupHeader = attr.label;
2662 .directive("b2bDropdownList", ['$compile', '$templateCache', 'b2bUserAgent', function ($compile, $templateCache, b2bUserAgent) {
2665 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
2666 if ((scope.isInputDropdown && b2bUserAgent.notMobile()) || (!scope.isInputDropdown)) {
2667 var innerHtml = angular.element('<div></div>').append(elem.html());
2668 innerHtml = ($compile(innerHtml)(scope)).html();
2669 var template = angular.element($templateCache.get('b2bTemplate/dropdowns/b2bDropdownListDesktop.html'));
2670 template.attr('ng-repeat', attr.optionRepeat);
2671 template.attr('value', elem.attr('value'));
2672 template.attr('search-key', elem.attr('value'));
2673 if (elem.attr('aria-describedby')){
2674 template.attr('aria-describedby', attr.ariaDescribedby);
2676 if (elem.attr('imgsrc')) {
2677 if (elem.attr('imgalt')) {
2678 template.append('<img role="presentation" ng-src="' + elem.attr('imgsrc') + '" alt="' + elem.attr('imgalt') + '"/>');
2680 template.append('<img role="presentation" ng-src="' + elem.attr('imgsrc') + '" alt=""/>');
2683 template.append(innerHtml);
2684 elem.replaceWith(template);
2685 } else if (scope.isInputDropdown && b2bUserAgent.isMobile()) {
2686 var template = angular.element(elem.prop('outerHTML'));
2687 template.attr('ng-repeat', attr.optionRepeat);
2688 if (elem.attr('aria-describedby')){
2689 template.attr('aria-describedby', attr.ariaDescribedby);
2691 template.removeAttr('b2b-dropdown-list');
2692 template.removeAttr('option-repeat');
2693 template = $compile(template)(scope);
2694 elem.replaceWith(template);
2700 .directive("b2bDropdownListDesktop", ['$sce', 'keymap', 'b2bDropdownConfig', function ($sce, keymap, b2bDropdownConfig) {
2704 link: function (scope, elem, attr, ctrl) {
2705 var dropdownListValue = scope.dropdownListValue = attr.value;
2706 scope.dropdown.totalIndex++;
2707 var dropdownListIndex = scope.dropdown.totalIndex;
2708 scope.dropdownListValues.push(dropdownListValue);
2709 scope.dropdownLists[dropdownListValue] = [];
2710 scope.dropdownLists[dropdownListValue][0] = scope;
2711 scope.dropdownLists[dropdownListValue][1] = elem;
2712 scope.dropdownLists[dropdownListValue][2] = dropdownListIndex;
2713 scope.updateDropdownValue = function () {
2714 scope.currentSelected.value = dropdownListValue;
2715 if (scope.isInputDropdown) {
2716 scope.currentSelected.text = elem.text();
2717 } else if ((scope.dropdownType === b2bDropdownConfig.linkMenuKeyword) || (scope.dropdownType === b2bDropdownConfig.menuKeyword && scope.dropdownSize === b2bDropdownConfig.smallKeyword)) {
2718 scope.currentSelected.text = dropdownListValue;
2719 } else if (scope.dropdownType === b2bDropdownConfig.menuKeyword) {
2720 scope.currentSelected.text = $sce.trustAsHtml(elem.html());
2722 if (scope.isInputDropdown) {
2723 scope.currentSelected.label = elem.text();
2724 } else if (scope.dropdownType === b2bDropdownConfig.linkMenuKeyword) {
2725 scope.currentSelected.label = dropdownListValue;
2726 } else if (scope.dropdownType === b2bDropdownConfig.menuKeyword) {
2727 scope.currentSelected.label = elem.text();
2729 scope.currentSelected.index = dropdownListIndex;
2730 scope.updateModel();
2732 scope.selectDropdownItem = function () {
2734 scope.updateDropdownValue();
2735 scope.toggleDropdown(false);
2737 scope.highlightDropdown = function () {
2738 scope.dropdown.highlightedValue = dropdownListValue;
2740 elem.bind('mouseover', function (ev) {
2743 elem.bind('keydown', function (ev) {
2746 ev.keyCode = ev.which;
2747 } else if (ev.charCode) {
2748 ev.keyCode = ev.charCode;
2751 if (ev.altKey === true && ev.keyCode === keymap.KEY.UP) {
2752 scope.toggleDropdown(false);
2753 ev.preventDefault();
2754 ev.stopPropagation();
2755 } else if (ev.keyCode === keymap.KEY.TAB || ev.keyCode === keymap.KEY.ESC) {
2756 scope.toggleDropdown(false);
2757 ev.preventDefault();
2758 ev.stopPropagation();
2766 .directive("b2bDropdownRepeat", ['$compile', 'b2bUserAgent', function ($compile, b2bUserAgent) {
2769 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
2770 if ((scope.isInputDropdown && b2bUserAgent.notMobile()) || (!scope.isInputDropdown)) {
2771 var innerHtml = angular.element('<div></div>').append(elem.html());
2772 innerHtml = ($compile(innerHtml)(scope)).html();
2773 var template = angular.element('<div></div>');
2774 template.attr('ng-repeat', attr.b2bDropdownRepeat);
2775 template.append(innerHtml);
2776 elem.replaceWith(template);
2777 } else if (scope.isInputDropdown && b2bUserAgent.isMobile()) {
2784 .directive("b2bDropdownValidation", ['$timeout', function ($timeout) {
2788 link: function (scope, elem, attr, ctrl) {
2789 $timeout(function () {
2790 scope.setNgModelController(attr.name, ctrl);
2792 scope.setDirty = function () {
2793 if (ctrl.$dirty === false) {
2795 ctrl.$pristine = false;
2798 scope.setTouched = function () {
2799 if (ctrl.$touched === false) {
2800 ctrl.$touched = true;
2801 ctrl.$pristine = false;
2804 scope.setRequired = function (flag) {
2805 ctrl.$setValidity('required', flag);
2811 .directive('b2bDropdownOptionalCta', [function () {
2817 compile: function (elem, attr, transclude) {
2818 return function link(scope, elem, attr, ctrl) {
2819 if (scope.setOptionalCta) {
2820 scope.setOptionalCta(transclude(scope, function () {}));
2829 * @name Forms.att:File Upload
2832 * <file src="src/fileUpload/docs/readme.md" />
2836 <form id="dragDropFile">
2837 <div b2b-file-drop file-model="fileModel" on-drop="triggerFileUpload()" align="center">
2839 <br>To upload a file, drag & drop it here or
2840 <span b2b-file-link file-model="fileModel" on-file-select="triggerFileUpload()" >
2841 click here to select from your computer.
2848 * <section id="code">
2849 <example module="b2b.att">
2850 <file src="src/fileUpload/docs/demo.html" />
2851 <file src="src/fileUpload/docs/demo.js" />
2856 angular.module('b2b.att.fileUpload', ['b2b.att.utilities'])
2857 .directive('b2bFileDrop', [function() {
2864 controller: ['$scope', '$attrs', function($scope, $attrs) {
2865 this.onDrop = $scope.onDrop;
2867 link: function(scope, element) {
2868 element.addClass('b2b-dragdrop');
2872 if (e.originalEvent) {
2873 e.dataTransfer = e.originalEvent.dataTransfer;
2875 e.dataTransfer.dropEffect = 'move';
2876 // allows us to drop
2877 if (e.preventDefault) {
2880 element.addClass('b2b-dragdrop-over');
2887 // allows us to drop
2888 if (e.preventDefault) {
2891 element.addClass('b2b-dragdrop-over');
2898 element.removeClass('b2b-dragdrop-over');
2905 // Stops some browsers from redirecting.
2906 if (e.preventDefault) {
2909 if (e.stopPropagation) {
2910 e.stopPropagation();
2912 if (e.originalEvent) {
2913 e.dataTransfer = e.originalEvent.dataTransfer;
2915 element.removeClass('b2b-dragdrop-over');
2916 if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
2917 scope.fileModel = e.dataTransfer.files[0];
2919 if (angular.isFunction(scope.onDrop)) {
2929 .directive('b2bFileLink', [function() {
2932 require: '^?b2bFileDrop',
2935 templateUrl: 'b2bTemplate/fileUpload/fileUpload.html',
2940 controller: ['$scope', function($scope) {
2941 this.setFileModel = function(fileModel) {
2942 if ($scope.takeFileModelFromParent) {
2943 $scope.$parent.fileModel = fileModel;
2944 $scope.$parent.$apply();
2946 $scope.fileModel = fileModel;
2950 this.callbackFunction = function() {
2951 if (angular.isFunction($scope.onFileSelect)) {
2952 $scope.onFileSelect();
2957 link: function(scope, element, attr, b2bFileDropCtrl) {
2958 scope.takeFileModelFromParent = false;
2959 if (!(attr.fileModel) && b2bFileDropCtrl) {
2960 scope.takeFileModelFromParent = true;
2962 if (!(attr.onFileSelect) && b2bFileDropCtrl) {
2963 scope.onFileSelect = b2bFileDropCtrl.onDrop;
2968 .directive('b2bFileChange', ['$log', '$rootScope', function($log, $rootScope) {
2971 require: '^b2bFileLink',
2972 link: function(scope, element, attr, b2bFileLinkCtrl) {
2973 element.bind('change', changeFileModel);
2975 function changeFileModel(e) {
2976 if (e.target.files && e.target.files.length > 0) {
2977 b2bFileLinkCtrl.setFileModel(e.target.files[0]);
2978 b2bFileLinkCtrl.callbackFunction();
2980 var strFileName = e.target.value;
2982 var objFSO = new ActiveXObject("Scripting.FileSystemObject");
2983 b2bFileLinkCtrl.setFileModel(objFSO.getFile(strFileName));
2984 b2bFileLinkCtrl.callbackFunction();
2986 var errMsg = "There was an issue uploading " + strFileName + ". Please try again.";
2988 $rootScope.$broadcast('b2b-file-link-failure', errMsg);
2997 * @name Navigation.att:filters
3000 * <file src="src/filters/docs/readme.md" />
3003 * <div b2b-filters></div>
3006 * <section id="code">
3007 <b>HTML + AngularJS</b>
3008 <example module="b2b.att">
3009 <file src="src/filters/docs/demo.html" />
3010 <file src="src/filters/docs/demo.js" />
3015 angular.module('b2b.att.filters', ['b2b.att.utilities', 'b2b.att.multipurposeExpander'])
3016 .filter('filtersSelectedItemsFilter', [function () {
3017 return function (listOfItemsArray) {
3019 if (!listOfItemsArray) {
3020 listOfItemsArray = [];
3023 var returnArray = [];
3025 for (var i = 0; i < listOfItemsArray.length; i++) {
3026 for (var j = 0; j < listOfItemsArray[i].filterTypeItems.length; j++) {
3027 if (listOfItemsArray[i].filterTypeItems[j].selected && !listOfItemsArray[i].filterTypeItems[j].inProgress) {
3028 returnArray.push(listOfItemsArray[i].filterTypeItems[j]);
3038 * @name Messages, modals & alerts.att:flyout
3041 * <file src="src/flyout/docs/readme.md" />
3043 * <section id="code">
3044 <example module="b2b.att">
3045 <file src="src/flyout/docs/demo.html" />
3046 <file src="src/flyout/docs/demo.js" />
3051 angular.module('b2b.att.flyout', ['b2b.att.utilities', 'b2b.att.position'])
3052 .directive('b2bFlyout', ['$timeout', 'b2bDOMHelper', 'keymap', 'events', function ($timeout, b2bDOMHelper, keymap, events) {
3056 templateUrl: 'b2bTemplate/flyout/flyout.html',
3057 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
3058 scope.flyoutOpened = false;
3059 var contentScope = '';
3060 var togglerScope = '';
3061 this.registerContentScope = function (scp) {
3064 this.registerTogglerScope = function (scp) {
3068 this.toggleFlyoutState = function () {
3070 contentScope.toggleFlyout();
3073 this.getTogglerDimensions = function () {
3074 return togglerScope.getTogglerDimensions();
3076 this.setTogglerFocus = function () {
3077 return togglerScope.setTogglerFocus();
3080 this.closeFlyout = function (e) {
3081 contentScope.closeFromChild(e);
3083 this.gotFocus = function () {
3084 contentScope.gotFocus();
3087 this.updateAriaModel = function (val) {
3088 scope.flyoutOpened = val;
3091 var firstTabableElement = undefined,
3092 lastTabableElement = undefined;
3094 var firstTabableElementKeyhandler = function (e) {
3096 e.keyCode = e.which;
3098 if (e.keyCode === keymap.KEY.TAB && e.shiftKey && scope.flyoutOpened) {
3099 contentScope.gotFocus();
3100 events.preventDefault(e);
3101 events.stopPropagation(e);
3105 var lastTabableElementKeyhandler = function (e) {
3107 e.keyCode = e.which;
3109 if (e.keyCode === keymap.KEY.TAB && !e.shiftKey) {
3110 contentScope.gotFocus();
3111 events.preventDefault(e);
3112 events.stopPropagation(e);
3115 this.associateTabEvent = function(){
3116 $timeout(function () {
3117 var element = elem[0].getElementsByClassName('b2b-flyout-container')[0];
3118 firstTabableElement = b2bDOMHelper.firstTabableElement(element);
3119 lastTabableElement = b2bDOMHelper.lastTabableElement(element);
3120 if(angular.isUndefined(firstTabableElement)){
3121 angular.element(element).css('display','block');
3122 firstTabableElement = b2bDOMHelper.firstTabableElement(element);
3123 lastTabableElement = b2bDOMHelper.lastTabableElement(element);
3124 angular.element(element).css('display','none');
3126 angular.element(firstTabableElement).bind('keydown', firstTabableElementKeyhandler);
3127 angular.element(lastTabableElement).bind('keydown', lastTabableElementKeyhandler);
3130 this.updateTabbableElements = function(){
3131 $timeout(function () {
3132 var element = elem[0].getElementsByClassName('b2b-flyout-container')[0];
3133 angular.element(element).css('display','block');
3134 firstTabableElement = b2bDOMHelper.firstTabableElement(element);
3135 lastTabableElement = b2bDOMHelper.lastTabableElement(element);
3136 angular.element(firstTabableElement).bind('keydown', firstTabableElementKeyhandler);
3137 angular.element(lastTabableElement).bind('keydown', lastTabableElementKeyhandler);
3138 angular.element(element).css('display','none');
3141 this.unbindTabbaleEvents = function(){
3142 if(angular.isDefined(firstTabableElement)){
3143 angular.element(firstTabableElement).unbind('keydown', firstTabableElementKeyhandler);
3146 if(angular.isDefined(lastTabableElement)){
3147 angular.element(lastTabableElement).unbind('keydown', lastTabableElementKeyhandler);
3151 link: function (scope, element, attrs, ctrl) {
3156 .directive('b2bFlyoutToggler', [function () {
3159 require: '^b2bFlyout',
3160 link: function (scope, element, attrs, ctrl) {
3161 element.bind('click', function (e) {
3162 ctrl.toggleFlyoutState();
3165 scope.getTogglerDimensions = function () {
3166 return element[0].getBoundingClientRect();
3169 scope.setTogglerFocus = function () {
3173 ctrl.registerTogglerScope(scope);
3177 .directive('b2bFlyoutContent', ['$position', '$timeout', '$documentBind', '$isElement', '$document', function ($position, $timeout, $documentBind, $isElement, $document) {
3182 require: '^b2bFlyout',
3184 horizontalPlacement: '@',
3185 verticalPlacement: '@',
3188 contentUpdated: "=?"
3190 templateUrl: 'b2bTemplate/flyout/flyoutContent.html',
3191 link: function (scope, element, attrs, ctrl) {
3192 var flyoutStyleArray, eachCssProperty, cssPropertyKey, cssPropertyVal, temp;
3193 scope.openFlyout = false;
3194 if (!scope.horizontalPlacement) {
3195 scope.horizontalPlacement = 'center';
3197 if (!scope.verticalPlacement) {
3198 scope.verticalPlacement = 'below';
3201 scope.toggleFlyout = function () {
3203 scope.openFlyout = !scope.openFlyout;
3205 if (scope.openFlyout) {
3207 if (angular.isDefined(scope.flyoutStyle) && scope.flyoutStyle != "") {
3208 flyoutStyleArray = scope.flyoutStyle.split(";");
3209 for (i = 0; i < flyoutStyleArray.length; i++) {
3210 eachCssProperty = flyoutStyleArray[i].split(":");
3211 if (eachCssProperty.length == 2) {
3212 cssPropertyKey = eachCssProperty[0].trim();
3213 cssPropertyVal = eachCssProperty[1].trim();
3214 angular.element(element[0])[0].style[cssPropertyKey] = cssPropertyVal;
3219 angular.element(element[0]).css({
3224 var flyoutIcons = angular.element(document.querySelectorAll(".b2b-flyout-icon"));
3225 angular.forEach(flyoutIcons, function (elm) {
3226 angular.element(elm)[0].blur();
3229 $timeout(function () {
3230 ctrl.setTogglerFocus();
3232 var togglerDimensions = ctrl.getTogglerDimensions();
3233 var flyoutDimensions = element[0].getBoundingClientRect();
3235 switch (scope.horizontalPlacement) {
3237 angular.element(element[0]).css({
3238 'left': ((togglerDimensions.width / 2) - 26) + 'px'
3242 angular.element(element[0]).css({
3243 'right': ((togglerDimensions.width / 2) - 23) + 'px'
3248 var marginLeft = 10-(flyoutDimensions.width)-20;
3249 angular.element(element[0]).css({
3250 'margin-left': marginLeft + 'px'
3254 angular.element(element[0]).css({
3255 'left': ((togglerDimensions.width + 9 )) + 'px'
3260 var marginLeft = (togglerDimensions.width / 2) - (flyoutDimensions.width / 2) - 8;
3261 angular.element(element[0]).css({
3262 'margin-left': marginLeft + 'px'
3266 switch (scope.verticalPlacement) {
3268 angular.element(element[0]).css({
3269 'top': -(flyoutDimensions.height + 13) + 'px'
3273 angular.element(element[0]).css({
3274 'top': -((togglerDimensions.height-13))+ 'px'
3278 angular.element(element[0]).css({
3279 'top': -(flyoutDimensions.height - 23)+ 'px'
3283 angular.element(element[0]).css({
3284 'top': (togglerDimensions.height + 13) + 'px'
3288 angular.element(element[0]).css({
3297 scope.gotFocus = function () {
3298 scope.openFlyout = false;
3300 ctrl.setTogglerFocus();
3304 scope.closeFromChild = function (e) {
3305 scope.openFlyout = false;
3307 ctrl.setTogglerFocus();
3311 scope.hideFlyout = function () {
3312 angular.element(element[0]).css({
3318 scope.closeFlyout = function (e) {
3319 var isElement = $isElement(angular.element(e.target), element, $document);
3320 if ((e.type === "keydown" && e.which === 27) || ((e.type === "click" || e.type==="touchend") && !isElement)) {
3321 scope.openFlyout = false;
3323 ctrl.setTogglerFocus();
3328 scope.$watch('openFlyout', function () {
3329 ctrl.updateAriaModel(scope.openFlyout);
3332 $documentBind.click('openFlyout', scope.closeFlyout, scope);
3333 $documentBind.event('keydown', 'openFlyout', scope.closeFlyout, scope);
3334 $documentBind.event('touchend', 'openFlyout', scope.closeFlyout, scope);
3335 ctrl.registerContentScope(scope);
3337 if (angular.isDefined(scope.contentUpdated) && scope.contentUpdated !== null) {
3338 scope.$watch('contentUpdated', function (newVal, oldVal) {
3340 if (newVal !== oldVal) {
3341 ctrl.unbindTabbaleEvents();
3342 ctrl.associateTabEvent();
3344 scope.contentUpdated = false;
3352 .directive('b2bCloseFlyout', [function () {
3355 require: '^b2bFlyout',
3359 link: function (scope, element, attrs, ctrl) {
3360 element.bind('click', function (e) {
3361 scope.closeFlyout(e);
3362 ctrl.closeFlyout(e);
3367 .directive('b2bFlyoutTrapFocusInside', [function () {
3371 require: '^b2bFlyout',
3372 link: function (scope, elem, attr, ctrl) {
3373 /* Before opening modal, find the focused element */
3374 ctrl.updateTabbableElements();
3380 * @name Layouts.att:footer
3383 * <file src="src/footer/docs/readme.md" />
3387 <footer class="b2b-footer-wrapper" role="contentinfo" aria-label="footer">
3388 <div class="b2b-footer-container" b2b-column-switch-footer footer-link-items='footerItems'>
3390 <div class="divider-bottom-footer">
3391 <div class="span2 dispalyInline"> </div>
3392 <div class="span6 dispalyInline">
3393 <ul class="footer-nav-content">
3394 <li><a href="Terms_of_use.html" title="Terms of use" id="foot0">Terms of use</a>|</li>
3395 <li><a href="Privacy_policy.html" title="Privacy policy" id="foot1" class="active">Privacy policy</a>|</li>
3396 <li><a href="Tollfree_directory_assistance.html" title="Tollfree directory assistance" id="foot2">Tollfree directory assistance</a>|</li>
3397 <li><a href="compliance.html" title="Accessibility" id="foot3">Accessibility</a></li>
3400 <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.
3404 <div class="span3 footerLogo dispalyInline">
3405 <a href="index.html" class="footer-logo">
3406 <i class="icon-primary-att-globe"><span class="hidden-spoken">A T & T</span></i>
3407 <h2 class="logo-title">AT&T</h2>
3416 * <section id="code">
3417 <example module="b2b.att">
3418 <file src="src/footer/docs/demo.html" />
3419 <file src="src/footer/docs/demo.js" />
3424 angular.module('b2b.att.footer', ['b2b.att.utilities']).
3425 directive('b2bColumnSwitchFooter', [function() {
3430 footerLinkItems: "="
3432 templateUrl: 'b2bTemplate/footer/footer_column_switch_tpl.html',
3433 link: function(scope) {
3434 var tempFooterColumns = scope.footerLinkItems.length;
3435 scope.footerColumns = 3;
3436 if ( (tempFooterColumns === 5) || (tempFooterColumns === 4) ) {
3437 scope.footerColumns = tempFooterColumns;
3448 * @name Layouts.att:header
3451 * <file src="src/header/docs/readme.md" />
3454 * <li b2b-header-menu class="header__item b2b-headermenu" ng-repeat="item in tabItems" role="presentation">
3455 <a href="#" class="menu__item" role="menuitem">{{item.title}}</a>
3456 <div class="header-secondary-wrapper">
3457 <ul class="header-secondary" role="menu">
3458 <li class="header-subitem" b2b-header-submenu ng-repeat="i in item.subitems" role="presentation">
3459 <a href="#" class="menu__item" aria-haspopup="true" role="menuitem">{{i.value}}</a>
3460 <div class="header-tertiary-wrapper" ng-if="i.links">
3461 <ul class="header-tertiary" role="menu">
3462 <li b2b-header-tertiarymenu ng-repeat="link in i.links" role="presentation">
3463 <label>{{link.title}}</label>
3464 <div b2b-tertiary-link ng-repeat="title in link.value">
3465 <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>
3466 <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>
3467 <ul class="header-quarternary" role="menu" ng-if="title.subitems">
3468 <li b2b-header-quarternarymenu role="presentation">
3469 <a href="{{nav.href}}" ng-repeat="nav in title.subitems" role="menuitem" aria-haspopup="true">{{nav.title}}</a>
3482 * <section id="code">
3483 <example module="b2b.att.header">
3484 <file src="src/header/docs/demo.html" />
3485 <file src="src/header/docs/demo.js" />
3490 angular.module('b2b.att.header', ['b2b.att.dropdowns','b2b.att.utilities'])
3491 .directive('b2bHeaderMenu', ['keymap', '$documentBind', '$timeout', '$isElement', '$document', function (keymap, $documentBind, $timeout, $isElement, $document) {
3494 controller:['$scope',function($scope){
3495 this.nextSiblingFocus = function (elObj,flag) {
3496 if (elObj.nextElementSibling) {
3498 var nextmenuItem = this.getFirstElement(elObj.nextElementSibling,'a');
3499 nextmenuItem.focus();
3501 elObj.nextElementSibling.focus();
3506 this.previousSiblingFocus = function (elObj,flag) {
3507 if (elObj.previousElementSibling) {
3509 var prevmenuItem = this.getFirstElement(elObj.previousElementSibling,'a');
3510 prevmenuItem.focus();
3512 elObj.previousElementSibling.focus();
3517 this.getFirstElement = function(elmObj,selector){
3518 return elmObj.querySelector(selector);
3521 link: function (scope, elem,attr,ctrl) {
3522 scope.showMenu = false;
3523 var activeElm, subMenu, tertiaryMenu, el= angular.element(elem)[0],
3524 menuItem = angular.element(elem[0].children[0]);
3525 menuItem.bind('click', function () {
3526 elem.parent().children().removeClass('active');
3527 elem.addClass('active');
3528 var elems= this.parentElement.parentElement.querySelectorAll('li[b2b-header-menu]>a');
3529 for (var i=0; i<elems.length; i++) {
3530 elems[i].setAttribute("aria-expanded",false);
3532 scope.showMenu = true;
3533 var elmTofocus = ctrl.getFirstElement(this.parentElement,'li[b2b-header-submenu]');
3534 elmTofocus.firstElementChild.focus();
3535 this.setAttribute('aria-expanded',true);
3539 elem.bind('keydown', function (evt) {
3540 activeElm = document.activeElement;
3541 subMenu = ctrl.getFirstElement(activeElm.parentElement,'li[b2b-header-submenu]');
3542 tertiaryMenu = ctrl.getFirstElement(activeElm.parentElement,'li[b2b-header-tertiarymenu]');
3543 switch (evt.keyCode) {
3544 case keymap.KEY.ENTER:
3545 case keymap.KEY.SPACE:
3549 evt.stopPropagation();
3550 evt.preventDefault();
3551 if (activeElm.parentElement.hasAttribute('b2b-header-submenu')) {
3552 menuItem[0].focus();
3555 case keymap.KEY.DOWN:
3556 evt.stopPropagation();
3557 evt.preventDefault();
3559 subMenu.firstElementChild.focus();
3560 } else if (tertiaryMenu) {
3561 var firstSubitem = ctrl.getFirstElement(tertiaryMenu,'a.header-tertiaryitem');
3562 firstSubitem.focus();
3565 case keymap.KEY.RIGHT:
3566 evt.stopPropagation();
3567 evt.preventDefault();
3568 if (activeElm.parentElement.hasAttribute('b2b-header-submenu')) {
3569 var elm = angular.element(activeElm.parentElement)[0];
3570 ctrl.nextSiblingFocus(elm,true);
3571 } else if (activeElm.parentElement.parentElement.hasAttribute('b2b-header-tertiarymenu')) {
3572 var tertiaryLI = angular.element(activeElm.parentElement.parentElement)[0];
3573 if (tertiaryLI.nextElementSibling) {
3574 var nextElm = ctrl.getFirstElement(tertiaryLI.nextElementSibling,"a.header-tertiaryitem");
3578 else if(activeElm.parentElement.hasAttribute('b2b-header-menu')){
3579 ctrl.nextSiblingFocus(el,true);
3582 case keymap.KEY.LEFT:
3583 evt.stopPropagation();
3584 evt.preventDefault();
3585 if (activeElm.parentElement.hasAttribute('b2b-header-submenu')) {
3586 var previousElm = angular.element(activeElm.parentElement)[0];
3587 ctrl.previousSiblingFocus(previousElm,true);
3588 } else if (activeElm.parentElement.parentElement.hasAttribute('b2b-header-tertiarymenu')) {
3589 var tertiaryLI = angular.element(activeElm.parentElement.parentElement)[0];
3590 if (tertiaryLI.previousElementSibling) {
3591 var prevElm = ctrl.getFirstElement(tertiaryLI.previousElementSibling,"a.header-tertiaryitem");
3595 else if(activeElm.parentElement.hasAttribute('b2b-header-menu')) {
3596 ctrl.previousSiblingFocus(el,true);
3599 case keymap.KEY.ESC:
3600 evt.stopPropagation();
3601 evt.preventDefault();
3602 scope.showMenu = false;
3603 elem.removeClass('active');
3604 menuItem.attr('aria-expanded',false);
3605 $timeout(function(){
3606 menuItem[0].focus();
3614 var outsideClick = function (e) {
3615 var isElement = $isElement(angular.element(e.target), elem, $document);
3617 scope.showMenu = false;
3618 elem.removeClass('active');
3622 $documentBind.click('showMenu', outsideClick, scope);
3625 }]).directive('b2bHeaderSubmenu', ['$timeout',function ($timeout) {
3628 link: function (scope, elem) {
3629 var caretSign = angular.element("<i class='menuCaret'></i>");
3630 $timeout(function(){
3631 var menuItem = angular.element(elem[0].children[0]);
3632 menuItem.bind('focus mouseenter', function () {
3633 elem.parent().children().removeClass('active');
3634 elem.addClass('active');
3635 if(elem[0].childElementCount > 1){ // > 1 has third level menu
3636 menuItem.attr('aria-expanded',true);
3637 menuItem.attr('aria-haspopup',true);
3639 var caretLeft = (elem[0].offsetLeft + elem[0].offsetWidth/2) - 10;
3640 caretSign.css({left: caretLeft + 'px'});
3641 angular.element(caretSign);
3642 var tertiaryItems = elem[0].querySelectorAll('[b2b-header-tertiarymenu]');
3643 if(tertiaryItems.length >=1){
3644 elem.append(caretSign);
3647 menuItem.bind('blur', function () {
3648 $timeout(function () {
3649 var parentElm = document.activeElement.parentElement.parentElement;
3651 if (!(parentElm.hasAttribute('b2b-header-tertiarymenu'))) {
3652 elem.removeClass('active');
3653 if(elem[0].childElementCount > 1){ // > 1 has third level menu
3654 menuItem.attr('aria-expanded',false);
3656 var caret = elem[0].querySelector('.menuCaret');
3667 }]).directive('b2bHeaderTertiarymenu', ['$timeout','keymap', function ($timeout,keymap){
3670 require:'^b2bHeaderMenu',
3671 link: function (scope, elem,attr,ctrl) {
3673 elem.bind('keydown', function (evt) {
3674 var activeElm = document.activeElement;
3675 var activeParentElm = activeElm.parentElement;
3676 var activeParentObj = angular.element(activeParentElm)[0];
3678 if(activeParentElm.hasAttribute('b2b-tertiary-link')){
3679 var quarterNav = angular.element(activeParentElm)[0].querySelector('li[b2b-header-quarternarymenu]');
3681 var links = ctrl.getFirstElement(angular.element(quarterNav)[0],'a');
3684 var tertiaryMenu = activeElm.parentElement.parentElement.parentElement;
3685 var tertiaryMenuFlag = tertiaryMenu.hasAttribute('b2b-tertiary-link');
3687 switch (evt.keyCode) {
3688 case keymap.KEY.DOWN:
3689 evt.stopPropagation();
3690 evt.preventDefault();
3691 if (activeParentElm.hasAttribute('b2b-tertiary-link')) {
3692 if(angular.element(quarterNav).hasClass('active')){
3694 }else if(activeParentObj.nextElementSibling){
3695 ctrl.nextSiblingFocus(activeParentObj,true);
3698 else if(angular.element(activeParentElm).hasClass('active')){
3699 ctrl.nextSiblingFocus(activeElm);
3703 evt.stopPropagation();
3704 evt.preventDefault();
3705 if(activeParentElm.hasAttribute('b2b-tertiary-link')){
3706 if(activeParentObj.previousElementSibling.hasAttribute('b2b-tertiary-link')){
3707 ctrl.previousSiblingFocus(activeParentObj,true);
3709 var elm = angular.element(activeElm.parentElement.parentElement.parentElement.parentElement.parentElement)[0];
3710 ctrl.getFirstElement(elm,"a").focus();
3712 }else if(angular.element(activeParentElm).hasClass('active')){
3713 if (activeElm.previousElementSibling) {
3714 ctrl.previousSiblingFocus(activeElm);
3715 }else if (tertiaryMenuFlag) {
3716 var elm = angular.element(tertiaryMenu)[0];
3717 ctrl.getFirstElement(elm,"a.header-tertiaryitem").focus();
3727 }]).directive('b2bHeaderTogglemenu', ['$timeout', 'keymap', function ($timeout, keymap) {
3730 require: '^b2bHeaderMenu',
3731 link: function (scope, elem, attrs, ctrl) {
3733 $timeout(function () {
3734 quarterNav = angular.element(elem.parent())[0].querySelector('li[b2b-header-quarternarymenu]');
3735 elem.bind('click', function () {
3736 angular.element(quarterNav).toggleClass('active');
3741 }]).directive('b2bHeaderResponsive', ['$timeout',function ($timeout) {
3744 controller: function($scope){
3745 this.applyMediaQueries = function(value){
3746 document.querySelector('style').textContent +=
3747 "@media screen and (max-width:950px) { \
3748 .header__item.profile { right: " + value + "px; } \
3751 this.arrangeResponsiveHeader = function(children){
3753 * clientWidth of 1090 === max-width of 1100px
3754 * clientWidth of 920 === max-width of 950px
3755 * see b2b-angular.css for rest of responsive header CSS
3757 if (document.documentElement.clientWidth <= 920) {
3760 this.applyMediaQueries(200);
3763 this.applyMediaQueries(200);
3765 default: // anthing above 3, however, should not have more than 3 to date
3766 this.applyMediaQueries(200);
3771 link: function (scope, elem, attrs, ctrl) {
3776 $timeout(function(){
3777 profile = document.querySelector('li.header__item.profile');
3778 children = angular.element(profile).children().length;
3780 ctrl.arrangeResponsiveHeader(children); // shift right-side icon flyovers
3784 window.addEventListener('resize', function(event){ // caret adjustmet
3785 var activeSubmenu = elem[0].querySelector('[b2b-header-menu] [b2b-header-submenu].active');
3786 var activeSubmenuEl = angular.element(activeSubmenu);
3788 var caretSign = activeSubmenu.querySelector('i.menuCaret');
3790 var caretSignEl = angular.element(caretSign);
3791 var caretLeft = (activeSubmenu.offsetLeft + activeSubmenu.offsetWidth/2) - 10;
3792 caretSignEl.css({left: caretLeft + 'px'});
3796 ctrl.arrangeResponsiveHeader(children); // shift right-side icon flyovers
3804 * @name Layouts.att:headings
3807 * <file src="src/headings/docs/readme.md" />
3810 <h1 class="heading-page">38px page heading</h1>
3811 <h2 class="heading-major-section">30px major section heading</h2>
3812 <h3 class="heading-sub-section">24px sub-section heading</h3>
3813 <h2 class="heading-medium">20px medium heading</h2>
3814 <h2 class="heading-medium-emphasis">20px medium emphasis heading</h2>
3815 <h3 class="heading-small">18px small heading</h3>
3816 <h3 class="heading-small-emphasis">18px small emphasis heading</h3>
3817 <h3 class="heading-micro">13px micro heading</h3>
3819 <h2 class="heading-group">Lead</h2>
3820 <h1 class="heading-page">This is 38px heading</h1>
3821 <h2 class="lead">This is lead text...The next big thing since the last big thing we announced.</h2>
3822 <h2 class="heading-group">Eyebrow</h2>
3823 <h3 class="eyebrow">EYEBROW TEXT</h3>
3824 <h2 class="heading-major-section">This is a 30px heading</h2>
3825 <h3 class="eyebrow">EYEBROW TEXT</h3>
3826 <h3 class="heading-sub-section">24px sub-section heading</h3>
3827 <h2 class="heading-group">Subheading</h2>
3828 <h2 class="heading-major-section">This is a 30px heading</h2>
3829 <h3 class="subheading">A subheading here to support what was said above</h3>
3832 <b>HTML + AngularJS</b>
3833 <example module="b2b.att">
3834 <file src="src/headings/docs/demo.html" />
3839 var b2bLegalCopy = angular.module('b2b.att.headings', []);
3842 * @name Tabs, tables & accordions.att:horizontalTable
3845 * <file src="src/horizontalTable/docs/readme.md" />
3848 * @param {int} sticky - Number of sticky columns to have. Maximum of 3.
3849 * @param {boolean} refresh - A boolean that when set to true will force a re-render of table. Only use when using 'bulk mode'
3851 * <section id="code">
3852 <example module="b2b.att">
3853 <file src="src/horizontalTable/docs/demo.html" />
3854 <file src="src/horizontalTable/docs/demo.js" />
3859 angular.module('b2b.att.horizontalTable', [])
3860 .constant('b2bHorizontalTableConfig', {
3861 'maxStickyColumns': 3
3863 .directive('b2bHorizontalTable', ['$timeout', 'b2bHorizontalTableConfig', 'b2bDOMHelper', function ($timeout, b2bHorizontalTableConfig, b2bDOMHelper) {
3869 numOfStickyCols: '=?sticky',
3873 templateUrl: 'b2bTemplate/horizontalTable/horizontalTable.html',
3874 link: function (scope, element, attrs, ctrl) {
3875 scope.numOfStickyCols = scope.numOfStickyCols || 1;
3876 scope.viewportIndex = scope.numOfStickyCols;
3878 // JM520E: This is a temporary hack until I solve the ngRepeat issue
3880 if (element.find('th').length < scope.numOfStickyCols) {
3881 // DOM ngRepeat is not ready, let's check back in 10 ms
3882 console.info('THs are not ready, trying again in 10ms');
3883 $timeout(hack, 10, false);
3890 if (attrs.refresh !== undefined && attrs.refresh !== '') {
3891 scope.$watch('refresh', function(oldVal, newVal) {
3892 if (scope.refresh) {
3893 // From testing it takes about 30 ms before ngRepeat executes, so let's set initial timeout
3894 // NOTE: May need to expose timeout to developers. Application is known to have digest cycle of 3-5k watches.
3895 $timeout(init, 100, false);
3896 scope.refresh = false;
3901 var tableElement = element.find('table');
3902 var thElements = element.find('th');
3903 var innerContainer = angular.element(element[0].querySelector('.b2b-horizontal-table-inner-container'));
3904 var outerContainer = angular.element(element[0].querySelector('.b2b-horizontal-table'));
3906 var tableColumns = [];
3907 var tableRows = element.find('tr');
3911 var totalWidth = element.children()[0].offsetWidth;
3912 var lastVisibleColumn = 0;
3913 var collectiveColumnWidth = [];
3914 var collectiveRowHeight = [];
3915 var columnSets = [];
3917 var stickyPixels = 0;
3919 var displayNoneCSS = {'display': 'none'};
3920 var displayBlockCSS = {'display': 'table-cell'};
3922 function calculateVisibleColumns(startingPoint) {
3924 visibleColumns = startingPoint || scope.numOfStickyCols;
3926 while(usedWidth < stickyPixels && visibleColumns < collectiveColumnWidth.length) {
3927 if (usedWidth+collectiveColumnWidth[visibleColumns] > stickyPixels) {
3928 if (startingPoint === visibleColumns) {
3929 return visibleColumns; // The next cell is too large to fit, it should be only thing to fit
3932 return visibleColumns;
3934 usedWidth += collectiveColumnWidth[visibleColumns];
3938 if (usedWidth > stickyPixels) {
3939 return --visibleColumns;
3941 return visibleColumns;
3944 function updateCellDisplay(set) {
3945 for (var i = scope.numOfStickyCols; i < tableColumns.length; i++) {
3946 angular.element(tableColumns[i]).css(displayNoneCSS);
3949 for (var i = set[0]; i <= set[1]; i++) {
3950 angular.element(tableColumns[i]).css(displayBlockCSS);
3954 function forceDigest() {
3955 if (!scope.$$phase) {
3960 function findMax(arr, prop) {
3965 for (var i = 0; i < arr.length; i++) {
3967 prevDisplay = angular.element(item).css('display');
3968 if (scope.$$phase) {
3971 if (prop === 'width') {
3972 localVal = Math.ceil(parseInt(window.getComputedStyle(item).width.split('px')[0], 10)) + 30; // 30 px is padding
3973 } else if (prop === 'offsetWidth') {
3974 localVal = item.offsetWidth;
3975 } else if (prop === 'height') {
3976 localVal = item.offsetHeight;
3979 if (localVal >= max) {
3988 // Reset this from a previous execution
3990 collectiveColumnWidth = [];
3991 collectiveRowHeight = [];
3994 lastVisibleColumn = 0;
3997 visibleColumns = [];
4000 tableElement = element.find('table');
4001 thElements = element.find('th');
4002 innerContainer = angular.element(element[0].querySelector('.b2b-horizontal-table-inner-container'));
4003 outerContainer = angular.element(element[0].querySelector('.b2b-horizontal-table'));
4004 totalWidth = element.children()[0].offsetWidth;
4007 tableRows = element.find('tr');
4008 totalWidth = element.children()[0].offsetWidth;
4010 scope.disableLeft = true;
4011 scope.disableRight = false;
4013 if (scope.numOfStickyCols > b2bHorizontalTableConfig.maxStickyColumns) {
4014 throw new Error("Table can only support 3 sticky columns.");
4017 angular.forEach(tableRows, function(row, rowIndex) {
4018 collectiveRowHeight.push(findMax(row.children, 'height'));
4019 for(var j = 0; j < row.children.length; j++) {
4020 if (tableColumns[j] === undefined) {
4021 tableColumns[j] = [];
4023 tableColumns[j].push(row.children[j]);
4027 // We need to reset all the displayNones from previous runs, if applicable
4028 if (attrs.refresh !== undefined && attrs.refresh !== '') {
4029 for (var i = scope.numOfStickyCols+1; i < tableColumns.length; i++) {
4030 angular.element(tableColumns[i]).css(displayBlockCSS);
4034 for (var i = 0; i < tableColumns.length; i++) {
4035 collectiveColumnWidth.push(findMax(tableColumns[i], 'width')); //offsetWidth doesn't take into account custom css inside
4037 for(var i = 0; i < scope.numOfStickyCols; i++) {
4038 maxWidth += collectiveColumnWidth[i];
4041 stickyPixels = totalWidth-maxWidth;
4044 // At this point, for each tr, I need to set the properties (height) and each numOfStickyCols children
4045 // should be set with sticky properties (margin-left and width)
4046 var width = maxWidth;
4047 for(var i = 0; i < scope.numOfStickyCols; i++) {
4048 for (var j = 0; j < tableRows.length; j++) {
4049 trObject = angular.element(tableRows[j].children[i]);
4051 angular.element(trObject).css({
4052 'margin-left': -(width + 2) + 'px',
4053 'width': (collectiveColumnWidth[i] + 3) + 'px', // instead of taking the max width, grab max width for that column
4054 'height': collectiveRowHeight[j] + 'px',
4055 'position': 'absolute',
4056 'background-color': 'lightgrey'
4061 width -= collectiveColumnWidth[i];
4064 innerContainer.css({
4065 'padding-left': (maxWidth + 2) + 'px'
4069 // Let's precompute all the (set) combinations beforehand
4071 for (var i = scope.numOfStickyCols; i < tableColumns.length;) {
4072 visibleColumns = calculateVisibleColumns(i);
4073 columnSets.push([i, visibleColumns]);
4074 i = visibleColumns + 1;
4077 updateCellDisplay(columnSets[setIndex]);
4078 checkScrollArrows();
4080 scope.numOfCols = tableColumns.length;
4082 console.log('Bulk Mode is ' + (attrs.bulkMode ? 'enabled': 'disabled'));
4083 console.log('tableColumns', tableColumns);
4084 console.log('collectiveColumnWidth: ', collectiveColumnWidth);
4085 console.log('maxWidth: ', maxWidth);
4088 function checkScrollArrows() {
4089 scope.disableLeft = (setIndex === 0);
4090 scope.disableRight = !(setIndex < columnSets.length-1);
4094 scope.moveViewportLeft = function () {
4096 updateCellDisplay(columnSets[setIndex]);
4097 checkScrollArrows();
4099 if (scope.disableLeft) {
4100 element.find('span')[0].focus();
4104 scope.moveViewportRight = function () {
4106 updateCellDisplay(columnSets[setIndex]);
4107 checkScrollArrows();
4109 if (scope.disableRight) {
4110 element.find('span')[0].focus();
4114 scope.getColumnSet = function () {
4115 return columnSets[setIndex];
4118 innerContainer.bind('scroll', function () {
4119 $timeout(function () {
4120 checkScrollArrows();
4129 * @name Forms.att:hourPicker
4132 * <file src="src/hourPicker/docs/readme.md" />
4135 * <div b2b-hourpicker ng-model="hourpickerValue.value"></div>
4138 * <section id="code">
4139 <example module="b2b.att">
4140 <file src="src/hourPicker/docs/demo.html" />
4141 <file src="src/hourPicker/docs/demo.js" />
4146 angular.module('b2b.att.hourPicker', ['b2b.att.utilities'])
4148 .constant('b2bHourpickerConfig', {
4185 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'],
4186 startTimeDefaultOptionIndex: -1,
4187 startTimeDefaultMeridiem: "am",
4188 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'],
4189 endTimeDefaultOptionIndex: -1,
4190 endTimeDefaultMeridiem: "pm",
4194 .factory('b2bNormalizeHourpickerValues', [function () {
4195 var _normalize = function (hourpickerValues) {
4196 if (angular.isDefined(hourpickerValues) && hourpickerValues != null) {
4197 var finalHourpickerValues = [];
4198 var hourpickerValue = {};
4200 for (var i = 0; i < hourpickerValues.length; i++) {
4201 days = hourpickerValues[i].days ? hourpickerValues[i].days : {};
4202 hourpickerValue.startTime = hourpickerValues[i].startTime ? hourpickerValues[i].startTime : '';
4203 hourpickerValue.startMeridiem = hourpickerValues[i].startMeridiem ? hourpickerValues[i].startMeridiem : '';
4204 hourpickerValue.endTime = hourpickerValues[i].endTime ? hourpickerValues[i].endTime : '';
4205 hourpickerValue.endMeridiem = hourpickerValues[i].endMeridiem ? hourpickerValues[i].endMeridiem : '';
4206 hourpickerValue.days = [];
4208 var retrieveDaysText = function (daysDetails) {
4213 for (var i in days) {
4214 if (days[i].value) {
4219 first = daysTexts[0];
4220 last = daysTexts[0];
4222 hourpickerValue.days[index] = days[first].caption;
4223 if (daysTexts.length > 1) {
4224 for (var i = 1; i < daysTexts.length; i++) {
4225 if (daysTexts[i] - last === 1) {
4226 last = daysTexts[i];
4227 hourpickerValue.days[index] = days[first].caption + ' - ' + days[last].caption;
4230 first = last = daysTexts[i];
4231 hourpickerValue.days[index] = days[first].caption;
4238 finalHourpickerValues.push(angular.copy(hourpickerValue));
4241 return angular.copy(finalHourpickerValues);
4246 normalize: _normalize
4250 .directive('b2bHourpicker', ['b2bHourpickerConfig', 'b2bNormalizeHourpickerValues', function (b2bHourpickerConfig, b2bNormalizeHourpickerValues) {
4256 templateUrl: 'b2bTemplate/hourPicker/b2bHourpicker.html',
4257 controller: ['$scope', function (scope) {
4260 link: function (scope, elem, attr, ctrl) {
4261 scope.hourpicker = {};
4262 scope.hourpicker.dayOptions = attr.dayOptions ? scope.$parent.$eval(attr.dayOptions) : b2bHourpickerConfig.dayOptions;
4263 scope.hourpicker.startTimeOptions = attr.startTimeOptions ? scope.$parent.$eval(attr.startTimeOptions) : b2bHourpickerConfig.startTimeOptions;
4264 scope.hourpicker.endTimeOptions = attr.endTimeOptions ? scope.$parent.$eval(attr.endTimeOptions) : b2bHourpickerConfig.endTimeOptions;
4265 scope.hourpicker.startTimeDefaultOptionIndex = attr.startTimeDefaultOptionIndex ? scope.$parent.$eval(attr.startTimeDefaultOptionIndex) : b2bHourpickerConfig.startTimeDefaultOptionIndex;
4266 scope.hourpicker.endTimeDefaultOptionIndex = attr.endTimeDefaultOptionIndex ? scope.$parent.$eval(attr.endTimeDefaultOptionIndex) : b2bHourpickerConfig.endTimeDefaultOptionIndex;
4267 scope.hourpicker.startTimeDefaultMeridiem = attr.startTimeDefaultMeridiem ? scope.$parent.$eval(attr.startTimeDefaultMeridiem) : b2bHourpickerConfig.startTimeDefaultMeridiem;
4268 scope.hourpicker.endTimeDefaultMeridiem = attr.endTimeDefaultMeridiem ? scope.$parent.$eval(attr.endTimeDefaultMeridiem) : b2bHourpickerConfig.endTimeDefaultMeridiem;
4269 scope.hourpicker.sameDayOption = attr.sameDayOption ? scope.$parent.$eval(attr.sameDayOption) : b2bHourpickerConfig.sameDayOption;
4270 scope.hourpicker.editMode = -1;
4272 scope.hourpickerValues = [];
4273 scope.finalHourpickerValues = [];
4274 scope.addHourpickerValue = function (hourpickerPanelValue) {
4275 if (hourpickerPanelValue) {
4276 if (scope.hourpicker.editMode > -1) {
4277 scope.hourpickerValues[scope.hourpicker.editMode] = hourpickerPanelValue;
4278 scope.hourpicker.editMode = -1;
4280 scope.hourpickerValues.push(hourpickerPanelValue);
4283 scope.finalHourpickerValues = b2bNormalizeHourpickerValues.normalize(angular.copy(scope.hourpickerValues));
4284 ctrl.$setViewValue(angular.copy(scope.hourpickerValues));
4286 ctrl.$render = function () {
4287 if (angular.isDefined(ctrl.$modelValue)) {
4288 scope.hourpickerValues = angular.copy(ctrl.$modelValue);
4289 scope.finalHourpickerValues = b2bNormalizeHourpickerValues.normalize(angular.copy(scope.hourpickerValues));
4292 scope.editHourpickerValue = function (index) {
4293 scope.hourpickerPanelValue = angular.copy(scope.hourpickerValues[index]);
4294 scope.hourpicker.editMode = index;
4296 scope.deleteHourpickerValue = function (index) {
4297 scope.hourpickerValues.splice(index, 1);
4298 scope.resetHourpickerPanelValue();
4299 scope.addHourpickerValue();
4302 scope.setValidity = function (errorType, errorValue) {
4303 ctrl.$setValidity(errorType, errorValue);
4309 .directive('b2bHourpickerPanel', [function () {
4313 templateUrl: 'b2bTemplate/hourPicker/b2bHourpickerPanel.html',
4314 controller: ['$scope', function (scope) {
4317 link: function (scope, elem, attr, ctrl) {
4318 var hourpickerPanelValueTemplate = {
4321 startMeridiem: 'am',
4325 for (var i = 0; i < scope.hourpicker.dayOptions.length; i++) {
4326 hourpickerPanelValueTemplate.days[i] = {
4328 title: scope.hourpicker.dayOptions[i].title,
4329 caption: scope.hourpicker.dayOptions[i].caption
4332 scope.hourpickerPanelValue = {};
4333 scope.disableAddBtn = true;
4335 scope.$watch('hourpickerPanelValue.days', function(){
4336 for(var i in scope.hourpickerPanelValue.days)
4338 if(scope.hourpickerPanelValue.days[i].value)
4340 scope.disableAddBtn = false;
4343 scope.disableAddBtn = true;
4347 scope.resetHourpickerPanelValue = function () {
4348 scope.hourpickerPanelValue = angular.copy(hourpickerPanelValueTemplate);
4349 if (scope.hourpicker.startTimeDefaultOptionIndex > -1) {
4350 scope.hourpickerPanelValue.startTime = scope.hourpicker.startTimeOptions[scope.hourpicker.startTimeDefaultOptionIndex];
4352 if (scope.hourpicker.endTimeDefaultOptionIndex > -1) {
4353 scope.hourpickerPanelValue.endTime = scope.hourpicker.endTimeOptions[scope.hourpicker.endTimeDefaultOptionIndex];
4355 scope.hourpickerPanelValue.startMeridiem = scope.hourpicker.startTimeDefaultMeridiem;
4356 scope.hourpickerPanelValue.endMeridiem = scope.hourpicker.endTimeDefaultMeridiem;
4357 scope.hourpicker.editMode = -1;
4358 scope.setValidity('invalidHourpickerData', true);
4359 scope.setValidity('invalidHourpickerTimeRange', true);
4361 scope.resetHourpickerPanelValue();
4362 scope.updateHourpickerValue = function () {
4363 if (scope.isFormValid() && !scope.isTimeOverlap()) {
4364 scope.addHourpickerValue(angular.copy(scope.hourpickerPanelValue));
4365 scope.resetHourpickerPanelValue();
4369 scope.isFormValid = function () {
4370 var isStartTimeAvailable = scope.hourpickerPanelValue.startTime ? true : false;
4371 var isStartMeridiemAvailable = scope.hourpickerPanelValue.startMeridiem ? true : false;
4372 var isEndTimeAvailable = scope.hourpickerPanelValue.endTime ? true : false;
4373 var isEndMeridiemAvailable = scope.hourpickerPanelValue.endMeridiem ? true : false;
4374 var currentStartTime = getTime(scope.hourpickerPanelValue.startTime, scope.hourpickerPanelValue.startMeridiem);
4375 var currentEndTime = getTime(scope.hourpickerPanelValue.endTime, scope.hourpickerPanelValue.endMeridiem);
4376 var isTimeInProperSequence = currentEndTime > currentStartTime;
4377 var isDayChecked = false;
4378 for (var i in scope.hourpickerPanelValue.days) {
4379 if (scope.hourpickerPanelValue.days[i].value) {
4380 isDayChecked = true;
4385 if (isStartTimeAvailable && isStartMeridiemAvailable && isEndTimeAvailable && isEndMeridiemAvailable && isTimeInProperSequence && isDayChecked) {
4386 scope.setValidity('invalidHourpickerData', true);
4389 scope.setValidity('invalidHourpickerData', false);
4393 scope.isTimeOverlap = function () {
4394 var selectedDays = [];
4395 for (var i in scope.hourpickerPanelValue.days) {
4396 if (scope.hourpickerPanelValue.days[i].value) {
4397 selectedDays.push(i);
4401 var currentStartTime, currentEndTime, existingStartTime, existingEndTime;
4402 currentStartTime = getTime(scope.hourpickerPanelValue.startTime, scope.hourpickerPanelValue.startMeridiem);
4403 currentEndTime = getTime(scope.hourpickerPanelValue.endTime, scope.hourpickerPanelValue.endMeridiem);
4404 for (var i = 0; i < scope.hourpickerValues.length; i++) {
4406 if (i === scope.hourpicker.editMode) {
4410 for (var j = 0; j < selectedDays.length; j++) {
4411 existingStartTime = getTime(scope.hourpickerValues[i].startTime, scope.hourpickerValues[i].startMeridiem);
4412 existingEndTime = getTime(scope.hourpickerValues[i].endTime, scope.hourpickerValues[i].endMeridiem);
4413 if (scope.hourpickerValues[i].days[selectedDays[j]].value) {
4414 if(!scope.hourpicker.sameDayOption){
4415 scope.setValidity('dayAlreadySelected', false);
4417 } else if ((currentStartTime > existingStartTime && currentStartTime < existingEndTime) || (currentEndTime > existingStartTime && currentEndTime < existingEndTime)) {
4418 scope.setValidity('invalidHourpickerTimeRange', false);
4420 } else if ((existingStartTime > currentStartTime && existingStartTime < currentEndTime) || (existingEndTime > currentStartTime && existingEndTime < currentEndTime)) {
4421 scope.setValidity('invalidHourpickerTimeRange', false);
4423 } else if ((currentStartTime === existingStartTime) && (currentEndTime === existingEndTime)) {
4424 scope.setValidity('invalidHourpickerTimeRange', false);
4431 scope.setValidity('dayAlreadySelected', true);
4432 scope.setValidity('invalidHourpickerTimeRange', true);
4435 var getTime = function (timeString, meridiem) {
4436 var tempDate = new Date();
4437 if (timeString && meridiem) {
4438 var timeSplit = timeString.split(':');
4439 var hour = ((meridiem === 'PM' || meridiem === 'pm') && timeSplit[0] !== '12') ? parseInt(timeSplit[0], 10) + 12 : parseInt(timeSplit[0], 10);
4440 tempDate.setHours(hour, parseInt(timeSplit[1], 10), 0, 0);
4443 return tempDate.getTime();
4449 .directive('b2bHourpickerValue', [function () {
4453 templateUrl: 'b2bTemplate/hourPicker/b2bHourpickerValue.html',
4454 controller: ['$scope', function (scope) {
4457 link: function (scope, elem, attr, ctrl) {
4458 scope.hourpickerValue = {};
4459 scope.hourpickerValue.startTime = attr.startTime ? scope.$eval(attr.startTime) : '';
4460 scope.hourpickerValue.startMeridiem = attr.startMeridiem ? scope.$eval(attr.startMeridiem) : '';
4461 scope.hourpickerValue.endTime = attr.endTime ? scope.$eval(attr.endTime) : '';
4462 scope.hourpickerValue.endMeridiem = attr.endMeridiem ? scope.$eval(attr.endMeridiem) : '';
4463 scope.hourpickerValue.days = attr.days ? scope.$eval(attr.days).join(', ') : '';
4464 scope.hourpickerValue.index = attr.b2bHourpickerValue ? scope.$eval(attr.b2bHourpickerValue) : -1;
4470 * @name Template.att:inputTemplate
4473 * <file src="src/inputTemplate/docs/readme.md" />
4476 * <input type="text" id="fieldId" placeholder="placholder text here" class="span12 input-enhanced" name="fieldName">
4480 <b>HTML + AngularJS</b>
4481 <example module="b2b.att">
4482 <file src="src/inputTemplate/docs/demo.html" />
4483 <file src="src/inputTemplate/docs/demo.js" />
4487 angular.module('b2b.att.inputTemplate', []);
4491 * @name Navigation.att:leftNavigation
4494 * <file src="src/leftNavigation/docs/readme.md" />
4497 * <b2b-left-navigation data-menu="menuData"></b2b-left-navigation>
4500 * <section id="code">
4501 <example module="b2b.att">
4502 <file src="src/leftNavigation/docs/demo.html" />
4503 <file src="src/leftNavigation/docs/demo.js" />
4508 angular.module('b2b.att.leftNavigation', [])
4509 .directive('b2bLeftNavigation', [function () {
4512 templateUrl: 'b2bTemplate/leftNavigation/leftNavigation.html',
4516 link: function (scope, element, attrs, ctrl) {
4520 scope.toggleNav = function (val,link) {
4521 /**Added for ECOMP: make parent menu a link if no child menus.**/
4522 if(link!=null && link!=''){
4523 location.href = link;
4527 if (val === scope.idx) {
4533 /*New function for ECOMP*/
4534 scope.toggleDrawer = function(showmenu){
4535 scope.idx=-1; /*hide the sunmenus*/
4537 //scope.openList.length=0;
4538 document.getElementById('page-content').style.marginLeft = "50px";
4541 document.getElementById('page-content').style.marginLeft = "250px";
4543 scope.liveLink = function (evt, val1, val2) {
4544 scope.itemIdx = val1;
4545 scope.navIdx = val2;
4546 evt.stopPropagation();
4553 * @name Buttons, links & UI controls.att:links
4556 * <file src="src/links/docs/readme.md" />
4558 * <!-- See below examples for link implementation -->
4562 <b>HTML + AngularJS</b>
4563 <example module="b2b.att">
4564 <file src="src/links/docs/demo.html" />
4565 <file src="src/links/docs/demo.js" />
4569 angular.module('b2b.att.links', []);
4572 * @name Misc.att:listbox
4575 * <file src="src/listbox/docs/readme.md" />
4577 * @param {int} currentIndex - Current index of selected listbox item. Is not supported on multiselect listbox
4578 * @param {Array} listboxData - Data of listbox items. Should include full data regardless if HTML will be filtered.
4581 * <section id="code">
4582 <example module="b2b.att">
4583 <file src="src/listbox/docs/demo.html" />
4584 <file src="src/listbox/docs/demo.js" />
4589 angular.module('b2b.att.listbox', ['b2b.att.utilities'])
4590 .directive('b2bListBox', ['keymap', 'b2bDOMHelper', '$rootScope', function(keymap, b2bDOMHelper, $rootScope) {
4599 templateUrl: 'b2bTemplate/listbox/listbox.html',
4600 link: function(scope, elem, attr) {
4602 if (attr.ariaMultiselectable !== undefined || attr.ariaMultiselectable === 'true') {
4603 scope.multiselectable = true;
4605 scope.multiselectable = false;
4608 var shiftKey = false;
4610 var prevDirection = undefined; // previous direction is used for an edge case when shifting
4611 var shiftKeyPressed = false; // Used to handle shift clicking
4612 var ctrlKeyPressed = false;
4614 var currentIndexSet = {
4616 'listboxDataIndex': 0
4619 /*scope.$watch('currentIndex', function(oldVal, newVal) {
4620 if (angular.equals(oldVal, newVal)) return;
4621 if (!scope.multiselectable) {
4622 // This doesn't garuntee anything. index will update on focus based on rules
4623 currentIndexSet.listboxDataIndex = scope.currentIndex;
4624 // Should this occur?
4625 //scope.listboxData[currentIndexSet.listboxDataIndex].selected = true;
4627 // Update elementIndex
4628 elements = elem.children();
4629 var indecies = Array.prototype.map.call(elements, function(item) {
4630 return parseInt(angular.element(item).attr('data-index'), 10);
4631 }).filter(function(item) {
4632 return item === scope.currentIndex;
4634 currentIndex.elementIndex = indecies[0];
4635 //focusOnElement(currentIndexSet.elementIndex); // This isn't shifting focus
4636 if (!scope.$$phase) {
4642 function isTrue(item) {
4643 if (item.selected === true) {
4648 function incrementIndex(elem) {
4649 $rootScope.$apply();
4651 var nextElem = elem.next();
4652 if (!angular.isDefined(nextElem) || nextElem.length === 0) {
4656 currentIndexSet.elementIndex += 1;
4657 currentIndexSet.listboxDataIndex = parseInt(nextElem.attr('data-index'), 10);
4658 scope.currentIndex = currentIndexSet.listboxDataIndex;
4660 if (currentIndexSet.elementIndex >= elements.length - 1) {
4661 currentIndexSet.elementIndex = elements.length-1;
4665 function decrementIndex(elem) {
4666 $rootScope.$apply();
4667 var prevElem = angular.element(b2bDOMHelper.previousElement(elem));
4668 if (!angular.isDefined(prevElem) || prevElem.length === 0) {
4672 currentIndexSet.elementIndex -= 1;
4673 currentIndexSet.listboxDataIndex = parseInt(prevElem.attr('data-index'), 10);
4674 scope.currentIndex = currentIndexSet.listboxDataIndex;
4676 if (currentIndexSet.elementIndex <= 0) {
4677 currentIndexSet.elementIndex = 0;
4681 var focusOnElement = function(index) {
4683 elements[index].focus();
4687 function selectItems(startIndex, endIndex, forceValue) {
4688 for (var i = startIndex; i < endIndex; i++) {
4689 if (forceValue === undefined) {
4690 // We will flip the value
4691 scope.listboxData[i].selected = !scope.listboxData[i].selected;
4693 scope.listboxData[i].selected = forceValue;
4697 if (!scope.$$phase) {
4702 elem.bind('focus', function(evt) {
4703 // If multiselectable or not and nothing is selected, put focus on first element
4704 // If multiselectable and a range is set, put focus on first element of range
4705 // If not multiselectable and something selected, put focus on element
4706 elements = elem.children();
4707 var selectedItems = scope.listboxData.filter(isTrue);
4708 var elementsIndies = Array.prototype.map.call(elements, function(item) {
4709 return parseInt(angular.element(item).attr('data-index'), 10);
4712 if (selectedItems.length == 0) {
4714 currentIndexSet.listboxDataIndex = 0;
4715 } else if (attr.ariaMultiselectable) {
4716 var index = scope.listboxData.indexOf(selectedItems[0]);
4717 var indies = elementsIndies.filter(function(item) {
4718 return (item === index);
4721 if (indies.length === 0 || indies[0] != index) {
4723 currentIndexSet.elementIndex = elementsIndies[0];
4724 currentIndexSet.listboxDataIndex = 0;
4725 focusOnElement(currentIndexSet.elementIndex);
4727 focusOnElement(indies[0]);
4728 currentIndexSet.elementIndex = indies[0];
4729 currentIndexSet.listboxDataIndex = index;
4732 focusOnElement(currentIndexSet.elementIndex);
4734 scope.currentIndex = currentIndexSet.listboxDataIndex;
4736 if (!scope.$$phase) {
4740 elem.bind('keyup', function(evt) {
4741 if (evt.keyCode === keymap.KEY.SHIFT) {
4742 shiftKeyPressed = false;
4743 } else if (evt.keyCode === keymap.KEY.CTRL) {
4744 ctrlKeyPressed = false;
4748 elem.bind('keydown', function(evt) {
4749 var keyCode = evt.keyCode;
4750 elements = elem.children();
4751 if (keyCode === keymap.KEY.SHIFT) {
4752 shiftKeyPressed = true;
4753 } else if (evt.keyCode === keymap.KEY.CTRL) {
4754 ctrlKeyPressed = true;
4760 if (scope.multiselectable && evt.ctrlKey) {
4761 var arr = scope.listboxData.filter(isTrue);
4762 var elementsIndies = Array.prototype.map.call(elements, function(item) {
4763 return parseInt(angular.element(item).attr('data-index'), 10);
4765 var val = !(arr.length === scope.listboxData.length);
4766 for (var i = 0; i < elementsIndies.length; i++) {
4767 scope.listboxData[elementsIndies[i]].selected = val;
4770 if (!scope.$$phase) {
4774 evt.preventDefault();
4775 evt.stopPropagation();
4779 case keymap.KEY.END:
4781 if (scope.multiselectable && evt.ctrlKey && evt.shiftKey) {
4782 var elementsIndies = Array.prototype.map.call(elements, function(item) {
4783 return parseInt(angular.element(item).attr('data-index'), 10);
4784 }).filter(function(item) {
4785 return (item >= currentIndexSet.listboxDataIndex);
4787 for (var i = 0; i < elementsIndies.length; i++) {
4788 scope.listboxData[elementsIndies[i]].selected = true;
4790 evt.preventDefault();
4791 evt.stopPropagation();
4793 if (!scope.$$phase) {
4799 case keymap.KEY.HOME:
4801 if (scope.multiselectable && evt.ctrlKey && evt.shiftKey) {
4802 selectItems(0, currentIndexSet.listboxDataIndex+1, true); // currentIndex+1 is what is being focused on
4803 evt.preventDefault();
4804 evt.stopPropagation();
4808 case keymap.KEY.LEFT:
4811 if (currentIndexSet.listboxDataIndex === 0) {
4812 evt.preventDefault();
4813 evt.stopPropagation();
4817 decrementIndex(elements.eq(currentIndexSet.elementIndex));
4818 if (scope.multiselectable && (evt.shiftKey || evt.ctrlKey)) {
4820 if (prevDirection === 'DOWN') {
4821 scope.listboxData[currentIndexSet.listboxDataIndex+1].selected = !scope.listboxData[currentIndexSet.listboxDataIndex+1].selected;
4823 scope.listboxData[currentIndexSet.listboxDataIndex].selected = !scope.listboxData[currentIndexSet.listboxDataIndex].selected;
4825 prevDirection = 'UP';
4827 // If no modifier keys are selected, all other items need to be unselected.
4828 prevDirection = undefined;
4829 selectItems(0, scope.listboxData.length, false);
4830 scope.listboxData[currentIndexSet.listboxDataIndex].selected = true;
4832 focusOnElement(currentIndexSet.elementIndex);
4833 if(!scope.$$phase) {
4836 evt.preventDefault();
4837 evt.stopPropagation();
4840 case keymap.KEY.RIGHT:
4841 case keymap.KEY.DOWN:
4843 if (currentIndexSet.listboxDataIndex === scope.listboxData.length-1) {
4844 evt.preventDefault();
4845 evt.stopPropagation();
4849 incrementIndex(elements.eq(currentIndexSet.elementIndex));
4851 if (scope.multiselectable && (evt.shiftKey || evt.ctrlKey)) {
4853 if (prevDirection === 'UP') {
4854 scope.listboxData[currentIndexSet.listboxDataIndex-1].selected = !scope.listboxData[currentIndexSet.listboxDataIndex-1].selected;
4857 scope.listboxData[currentIndexSet.listboxDataIndex].selected = !scope.listboxData[currentIndexSet.listboxDataIndex].selected;
4859 prevDirection = 'DOWN';
4861 // If no modifier keys are selected, all other items need to be unselected.
4862 prevDirection = undefined;
4863 selectItems(0, scope.listboxData.length, false);
4864 scope.listboxData[currentIndexSet.listboxDataIndex].selected = true;
4867 focusOnElement(currentIndexSet.elementIndex);
4868 if(!scope.$$phase) {
4871 evt.preventDefault();
4872 evt.stopPropagation();
4875 case keymap.KEY.TAB:
4877 var previousElement = b2bDOMHelper.previousElement(elem.parent().parent(), true);
4878 evt.preventDefault();
4879 previousElement.focus();
4887 elem.bind('click', function(evt) {
4888 var index = parseInt(evt.target.dataset.index, 10);
4889 if (index === undefined || isNaN(index)) {
4892 if (scope.multiselectable && currentIndexSet.listboxDataIndex !== undefined) {
4893 if (shiftKeyPressed) {
4894 var min = Math.min(index, currentIndexSet.listboxDataIndex);
4895 var max = Math.max(index, currentIndexSet.listboxDataIndex);
4897 if (index === min) { // clicking up
4898 var firstIndex = scope.listboxData.findIndex(function(item) { return item.selected === true;});
4899 // Given the firstIndex, let's find the matching element to get proper element match
4900 elements = elem.children();
4901 elements.eq(firstIndex)
4902 var elementsThatMatch = Array.prototype.filter.call(elements, function(item) {
4903 if (parseInt(angular.element(item).attr('data-index'), 10) === firstIndex) {
4907 firstIndex = parseInt(angular.element(elementsThatMatch).attr('data-index'), 10);
4909 if (index <= firstIndex && scope.listboxData.filter(isTrue).length > 1) {
4910 // Break the selection into 2
4911 selectItems(firstIndex + 1, max + 1, undefined); // + 1 needed because selectItems only selects up to MAX
4912 selectItems(min, firstIndex, undefined);
4913 } else if (scope.listboxData.filter(isTrue).length == 1){
4914 selectItems(min, max, undefined);
4916 selectItems(min + 1, max + 1, undefined);
4918 } else { // clicking down
4919 selectItems(min + 1, max + 1, scope.listboxData[min].selected);
4921 } else if (ctrlKeyPressed) {
4922 scope.listboxData[index].selected = !scope.listboxData[index].selected;
4924 selectItems(0, scope.listboxData.length, false);
4925 scope.listboxData[index].selected = !scope.listboxData[index].selected;
4928 selectItems(0, scope.listboxData.length, false);
4929 scope.listboxData[index].selected = !scope.listboxData[index].selected;
4931 currentIndexSet.elementIndex = index;
4932 currentIndexSet.listboxDataIndex = index;
4933 scope.currentIndex = currentIndexSet.listboxDataIndex;
4934 if (!scope.$$phase) {
4937 focusOnElement(index);
4944 * @name Videos, audio & animation.att:loaderAnimation
4947 * <file src="src/loaderAnimation/docs/readme.md" />
4950 * <!-- Below demo js shows-->
4951 * Angular library uses Global.css's icon-primary-spinner.
4954 * <section id="code">
4955 <example module="b2b.att">
4956 <file src="src/loaderAnimation/docs/demo.html" />
4957 <file src="src/loaderAnimation/docs/demo.js" />
4962 angular.module('b2b.att.loaderAnimation', [])
4963 .constant('b2bSpinnerConfig', {
4964 loadingText: 'Loading...',
4965 startEvent: 'startButtonSpinner',
4966 stopEvent: 'stopButtonSpinner'
4968 .constant("progressTrackerConfig", {
4969 loadingText: 'Loading...',
4971 activationDelay: "",
4972 minDurationPromise: "",
4973 activationDelayPromise: ""
4976 .provider('progressTracker', function () {
4977 this.$get = ['$q', '$timeout', function ($q, $timeout) {
4978 function cancelTimeout(promise) {
4980 $timeout.cancel(promise);
4983 return function ProgressTracker(options) {
4984 //do new if user doesn't
4985 if (!(this instanceof ProgressTracker)) {
4986 return new ProgressTracker(options);
4989 options = options || {};
4990 //Array of promises being tracked
4993 //Allow an optional "minimum duration" that the tracker has to stay active for.
4994 var minDuration = options.minDuration;
4995 //Allow a delay that will stop the tracker from activating until that time is reached
4996 var activationDelay = options.activationDelay;
4997 var minDurationPromise;
4998 var activationDelayPromise;
4999 self.active = function () {
5000 //Even if we have a promise in our tracker, we aren't active until delay is elapsed
5001 if (activationDelayPromise) {
5004 return tracked.length > 0;
5006 self.tracking = function () {
5007 //Even if we aren't active, we could still have a promise in our tracker
5008 return tracked.length > 0;
5010 self.destroy = self.cancel = function () {
5011 minDurationPromise = cancelTimeout(minDurationPromise);
5012 activationDelayPromise = cancelTimeout(activationDelayPromise);
5013 for (var i = tracked.length - 1; i >= 0; i--) {
5014 tracked[i].resolve();
5018 //Create a promise that will make our tracker active until it is resolved.
5019 // @return deferred - our deferred object that is being tracked
5020 self.createPromise = function () {
5021 var deferred = $q.defer();
5022 tracked.push(deferred);
5023 //If the tracker was just inactive and this the first in the list of promises, we reset our delay and minDuration again.
5024 if (tracked.length === 1) {
5025 if (activationDelay) {
5026 activationDelayPromise = $timeout(function () {
5027 activationDelayPromise = cancelTimeout(activationDelayPromise);
5029 }, activationDelay);
5034 deferred.promise.then(onDone(false), onDone(true));
5037 function startMinDuration() {
5039 minDurationPromise = $timeout(angular.noop, minDuration);
5042 //Create a callback for when this promise is done. It will remove our tracked promise from the array if once minDuration is complete
5044 return function () {
5045 (minDurationPromise || $q.when()).then(function () {
5046 var index = tracked.indexOf(deferred);
5047 tracked.splice(index, 1);
5048 //If this is the last promise, cleanup the timeouts for activationDelay
5049 if (tracked.length === 0) {
5050 activationDelayPromise = cancelTimeout(activationDelayPromise);
5056 self.addPromise = function (promise) {
5058 // we cannot assign then function in other var and then add the resolve and reject
5059 var thenFxn = promise && (promise.then || promise.$then || (promise.$promise && promise.$promise.then));
5061 throw new Error("progressTracker expects a promise object :: Not found");
5063 var deferred = self.createPromise();
5064 //When given promise is done, resolve our created promise
5065 //Allow $then for angular-resource objects
5067 promise.then(function (value) {
5068 deferred.resolve(value);
5070 }, function (value) {
5071 deferred.reject(value);
5072 return $q.reject(value);
5081 .config(['$httpProvider', function ($httpProvider) {
5082 $httpProvider.interceptors.push(['$q', 'progressTracker', function ($q) {
5084 request: function (config) {
5085 if (config.tracker) {
5086 if (!angular.isArray(config.tracker)) {
5087 config.tracker = [config.tracker];
5089 config.$promiseTrackerDeferred = config.$promiseTrackerDeferred || [];
5091 angular.forEach(config.tracker, function (tracker) {
5092 var deferred = tracker.createPromise();
5093 config.$promiseTrackerDeferred.push(deferred);
5096 return $q.when(config);
5098 response: function (response) {
5099 if (response.config && response.config.$promiseTrackerDeferred) {
5100 angular.forEach(response.config.$promiseTrackerDeferred, function (deferred) {
5101 deferred.resolve(response);
5104 return $q.when(response);
5106 responseError: function (response) {
5107 if (response.config && response.config.$promiseTrackerDeferred) {
5108 angular.forEach(response.config.$promiseTrackerDeferred, function (deferred) {
5109 deferred.reject(response);
5112 return $q.reject(response);
5118 .directive('b2bClickSpin', ['$timeout', '$parse', '$rootScope', 'progressTracker', function ($timeout, $parse, $rootScope, progressTracker) {
5121 link: function (scope, elm, attrs) {
5122 var fn = $parse(attrs.b2bClickSpin);
5123 elm.on('click', function (event) {
5124 var promise = $timeout(function () {console.log("inside Promise")}, 5000);
5125 scope.$apply(function () {
5130 //comment this line if not running unit test
5131 $rootScope.loadingTracker = progressTracker({
5134 $rootScope.loadingTracker.addPromise(promise);
5135 angular.forEach("$routeChangeSuccess $viewContentLoaded $locationChangeSuccess".split(" "), function (event) {
5136 $rootScope.$on(event, function () {
5138 $timeout.cancel(promise);
5146 .directive('b2bProgressTracker', ['progressTrackerConfig', function (ptc) {
5150 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>'
5154 .directive('b2bLoadButton', ['b2bSpinnerConfig', '$timeout', function (spinnerConfig, $timeout) {
5155 var spinButton = function (state, element, data) {
5157 var attr = element.html() ? 'html' : 'val';
5158 state = state + 'Text';
5159 if (state === 'loadingText') {
5160 element[attr](data[state]);
5161 element.attr("disabled",'disabled');
5162 element.addClass('disabled');
5163 } else if (state === 'resetText') {
5164 element[attr](data[state]);
5165 element.removeAttr("disabled");
5166 element.removeClass('disabled');
5174 promise: '=promise',
5175 startEvent: '@startEvent',
5176 stopEvent: '@stopEvent'
5178 link: function (scope, element, attr) {
5179 var validAttr = element.html() ? 'html' : 'val';
5185 var updateLoadingText = function (val) {
5186 var loadingText = val;
5187 if (!angular.isDefined(loadingText) || loadingText === "") {
5188 loadingText = spinnerConfig.loadingText;
5190 data.loadingText = validAttr === 'html' ? "<i class=\"icon-primary-spinner small\"></i>" + loadingText : loadingText;
5192 var updateResetText = function (val) {
5193 data.resetText = val;
5196 attr.$observe('b2bLoadButton', function (val) {
5197 updateLoadingText(val);
5199 $timeout(function () {
5200 updateResetText(element[validAttr]());
5203 if (!angular.isDefined(scope.startEvent) || scope.startEvent === "") {
5204 scope.startEvent = spinnerConfig.startEvent;
5207 if (!angular.isDefined(scope.stopEvent) || scope.stopEvent === "") {
5208 scope.stopEvent = spinnerConfig.stopEvent;
5211 scope.$watch('promise', function () {
5212 if (angular.isDefined(scope.promise) && angular.isFunction(scope.promise.then)) {
5213 spinButton('loading', element, data);
5214 scope.promise.then(function () {
5215 spinButton('reset', element, data);
5217 spinButton('reset', element, data);
5222 scope.$on(scope.startEvent, function () {
5223 spinButton('loading', element, data);
5224 scope.$on(scope.stopEvent, function () {
5225 spinButton('reset', element, data);
5236 * @name Misc.att:messageWrapper
5238 * @param {boolean} trigger - A boolean that triggers directive to switch focus
5239 * @param {integer} delay - Extra delay added to trigger code to allow for DOM to be ready. Default is 10ms.
5240 * @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)
5241 * @param {string} trapFocus - Attribute-based API to trap focus within the message. This should be enabled by default on all toast messages.
5243 * <file src="src/messageWrapper/docs/readme.md" />
5245 * <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>
5248 * <section id="code">
5249 <b>HTML + AngularJS</b>
5250 <example module="b2b.att">
5251 <file src="src/messageWrapper/docs/demo.html" />
5252 <file src="src/messageWrapper/docs/demo.js" />
5257 angular.module('b2b.att.messageWrapper', ['b2b.att.utilities'])
5258 .directive('b2bMessageWrapper', ['b2bDOMHelper', '$compile', '$timeout', '$log', function(b2bDOMHelper, $compile, $timeout, $log) {
5267 template: '<div ng-transclude></div>',
5268 link: function(scope, elem, attrs) {
5269 scope.delay = scope.delay || 10;
5271 if (attrs.trapFocus != undefined && !elem.children().eq(0).attr('b2b-trap-focus-inside-element')) {
5272 // Append b2bTrapFocusInsideElement onto first child and recompile
5273 elem.children().eq(0).attr('b2b-trap-focus-inside-element', 'false');
5274 elem.children().eq(0).attr('trigger', scope.trigger);
5275 $compile(elem.contents())(scope);
5278 var firstElement = undefined,
5279 launchingElement = undefined;
5281 scope.$watch('trigger', function(oldVal, newVal) {
5282 if (oldVal === newVal) return;
5283 if (!angular.isDefined(launchingElement)) {
5284 launchingElement = document.activeElement;
5286 $timeout(function() {
5287 if (scope.trigger) {
5289 if (attrs.noFocus === true || attrs.noFocus === "") {
5290 elem.children()[0].focus();
5292 firstElement = b2bDOMHelper.firstTabableElement(elem);
5294 if (angular.isDefined(firstElement)) {
5295 firstElement.focus();
5300 if (angular.isDefined(launchingElement) && launchingElement.nodeName !== 'BODY') {
5301 if (launchingElement === document.activeElement) {
5305 if (b2bDOMHelper.isInDOM(launchingElement) && b2bDOMHelper.isTabable(launchingElement)) {
5306 // At this point, launchingElement is still a valid element, but focus will fail and
5307 // activeElement will become body, hence we want to apply custom logic and find previousElement
5308 var prevLaunchingElement = launchingElement;
5309 launchingElement.focus();
5311 if (document.activeElement !== launchingElement || document.activeElement.nodeName === 'BODY') {
5312 launchingElement = b2bDOMHelper.previousElement(angular.element(prevLaunchingElement), true);
5313 launchingElement.focus();
5316 launchingElement = b2bDOMHelper.previousElement(launchingElement, true);
5317 launchingElement.focus();
5328 * @name Messages, modals & alerts.att:modalsAndAlerts
5331 * <file src="src/modalsAndAlerts/docs/readme.md" />
5334 * <button class="btn" b2b-modal="b2bTemplate/modalsAndAlerts/demo_modal.html" modal-ok="ok()" modal-cancel="cancel()">Launch demo modal</button>
5337 * <section id="code">
5338 <example module="b2b.att">
5339 <file src="src/modalsAndAlerts/docs/demo.html" />
5340 <file src="src/modalsAndAlerts/docs/demo.js" />
5345 angular.module('b2b.att.modalsAndAlerts', ['b2b.att.position', 'b2b.att.transition', 'b2b.att.utilities'])
5348 * A helper, internal data structure that acts as a map but also allows getting / removing
5349 * elements in the LIFO order
5351 .factory('$$stackedMap', function () {
5353 createNew: function () {
5357 add: function (key, value) {
5363 get: function (key) {
5364 for (var i = 0; i < stack.length; i++) {
5365 if (key === stack[i].key) {
5372 for (var i = 0; i < stack.length; i++) {
5373 keys.push(stack[i].key);
5378 return stack[stack.length - 1];
5380 remove: function (key) {
5382 for (var i = 0; i < stack.length; i++) {
5383 if (key === stack[i].key) {
5388 return stack.splice(idx, 1)[0];
5390 removeTop: function () {
5391 return stack.splice(stack.length - 1, 1)[0];
5393 length: function () {
5394 return stack.length;
5399 }).factory('trapFocusInElement', ['$document', '$isElement', 'DOMHelper', 'keymap', function ($document, $isElement, DOMHelper, keymap) {
5400 var elementStack = [];
5401 var stackHead = undefined;
5402 var firstTabableElement, lastTabableElement;
5404 var trapKeyboardFocusInFirstElement = function (e) {
5406 e.keyCode = e.which;
5409 if (e.shiftKey === true && e.keyCode === keymap.KEY.TAB) {
5410 lastTabableElement[0].focus();
5411 e.preventDefault(e);
5412 e.stopPropagation(e);
5417 var trapKeyboardFocusInLastElement = function (e) {
5419 e.keyCode = e.which;
5422 if (e.shiftKey === false && e.keyCode === keymap.KEY.TAB) {
5423 firstTabableElement[0].focus();
5424 e.preventDefault(e);
5425 e.stopPropagation(e);
5429 var trapFocusInElement = function (flag, firstTabableElementParam, lastTabableElementParam) {
5430 var bodyElements = $document.find('body').children();
5432 firstTabableElement = firstTabableElementParam ? firstTabableElementParam : angular.element(DOMHelper.firstTabableElement(stackHead));
5433 lastTabableElement = lastTabableElementParam ? lastTabableElementParam : angular.element(DOMHelper.lastTabableElement(stackHead));
5436 for (var i = 0; i < bodyElements.length; i++) {
5437 if (bodyElements[i] !== stackHead[0]) {
5438 bodyElements.eq(i).attr('aria-hidden', true);
5441 firstTabableElement.bind('keydown', trapKeyboardFocusInFirstElement);
5442 lastTabableElement.bind('keydown', trapKeyboardFocusInLastElement);
5444 for (var j = 0; j < bodyElements.length; j++) {
5445 if (bodyElements[j] !== stackHead[0]) {
5446 bodyElements.eq(j).removeAttr('aria-hidden');
5449 firstTabableElement.unbind('keydown', trapKeyboardFocusInFirstElement);
5450 lastTabableElement.unbind('keydown', trapKeyboardFocusInLastElement);
5453 var toggleTrapFocusInElement = function (flag, element) {
5454 if (angular.isDefined(flag) && angular.isDefined(element)) {
5455 if (angular.isUndefined(stackHead)) {
5456 stackHead = element;
5457 trapFocusInElement(flag);
5460 trapFocusInElement(false);
5461 elementStack.push(stackHead);
5462 stackHead = element;
5463 trapFocusInElement(true);
5465 if (stackHead.prop('$$hashKey') === element.prop('$$hashKey')) {
5466 trapFocusInElement(false);
5467 stackHead = elementStack.pop();
5468 if (angular.isDefined(stackHead)) {
5469 trapFocusInElement(true);
5475 if (angular.isDefined(stackHead)) {
5476 trapFocusInElement(false, firstTabableElement, lastTabableElement);
5477 trapFocusInElement(true);
5482 return toggleTrapFocusInElement;
5486 * A helper directive for the $modal service. It creates a backdrop element.
5488 .directive('b2bModalBackdrop', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
5492 templateUrl: 'b2bTemplate/modalsAndAlerts/b2b-backdrop.html',
5493 link: function (scope, element, attrs) {
5494 scope.close = function (evt) {
5495 var modal = $modalStack.getTop();
5496 if (modal && modal.value.backdrop && modal.value.backdrop !== 'static') {
5497 evt.preventDefault();
5498 evt.stopPropagation();
5499 $modalStack.dismiss(modal.key, 'backdrop click');
5506 .directive('b2bModalWindow', ['$timeout', 'windowOrientation', '$window', function ($timeout, windowOrientation, $window) {
5514 templateUrl: 'b2bTemplate/modalsAndAlerts/b2b-window.html',
5515 controller: ['$scope', '$element', '$attrs', function (scope, element, attrs) {
5516 scope.windowClass = attrs.windowClass || '';
5517 scope.sizeClass = attrs.sizeClass || '';
5518 scope.isNotifDialog = false;
5520 this.setTitle = function (title) {
5521 scope.title = title;
5523 this.setContent = function (content) {
5524 scope.content = content;
5525 scope.isNotifDialog = true;
5527 this.isDockedModal = scope.windowClass.indexOf('modal-docked') > -1;
5529 link: function (scope, element, attrs, ctrl) {
5530 if (ctrl.isDockedModal) {
5531 scope.isModalLandscape = false;
5533 var window = angular.element($window);
5534 scope.updateCss = function () {
5535 if (windowOrientation.isPotrait()) { // Potrait Mode
5536 scope.isModalLandscape = false;
5537 } else if (windowOrientation.isLandscape()) { // Landscape Mode
5538 scope.isModalLandscape = true;
5542 $timeout(function () {
5546 window.bind('orientationchange', function () {
5550 window.bind('resize', function () {
5555 angular.element(element[0].querySelectorAll(".awd-select-list")).css({
5556 "max-height": "200px"
5560 var isIE = /msie|trident/i.test(navigator.userAgent);
5562 if(angular.element(element[0].querySelector('.corner-button button.close')).length > 0){
5563 angular.element(element[0].querySelector('.corner-button button.close')).bind('focus', function () {
5564 angular.element(element[0].querySelector('.b2b-modal-header'))[0].scrollLeft = 0;
5565 angular.element(element[0].querySelector('.b2b-modal-header'))[0].scrollTop = 0;
5574 .directive('b2bModalTitle', [function () {
5577 require: '^b2bModalWindow',
5578 link: function (scope, elem, attr, ctrl) {
5579 ctrl.setTitle(attr.id);
5584 .directive('b2bModalContent', [function () {
5587 require: '^b2bModalWindow',
5588 link: function (scope, elem, attr, ctrl) {
5589 ctrl.setContent(attr.id);
5595 .directive('b2bModalBody', ['$timeout', '$position', '$document', '$window', 'windowOrientation', 'b2bAwdBreakpoints', function ($timeout, $position, $document, $window, windowOrientation, b2bAwdBreakpoints) {
5601 require: '^b2bModalWindow',
5602 link: function (scope, element, attrs, ctrl) {
5603 var window = angular.element($window);
5604 var body = $document.find('body').eq(0);
5605 scope.setModalHeight = function () {
5606 var modalHeaderHeight, modalFooterHeight, modalBodyHeight, windowHeight, windowWidth, modalHeight;
5607 modalHeaderHeight = 0;
5608 modalFooterHeight = 0;
5609 windowHeight = $window.innerHeight;
5610 windowWidth = $window.innerWidth;
5612 'height': windowHeight + 'px'
5615 if (ctrl.isDockedModal) {
5616 var modalElements = element.parent().children();
5617 for (var i = 0; i < modalElements.length; i++) {
5618 if (modalElements.eq(i).hasClass('b2b-modal-header')) {
5619 modalHeaderHeight = $position.position(modalElements.eq(i)).height;
5620 } else if (modalElements.eq(i).hasClass('b2b-modal-footer')) {
5621 modalFooterHeight = $position.position(modalElements.eq(i)).height;
5625 modalHeight = $position.position(element.parent()).height;
5627 modalBodyHeight = modalHeight - (modalHeaderHeight + modalFooterHeight) + 'px';
5629 if (windowOrientation.isPotrait()) { // Potrait Mode
5630 element.removeAttr('style').css({
5631 height: modalBodyHeight
5633 } else if (windowOrientation.isLandscape() && windowWidth < b2bAwdBreakpoints.breakpoints.mobile.max) { // Landscape Mode Mobile
5634 element.removeAttr('style');
5635 } else if (windowOrientation.isLandscape() && windowWidth >= b2bAwdBreakpoints.breakpoints.mobile.max) { // Landscape Mode Non-Mobile
5636 element.removeAttr('style').css({
5637 height: modalBodyHeight
5643 $timeout(function () {
5644 scope.setModalHeight();
5647 window.bind('orientationchange', function () {
5648 scope.setModalHeight();
5651 window.bind('resize', function () {
5652 scope.setModalHeight();
5659 .directive('b2bModalFooter', ['windowOrientation', '$window', function (windowOrientation, $window) {
5665 link: function (scope, element, attrs) {
5671 .factory('$modalStack', ['$document', '$compile', '$rootScope', '$$stackedMap', '$log', '$timeout', 'trapFocusInElement', function ($document, $compile, $rootScope, $$stackedMap, $log, $timeout, trapFocusInElement) {
5672 var backdropjqLiteEl, backdropDomEl;
5673 var backdropScope = $rootScope.$new(true);
5674 var body = $document.find('body').eq(0);
5675 var html = $document.find('html').eq(0);
5676 var openedWindows = $$stackedMap.createNew();
5677 var $modalStack = {};
5679 function backdropIndex() {
5680 var topBackdropIndex = -1;
5681 var opened = openedWindows.keys();
5682 for (var i = 0; i < opened.length; i++) {
5683 if (openedWindows.get(opened[i]).value.backdrop) {
5684 topBackdropIndex = i;
5687 return topBackdropIndex;
5690 $rootScope.$watch(backdropIndex, function (newBackdropIndex) {
5691 backdropScope.index = newBackdropIndex;
5694 function removeModalWindow(modalInstance) {
5695 //background scroll fix
5696 html.removeAttr('style');
5697 body.removeAttr('style');
5698 body.removeClass('styled-by-modal');
5700 var modalWindow = openedWindows.get(modalInstance).value;
5701 trapFocusInElement(false, modalWindow.modalDomEl);
5703 //clean up the stack
5704 openedWindows.remove(modalInstance);
5706 //remove window DOM element
5707 modalWindow.modalDomEl.remove();
5709 //remove backdrop if no longer needed
5710 if (backdropDomEl && backdropIndex() === -1) {
5711 backdropDomEl.remove();
5712 backdropDomEl = undefined;
5716 modalWindow.modalScope.$destroy();
5719 $document.bind('keydown', function (evt) {
5722 if (evt.which === 27) {
5723 modal = openedWindows.top();
5724 if (modal && modal.value.keyboard) {
5725 $rootScope.$apply(function () {
5726 $modalStack.dismiss(modal.key);
5732 $modalStack.open = function (modalInstance, modal) {
5734 openedWindows.add(modalInstance, {
5735 deferred: modal.deferred,
5736 modalScope: modal.scope,
5737 backdrop: modal.backdrop,
5738 keyboard: modal.keyboard
5741 var angularDomEl = angular.element('<div b2b-modal-window></div>');
5742 angularDomEl.attr('window-class', modal.windowClass);
5743 angularDomEl.attr('size-class', modal.sizeClass);
5744 angularDomEl.attr('index', openedWindows.length() - 1);
5745 angularDomEl.html(modal.content);
5747 var modalDomEl = $compile(angularDomEl)(modal.scope);
5748 openedWindows.top().value.modalDomEl = modalDomEl;
5749 //background page scroll fix
5751 'overflow-y': 'hidden'
5754 'overflow-y': 'hidden',
5756 'height': window.innerHeight + 'px'
5758 body.addClass('styled-by-modal');
5759 body.append(modalDomEl);
5761 if (backdropIndex() >= 0 && !backdropDomEl) {
5762 backdropjqLiteEl = angular.element('<div b2b-modal-backdrop></div>');
5763 backdropDomEl = $compile(backdropjqLiteEl)(backdropScope);
5764 body.append(backdropDomEl);
5767 $timeout(function () {
5769 if (modal.scope.$$childHead.isNotifDialog) {
5770 angular.element(modalDomEl).find('button')[0].focus();
5772 angular.element(modalDomEl)[0].focus();
5774 trapFocusInElement(true, angular.element(modalDomEl).eq(0));
5778 $modalStack.close = function (modalInstance, result) {
5779 var modal = openedWindows.get(modalInstance);
5781 modal.value.deferred.resolve(result);
5782 removeModalWindow(modalInstance);
5786 $modalStack.dismiss = function (modalInstance, reason) {
5787 var modalWindow = openedWindows.get(modalInstance).value;
5789 modalWindow.deferred.reject(reason);
5790 removeModalWindow(modalInstance);
5794 $modalStack.getTop = function () {
5795 return openedWindows.top();
5801 .provider('$modal', function () {
5802 var $modalProvider = {
5804 backdrop: true, //can be also false or 'static'
5807 $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {
5810 function getTemplatePromise(options) {
5811 return options.template ? $q.when(options.template) :
5812 $http.get(options.templateUrl, {
5813 cache: $templateCache
5814 }).then(function (result) {
5819 function getResolvePromises(resolves) {
5820 var promisesArr = [];
5821 angular.forEach(resolves, function (value, key) {
5822 if (angular.isFunction(value) || angular.isArray(value)) {
5823 promisesArr.push($q.when($injector.invoke(value)));
5829 $modal.open = function (modalOptions) {
5831 var modalResultDeferred = $q.defer();
5832 var modalOpenedDeferred = $q.defer();
5833 //prepare an instance of a modal to be injected into controllers and returned to a caller
5834 var modalInstance = {
5835 result: modalResultDeferred.promise,
5836 opened: modalOpenedDeferred.promise,
5837 close: function (result) {
5838 $modalStack.close(modalInstance, result);
5840 dismiss: function (reason) {
5841 $modalStack.dismiss(modalInstance, reason);
5845 //merge and clean up options
5846 modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
5847 modalOptions.resolve = modalOptions.resolve || {};
5850 if (!modalOptions.template && !modalOptions.templateUrl) {
5851 throw new Error('One of template or templateUrl options is required.');
5854 var templateAndResolvePromise =
5855 $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
5858 templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
5860 var modalScope = (modalOptions.scope || $rootScope).$new();
5861 modalScope.$close = modalInstance.close;
5862 modalScope.$dismiss = modalInstance.dismiss;
5864 var ctrlInstance, ctrlLocals = {};
5865 var resolveIter = 1;
5868 if (modalOptions.controller) {
5869 ctrlLocals.$scope = modalScope;
5870 ctrlLocals.$modalInstance = modalInstance;
5871 angular.forEach(modalOptions.resolve, function (value, key) {
5872 ctrlLocals[key] = tplAndVars[resolveIter++];
5875 ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
5878 $modalStack.open(modalInstance, {
5880 deferred: modalResultDeferred,
5881 content: tplAndVars[0],
5882 backdrop: modalOptions.backdrop,
5883 keyboard: modalOptions.keyboard,
5884 windowClass: modalOptions.windowClass,
5885 sizeClass: modalOptions.sizeClass
5888 }, function resolveError(reason) {
5889 modalResultDeferred.reject(reason);
5892 templateAndResolvePromise.then(function () {
5893 modalOpenedDeferred.resolve(true);
5895 modalOpenedDeferred.reject(false);
5898 return modalInstance;
5905 return $modalProvider;
5908 .directive("b2bModal", ["$modal", "$log", '$scrollTo', function ($modal, $log, $scrollTo) {
5913 modalController: '@',
5919 link: function (scope, elm, attr) {
5920 elm.bind('click', function (ev) {
5921 var currentPosition = ev.pageY - ev.clientY;
5922 ev.preventDefault();
5923 if (angular.isDefined(elm.attr("href")) && elm.attr("href") !== "") {
5924 scope.b2bModal = elm.attr("href");
5927 templateUrl: scope.b2bModal,
5928 controller: scope.modalController,
5929 windowClass: scope.windowClass,
5930 sizeClass: scope.sizeClass
5931 }).result.then(function (value) {
5936 }, function (value) {
5947 .directive("utilityFilter", ["$modal", "$log", '$scrollTo', function ($modal, $log, $scrollTo) {
5954 templateUrl: 'b2bTemplate/modal/u-filter.html',
5955 link: function (scope, element, attribute, ctrl) {
5956 //controller to be passed to $modal service
5957 scope.options = angular.copy(scope.$parent.$eval(attribute.ngModel));
5958 scope.$parent.$watch(attribute.ngModel, function (newVal, oldVal) {
5959 if (newVal !== oldVal) {
5960 scope.options = newVal;
5963 var modalCtrl = function ($scope, options) {
5964 $scope.options = angular.copy(options);
5967 if (angular.isDefined(scope.utilityFilter)) {
5968 scope.templateUrl = scope.utilityFilter;
5970 scope.templateUrl = 'b2bTemplate/modal/u-filter-window.html';
5972 element.bind('click', function (ev) {
5973 var currentPosition = ev.pageY - ev.clientY;
5975 templateUrl: scope.templateUrl,
5976 controller: modalCtrl,
5978 options: function () {
5979 return scope.options;
5982 }).result.then(function (value) {
5983 ctrl.$setViewValue(value);
5985 $scrollTo(0, currentPosition, 0);
5988 $scrollTo(0, currentPosition, 0);
5996 * @name Forms.att:monthSelector
5999 * <file src="src/monthSelector/docs/readme.md" />
6002 * <div b2b-monthpicker ng-model="dt" min="minDate" max="maxDate" mode="monthpicker"></div>
6005 * <section id="code">
6006 <example module="b2b.att">
6007 <file src="src/monthSelector/docs/demo.html" />
6008 <file src="src/monthSelector/docs/demo.js" />
6013 angular.module('b2b.att.monthSelector', ['b2b.att.position', 'b2b.att.utilities'])
6015 .constant('b2bMonthpickerConfig', {
6016 dateFormat: 'MM/dd/yyyy',
6018 monthFormat: 'MMMM',
6020 dayHeaderFormat: 'EEEE',
6021 dayTitleFormat: 'MMMM yyyy',
6022 disableWeekend: false,
6023 disableSunday: false,
6025 onSelectClose: null,
6032 legendMessage: null,
6033 calendarDisabled: false,
6035 orientation: 'left',
6038 helperText: 'The date you selected is $date. Double tap to open calendar. Select a date to close the calendar.',
6039 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.',
6040 MonthpickerEvalAttributes: ['dateFormat', 'dayFormat', 'monthFormat', 'yearFormat', 'dayHeaderFormat', 'dayTitleFormat', 'disableWeekend', 'disableSunday', 'startingDay', 'collapseWait', 'orientation','mode','id'],
6041 MonthpickerWatchAttributes: ['min', 'max', 'due', 'from', 'legendIcon', 'legendMessage', 'ngDisabled'],
6042 MonthpickerFunctionAttributes: ['disableDates', 'onSelectClose']
6045 .factory('b2bMonthpickerService', ['b2bMonthpickerConfig', 'dateFilter', function (b2bMonthpickerConfig, dateFilter) {
6046 var setAttributes = function (attr, elem) {
6047 if (angular.isDefined(attr) && attr !== null && angular.isDefined(elem) && elem !== null) {
6048 var attributes = b2bMonthpickerConfig.MonthpickerEvalAttributes.concat(b2bMonthpickerConfig.MonthpickerWatchAttributes, b2bMonthpickerConfig.MonthpickerFunctionAttributes);
6049 for (var key in attr) {
6050 var val = attr[key];
6051 if (attributes.indexOf(key) !== -1 && angular.isDefined(val)) {
6052 elem.attr(key.toSnakeCase(), key);
6058 var bindScope = function (attr, scope) {
6059 if (angular.isDefined(attr) && attr !== null && angular.isDefined(scope) && scope !== null) {
6060 var evalFunction = function (key, val) {
6061 scope[key] = scope.$parent.$eval(val);
6064 var watchFunction = function (key, val) {
6065 scope.$parent.$watch(val, function (value) {
6068 scope.$watch(key, function (value) {
6069 scope.$parent[val] = value;
6073 var evalAttributes = b2bMonthpickerConfig.MonthpickerEvalAttributes;
6074 var watchAttributes = b2bMonthpickerConfig.MonthpickerWatchAttributes;
6075 for (var key in attr) {
6076 var val = attr[key];
6077 if (evalAttributes.indexOf(key) !== -1 && angular.isDefined(val)) {
6078 evalFunction(key, val);
6079 } else if (watchAttributes.indexOf(key) !== -1 && angular.isDefined(val)) {
6080 watchFunction(key, val);
6087 setAttributes: setAttributes,
6088 bindScope: bindScope
6092 .controller('b2bMonthpickerController', ['$scope', '$attrs', 'dateFilter', '$element', '$position', 'b2bMonthpickerConfig', function ($scope, $attrs, dateFilter, $element, $position, dtConfig) {
6094 date: getValue($attrs.dateFormat, dtConfig.dateFormat),
6095 day: getValue($attrs.dayFormat, dtConfig.dayFormat),
6096 month: getValue($attrs.monthFormat, dtConfig.monthFormat),
6097 year: getValue($attrs.yearFormat, dtConfig.yearFormat),
6098 dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat),
6099 dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat),
6100 disableWeekend: getValue($attrs.disableWeekend, dtConfig.disableWeekend),
6101 disableSunday: getValue($attrs.disableSunday, dtConfig.disableSunday),
6102 disableDates: getValue($attrs.disableDates, dtConfig.disableDates)
6104 startingDay = getValue($attrs.startingDay, dtConfig.startingDay);
6106 $scope.minDate = dtConfig.minDate ? $scope.resetTime(dtConfig.minDate) : null;
6107 $scope.maxDate = dtConfig.maxDate ? $scope.resetTime(dtConfig.maxDate) : null;
6108 $scope.dueDate = dtConfig.dueDate ? $scope.resetTime(dtConfig.dueDate) : null;
6109 $scope.fromDate = dtConfig.fromDate ? $scope.resetTime(dtConfig.fromDate) : null;
6110 $scope.legendIcon = dtConfig.legendIcon ? dtConfig.legendIcon : null;
6111 $scope.legendMessage = dtConfig.legendMessage ? dtConfig.legendMessage : null;
6112 $scope.ngDisabled = dtConfig.calendarDisabled ? dtConfig.calendarDisabled : null;
6113 $scope.collapseWait = getValue($attrs.collapseWait, dtConfig.collapseWait);
6114 $scope.orientation = getValue($attrs.orientation, dtConfig.orientation);
6115 $scope.onSelectClose = getValue($attrs.onSelectClose, dtConfig.onSelectClose);
6116 $scope.mode = getValue($attrs.mode, dtConfig.mode);
6118 $scope.inline = $attrs.inline === 'true' ? true : dtConfig.inline;
6120 function getValue(value, defaultValue) {
6121 return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue;
6124 function getDaysInMonth(year, month) {
6125 return new Date(year, month, 0).getDate();
6128 function getDates(startDate, n) {
6129 var dates = new Array(n);
6130 var current = startDate,
6133 dates[i++] = new Date(current);
6134 current.setDate(current.getDate() + 1);
6139 this.updatePosition = function (b2bMonthpickerPopupTemplate) {
6140 $scope.position = $position.offset($element);
6141 if($element.find('input').length > 0 ){
6142 $scope.position.top += $element.find('input').prop('offsetHeight');
6144 $scope.position.top += $element.find('a').prop('offsetHeight');
6147 if ($scope.orientation === 'right') {
6148 $scope.position.left -= (((b2bMonthpickerPopupTemplate && b2bMonthpickerPopupTemplate.prop('offsetWidth')) || 290) - $element.find('input').prop('offsetWidth'));
6152 function isSelected(dt) {
6153 if (dt && angular.isDate($scope.currentDate) && compare(dt, $scope.currentDate) === 0) {
6159 function isFromDate(dt) {
6160 if (dt && angular.isDate($scope.fromDate) && compare(dt, $scope.fromDate) === 0) {
6166 function isDateRange(dt) {
6167 if (dt && $scope.fromDate && angular.isDate($scope.currentDate) && (compare(dt, $scope.fromDate) >= 0) && (compare(dt, $scope.currentDate) <= 0)) {
6169 } else if (dt && $scope.fromDate && compare(dt, $scope.fromDate) === 0) {
6175 function isOld(date, currentMonthDate) {
6176 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())) {
6183 function isNew(date, currentMonthDate) {
6184 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())) {
6191 function isPastDue(dt) {
6192 if ($scope.dueDate) {
6193 return (dt > $scope.dueDate);
6198 function isDueDate(dt) {
6199 if ($scope.dueDate) {
6200 return (dt.getTime() === $scope.dueDate.getTime());
6205 var isDisabled = function (date, currentMonthDate) {
6206 if ($attrs.from && !angular.isDate($scope.fromDate)) {
6209 if (format.disableWeekend === true && (dateFilter(date, format.dayHeader) === "Saturday" || dateFilter(date, format.dayHeader) === "Sunday")) {
6212 if (format.disableSunday === true && (dateFilter(date, format.dayHeader) === "Sunday")) {
6215 if (isOld(date, currentMonthDate) || isNew(date, currentMonthDate)) {
6218 return (($scope.minDate && compare(date, $scope.minDate) < 0) || ($scope.maxDate && compare(date, $scope.maxDate) > 0) || (format.disableDates && format.disableDates({
6223 var isDisabledMonth = function (date, currentMonthDate) {
6224 if ($attrs.from && !angular.isDate($scope.fromDate)) {
6227 if (format.disableWeekend === true && (dateFilter(date, format.dayHeader) === "Saturday" || dateFilter(date, format.dayHeader) === "Sunday")) {
6230 if (format.disableSunday === true && (dateFilter(date, format.dayHeader) === "Sunday")) {
6233 return (($scope.minDate && compare(date, $scope.minDate) < 0) || ($scope.maxDate && compare(date, $scope.maxDate) > 0) || (format.disableDates && format.disableDates({
6238 var compare = function (date1, date2) {
6239 return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()));
6242 function isMinDateAvailable(startDate, endDate) {
6243 if (($scope.minDate && $scope.minDate.getTime() >= startDate.getTime()) && ($scope.minDate.getTime() <= endDate.getTime())) {
6244 $scope.disablePrev = true;
6245 $scope.visibilityPrev = "hidden";
6247 $scope.disablePrev = false;
6248 $scope.visibilityPrev = "visible";
6252 function isMaxDateAvailable(startDate, endDate) {
6253 if (($scope.maxDate && $scope.maxDate.getTime() >= startDate.getTime()) && ($scope.maxDate.getTime() <= endDate.getTime())) {
6254 $scope.disableNext = true;
6255 $scope.visibilityNext = "hidden";
6257 $scope.disableNext = false;
6258 $scope.visibilityNext = "visible";
6262 function isYearInRange(currentYear) {
6264 if ($scope.minDate && currentYear === $scope.minDate.getFullYear()) {
6265 $scope.disablePrev = true;
6266 $scope.visibilityPrev = "hidden";
6268 $scope.disablePrev = false;
6269 $scope.visibilityPrev = "visible";
6272 if ($scope.maxDate && currentYear === $scope.maxDate.getFullYear()) {
6273 $scope.disableNext = true;
6274 $scope.visibilityNext = "hidden";
6276 $scope.disableNext = false;
6277 $scope.visibilityNext = "visible";
6282 this.focusNextPrev = function(b2bMonthpickerPopupTemplate,init){
6284 if (!$scope.disablePrev){
6285 b2bMonthpickerPopupTemplate[0].querySelector('th.prev').focus();
6286 }else if (!$scope.disableNext){
6287 b2bMonthpickerPopupTemplate[0].querySelector('th.next').focus();
6289 b2bMonthpickerPopupTemplate[0].querySelector('th.b2b-monthSelector-label').focus();
6292 if ($scope.disableNext || $scope.disablePrev){
6293 b2bMonthpickerPopupTemplate[0].querySelector('th.b2b-monthSelector-label').focus();
6298 function getLabel(label) {
6301 pre: label.substr(0, 1).toUpperCase(),
6309 function makeDate(date, dayFormat, dayHeaderFormat, isSelected, isFromDate, isDateRange, isOld, isNew, isDisabled, dueDate, pastDue) {
6312 label: dateFilter(date, dayFormat),
6313 header: dateFilter(date, dayHeaderFormat),
6314 selected: !!isSelected,
6315 fromDate: !!isFromDate,
6316 dateRange: !!isDateRange,
6319 disabled: !!isDisabled,
6322 focusable: !((isDisabled && !(isSelected || isDateRange)) || (isOld || isNew))
6329 getVisibleDates: function (date) {
6330 var year = date.getFullYear(),
6331 month = date.getMonth(),
6332 firstDayOfMonth = new Date(year, month, 1),
6333 lastDayOfMonth = new Date(year, month + 1, 0);
6334 var difference = startingDay - firstDayOfMonth.getDay(),
6335 numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : -difference,
6336 firstDate = new Date(firstDayOfMonth),
6339 if (numDisplayedFromPreviousMonth > 0) {
6340 firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
6341 numDates += numDisplayedFromPreviousMonth; // Previous
6343 numDates += getDaysInMonth(year, month + 1); // Current
6344 numDates += (7 - numDates % 7) % 7; // Next
6346 var days = getDates(firstDate, numDates),
6347 labels = new Array(7);
6348 for (var i = 0; i < numDates; i++) {
6349 var dt = new Date(days[i]);
6350 days[i] = makeDate(dt,
6358 isDisabled(dt, date),
6362 for (var j = 0; j < 7; j++) {
6363 labels[j] = getLabel(dateFilter(days[j].date, format.dayHeader));
6365 isMinDateAvailable(firstDayOfMonth, lastDayOfMonth);
6366 isMaxDateAvailable(firstDayOfMonth, lastDayOfMonth);
6369 title: dateFilter(date, format.dayTitle),
6380 getVisibleDates: function(date) {
6383 year = date.getFullYear();
6384 for (var i = 0; i < 12; i++) {
6385 var dt = new Date(year,i,1);
6386 months[i] = makeDate(dt,
6394 isDisabledMonth(dt, date),
6398 isYearInRange(year);
6399 return {objects: months, title: dateFilter(date, format.year), labels: labels};
6407 .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) {
6415 templateUrl: function (elem, attr) {
6416 if (attr.inline === 'true') {
6417 return 'b2bTemplate/monthSelector/monthSelector-popup.html';
6418 }else if (attr.link === 'true') {
6419 return 'b2bTemplate/monthSelector/monthSelectorLink.html';
6421 return 'b2bTemplate/monthSelector/monthSelector.html';
6425 require: ['b2bMonthpickerPopup', 'ngModel', '?^b2bMonthpickerGroup'],
6426 controller: 'b2bMonthpickerController',
6427 link: function (scope, element, attrs, ctrls) {
6428 var MonthpickerCtrl = ctrls[0],
6430 b2bMonthpickerGroupCtrl = ctrls[2];
6431 var b2bMonthpickerPopupTemplate;
6434 $log.error("ng-model is required.");
6435 return; // do nothing if no ng-model
6438 // Configuration parameters
6439 var mode = scope.mode,
6441 scope.isOpen = false;
6445 scope.triggerInterval=undefined;
6448 if (b2bMonthpickerGroupCtrl) {
6449 b2bMonthpickerGroupCtrl.registerMonthpickerScope(scope);
6452 element.bind('keydown', function (ev) {
6455 ev.keyCode = ev.which;
6456 } else if (ev.charCode) {
6457 ev.keyCode = ev.charCode;
6460 if(ev.keyCode === keymap.KEY.ESC)
6462 scope.isOpen = false;
6463 toggleCalendar(scope.isOpen);
6468 element.find('button').bind('click', function () {
6472 element.find('a').bind('click', function () {
6477 element.find('input').bind('click', function () {
6481 var onClicked = function() {
6482 if (!scope.ngDisabled) {
6483 scope.isOpen = !scope.isOpen;
6484 toggleCalendar(scope.isOpen);
6485 MonthpickerCtrl.updatePosition(b2bMonthpickerPopupTemplate);
6490 var toggleCalendar = function (flag) {
6491 if (!scope.inline) {
6493 b2bMonthpickerPopupTemplate = angular.element($templateCache.get('b2bTemplate/monthSelector/monthSelector-popup.html'));
6494 b2bMonthpickerPopupTemplate.attr('b2b-trap-focus-inside-element', 'false');
6495 b2bMonthpickerPopupTemplate.attr('trigger', 'true');
6496 b2bMonthpickerPopupTemplate = $compile(b2bMonthpickerPopupTemplate)(scope);
6497 $document.find('body').append(b2bMonthpickerPopupTemplate);
6498 b2bMonthpickerPopupTemplate.bind('keydown', escPress);
6499 $timeout(function () {
6500 scope.getFocus = true;
6503 $timeout(function () {
6504 scope.getFocus = false;
6506 MonthpickerCtrl.focusNextPrev(b2bMonthpickerPopupTemplate,true);
6509 scope.triggerInterval = $interval(function () {
6510 //This value is updated to trigger init() function of directive on year change.
6511 scope.trigger=(scope.trigger === 0 ? 1 : 0);
6515 b2bMonthpickerPopupTemplate.unbind('keydown', escPress);
6516 if(scope.triggerInterval)
6518 $interval.cancel(scope.triggerInterval);
6519 scope.triggerInterval=undefined;
6521 b2bMonthpickerPopupTemplate.remove();
6522 if(element.find('button').length > 0){
6523 element.find('button')[0].focus();
6525 element.find('a')[0].focus();
6528 scope.getFocus = false;
6533 var outsideClick = function (e) {
6534 var isElement = $isElement(angular.element(e.target), element, $document);
6535 var isb2bMonthpickerPopupTemplate = $isElement(angular.element(e.target), b2bMonthpickerPopupTemplate, $document);
6536 if (!(isElement || isb2bMonthpickerPopupTemplate)) {
6537 scope.isOpen = false;
6538 toggleCalendar(scope.isOpen);
6543 var escPress = function (ev) {
6546 ev.keyCode = ev.which;
6547 } else if (ev.charCode) {
6548 ev.keyCode = ev.charCode;
6552 if (ev.keyCode === keymap.KEY.ESC) {
6553 scope.isOpen = false;
6554 toggleCalendar(scope.isOpen);
6555 ev.preventDefault();
6556 ev.stopPropagation();
6557 } else if (ev.keyCode === 33) {
6558 !scope.disablePrev && scope.move(-1);
6559 $timeout(function () {
6560 scope.getFocus = true;
6562 $timeout(function () {
6563 scope.getFocus = false;
6567 ev.preventDefault();
6568 ev.stopPropagation();
6569 } else if (ev.keyCode === 34) {
6570 !scope.disableNext && scope.move(1);
6571 $timeout(function () {
6572 scope.getFocus = true;
6574 $timeout(function () {
6575 scope.getFocus = false;
6579 ev.preventDefault();
6580 ev.stopPropagation();
6586 $documentBind.click('isOpen', outsideClick, scope);
6588 scope.$on('$destroy', function () {
6590 scope.isOpen = false;
6591 toggleCalendar(scope.isOpen);
6595 scope.resetTime = function (date) {
6596 if (typeof date === 'string') {
6597 date = date + 'T12:00:00';
6600 if (!isNaN(new Date(date))) {
6601 dt = new Date(date);
6602 if(scope.mode === 1){
6603 dt = new Date(dt.getFullYear(), dt.getMonth());
6605 dt = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate());
6610 return new Date(dt.getFullYear(), dt.getMonth(), dt.getDate());
6614 scope.$parent.$watch($parse(attrs.min), function (value) {
6615 scope.minDate = value ? scope.resetTime(value) : null;
6620 scope.$parent.$watch($parse(attrs.max), function (value) {
6621 scope.maxDate = value ? scope.resetTime(value) : null;
6626 scope.$parent.$watch($parse(attrs.due), function (value) {
6627 scope.dueDate = value ? scope.resetTime(value) : null;
6632 scope.$parent.$watch($parse(attrs.from), function (value) {
6633 scope.fromDate = value ? scope.resetTime(value) : null;
6638 if (attrs.legendIcon) {
6639 scope.$parent.$watch(attrs.legendIcon, function (value) {
6640 scope.legendIcon = value ? value : null;
6644 if (attrs.legendMessage) {
6645 scope.$parent.$watch(attrs.legendMessage, function (value) {
6646 scope.legendMessage = value ? value : null;
6650 if (attrs.ngDisabled) {
6651 scope.$parent.$watch(attrs.ngDisabled, function (value) {
6652 scope.ngDisabled = value ? value : null;
6657 // Split array into smaller arrays
6658 function split(arr, size) {
6660 while (arr.length > 0) {
6661 arrays.push(arr.splice(0, size));
6666 var moveMonth = function(selectedDate, direction) {
6667 var step = MonthpickerCtrl.modes[scope.mode].step;
6668 selectedDate.setDate(1);
6669 selectedDate.setMonth(selectedDate.getMonth() + direction * (step.months || 0));
6670 selectedDate.setFullYear(selectedDate.getFullYear() + direction * (step.years || 0));
6672 return selectedDate;
6675 function refill(date) {
6676 if (angular.isDate(date) && !isNaN(date)) {
6677 selected = new Date(date);
6680 selected = new Date();
6685 var selectedCalendar;
6686 if(scope.mode === 1){
6687 if(!angular.isDate(selected))
6689 selected = new Date();
6691 selectedCalendar = moveMonth(angular.copy(selected), -1);
6693 selectedCalendar = angular.copy(selected);
6696 var currentMode = MonthpickerCtrl.modes[mode],
6697 data = currentMode.getVisibleDates(selected);
6699 scope.rows = split(data.objects, currentMode.split);
6702 var startFlag=false;
6703 var firstSelected = false;
6704 for(var i=0; i<scope.rows.length; i++)
6706 for(var j=0; j<scope.rows[i].length; j++)
6708 if(!scope.rows[i][j].disabled && !firstSelected)
6711 var firstDay = scope.rows[i][j];
6714 if(scope.rows[i][j].selected)
6725 if(!flag && firstSelected)
6727 firstDay.firstFocus=true;
6730 scope.labels = data.labels || [];
6731 scope.title = data.title;
6735 scope.select = function (date,$event) {
6736 var dt = new Date(date.getFullYear(), date.getMonth(), date.getDate());
6737 scope.currentDate = dt;
6738 if (!scope.onSelectClose || (scope.onSelectClose && scope.onSelectClose({
6741 if (angular.isNumber(scope.collapseWait)) {
6742 $timeout(function () {
6743 scope.isOpen = false;
6744 toggleCalendar(scope.isOpen);
6745 }, scope.collapseWait);
6747 scope.isOpen = false;
6748 toggleCalendar(scope.isOpen);
6753 scope.move = function (direction,$event) {
6754 var step = MonthpickerCtrl.modes[mode].step;
6755 selected.setDate(1);
6756 selected.setMonth(selected.getMonth() + direction * (step.months || 0));
6757 selected.setFullYear(selected.getFullYear() + direction * (step.years || 0));
6759 scope.getFocus = true;
6760 $timeout(function () {
6761 if (attrs.inline === 'true') {
6762 MonthpickerCtrl.focusNextPrev(element,false);
6764 MonthpickerCtrl.focusNextPrev(b2bMonthpickerPopupTemplate,false);
6767 $event.preventDefault();
6768 $event.stopPropagation();
6771 scope.$watch('currentDate', function (value) {
6772 if (angular.isDefined(value) && value !== null) {
6777 ngModel.$setViewValue(value);
6780 ngModel.$render = function () {
6781 scope.currentDate = ngModel.$viewValue;
6784 var stringToDate = function (value) {
6785 if (!isNaN(new Date(value))) {
6786 value = new Date(value);
6790 ngModel.$formatters.unshift(stringToDate);
6795 .directive('b2bMonthpicker', ['$compile', '$log', 'b2bMonthpickerConfig', 'b2bMonthpickerService', function ($compile, $log, b2bMonthpickerConfig, b2bMonthpickerService) {
6803 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
6804 var dateFormatString = angular.isDefined(attr.dateFormat) ? scope.$parent.$eval(attr.dateFormat) : b2bMonthpickerConfig.dateFormat;
6805 var helperText = angular.isDefined(attr.helperText) ? scope.$parent.$eval(attr.helperText) : b2bMonthpickerConfig.helperText;
6806 helperText = helperText.replace('$date', '{{dt | date : \'' + dateFormatString + '\'}}');
6808 var descriptionText = angular.isDefined(attr.descriptionText) ? scope.$parent.$eval(attr.descriptionText) : b2bMonthpickerConfig.descriptionText;
6812 if (elem.prop('nodeName') !== 'INPUT' && elem.prop('nodeName') !== 'A') {
6816 var selectedDateMessage = "";
6818 if (elem.prop('nodeName') !== 'A'){
6819 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>';
6820 elem.attr('tabindex', '-1');
6821 elem.attr('aria-hidden', 'true');
6822 elem.attr('readonly', 'true');
6824 selectedDateMessage = ''
6825 elem.attr('aria-label', helperText);
6828 var descriptionTextSpan = '<span class="offscreen-text" id="monthpicker-description'+scope.$id+'">'+descriptionText+'</span>';
6829 elem.removeAttr('b2b-Monthpicker');
6830 elem.removeAttr('ng-model');
6831 elem.removeAttr('ng-disabled');
6832 elem.addClass('Monthpicker-input');
6833 elem.attr('ng-model', 'dt');
6834 elem.attr('aria-describedby', 'monthpicker-description'+scope.$id);
6838 elem.attr('ng-disabled', 'ngDisabled');
6839 elem.attr('b2b-format-date', dateFormatString);
6841 var wrapperElement = angular.element('<div></div>');
6842 wrapperElement.attr('b2b-Monthpicker-popup', '');
6843 wrapperElement.attr('ng-model', 'dt');
6845 wrapperElement.attr('inline', inline);
6847 if (elem.prop('nodeName') === 'A'){
6848 wrapperElement.attr('link', true);
6850 b2bMonthpickerService.setAttributes(attr, wrapperElement);
6851 b2bMonthpickerService.bindScope(attr, scope);
6853 wrapperElement.html('');
6854 wrapperElement.append(selectedDateMessage);
6855 wrapperElement.append('');
6856 wrapperElement.append(descriptionTextSpan);
6857 wrapperElement.append('');
6858 wrapperElement.append(elem.prop('outerHTML'));
6860 var elm = wrapperElement.prop('outerHTML');
6861 elm = $compile(elm)(scope);
6862 elem.replaceWith(elm);
6864 link: function (scope, elem, attr, ctrl) {
6866 $log.error("ng-model is required.");
6867 return; // do nothing if no ng-model
6870 scope.$watch('dt', function (value) {
6871 ctrl.$setViewValue(value);
6873 ctrl.$render = function () {
6874 scope.dt = ctrl.$viewValue;
6880 .directive('b2bMonthpickerGroup', [function () {
6883 controller: ['$scope', '$element', '$attrs', function (scope, elem, attr) {
6884 this.$$headers = [];
6885 this.$$footers = [];
6886 this.registerMonthpickerScope = function (MonthpickerScope) {
6887 MonthpickerScope.headers = this.$$headers;
6888 MonthpickerScope.footers = this.$$footers;
6891 link: function (scope, elem, attr, ctrl) {}
6895 .directive('b2bFormatDate', ['dateFilter', function (dateFilter) {
6899 link: function (scope, elem, attr, ctrl) {
6900 var b2bFormatDate = "";
6901 attr.$observe('b2bFormatDate', function (value) {
6902 b2bFormatDate = value;
6904 var dateToString = function (value) {
6905 if (!isNaN(new Date(value))) {
6906 return dateFilter(new Date(value), b2bFormatDate);
6910 ctrl.$formatters.unshift(dateToString);
6915 .directive('b2bMonthpickerHeader', [function () {
6918 require: '^b2bMonthpickerGroup',
6922 compile: function (elem, attr, transclude) {
6923 return function link(scope, elem, attr, ctrl) {
6925 ctrl.$$headers.push(transclude(scope, function () {}));
6933 .directive('b2bMonthpickerFooter', [function () {
6936 require: '^b2bMonthpickerGroup',
6940 compile: function (elem, attr, transclude) {
6941 return function link(scope, elem, attr, ctrl) {
6943 ctrl.$$footers.push(transclude(scope, function () {}));
6952 * @name Navigation.att:multiLevelNavigation
6955 * <file src="src/multiLevelNavigation/docs/readme.md" />
6958 * <div class="b2b-ml-nav">
6960 * <li aria-label="{{child.name}}" tabindex="-1" b2b-ml-nav="{{child.type}}" role="treeitem" ng-repeat="child in treeStructure">
6961 * <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>
6962 * <!-- Below UL tag is RECURSIVE to generate n-childs -->
6963 * <ul role="group" ng-if="child.child">
6964 * <li aria-label="{{child.name}}" b2b-ml-nav="{{child.type}}" tabindex="-1" role="treeitem" ng-repeat="child in child.child">
6965 * <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>
6966 * <!-- RECURSIVE UL tag goes here -->
6974 * <section id="code">
6975 <example module="b2b.att">
6976 <file src="src/multiLevelNavigation/docs/demo.html" />
6977 <file src="src/multiLevelNavigation/docs/demo.js" />
6982 angular.module('b2b.att.multiLevelNavigation', ['b2b.att.utilities'])
6983 //directive b2bMlNav Test coverage 100% on 5/13
6984 .directive('b2bMlNav', ['keymap', function (keymap) {
6987 link: function (scope, element) {
6988 var rootE, parentE, upE, downE, lastE, homeE, endE;
6989 //default root tree element tabindex set zero
6990 if (element.parent().parent().hasClass('b2b-ml-nav') && (element[0].previousElementSibling === null)) {
6991 element.attr('tabindex', 0);
6993 //check root via class
6994 var isRoot = function (elem) {
6995 if (elem.parent().parent().eq(0).hasClass('b2b-ml-nav')) {
7002 //for any expandable tree item on click
7003 var toggleState = function (e) {
7004 if (angular.element(e.target).attr("b2b-ml-nav") !== "endNode") {
7005 var eLink = element.find('a').eq(0);
7006 if (eLink.hasClass('active')) {
7007 eLink.removeClass('active');
7008 eLink.parent().attr("aria-expanded", "false");
7009 eLink.find('i').eq(0).removeClass('icon-primary-expanded');
7010 eLink.find('i').eq(0).addClass('icon-primary-collapsed');
7012 eLink.addClass('active');
7013 eLink.parent().attr("aria-expanded", "true");
7014 eLink.find('i').eq(0).removeClass('icon-primary-collapsed');
7015 eLink.find('i').eq(0).addClass('icon-primary-expanded');
7019 //function finds the main root-item from particular tree-group
7020 var findRoot = function (elem) {
7025 if (elem.attr("b2b-ml-nav") === "middleNode" || elem.attr("b2b-ml-nav") === "endNode") {
7026 parentE = elem.parent().parent();
7030 if (parentE.attr("b2b-ml-nav") === "rootNode") {
7036 //finds the last visible node of the previous tree-group
7037 var findPreActive = function (elem) {
7038 if (!(elem.hasClass("active"))) {
7041 var childElems = angular.element(elem[0].nextElementSibling.children);
7042 lastE = angular.element(childElems[childElems.length - 1]);
7043 if (lastE.attr("b2b-ml-nav") === "middleNode" && lastE.find('a').eq(0).hasClass('active')) {
7044 findPreActive(lastE.find('a').eq(0));
7049 //find above visible link
7050 var findUp = function (elem) {
7051 if (elem[0].previousElementSibling !== null) {
7052 upE = angular.element(elem[0].previousElementSibling);
7054 upE = elem.parent().parent();
7056 if (isRoot(elem) || (upE.attr('b2b-ml-nav') === "middleNode" && upE[0] !== elem.parent().parent()[0])) {
7057 findPreActive(upE.find('a').eq(0));
7060 //find below visible link
7061 var findDown = function (elem) {
7062 if (elem.hasClass('active')) {
7063 downE = elem.next().find('li').eq(0);
7065 if (elem.parent().next().length !== 0) {
7066 downE = elem.parent().next().eq(0);
7068 if (elem.parent().parent().parent().next().length !== 0) {
7069 downE = elem.parent().parent().parent().next().eq(0);
7072 downE = elem.parent().eq(0);
7076 //finds last root-group element of the tree
7077 var findEnd = function (elem) {
7079 endE = angular.element(rootE.parent()[0].children[rootE.parent()[0].children.length - 1]);
7081 //finds first root element of tree
7082 var findHome = function (elem) {
7084 homeE = angular.element(rootE.parent()[0].children[0]);
7086 element.bind('click', function (e) {
7087 if(element.attr("b2b-ml-nav") !== "endNode") {
7090 e.stopPropagation();
7092 element.bind('focus', function (e) {
7093 if(element.attr("b2b-ml-nav") !== "endNode") {
7094 if(element.find('a').eq(0).hasClass('active')) {
7095 element.attr("aria-expanded", true);
7098 element.attr("aria-expanded", false);
7103 //Keyboard functionality approach:
7105 //set set tabindex -1 on the current focus element
7106 //find the next element to be focussed, set tabindex 0 and throw focus
7107 element.bind('keydown', function (evt) {
7108 switch (evt.keyCode) {
7109 case keymap.KEY.ENTER:
7110 case keymap.KEY.SPACE:
7111 element.triggerHandler('click');
7112 evt.stopPropagation();
7113 evt.preventDefault();
7115 case keymap.KEY.END:
7116 evt.preventDefault();
7117 element.attr('tabindex', -1);
7119 endE.eq(0).attr('tabindex', 0);
7121 evt.stopPropagation();
7123 case keymap.KEY.HOME:
7124 evt.preventDefault();
7125 element.attr('tabindex', -1);
7127 homeE.eq(0).attr('tabindex', 0);
7129 evt.stopPropagation();
7131 case keymap.KEY.LEFT:
7132 evt.preventDefault();
7133 if (!isRoot(element)) {
7134 element.attr('tabindex', -1);
7135 parentE = element.parent().parent();
7136 parentE.eq(0).attr('tabindex', 0);
7138 parentE.eq(0).triggerHandler('click');
7140 if (element.find('a').eq(0).hasClass('active')) {
7141 element.triggerHandler('click');
7144 evt.stopPropagation();
7147 evt.preventDefault();
7148 if (!(isRoot(element) && element[0].previousElementSibling === null)) {
7149 element.attr('tabindex', -1);
7151 upE.eq(0).attr('tabindex', 0);
7154 evt.stopPropagation();
7156 case keymap.KEY.RIGHT:
7157 evt.preventDefault();
7158 if (element.attr("b2b-ml-nav") !== "endNode") {
7159 if (!element.find('a').eq(0).hasClass('active')) {
7160 element.triggerHandler('click');
7162 element.attr('tabindex', -1);
7163 findDown(element.find('a').eq(0));
7164 downE.eq(0).attr('tabindex', 0);
7167 evt.stopPropagation();
7169 case keymap.KEY.DOWN:
7170 evt.preventDefault();
7171 element.attr('tabindex', -1);
7172 if (!(element.attr("b2b-ml-nav") === "middleNode" && element.find('a').eq(0).hasClass('active')) && (element.next().length === 0)) {
7173 if(element.parent().parent().attr("b2b-ml-nav") !== "rootNode" && element.parent().parent()[0].nextElementSibling !== null)
7175 findDown(element.find('a').eq(0));
7176 downE.eq(0).attr('tabindex', 0);
7178 evt.stopPropagation();
7182 if (!(rootE.next().length === 0)) {
7183 rootE.next().eq(0).attr('tabindex', 0);
7184 rootE.next()[0].focus();
7186 rootE.eq(0).attr('tabindex', 0);
7189 evt.stopPropagation();
7192 findDown(element.find('a').eq(0));
7193 downE.eq(0).attr('tabindex', 0);
7195 evt.stopPropagation();
7206 * @name Tabs, tables & accordions.att:multipurposeExpander
7209 * <file src="src/multipurposeExpander/docs/readme.md" />
7212 * <!--With Close Other -->
7213 * <b2b-expander-group close-others="true">
7214 * <b2b-expanders class="mpc-expanders" is-open="testmpc">
7215 * <b2b-expander-heading ng-class=" { 'b2b-toggle-header-active': !testmpc, 'b2b-toggle-header-inactive': testmpc } ">Heading content goes here</b2b-expander-heading>
7216 * <b2b-expander-body>
7217 <p>body content goes here</p>
7218 </b2b-expander-body>
7220 * </b2b-expander-group>
7222 * <!-- Without Close Other -->
7223 * <b2b-expanders class="mpc-expanders" is-open="testmpc2">
7224 * <b2b-expander-heading ng-class=" { 'b2b-toggle-header-active': !testmpc2, 'b2b-toggle-header-inactive': testmpc2 } ">Heading content goes here</b2b-expander-heading>
7225 * <b2b-expander-body>
7226 <p>body content goes here</p>
7227 </b2b-expander-body>
7231 * <section id="code">
7232 <example module="b2b.att.multipurposeExpander">
7233 <file src="src/multipurposeExpander/docs/demo.html" />
7234 <file src="src/multipurposeExpander/docs/demo.js" />
7240 angular.module('b2b.att.multipurposeExpander', ['b2b.att', 'b2b.att.collapse'])
7241 .directive('b2bExpanderGroup', function () {
7245 template: "<ng-transclude></ng-transclude>",
7246 controller:['$scope','$attrs', function($scope,$attrs){
7249 this.scope = $scope;
7251 this.addGroup = function (groupScope) {
7253 groupScope.index = this.groups.length;
7254 this.groups.push(groupScope);
7255 if(this.groups.length > 0){
7258 groupScope.$on('$destroy', function () {
7259 that.removeGroup(groupScope);
7263 this.closeOthers = function (openGroup) {
7264 var closeOthers = angular.isDefined($attrs.closeOthers);
7265 if (closeOthers && !$scope.forceExpand) {
7266 angular.forEach(this.groups, function (group) {
7267 if (group !== openGroup) {
7268 group.isOpen = false;
7272 if (this.groups.indexOf(openGroup) === (this.groups.length - 1) && $scope.forceExpand) {
7273 $scope.forceExpand = false;
7276 this.removeGroup = function (group) {
7277 var index = this.groups.indexOf(group);
7279 this.groups.splice(this.groups.indexOf(group), 1);
7287 .directive('b2bExpanders', function () {
7291 require:['b2bExpanders','?^b2bExpanderGroup'],
7293 scope:{isOpen:'=?'},
7294 template: "<div ng-transclude></div>",
7295 controller: ['$scope', function ($scope){
7296 var bodyScope = null;
7297 var expanderScope = null;
7298 this.isOpened = function(){
7307 this.setScope = function (scope) {
7309 bodyScope.isOpen = $scope.isOpen;
7311 this.setExpanderScope = function (scope) {
7312 expanderScope = scope;
7314 this.toggle = function () {
7315 $scope.isOpen = bodyScope.isOpen = !bodyScope.isOpen;
7316 return bodyScope.isOpen;
7319 this.watchToggle = function(io){
7320 bodyScope.isOpen = io;
7321 expanderScope.updateIcons(io);
7324 link: function (scope, elem, attr, myCtrl)
7326 //scope.isOpen = false;
7328 myCtrl[1].addGroup(scope);
7330 scope.$watch('isOpen', function(val){
7331 myCtrl[0].watchToggle(scope.isOpen);
7332 if(val && myCtrl[1]){
7333 myCtrl[1].closeOthers(scope);
7340 .directive('b2bExpanderHeading', function () {
7342 require: "^b2bExpanders",
7347 template: "<div style='padding:10px 10px 10px 0 !important' ng-transclude></div>"
7351 .directive('b2bExpanderBody', function () {
7354 require: "^b2bExpanders",
7358 template: "<div b2b-collapse='!isOpen' ><div ng-transclude></div></div>",
7359 link: function (scope, elem, attr, myCtrl) {
7360 scope.isOpen = false;
7361 myCtrl.setScope(scope);
7366 .directive('b2bExpanderToggle', function () {
7369 require: "^b2bExpanders",
7375 link: function (scope, element, attr, myCtrl)
7377 myCtrl.setExpanderScope(scope);
7378 var isOpen = myCtrl.isOpened();
7380 scope.setIcon = function () {
7381 element.attr("role", "tab");
7383 if (scope.expandIcon && scope.collapseIcon)
7386 element.removeClass(scope.expandIcon);
7387 element.addClass(scope.collapseIcon);
7389 element.attr("aria-expanded", "true");
7392 element.removeClass(scope.collapseIcon);
7393 element.addClass(scope.expandIcon);
7395 element.attr("aria-expanded", "false");
7400 element.bind('click', function (){
7403 scope.updateIcons = function(nStat){
7407 scope.toggleit = function (){
7408 isOpen = myCtrl.toggle();
7418 * @name Messages, modals & alerts.att:notesMessagesAndErrors
7421 * <file src="src/notesMessagesAndErrors/docs/readme.md" />
7427 * <section id="code">
7428 <example module="b2b.att">
7429 <file src="src/notesMessagesAndErrors/docs/demo.html" />
7430 <file src="src/notesMessagesAndErrors/docs/demo.js" />
7435 angular.module('b2b.att.notesMessagesAndErrors', []);
7438 * @name Template.att:Notification Card
7441 * <file src="src/notificationCardTemplate/docs/readme.md" />
7444 * <section id="code">
7445 <b>HTML + AngularJS</b>
7446 <example module="b2b.att">
7447 <file src="src/notificationCardTemplate/docs/demo.html" />
7448 <file src="src/notificationCardTemplate/docs/demo.js" />
7453 angular.module('b2b.att.notificationCardTemplate', [])
7457 * @name Template.att:Order Confirmation Template
7460 * <file src="src/orderConfirmationTemplate/docs/readme.md" />
7463 * <section id="code">
7464 <b>HTML + AngularJS</b>
7465 <example module="b2b.att">
7466 <file src="src/orderConfirmationTemplate/docs/demo.html" />
7467 <file src="src/orderConfirmationTemplate/docs/demo.js" />
7472 angular.module('b2b.att.orderConfirmationTemplate', []);
7476 * @name Navigation.att:pagination
7479 * <file src="src/pagination/docs/readme.md" />
7482 * <div b2b-pagination="" input-id="goto-page-2" total-pages="totalPages1" current-page="currentPage1" click-handler="customHandler" show-input="showInput"></div>
7485 * <section id="code">
7486 <example module="b2b.att">
7487 <file src="src/pagination/docs/demo.html" />
7488 <file src="src/pagination/docs/demo.js" />
7493 angular.module('b2b.att.pagination', ['b2b.att.utilities', 'ngTouch'])
7494 .directive('b2bPagination', ['b2bUserAgent', 'keymap', '$window', '$timeout', function (b2bUserAgent, keymap, $window, $timeout) {
7505 templateUrl: 'b2bTemplate/pagination/b2b-pagination.html',
7506 link: function (scope, elem) {
7507 scope.isMobile = b2bUserAgent.isMobile();
7508 scope.notMobile = b2bUserAgent.notMobile();
7511 scope.$watch('totalPages', function (value) {
7512 if (angular.isDefined(value) && value !== null) {
7515 scope.totalPages = 1;
7519 for (var i = 1; i <= value; i++) {
7520 scope.pages.push(i);
7522 } else if (value > 10) {
7523 var midVal = Math.ceil(value / 2);
7524 scope.pages = [midVal - 2, midVal - 1, midVal, midVal + 1, midVal + 2];
7526 if(scope.currentPage === undefined || scope.currentPage === 1)
7528 currentPageChanged(1);
7532 scope.$watch('currentPage', function (value) {
7533 currentPageChanged(value);
7534 callbackHandler(value);
7536 var callbackHandler = function (num) {
7537 if (angular.isFunction(scope.clickHandler)) {
7538 scope.clickHandler(num);
7542 function currentPageChanged(value) {
7543 if (angular.isDefined(value) && value !== null) {
7544 if (!value || value < 1) {
7547 if (value > scope.totalPages) {
7548 value = scope.totalPages;
7550 if (scope.currentPage !== value) {
7551 scope.currentPage = value;
7552 callbackHandler(scope.currentPage);
7554 if (scope.totalPages > 10) {
7555 var val = parseInt(value);
7557 scope.pages = [1, 2, 3, 4, 5, 6, 7, 8];
7558 } else if (val > 6 && val <= scope.totalPages - 5) {
7559 scope.pages = [val - 1, val, val + 1];
7560 } else if (val >= scope.totalPages - 5) {
7561 scope.pages = [scope.totalPages - 7, scope.totalPages - 6, scope.totalPages - 5, scope.totalPages - 4, scope.totalPages - 3, scope.totalPages - 2, scope.totalPages - 1, scope.totalPages];
7564 if (scope.isMobile) {
7565 var inWidth = $window.innerWidth;
7567 if (inWidth <= 400) {
7569 } else if (inWidth > 400 && inWidth < 500) {
7571 } else if (inWidth >= 500 && inWidth < 600) {
7573 } else if (inWidth >= 600 && inWidth < 700) {
7575 } else if (inWidth >= 700 && inWidth < 800) {
7579 var val = parseInt(value);
7581 scope.meanVal = Math.floor(viewLimit / 2);
7582 var lowerLimit = (val - scope.meanVal) < 1 ? 1 : val - scope.meanVal;
7583 var upperLimit = (lowerLimit + viewLimit - 1) > scope.totalPages ? scope.totalPages : lowerLimit + viewLimit - 1;
7585 for (var i = lowerLimit; i <= upperLimit; i++) {
7586 scope.pages.push(i);
7591 scope.gotoKeyClick = function (keyEvent) {
7592 if (keyEvent.which === keymap.KEY.ENTER) {
7593 scope.gotoBtnClick()
7596 scope.gotoBtnClick = function () {
7597 currentPageChanged(parseInt(scope.gotoPage));
7598 callbackHandler(scope.currentPage);
7599 var qResult = elem[0].querySelector('button');
7600 angular.element(qResult).attr('disabled','true');
7601 $timeout(function(){
7602 elem[0].querySelector('.b2b-pager__item--active').focus();
7604 scope.gotoPage = null;
7606 scope.onfocusIn = function(evt)
7608 var qResult = elem[0].querySelector('button');
7609 angular.element(qResult).removeAttr('disabled');
7611 scope.onfocusOut = function(evt)
7613 if(evt.target.value === "")
7615 var qResult = elem[0].querySelector('button');
7616 angular.element(qResult).attr('disabled','true');
7619 scope.next = function (event) {
7620 if (event != undefined) {
7621 event.preventDefault();
7623 if (scope.currentPage < scope.totalPages) {
7624 scope.currentPage += 1;
7625 callbackHandler(scope.currentPage);
7628 scope.prev = function (event) {
7629 if (event != undefined) {
7630 event.preventDefault();
7632 if (scope.currentPage > 1) {
7633 scope.currentPage -= 1;
7634 callbackHandler(scope.currentPage);
7637 scope.selectPage = function (value, event) {
7638 event.preventDefault();
7639 scope.currentPage = value;
7640 scope.focusedPage = value;
7641 callbackHandler(scope.currentPage);
7643 scope.checkSelectedPage = function (value) {
7644 if (scope.currentPage === value) {
7649 scope.isFocused = function (page) {
7650 return scope.focusedPage === page;
7657 * @name Navigation.att:paneSelector
7660 * <file src="src/paneSelector/docs/readme.md" />
7663 * Please refer demo.html tab in Example section below.
7667 <b>HTML + AngularJS</b>
7668 <example module="b2b.att">
7669 <file src="src/paneSelector/docs/demo.html" />
7670 <file src="src/paneSelector/docs/demo.js" />
7675 angular.module('b2b.att.paneSelector', ['b2b.att.tabs', 'b2b.att.utilities'])
7677 .filter('paneSelectorSelectedItemsFilter', [function () {
7678 return function (listOfItemsArray) {
7680 if (!listOfItemsArray) {
7681 listOfItemsArray = [];
7684 var returnArray = [];
7686 for (var i = 0; i < listOfItemsArray.length; i++) {
7687 if (listOfItemsArray[i].isSelected) {
7688 returnArray.push(listOfItemsArray[i]);
7696 .filter('paneSelectorFetchChildItemsFilter', [function () {
7697 return function (listOfItemsArray) {
7699 if (!listOfItemsArray) {
7700 listOfItemsArray = [];
7703 var returnArray = [];
7705 for (var i = 0; i < listOfItemsArray.length; i++) {
7706 for (var j = 0; j < listOfItemsArray[i].childItems.length; j++) {
7707 returnArray.push(listOfItemsArray[i].childItems[j]);
7715 .directive('b2bPaneSelector', [function () {
7719 templateUrl: 'b2bTemplate/paneSelector/paneSelector.html',
7725 .directive('b2bPaneSelectorPane', [ function () {
7729 templateUrl: 'b2bTemplate/paneSelector/paneSelectorPane.html',
7735 .directive('b2bTabVertical', ['$timeout', 'keymap', function ($timeout, keymap) {
7739 link: function (scope, element, attr, b2bTabCtrl) {
7745 // retreive the isolateScope
7746 var iScope = angular.element(element).isolateScope();
7748 $timeout(function () {
7749 angular.element(element[0].querySelector('a')).unbind('keydown');
7750 angular.element(element[0].querySelector('a')).bind('keydown', function (evt) {
7752 if (!(evt.keyCode)) {
7753 evt.keyCode = evt.which;
7756 switch (evt.keyCode) {
7757 case keymap.KEY.DOWN:
7758 evt.preventDefault();
7763 evt.preventDefault();
7764 iScope.previousKey();
7776 * @name Forms.att:phoneNumberInput
7779 * <file src="src/phoneNumberInput/docs/readme.md" />
7782 <form name="userForm1">
7783 <div class="form-row" ng-class="{'error':!userForm1.text.$valid && userForm1.text.$dirty}">
7784 <label>Phone Mask<span style="color:red">*</span>: (npa) nxx-line Model Value: {{mask.text}}</label>
7786 <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 />
7787 <div ng-messages="userForm1.text.$error" class="error-msg" aria-live="polite" aria-atomic="true">
7788 <span ng-message="required" role="alert">This field is mandatory!</span>
7789 <span ng-message="invalidPhoneNumber" role="alert">Please enter valid phone number!</span>
7790 <span ng-message="mask" role="alert">Please enter valid phone number!</span>
7797 * <section id="code">
7798 <example module="b2b.att">
7799 <file src="src/phoneNumberInput/docs/demo.html" />
7800 <file src="src/phoneNumberInput/docs/demo.js" />
7805 angular.module('b2b.att.phoneNumberInput', ['ngMessages', 'b2b.att.utilities'])
7806 .constant("CoreFormsUiConfig", {
7807 phoneMask: '(___) ___-____',
7808 phoneMaskDot: '___.___.____',
7809 phoneMaskHyphen: '___-___-____'
7811 .directive('b2bPhoneMask', ['$parse', 'CoreFormsUiConfig', 'keymap', function ($parse, CoreFormsUiConfig, keymap) {
7817 link: function (scope, iElement, iAttrs, ctrl) {
7818 var navigatorAgent = navigator.userAgent.toLowerCase(),
7819 isAndroid = navigatorAgent.indexOf("android") > -1,
7820 oldIE = navigatorAgent.indexOf('msie 8.0') !== -1;
7822 var validPhoneNumber = false;
7823 var currentKey = '';
7825 mask = "__________";
7827 switch (iAttrs.b2bPhoneMask) {
7829 mask = CoreFormsUiConfig.phoneMask;
7831 case "phoneMaskDot":
7832 mask = CoreFormsUiConfig.phoneMaskDot;
7834 case "phoneMaskHyphen":
7835 mask = CoreFormsUiConfig.phoneMaskHyphen;
7838 mask = CoreFormsUiConfig.phoneMask;
7841 iElement.attr("maxlength", mask.length);
7842 var checkValidity = function (unmaskedValue, rawValue) {
7844 if (angular.isUndefined(rawValue) || rawValue === '') {
7846 } else if (unmaskedValue) {
7847 valid = (unmaskedValue.length === 10);
7849 ctrl.$setValidity('invalidPhoneNumber', validPhoneNumber);
7850 ctrl.$setValidity('mask', valid);
7853 var handleKeyup = function (evt) {
7855 if (evt && evt.keyCode === keymap.KEY.SHIFT) {
7859 var index, formattedNumber;
7860 if (ctrl.$modelValue) {
7861 formattedNumber = ctrl.$modelValue;
7863 formattedNumber = iElement.val();
7865 if (!formattedNumber.length && currentKey === '') {
7868 var maskLength, inputNumbers, maskArray, tempArray, maskArrayLength;
7870 maskArray = mask.split("");
7871 maskArrayLength = maskArray.length;
7872 maskLength = formattedNumber.substring(0, mask.length);
7873 inputNumbers = formattedNumber.replace(/[^0-9]/g, "").split("");
7874 for (index = 0; index < maskArrayLength; index++) {
7875 tempArray.push(maskArray[index] === "_" ? inputNumbers.shift() : maskArray[index]);
7876 if (inputNumbers.length === 0) {
7880 formattedNumber = tempArray.join("");
7881 if (formattedNumber === '(') {
7882 formattedNumber = '';
7885 if ( (angular.isDefined(evt) && evt.which) && currentKey !== '') {
7886 if (maskArray[0] === currentKey && formattedNumber === '') {
7887 formattedNumber = '(';
7888 } else if (evt.which === keymap.KEY.SPACE && currentKey === ' ') {
7889 formattedNumber = formattedNumber + ') ';
7890 } else if (maskArray[0] === currentKey && formattedNumber === '') {
7891 formattedNumber = formattedNumber + currentKey;
7892 } else if (maskArray[formattedNumber.length] === currentKey) {
7893 formattedNumber = formattedNumber + currentKey;
7898 ctrl.$setViewValue(formattedNumber);
7900 return formattedNumber;
7904 // since we are only allowing 0-9, why even let the keypress go forward?
7905 // also added in delete... in case they want to delete :)
7906 var handlePress = function (e) {
7908 if ((e.which < 48 || e.which > 57) && (e.which < 96 || e.which > 105)) {
7909 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 &&
7911 (!(e.ctrlKey) && (e.which !== '118' || e.which !== '86')) &&
7913 (!(e.ctrlKey) && (e.which !== '99' || e.which !== '67')) &&
7915 (!(e.ctrlKey) && (e.which !== '120' || e.which !== '88'))) {
7916 e.preventDefault ? e.preventDefault() : e.returnValue = false;
7917 iElement.attr("aria-label", "Only numbers are allowed");
7918 validPhoneNumber = false;
7921 iElement.removeAttr("aria-label");
7922 validPhoneNumber = true;
7929 // i moved this out because i thought i might need focus as well..
7930 // to handle setting the model as the view changes
7931 var parser = function (fromViewValue) {
7932 var letters = /^[A-Za-z]+$/;
7933 var numbers = /^[0-9]+$/;
7934 if (angular.isUndefined(fromViewValue) || fromViewValue === '') {
7935 validPhoneNumber = true;
7937 if (fromViewValue.match(letters)) {
7938 validPhoneNumber = false;
7940 if (fromViewValue.match(numbers)) {
7941 validPhoneNumber = true;
7945 if (fromViewValue && fromViewValue.length > 0) {
7946 clean = fromViewValue.replace(/[^0-9]/g, '');
7948 checkValidity(clean, fromViewValue);
7952 //to handle reading the model and formatting it
7953 var formatter = function (fromModelView) {
7955 checkValidity(fromModelView);
7956 if (fromModelView) {
7957 input = handleKeyup();
7962 var setCurrentKey = function (e) {
7973 if (e.shiftKey === true) {
7978 if (e.shiftKey === true) {
7988 if (angular.isDefined(scope.ngModel)) {
7989 parser(scope.ngModel);
7992 ctrl.$parsers.push(parser);
7993 ctrl.$formatters.push(formatter);
7994 iElement.bind('keyup', handleKeyup);
7995 iElement.bind('keydown', handlePress);
8001 * @name Template.att:Profile Blocks
8004 * <file src="src/profileBlockTemplate/docs/readme.md" />
8006 * <section id="code">
8007 <example module="b2b.att">
8008 <file src="src/profileBlockTemplate/docs/demo.html" />
8009 <file src="src/profileBlockTemplate/docs/demo.js" />
8015 angular.module('b2b.att.profileBlockTemplate', [])
8021 * @name Layouts.att:profileCard
8024 * <file src="src/profileCard/docs/readme.md" />
8027 * <b2b-profile-card></b2b-profile-card>
8031 <example module="b2b.att">
8032 <file src="src/profileCard/docs/demo.html" />
8033 <file src="src/profileCard/docs/demo.js" />
8038 angular.module('b2b.att.profileCard', ['b2b.att'])
8039 .constant('profileStatus',{
8046 status: "Deactivated",
8062 role: "COMPANY ADMINISTRATOR"
8065 .directive('b2bProfileCard',['$http','$q','profileStatus', function($http,$q,profileStatus) {
8069 templateUrl: function(element, attrs){
8071 return 'b2bTemplate/profileCard/profileCard.html';
8074 return 'b2bTemplate/profileCard/profileCard-addUser.html';
8081 link: function(scope, elem, attr){
8082 scope.characterLimit = parseInt(attr.characterLimit, 10) || 25;
8083 scope.shouldClip = function(str) {
8084 return str.length > scope.characterLimit;
8087 scope.showEmailTooltip = false;
8090 function isImage(src) {
8091 var deferred = $q.defer();
8092 var image = new Image();
8093 image.onerror = function() {
8094 deferred.reject(false);
8096 image.onload = function() {
8097 deferred.resolve(true);
8099 if(src !== undefined && src.length>0 ){
8102 deferred.reject(false);
8104 return deferred.promise;
8108 isImage(scope.profile.img).then(function(img) {
8111 var splitName=(scope.profile.name).split(' ');
8113 for(var i=0;i<splitName.length;i++){
8114 scope.initials += splitName[i][0];
8116 if(scope.profile.role.toUpperCase() === profileStatus.role){
8119 var profileState=profileStatus.status[scope.profile.state.toUpperCase()];
8121 scope.profile.state=profileStatus.status[scope.profile.state.toUpperCase()].status;
8122 scope.colorIcon=profileStatus.status[scope.profile.state.toUpperCase()].color;
8123 if(scope.profile.state.toUpperCase()===profileStatus.status.PENDING.status.toUpperCase()||scope.profile.state.toUpperCase()===profileStatus.status.LOCKED.status.toUpperCase()){
8124 scope.profile.lastLogin=scope.profile.state;
8127 var today=new Date().getTime();
8128 var lastlogin=new Date(scope.profile.lastLogin).getTime();
8129 var diff=(today-lastlogin)/(1000*60*60*24);
8131 scope.profile.lastLogin="Today";
8134 scope.profile.lastLogin="Yesterday";
8142 * @name Forms.att:radios
8145 * <file src="src/radios/docs/readme.md" />
8150 * @param {boolean} refreshRadioGroup - A trigger that recalculates and updates the accessibility roles on radios in a group.
8154 <b>HTML + AngularJS</b>
8155 <example module="b2b.att">
8156 <file src="src/radios/docs/demo.html" />
8157 <file src="src/radios/docs/demo.js" />
8161 angular.module('b2b.att.radios', ['b2b.att.utilities'])
8162 .directive('b2bRadioGroupAccessibility', ['$timeout', 'b2bUserAgent', function($timeout, b2bUserAgent) {
8166 refreshRadioGroup: "=",
8168 link: function(scope, ele, attr) {
8170 var roleRadioElement, radioProductSelectElement, radioInputTypeElement;
8172 $timeout(calculateNumberOfRadio);
8174 scope.$watch('refreshRadioGroup', function(value) {
8175 if (value === true) {
8176 addingRoleAttribute();
8177 $timeout(calculateNumberOfRadio);
8178 scope.refreshRadioGroup = false;
8185 function calculateNumberOfRadio() {
8186 roleRadioElement = ele[0].querySelectorAll('[role="radio"]');
8188 radioProductSelectElement = ele[0].querySelectorAll('[role="radiogroup"] li.radio-box');
8190 radioInputTypeElement = ele[0].querySelectorAll('input[type="radio"]');
8192 for (var i = 0; i < radioInputTypeElement.length; i++) {
8193 var isChecked = radioInputTypeElement[i].checked ? 'true' : 'false';
8194 var isDisabled = radioInputTypeElement[i].disabled ? 'true' : 'false';
8195 var numOfx = i + 1 + ' of ' + radioInputTypeElement.length;
8196 angular.element(roleRadioElement[i]).attr({
8197 'aria-checked': isChecked,
8198 'aria-disabled': isDisabled,
8199 'data-opNum': numOfx
8201 if (b2bUserAgent.notMobile()) {
8202 angular.element(roleRadioElement[i]).removeAttr("role");
8205 if (radioProductSelectElement.length) {
8206 isChecked === 'true' ? angular.element(radioProductSelectElement[i]).addClass('active') : angular.element(radioProductSelectElement[i]).removeClass('active');
8209 if (/Android/i.test(navigator.userAgent)) {
8210 angular.element(roleRadioElement[i]).append('<span class="hidden-spoken">. ' + numOfx + '.</span>');
8214 angular.element(radioInputTypeElement[i]).bind('click', radioStateChangeonClick);
8219 function addingRoleAttribute() {
8220 for (var i = 0; i < radioInputTypeElement.length; i++) {
8221 if (b2bUserAgent.notMobile()) {
8222 angular.element(roleRadioElement[i]).attr("role", "radio");
8227 function radioStateChangeonClick() {
8228 for (var i = 0; i < radioInputTypeElement.length; i++) {
8229 var isChecked = radioInputTypeElement[i].checked ? 'true' : 'false';
8230 var isDisabled = radioInputTypeElement[i].disabled ? 'true' : 'false';
8231 if (radioProductSelectElement.length) {
8232 isChecked === 'true' ? angular.element(radioProductSelectElement[i]).addClass('active') : angular.element(radioProductSelectElement[i]).removeClass('active');
8234 angular.element(roleRadioElement[i]).attr({
8235 'aria-checked': isChecked,
8236 'aria-disabled': isDisabled
8248 * @name Forms.att:searchField
8251 * <file src="src/searchField/docs/readme.md" />
8254 * <div b2b-search-field dropdown-list="listdata" on-click-callback="clickFn(value)" class="span12" input-model='typedString' config-obj='keyConfigObj'></div>
8258 <example module="b2b.att">
8259 <file src="src/searchField/docs/demo.html" />
8260 <file src="src/searchField/docs/demo.js" />
8265 angular.module('b2b.att.searchField', ['b2b.att.utilities', 'b2b.att.position'])
8266 .filter('b2bFilterInput', [function() {
8267 return function(list, str, keyArray, displayListKey, isContainsSearch, searchSeperator) {
8270 var searchCondition;
8271 var conditionCheck = function(searchSeperator, listItem, displayListKey, splitString) {
8272 var displayTitle = null;
8274 for (var i = 0; i < displayListKey.length; i++) {
8276 displayTitle = listItem[displayListKey[i]].toLowerCase().indexOf(splitString[i].toLowerCase()) > -1;
8278 displayTitle = (splitString[i]) ? displayTitle && listItem[displayListKey[i]].toLowerCase().indexOf(splitString[i].toLowerCase().trim()) > -1 : displayTitle;
8282 angular.forEach(displayListKey, function(value) {
8283 if (!displayTitle) {
8284 displayTitle = listItem[value];
8286 displayTitle = displayTitle + (listItem[value] ? searchSeperator + ' ' + listItem[value] : '');
8290 return displayTitle;
8292 angular.forEach(list, function(listItem) {
8293 var splitString = str.indexOf(searchSeperator) > -1 ? str.split(searchSeperator) : false;
8294 var displayList = conditionCheck(searchSeperator, listItem, displayListKey, splitString)
8295 for (var i = 0; i < keyArray.length; i++) {
8296 searchLabel = keyArray[i];
8297 if (listItem[searchLabel]) {
8298 if (isContainsSearch) {
8299 var displaySearchList = listItem[searchLabel].toLowerCase().indexOf(str.toLowerCase()) > -1;
8300 if (splitString.length > 1) {
8301 displaySearchList = (splitString.length <= keyArray.length) ? displayList : false;
8303 searchCondition = displaySearchList;
8305 searchCondition = listItem[searchLabel].match(new RegExp('^' + str, 'gi'));
8307 if (searchCondition) {
8309 'title': conditionCheck(searchSeperator, listItem, displayListKey),
8310 'valueObj': listItem
8319 }]).directive('b2bSearchField', ['$filter', 'b2bFilterInputFilter', 'keymap', '$documentBind', '$isElement', '$document', 'events', '$timeout', function($filter, b2bFilterInput, keymap, $documentBind, $isElement, $document, events, $timeout) {
8323 dataList: '=dropdownList',
8324 onClickCallback: '&',
8332 templateUrl: 'b2bTemplate/searchField/searchField.html',
8333 controller: ['$scope', function($scope) {
8334 this.searchKeyArray = [];
8335 if ($scope.configObj.searchKeys) {
8336 this.searchKeyArray = $scope.configObj.searchKeys;
8338 if (angular.isUndefined($scope.disabled)) {
8339 $scope.disabled = false;
8341 this.triggerInput = function(searchString) {
8342 $scope.originalInputModel = searchString;
8343 if (searchString === '') {
8344 $scope.currentIndex = -1;
8345 $scope.filterList = [];
8346 $scope.showListFlag = false;
8347 } else if (searchString !== '' && !$scope.isFilterEnabled) {
8348 $scope.filterList = $filter('b2bFilterInput')($scope.dataList, searchString, this.searchKeyArray, $scope.configObj.displayListDataKey, $scope.configObj.isContainsSearch, $scope.configObj.searchSeperator);
8349 $scope.showListFlag = true;
8352 this.denyRegex = function() {
8353 return $scope.inputDeny;
8356 link: function(scope, elem) {
8357 scope.isFilterEnabled = false;
8358 scope.showListFlag = false;
8359 scope.currentIndex = -1;
8360 scope.setCurrentIdx = function(idx) {
8361 scope.currentIndex = idx;
8363 scope.inputModel = scope.filterList[idx].title;
8364 scope.objModel = scope.filterList[idx];
8367 scope.isActive = function(index, dropdownLength) {
8368 scope.dropdownLength = dropdownLength;
8369 return scope.currentIndex === index;
8371 scope.selectItem = function(idx) {
8372 scope.setCurrentIdx(idx);
8373 scope.onClickCallback({
8374 value: scope.inputModel,
8375 objValue: scope.objModel
8377 scope.showListFlag = false;
8378 $timeout(function() {
8379 elem.find('div').find('input')[0].focus();
8382 scope.startSearch = function() {
8383 scope.onClickCallback({
8384 value: scope.inputModel,
8385 objValue: scope.objModel
8388 scope.selectPrev = function() {
8389 if (scope.currentIndex > 0 && scope.filterList.length > 0) {
8390 scope.currentIndex = scope.currentIndex - 1;
8391 scope.setCurrentIdx(scope.currentIndex);
8392 } else if (scope.currentIndex === 0) {
8393 scope.currentIndex = scope.currentIndex - 1;
8394 scope.inputModel = scope.originalInputModel;
8395 scope.isFilterEnabled = true;
8398 scope.selectNext = function() {
8399 if (scope.currentIndex < scope.configObj.noOfItemsDisplay - 1) {
8400 if (scope.currentIndex < scope.filterList.length - 1) {
8401 scope.currentIndex = scope.currentIndex + 1;
8402 scope.setCurrentIdx(scope.currentIndex);
8406 scope.selectCurrent = function() {
8407 scope.selectItem(scope.currentIndex);
8409 scope.selectionIndex = function(e) {
8410 switch (e.keyCode) {
8411 case keymap.KEY.DOWN:
8412 events.preventDefault(e);
8413 scope.isFilterEnabled = true;
8417 events.preventDefault(e);
8418 scope.isFilterEnabled = true;
8421 case keymap.KEY.ENTER:
8422 events.preventDefault(e);
8423 scope.isFilterEnabled = true;
8424 scope.selectCurrent();
8426 case keymap.KEY.ESC:
8427 events.preventDefault(e);
8428 scope.isFilterEnabled = false;
8429 scope.showListFlag = false;
8430 scope.inputModel = '';
8433 scope.isFilterEnabled = false;
8436 if (elem[0].querySelector('.filtercontainer')) {
8437 elem[0].querySelector('.filtercontainer').scrollTop = (scope.currentIndex - 1) * 35;
8440 scope.$watch('filterList', function(newVal, oldVal) {
8441 if (newVal !== oldVal) {
8442 scope.currentIndex = -1;
8445 scope.blurInput = function() {
8446 $timeout(function() {
8447 scope.showListFlag = false;
8450 var outsideClick = function(e) {
8451 var isElement = $isElement(angular.element(e.target), elem.find('ul').eq(0), $document);
8452 if (!isElement && document.activeElement.tagName.toLowerCase() !== 'input') {
8453 scope.showListFlag = false;
8457 $documentBind.click('showListFlag', outsideClick, scope);
8461 .directive('b2bSearchInput', [function() {
8464 require: ['ngModel', '^b2bSearchField'],
8465 link: function(scope, elem, attr, ctrl) {
8466 var ngModelCtrl = ctrl[0];
8467 var attSearchBarCtrl = ctrl[1];
8468 var REGEX = ctrl[1].denyRegex();
8469 var parser = function(viewValue) {
8470 attSearchBarCtrl.triggerInput(viewValue);
8473 ngModelCtrl.$parsers.push(parser);
8475 if (REGEX !== undefined || REGEX !== '') {
8476 elem.bind('input', function() {
8477 var inputString = ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.replace(REGEX, '');
8478 if (inputString !== ngModelCtrl.$viewValue) {
8479 ngModelCtrl.$setViewValue(inputString);
8480 ngModelCtrl.$render();
8491 * @name Buttons, links & UI controls.att:Seek bar
8494 * <file src="src/seekBar/docs/readme.md" />
8497 * Horizontal Seek Bar
8498 * <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>
8501 * <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>
8505 <b>HTML + AngularJS</b>
8506 <example module="b2b.att">
8507 <file src="src/seekBar/docs/demo.html" />
8508 <file src="src/seekBar/docs/demo.js" />
8513 angular.module('b2b.att.seekBar', ['b2b.att.utilities','b2b.att.position'])
8514 .constant('b2bSeekBarConfig', {
8520 .directive('b2bSeekBar', ['$documentBind', 'events', 'b2bSeekBarConfig', 'keymap', '$compile', function($documentBind, events, b2bSeekBarConfig, keymap, $compile) {
8525 templateUrl: 'b2bTemplate/seekBar/seekBar.html',
8530 link: function(scope, elm, attr, ngModelCtrl) {
8531 scope.isDragging = false;
8532 scope.verticalSeekBar = false;
8535 var step = b2bSeekBarConfig.step;
8536 var skipInterval = b2bSeekBarConfig.skipInterval;
8537 var knob = angular.element(elm[0].querySelector('.b2b-seek-bar-knob-container'));
8538 var seekBarKnob = angular.element(knob[0].querySelector('.b2b-seek-bar-knob'));
8539 var trackContainer = angular.element(elm[0].querySelector('.b2b-seek-bar-track-container'));
8540 var trackFill = angular.element(elm[0].querySelector('.b2b-seek-bar-track-fill'));
8541 var trackContainerRect = {};
8543 var trackFillOrderPositioning;
8545 if (angular.isDefined(attr.vertical)) {
8546 scope.verticalSeekBar = true;
8547 axisPosition = "clientY";
8550 scope.verticalSeekBar = false;
8551 axisPosition = "clientX";
8553 var getValidStep = function(val) {
8554 val = parseFloat(val);
8555 // in case $modelValue came in string number
8556 if (angular.isNumber(val)) {
8557 val = Math.round((val - min) / step) * step + min;
8558 return Math.round(val * 1000) / 1000;
8562 var getPositionToPercent = function(x) {
8563 if (scope.verticalSeekBar) {
8564 return Math.max(0, Math.min(1, (trackContainerRect.bottom - x) / (trackFillOrderPositioning)));
8567 return Math.max(0, Math.min(1, (x - trackContainerRect.left) / (trackFillOrderPositioning)));
8571 var getPercentToValue = function(percent) {
8572 return (min + percent * (max - min));
8575 var getValueToPercent = function(val) {
8576 return (val - min) / (max - min);
8579 var getValidMinMax = function(val) {
8580 return Math.max(min, Math.min(max, val));
8583 var updateTrackContainerRect = function() {
8584 trackContainerRect = trackContainer[0].getBoundingClientRect();
8585 if (scope.verticalSeekBar) {
8586 if (!trackContainerRect.height) {
8587 trackFillOrderPositioning = trackContainer[0].scrollHeight;
8589 trackFillOrderPositioning = trackContainerRect.height;
8593 if (!trackContainerRect.width) {
8594 trackFillOrderPositioning = trackContainer[0].scrollWidth;
8596 trackFillOrderPositioning = trackContainerRect.width;
8603 var updateKnobPosition = function(percent) {
8604 var percentStr = (percent * 100) + '%';
8605 if (scope.verticalSeekBar) {
8606 knob.css('bottom', percentStr);
8607 trackFill.css('height', percentStr);
8610 knob.css('left', percentStr);
8611 trackFill.css('width', percentStr);
8615 var modelRenderer = function() {
8616 if (isNaN(ngModelCtrl.$viewValue)) {
8617 ngModelCtrl.$viewValue = ngModelCtrl.$modelValue || min;
8620 var viewVal = ngModelCtrl.$viewValue;
8621 scope.currentModelValue = viewVal;
8623 //wait for min, max and step to be set then only update UI to avoid NaN on percent calculation
8624 if ((min || min === 0) && max && step) {
8625 updateKnobPosition(getValueToPercent(viewVal));
8629 var setModelValue = function(val) {
8630 scope.currentModelValue = getValidMinMax(getValidStep(val));
8631 ngModelCtrl.$setViewValue(scope.currentModelValue);
8634 var updateMin = function(val) {
8635 min = parseFloat(val);
8637 min = b2bSeekBarConfig.min;
8642 var updateMax = function(val) {
8643 max = parseFloat(val);
8645 max = b2bSeekBarConfig.max;
8650 var updateStep = function(val) {
8651 step = parseFloat(val);
8652 if (!attr['skipInterval']) {
8653 skipInterval = step;
8657 var updateSkipInterval = function(val) {
8658 skipInterval = step * Math.ceil(val / (step!==0?step:1));
8661 angular.isDefined(attr.min) ? attr.$observe('min', updateMin) : updateMin(b2bSeekBarConfig.min);
8662 angular.isDefined(attr.max) ? attr.$observe('max', updateMax) : updateMax(b2bSeekBarConfig.max);
8663 if (angular.isDefined(attr.step)) {
8664 attr.$observe('step', updateStep);
8666 if (angular.isDefined(attr.skipInterval)) {
8667 attr.$observe('skipInterval', updateSkipInterval);
8669 scope.currentModelValue = getValidMinMax(getValidStep(min));
8670 var onMouseDown = function(e) {
8673 // left mouse button
8677 // right or middle mouse button
8682 scope.isDragging = true;
8683 seekBarKnob[0].focus();
8684 updateTrackContainerRect();
8685 if (attr['onDragInit']) {
8688 events.stopPropagation(e);
8689 events.preventDefault(e);
8690 scope.$apply(function() {
8691 setModelValue(getPercentToValue(getPositionToPercent(e[axisPosition])));
8695 var onMouseUp = function() {
8697 if (attr['onDragEnd']) {
8700 scope.isDragging = false;
8704 var onMouseMove = function(e) {
8705 if (scope.isDragging) {
8706 events.stopPropagation(e);
8707 events.preventDefault(e);
8709 scope.$apply(function() {
8710 setModelValue(getPercentToValue(getPositionToPercent(e[axisPosition])));
8715 function onKeyDown(e) {
8717 e.keyCode = e.which;
8720 switch (e.keyCode) {
8721 case keymap.KEY.LEFT:
8722 if (!scope.verticalSeekBar) {
8723 updateStep = -skipInterval;
8726 case keymap.KEY.RIGHT:
8727 if (!scope.verticalSeekBar) {
8728 updateStep = skipInterval;
8732 if (scope.verticalSeekBar) {
8733 updateStep = skipInterval;
8736 case keymap.KEY.DOWN:
8737 if (scope.verticalSeekBar) {
8738 updateStep = -skipInterval;
8746 events.stopPropagation(e);
8747 events.preventDefault(e);
8748 scope.$apply(function() {
8749 setModelValue(ngModelCtrl.$viewValue + updateStep);
8751 if (attr['onDragEnd']) {
8757 elm.on('keydown', onKeyDown);
8758 elm.on('mousedown', onMouseDown);
8760 $documentBind.event('mousemove', 'isDragging', onMouseMove, scope, true, 0);
8761 $documentBind.event('mouseup', 'isDragging', onMouseUp, scope, true, 0);
8763 ngModelCtrl.$render = function() {
8764 if (!scope.isDragging) {
8768 ngModelCtrl.$viewChangeListeners.push(modelRenderer);
8769 ngModelCtrl.$formatters.push(getValidMinMax);
8770 ngModelCtrl.$formatters.push(getValidStep);
8776 * @name Forms.att:selectorModule
8779 * <file src="src/selectorModule/docs/readme.md" />
8782 * <select name="myNameBig" type="large" b2b-dropdown ng-model="Controller Variable here">
8783 * <option b2b-dropdown-list option-repeat="option data here" imgsrc="image path" value="value">List Text</option>
8786 * <select name="myNameBig" type="large" b2b-dropdown ng-model="Controller Variable here">
8787 * <option b2b-dropdown-list option-repeat="option data here" imgsrc="image path" value="value">List Text</option>
8790 * <select name="myNameBig" b2b-dropdown ng-model="Controller Variable here">
8791 * <optgroup b2b-dropdown-group label="Group Label here">
8792 * <option b2b-dropdown-list option-repeat="option data here" imgsrc="image path" value="value">List Text</option>
8797 * <section id="code">
8798 <example module="b2b.att">
8799 <file src="src/selectorModule/docs/demo.html" />
8800 <file src="src/selectorModule/docs/demo.js" />
8805 angular.module('b2b.att.selectorModule', ['b2b.att.dropdowns']);
8808 * @name Layouts.att:separators
8811 * <file src="src/separators/docs/readme.md" />
8817 * <section id="code">
8818 <b>HTML + AngularJS</b>
8819 <example module="b2b.att">
8820 <file src="src/separators/docs/demo.html" />
8821 <file src="src/separators/docs/demo.js" />
8827 angular.module('b2b.att.separators', []);
8830 * @name Buttons, links & UI controls.att:slider
8833 * <file src="src/slider/docs/readme.md" />
8836 * <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>
8840 <b>HTML + AngularJS</b>
8841 <example module="b2b.att">
8842 <file src="src/slider/docs/demo.html" />
8843 <file src="src/slider/docs/demo.js" />
8848 angular.module('b2b.att.slider', ['b2b.att.utilities'])
8849 .constant('SliderConfig', {
8855 .directive('b2bSlider', ['$documentBind', 'SliderConfig', 'keymap', '$compile', '$log', function($documentBind, SliderConfig, keymap, $compile, $log) {
8860 templateUrl: 'b2bTemplate/slider/slider.html',
8864 trackFillColor: '=?',
8866 postAriaLabel: '=?',
8868 sliderSnapPoints: '=?',
8869 customAriaLabel: '=?',
8872 link: function(scope, elm, attr, ngModelCtrl) {
8873 scope.isDragging = false;
8874 scope.verticalSlider = false;
8877 var step = SliderConfig.step;
8878 var skipInterval = SliderConfig.skipInterval;
8879 var knob = angular.element(elm[0].querySelector('.slider-knob-container'));
8880 var sliderKnob = angular.element(knob[0].querySelector('.slider-knob'));
8881 var trackContainer = angular.element(elm[0].querySelector('.slider-track-container'));
8882 var trackFill = angular.element(elm[0].querySelector('.slider-track-fill'));
8883 var trackContainerRect = {};
8884 var axisPosition = "clientX";
8885 var trackFillOrderPositioning;
8887 //Forcefully disabling the vertical Slider code.
8888 if (angular.isDefined(attr.vertical)) {
8889 scope.verticalSlider = true;
8890 axisPosition = "clientY";
8893 if (angular.isDefined(scope.noAriaLabel) && scope.noAriaLabel !== '') {
8894 $log.warn('no-aria-label has been deprecated. This will be removed in v0.6.0.');
8896 if (angular.isDefined(scope.preAriaLabel) && scope.preAriaLabel !== '') {
8897 $log.warn('pre-aria-label has been deprecated. Please use label-id instead. This will be removed in v0.6.0.');
8899 if (angular.isDefined(scope.customAriaLabel) && scope.customAriaLabel !== '') {
8900 $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.');
8903 var binarySearchNearest = function (num, arr) {
8906 var hi = arr.length - 1;
8908 while (hi - lo > 1) {
8909 mid = Math.floor((lo + hi) / 2);
8910 if (arr[mid] < num) {
8916 if (num - arr[lo] < arr[hi] - num) {
8922 var getValidStep = function(val) {
8923 val = parseFloat(val);
8924 // in case $modelValue came in string number
8927 if(attr['sliderSnapPoints'] && scope.sliderSnapPoints.length > 0) {
8928 val = binarySearchNearest(val, scope.sliderSnapPoints);
8931 val = Math.round((val - min) / step) * step + min;
8934 return Math.round(val * 1000) / 1000;
8938 var getPositionToPercent = function(x) {
8939 if (scope.verticalSlider) {
8940 return Math.max(0, Math.min(1, (trackContainerRect.bottom - x) / (trackFillOrderPositioning)));
8943 return Math.max(0, Math.min(1, (x - trackContainerRect.left) / (trackFillOrderPositioning)));
8947 var getPercentToValue = function(percent) {
8948 return (min + percent * (max - min));
8951 var getValueToPercent = function(val) {
8952 return (val - min) / (max - min);
8955 var getValidMinMax = function(val) {
8956 return Math.max(min, Math.min(max, val));
8959 var updateTrackContainerRect = function() {
8960 trackContainerRect = trackContainer[0].getBoundingClientRect();
8961 if (scope.verticalSlider) {
8962 if (!trackContainerRect.height) {
8963 trackFillOrderPositioning = trackContainer[0].scrollHeight;
8965 trackFillOrderPositioning = trackContainerRect.height;
8969 if (!trackContainerRect.width) {
8970 trackFillOrderPositioning = trackContainer[0].scrollWidth;
8972 trackFillOrderPositioning = trackContainerRect.width;
8979 var updateKnobPosition = function(percent) {
8980 var percentStr = (percent * 100) + '%';
8981 if (scope.verticalSlider) {
8982 knob.css('bottom', percentStr);
8983 trackFill.css('height', percentStr);
8986 knob.css('left', percentStr);
8987 trackFill.css('width', percentStr);
8991 var modelRenderer = function() {
8993 if(attr['disabled']){
8997 if (isNaN(ngModelCtrl.$viewValue)) {
8998 ngModelCtrl.$viewValue = ngModelCtrl.$modelValue || min;
9001 var viewVal = ngModelCtrl.$viewValue;
9002 scope.currentModelValue = viewVal;
9004 //wait for min, max and step to be set then only update UI to avoid NaN on percent calculation
9005 if ((min || min === 0) && max && step) {
9006 updateKnobPosition(getValueToPercent(viewVal));
9010 var setModelValue = function(val) {
9011 scope.currentModelValue = getValidMinMax(getValidStep(val));
9012 ngModelCtrl.$setViewValue(scope.currentModelValue);
9015 var updateMin = function(val) {
9016 min = parseFloat(val);
9018 min = SliderConfig.min;
9024 var updateMax = function(val) {
9025 max = parseFloat(val);
9027 max = SliderConfig.max;
9033 var updateStep = function(val) {
9034 step = parseFloat(val);
9035 if (!attr['skipInterval']) {
9036 skipInterval = step;
9040 var updateSkipInterval = function(val) {
9041 skipInterval = step * Math.ceil(val / (step!==0?step:1));
9044 angular.isDefined(attr.min) ? attr.$observe('min', updateMin) : updateMin(SliderConfig.min);
9045 angular.isDefined(attr.max) ? attr.$observe('max', updateMax) : updateMax(SliderConfig.max);
9046 if (angular.isDefined(attr.step)) {
9047 attr.$observe('step', updateStep);
9049 if (angular.isDefined(attr.skipInterval)) {
9050 attr.$observe('skipInterval', updateSkipInterval);
9052 scope.currentModelValue = getValidMinMax(getValidStep(min));
9053 var onMouseDown = function(e) {
9055 if(attr['disabled']){
9061 // left mouse button
9065 // right or middle mouse button
9069 scope.isDragging = true;
9070 sliderKnob[0].focus();
9071 updateTrackContainerRect();
9072 if (attr['onDragInit']) {
9075 e.stopPropagation();
9077 scope.$apply(function() {
9078 setModelValue(getPercentToValue(getPositionToPercent(e[axisPosition])));
9082 var onMouseUp = function() {
9084 if (attr['onDragEnd']) {
9087 scope.isDragging = false;
9091 var onMouseMove = function(e) {
9092 if (scope.isDragging) {
9093 e.stopPropagation();
9096 scope.$apply(function() {
9097 setModelValue(getPercentToValue(getPositionToPercent(e[axisPosition])));
9102 function onKeyDown(e) {
9104 e.keyCode = e.which;
9107 switch (e.keyCode) {
9108 case keymap.KEY.DOWN:
9109 case keymap.KEY.LEFT:
9110 if(attr['sliderSnapPoints'] && scope.sliderSnapPoints.length > 0) {
9111 var currentIndex = scope.sliderSnapPoints.indexOf(ngModelCtrl.$viewValue);
9112 if (currentIndex > 0) {
9115 updateStep = scope.sliderSnapPoints[currentIndex];
9118 updateStep = ngModelCtrl.$viewValue - skipInterval;
9122 case keymap.KEY.RIGHT:
9123 if(attr['sliderSnapPoints'] && scope.sliderSnapPoints.length > 0) {
9124 var currentIndex = scope.sliderSnapPoints.indexOf(ngModelCtrl.$viewValue);
9125 if (currentIndex < scope.sliderSnapPoints.length-1) {
9128 updateStep = scope.sliderSnapPoints[currentIndex];
9131 updateStep = ngModelCtrl.$viewValue + skipInterval;
9134 case keymap.KEY.END:
9135 if(attr['sliderSnapPoints'] && scope.sliderSnapPoints.length > 0) {
9136 currentIndex = scope.sliderSnapPoints.length-1;
9137 updateStep = scope.sliderSnapPoints[currentIndex];
9139 setModelValue(scope.max);
9142 e.stopPropagation();
9144 case keymap.KEY.HOME:
9145 if(attr['sliderSnapPoints'] && scope.sliderSnapPoints.length > 0) {
9147 updateStep = scope.sliderSnapPoints[currentIndex];
9149 setModelValue(scope.min);
9152 e.stopPropagation();
9158 if (angular.isNumber(updateStep) && !attr['disabled']) {
9159 e.stopPropagation();
9161 scope.$apply(function() {
9162 setModelValue(updateStep);
9164 if (attr['onDragEnd']) {
9170 elm.on('keydown', onKeyDown);
9171 elm.on('mousedown', onMouseDown);
9173 $documentBind.event('mousemove', 'isDragging', onMouseMove, scope, true, 0);
9174 $documentBind.event('mouseup', 'isDragging', onMouseUp, scope, true, 0);
9176 attr.$observe('disabled', function (disabled) {
9178 sliderKnob.removeAttr('tabindex');
9180 sliderKnob.attr('tabindex', '0');
9184 elm.toggleClass("slider-disabled", disabled);
9186 if (angular.isDefined(attr.hideDisabledKnob)) {
9187 scope.hideKnob = disabled;
9191 ngModelCtrl.$render = function() {
9192 if (!scope.isDragging) {
9194 if (attr['onRenderEnd'] && !attr['disabled']) {
9195 scope.onRenderEnd({currentModelValue: scope.currentModelValue});
9199 ngModelCtrl.$viewChangeListeners.push(modelRenderer);
9200 ngModelCtrl.$formatters.push(getValidMinMax);
9201 ngModelCtrl.$formatters.push(getValidStep);
9207 * @name Forms.att:spinButton
9209 * @param {String} spin-button-id - An ID for the input field
9210 * @param {Integer} min - Minimum value for the input
9211 * @param {Integer} max - Maximum value for the input
9212 * @param {Integer} step - Value by which input field increments or decrements on up/down arrow keys
9213 * @param {Integer} page-step - Value by which input field increments or decrements on page up/down keys
9214 * @param {boolean} input-model-key - Default value for input field
9215 * @param {boolean} disabled-flag - A boolean that triggers directive once the min or max value has reached
9218 * <file src="src/spinButton/docs/readme.md" />
9221 * <section id="code">
9222 <example module="b2b.att">
9223 <file src="src/spinButton/docs/demo.html" />
9224 <file src="src/spinButton/docs/demo.js" />
9229 angular.module('b2b.att.spinButton', ['b2b.att.utilities'])
9230 .constant('b2bSpinButtonConfig', {
9235 inputModelKey: 'value',
9238 .directive('b2bSpinButton', ['keymap', 'b2bSpinButtonConfig', 'b2bUserAgent', function (keymap, b2bSpinButtonConfig, userAgent) {
9241 require: '?ngModel',
9248 pageStep: '=pageStep',
9250 inputValue: '=ngModel',
9254 templateUrl: 'b2bTemplate/spinButton/spinButton.html',
9255 controller: ['$scope', '$element', '$attrs', function (scope, element, attrs) {
9257 scope.isMobile = userAgent.isMobile();
9258 scope.notMobile = userAgent.notMobile();
9260 scope.min = attrs.min ? scope.min : b2bSpinButtonConfig.min;
9261 scope.max = attrs.max ? scope.max : b2bSpinButtonConfig.max;
9262 scope.step = attrs.step ? scope.step : b2bSpinButtonConfig.step;
9263 scope.pageStep = attrs.pageStep ? scope.pageStep : b2bSpinButtonConfig.pageStep;
9264 scope.inputModelKey = attrs.inputModelKey ? scope.inputModelKey : b2bSpinButtonConfig.inputModelKey;
9265 scope.disabledFlag = attrs.disabledFlag ? scope.disabledFlag : b2bSpinButtonConfig.disabledFlag;
9267 if (scope.min < 0) {
9270 if (scope.max > 999) {
9274 scope.isPlusDisabled = function () {
9275 return (scope.disabledFlag || scope.inputValue[scope.inputModelKey] >= scope.max);
9277 scope.isMinusDisabled = function () {
9278 return (scope.disabledFlag || scope.inputValue[scope.inputModelKey] <= scope.min);
9281 scope.getValidateInputValue = function (value) {
9282 if (value <= scope.min) {
9284 } else if (value >= scope.max) {
9291 scope.plus = function () {
9292 scope.inputValue[scope.inputModelKey] = scope.getValidateInputValue(parseInt(scope.inputValue[scope.inputModelKey], 10) + scope.step);
9294 scope.minus = function () {
9295 scope.inputValue[scope.inputModelKey] = scope.getValidateInputValue(parseInt(scope.inputValue[scope.inputModelKey], 10) - scope.step);
9297 scope.pagePlus = function () {
9298 scope.inputValue[scope.inputModelKey] = scope.getValidateInputValue(parseInt(scope.inputValue[scope.inputModelKey], 10) + scope.pageStep);
9300 scope.pageMinus = function () {
9301 scope.inputValue[scope.inputModelKey] = scope.getValidateInputValue(parseInt(scope.inputValue[scope.inputModelKey], 10) - scope.pageStep);
9305 link: function (scope, elem) {
9307 if (scope.notMobile) {
9308 angular.element(elem).find('input').attr('aria-live', 'off');
9309 angular.element(elem).find('input').attr('role', 'spinbutton');
9312 elem.find('input').bind('keydown', function (e) {
9313 if (e.keyCode === keymap.KEY.UP) {
9315 } else if (e.keyCode === keymap.KEY.DOWN){
9317 } else if (e.keyCode === keymap.KEY.HOME) {
9318 scope.inputValue[scope.inputModelKey] = parseInt(scope.min);
9319 } else if (e.keyCode === keymap.KEY.END) {
9320 scope.inputValue[scope.inputModelKey] = parseInt(scope.max);
9321 } else if (e.keyCode === keymap.KEY.PAGE_UP) {
9323 } else if (e.keyCode === keymap.KEY.PAGE_DOWN) {
9329 elem.find('input').bind('keyup', function () {
9330 if (scope.inputValue[scope.inputModelKey] === null ||
9331 scope.inputValue[scope.inputModelKey] === '' ||
9332 scope.inputValue[scope.inputModelKey] < scope.min) {
9333 scope.inputValue[scope.inputModelKey] = scope.min;
9335 } else if (angular.isUndefined(scope.inputValue[scope.inputModelKey]) ||
9336 scope.inputValue[scope.inputModelKey] > scope.max) {
9337 scope.inputValue[scope.inputModelKey] = scope.max;
9342 scope.focusInputSpinButton = function (evt) {
9343 evt.preventDefault();
9344 if (scope.notMobile) {
9345 elem[0].querySelector('input').focus();
9354 * @name Template.att:Static Route
9357 * <file src="src/staticRouteTemplate/docs/readme.md" />
9360 * <section id="code">
9361 <example module="b2b.att">
9362 <file src="src/staticRouteTemplate/docs/demo.html" />
9363 <file src="src/staticRouteTemplate/docs/demo.js" />
9368 angular.module('b2b.att.staticRouteTemplate', ['b2b.att.utilities'])
9372 * @name Progress & usage indicators.att:statusTracker
9375 * @param {array} statuses - An array of status objects
9377 * <file src="src/statusTracker/docs/readme.md" />
9381 <div ng-controller="statusTrackerController">
9382 <b2b-status-tracker statuses="statusObject"></b2b-status-tracker>
9387 <b>HTML + AngularJS</b>
9388 <example module="b2b.att">
9389 <file src="src/statusTracker/docs/demo.html" />
9390 <file src="src/statusTracker/docs/demo.js" />
9395 angular.module('b2b.att.statusTracker', ['b2b.att.utilities'])
9396 .constant('b2bStatusTrackerConfig', {
9399 .directive('b2bStatusTracker', ['b2bStatusTrackerConfig', function(b2bStatusTrackerConfig) {
9407 templateUrl: function(scope) {
9408 return 'b2bTemplate/statusTracker/statusTracker.html';
9410 link: function(scope, element, attr) {
9411 scope.currentViewIndex = 0;
9412 scope.b2bStatusTrackerConfig = b2bStatusTrackerConfig;
9414 scope.nextStatus = function() {
9415 if (scope.currentViewIndex+1 <= scope.statuses.length) {
9416 scope.currentViewIndex++;
9419 scope.previousStatus = function() {
9420 if (scope.currentViewIndex-1 >= 0) {
9421 scope.currentViewIndex--;
9424 scope.isInViewport = function(index) {
9425 return (index < scope.currentViewIndex+3 && index >= scope.currentViewIndex); // && index > scope.currentViewIndex-2
9427 scope.currentStatus = function(index) {
9428 if(index != undefined){
9429 if(!scope.statuses[index].complete) {
9430 if(index > 0 && scope.statuses[index-1].complete) {
9432 } else if(index == 0 && !scope.statuses[index].complete){
9445 * @name Progress & usage indicators.att:stepTracker
9448 * @param {array} stepsItemsObject - An array of step objects
9449 * @param {Integer} currenIindex - This indicates the current running step
9450 * @param {Integer} viewportIndex - This is optional. This can used to start the view port rather than 1 item.
9452 * <file src="src/stepTracker/docs/readme.md" />
9456 * <b2b-step-tracker steps-items-object="stepsObject" current-index="currentStepIndex" step-indicator-heading="stepHeading"></b2b-step-tracker>
9461 <b>HTML + AngularJS</b>
9462 <example module="b2b.att">
9463 <file src="src/stepTracker/docs/demo.html" />
9464 <file src="src/stepTracker/docs/demo.js" />
9468 angular.module('b2b.att.stepTracker', ['b2b.att.utilities'])
9469 .constant('b2bStepTrackerConfig', {
9472 .directive('b2bStepTracker', ['b2bStepTrackerConfig', function(b2bStepTrackerConfig) {
9477 stepsItemsObject:"=",
9481 templateUrl: 'b2bTemplate/stepTracker/stepTracker.html',
9482 link: function(scope, ele, attr) {
9483 if (angular.isDefined(scope.viewportIndex)) {
9484 scope.currentViewIndex = scope.viewportIndex - 1;
9486 scope.currentViewIndex = 0;
9489 scope.b2bStepTrackerConfig = b2bStepTrackerConfig;
9490 scope.nextStatus = function() {
9491 if (scope.currentViewIndex+1 <= scope.stepsItemsObject.length) {
9492 scope.currentViewIndex++;
9495 scope.previousStatus = function() {
9496 if (scope.currentViewIndex-1 >= 0) {
9497 scope.currentViewIndex--;
9500 scope.isInViewport = function(index) {
9501 return (index < scope.currentViewIndex+b2bStepTrackerConfig.maxViewItems && index >= scope.currentViewIndex);
9509 * @name Buttons, links & UI controls.att:switches
9512 * <file src="src/switches/docs/readme.md" />
9516 * <!-- On / Off Toggle switch -->
9517 * <label for="switch1" class="controlled-text-wrap"> This is ON
9518 * <input type="checkbox" role="checkbox" b2b-switches id="switch1" ng-model="switch1.value">
9521 * <!-- On / Off Toggle switch and DISABLED -->
9522 * <label for="switch2" class="controlled-text-wrap"> This is ON (disabled)
9523 * <input type="checkbox" role="checkbox" b2b-switches id="switch2" ng-model="switch1.value" ng-disabled="true" >
9528 * <section id="code">
9529 <b>HTML + AngularJS</b>
9530 <example module="b2b.att">
9531 <file src="src/switches/docs/demo.js" />
9532 <file src="src/switches/docs/demo.html" />
9536 angular.module('b2b.att.switches', ['b2b.att.utilities'])
9537 .directive('b2bSwitches', ['$compile', '$templateCache', 'keymap', 'events', function ($compile, $templateCache, keymap, events) {
9540 require: ['ngModel'],
9541 link: function (scope, element, attrs, ctrl) {
9542 var ngModelController = ctrl[0];
9544 element.parent().bind("keydown", function (e) {
9545 if (!attrs.disabled && (e.keyCode === keymap.KEY.ENTER || e.keyCode === keymap.KEY.SPACE)) {
9546 events.preventDefault(e);
9547 ngModelController.$setViewValue(!ngModelController.$viewValue);
9548 element.prop("checked", ngModelController.$viewValue);
9552 element.wrap('<div class="btn-switch">');
9553 //element.attr("tabindex", -1);
9554 if (navigator.userAgent.match(/iphone/i)){
9555 element.attr("aria-live", "polite");
9558 element.removeAttr('aria-live');
9561 var templateSwitch = angular.element($templateCache.get("b2bTemplate/switches/switches.html"));
9562 if (angular.isDefined(attrs.typeSpanish)) {
9563 templateSwitch = angular.element($templateCache.get("b2bTemplate/switches/switches-spanish.html"));
9566 templateSwitch = $compile(templateSwitch)(scope);
9567 element.parent().append(templateSwitch);
9569 element.bind("focus", function (e) {
9570 element.parent().addClass('focused');
9573 element.bind("blur", function (e) {
9574 element.parent().removeClass('focused');
9581 * @name Messages, modals & alerts.att:tableMessages
9584 * <file src="src/tableMessages/docs/readme.md" />
9587 <!-- no matching results -->
9588 <b2b-table-message msg-type="'noMatchingResults'">
9589 <p>No Matching Results</p>
9590 </b2b-table-message>
9592 <!-- info could not load -->
9593 <b2b-table-message msg-type="'infoCouldNotLoad'" on-refresh-click="refreshClicked()">
9594 </b2b-table-message>
9596 <!-- magnify search -->
9597 <b2b-table-message msg-type="'magnifySearch'">
9598 </b2b-table-message>
9600 <!-- loading data -->
9601 <b2b-table-message msg-type="'loadingTable'">
9602 <!-- custom html -->
9603 <p>The data is currently loading...</p>
9604 </b2b-table-message>
9608 <b>HTML + AngularJS</b>
9609 <example module="b2b.att">
9610 <file src="src/tableMessages/docs/demo.html" />
9611 <file src="src/tableMessages/docs/demo.js" />
9615 angular.module('b2b.att.tableMessages', [])
9616 .directive('b2bTableMessage', [function() {
9625 templateUrl: 'b2bTemplate/tableMessages/tableMessage.html',
9626 link: function(scope) {
9627 scope.refreshAction = function(evt) {
9628 scope.onRefreshClick(evt);
9636 * @name Tabs, tables & accordions.att:tableScrollbar
9639 * <file src="src/tableScrollbar/docs/readme.md" />
9643 <b2b-table-scrollbar>
9645 <thead type="header">
9647 <th role="columnheader" scope="col" key="Id" id="col1">Id</th>
9653 <td id="rowheader0" headers="col1">1002</td>
9658 </b2b-table-scrollbar>
9661 * <section id="code">
9662 <example module="b2b.att">
9663 <file src="src/tableScrollbar/docs/demo.html" />
9664 <file src="src/tableScrollbar/docs/demo.js" />
9669 angular.module('b2b.att.tableScrollbar', [])
9670 .directive('b2bTableScrollbar', ['$timeout', function ($timeout) {
9675 templateUrl: 'b2bTemplate/tableScrollbar/tableScrollbar.html',
9676 link: function (scope, element, attrs, ctrl) {
9677 var firstThWidth, firstTdWidth, firstColumnWidth, firstColumnHeight, trHeight = 0;
9678 var pxToScroll = '';
9679 var tableElement = element.find('table');
9680 var thElements = element.find('th');
9681 var tdElements = element.find('td');
9682 var innerContainer = angular.element(element[0].querySelector('.b2b-table-inner-container'));
9683 var outerContainer = angular.element(element[0].querySelector('.b2b-table-scrollbar'));
9685 scope.disableLeft = true;
9686 scope.disableRight = false;
9688 if (angular.isDefined(thElements[0])) {
9689 firstThWidth = thElements[0].offsetWidth;
9691 if (angular.isDefined(tdElements[0])) {
9692 firstTdWidth = tdElements[0].offsetWidth;
9694 firstColumnWidth = (firstThWidth > firstTdWidth) ? firstThWidth : firstTdWidth;
9696 innerContainer.css({
9697 'padding-left': (firstColumnWidth + 2) + 'px'
9700 angular.forEach(element.find('tr'), function (eachTr, index) {
9701 trObject = angular.element(eachTr);
9702 firstColumn = angular.element(trObject.children()[0]);
9704 angular.element(firstColumn).css({
9705 'margin-left': -(firstColumnWidth + 2) + 'px',
9706 'width': (firstColumnWidth + 2) + 'px',
9707 'position': 'absolute'
9710 trHeight = trObject[0].offsetHeight;
9711 firstColumnHeight = firstColumn[0].offsetHeight;
9712 if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
9713 firstColumnHeight += 1;
9716 if (trHeight !== firstColumnHeight - 1) {
9717 if (trHeight > firstColumnHeight) {
9718 if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
9721 angular.element(firstColumn).css({
9722 'height': (trHeight + 1) + 'px'
9725 angular.element(trObject).css({
9726 'height': (firstColumnHeight - 1) + 'px'
9733 pxToScroll = outerContainer[0].offsetWidth - firstColumnWidth;
9735 scope.scrollLeft = function () {
9736 innerContainer[0].scrollLeft = innerContainer[0].scrollLeft + 20 - pxToScroll;
9739 scope.scrollRight = function () {
9740 innerContainer[0].scrollLeft = innerContainer[0].scrollLeft + pxToScroll - 20;
9743 scope.checkScrollArrows = function () {
9744 if (innerContainer[0].scrollLeft == 0) {
9745 scope.disableLeft = true;
9747 scope.disableLeft = false;
9750 if (((innerContainer[0].offsetWidth - firstColumnWidth) + innerContainer[0].scrollLeft) >= tableElement[0].offsetWidth) {
9751 scope.disableRight = true;
9753 scope.disableRight = false;
9757 innerContainer.bind('scroll', function () {
9758 $timeout(function () {
9759 scope.checkScrollArrows();
9768 * @name Tabs, tables & accordions.att:tables
9771 * <file src="src/tables/docs/readme.md" />
9776 <table b2b-table table-data="tableData" search-string="searchString">
9777 <thead b2b-table-row type="header">
9779 <th b2b-table-header key="requestId" default-sort="a" id="col1">Header 1</th>
9780 <th b2b-table-header key="requestType" sortable="false" id="col2">Header 2</th>
9783 <tbody b2b-table-row type="body" row-repeat="rowData in tableData">
9785 <td b2b-table-body id="rowheader{{$index}}" headers="col1" ng-bind="rowData['requestId']"> </td>
9786 <td b2b-table-body ng-bind="rowData['requestType']"></td>
9792 * <section id="code">
9793 <example module="b2b.att">
9794 <file src="src/tables/docs/demo.html" />
9795 <file src="src/tables/docs/demo.js" />
9800 angular.module('b2b.att.tables', ['b2b.att.utilities'])
9801 .constant('b2bTableConfig', {
9802 defaultSortPattern: false, // true for descending & false for ascending
9803 highlightSearchStringClass: 'tablesorter-search-highlight',
9804 zebraStripCutOff: 6, // > zebraStripCutOff
9805 tableBreakpoints: [ // breakpoints are >= min and < max
9828 .directive('b2bTable', ['$filter', '$window', 'b2bTableConfig', '$timeout', function ($filter, $window, b2bTableConfig, $timeout) {
9838 searchCategory: "=",
9842 require: 'b2bTable',
9843 templateUrl: 'b2bTemplate/tables/b2bTable.html',
9844 controller: ['$scope', '$attrs', function ($scope, $attrs) {
9846 this.currentSortIndex = null;
9847 this.responsive = $scope.responsive = $attrs.responsive;
9848 this.maxTableColumns = -1;
9849 this.totalTableColums = 0;
9850 this.active = $scope.active = false;
9851 this.responsiveRowScopes = [];
9852 this.hideColumnPriority = [];
9853 this.hiddenColumn = [];
9854 this.setIndex = function (headerScope, priority) {
9855 this.headers.push(headerScope);
9856 if (this.responsive) {
9857 this.totalTableColums++;
9858 if (!isNaN(priority)) {
9859 this.hideColumnPriority[priority] = this.totalTableColums - 1;
9861 this.hideColumnPriority[this.totalTableColums - 1] = this.totalTableColums - 1;
9864 return this.totalTableColums - 1;
9866 this.getIndex = function (headerName) {
9867 for (var i = 0; i < this.headers.length; i++) {
9868 if (this.headers[i].headerName === headerName) {
9869 return this.headers[i].index;
9874 this.setResponsiveRow = function (responsiveRowScope) {
9875 this.responsiveRowScopes.push(responsiveRowScope);
9877 $scope.nextSort = '';
9878 this.sortData = function (columnIndex, reverse, externalSort) {
9879 if ($scope.$parent && $scope.$parent !== undefined) {
9880 $scope.$parent.columnIndex = columnIndex;
9881 $scope.$parent.reverse = reverse;
9883 this.currentSortIndex = columnIndex;
9884 if (externalSort === true) {
9886 $scope.nextSort = 'd'
9888 $scope.nextSort = 'a'
9891 $scope.currentPage = 1;
9892 this.resetSortPattern();
9894 this.getSearchString = function () {
9895 return $scope.searchString;
9897 this.resetSortPattern = function () {
9898 for (var i = 0; i < this.headers.length; i++) {
9899 var currentScope = this.headers[i];
9900 if (currentScope.index !== this.currentSortIndex) {
9901 currentScope.resetSortPattern();
9906 $scope.$watch('nextSort', function (val) {
9907 if ($scope.$parent && $scope.$parent !== undefined) {
9908 $scope.$parent.nextSort = val;
9913 link: function (scope, elem, attr, ctrl) {
9914 scope.searchCriteria = {};
9915 scope.tableBreakpoints = attr.tableConfig ? scope.$parent.$eval(attr.tableConfig) : angular.copy(b2bTableConfig.tableBreakpoints);
9916 scope.$watchCollection('tableData', function (value) {
9917 if (value && !isNaN(value.length)) {
9918 scope.totalRows = value.length;
9921 scope.$watch('currentPage', function (val) {
9922 if (scope.$parent && scope.$parent !== undefined) {
9923 scope.$parent.currentPage = val;
9927 scope.$watch('viewPerPage', function (val) {
9928 if (scope.$parent && scope.$parent !== undefined) {
9929 scope.$parent.viewPerPage = val;
9932 scope.$watch('totalRows', function (val) {
9933 if (scope.$parent && scope.$parent !== undefined) {
9934 if (val > b2bTableConfig.zebraStripCutOff) {
9935 scope.$parent.zebraStripFlag = true;
9937 scope.$parent.zebraStripFlag = false;
9941 scope.$watch(function () {
9942 return scope.totalRows / scope.viewPerPage;
9943 }, function (value) {
9944 if (!isNaN(value)) {
9945 scope.totalPage = Math.ceil(value);
9946 scope.currentPage = 1;
9949 var searchValCheck = function (val) {
9950 if (angular.isDefined(val) && val !== null && val !== "") {
9954 var setSearchCriteria = function (v1, v2) {
9955 if (searchValCheck(v1) && searchValCheck(v2)) {
9956 var index = ctrl.getIndex(v2);
9957 scope.searchCriteria = {};
9958 if (index !== null) {
9959 scope.searchCriteria[index] = v1;
9961 } else if (searchValCheck(v1) && (!angular.isDefined(v2) || v2 === null || v2 === "")) {
9962 scope.searchCriteria = {
9966 scope.searchCriteria = {};
9969 scope.$watch('searchCategory', function (newVal, oldVal) {
9970 if (newVal !== oldVal) {
9971 setSearchCriteria(scope.searchString, newVal);
9974 scope.$watch('searchString', function (newVal, oldVal) {
9975 if (newVal !== oldVal) {
9976 setSearchCriteria(newVal, scope.searchCategory);
9979 scope.$watchCollection('searchCriteria', function (val) {
9980 if (scope.$parent && scope.$parent !== undefined) {
9981 scope.$parent.searchCriteria = val;
9983 scope.totalRows = scope.tableData && ($filter('filter')(scope.tableData, val, false)).length || 0;
9984 scope.currentPage = 1;
9986 var window = angular.element($window);
9987 var findMaxTableColumns = function () {
9989 windowWidth = $window.innerWidth;
9990 ctrl.maxTableColumns = -1;
9991 for (var i in scope.tableBreakpoints) {
9992 if (windowWidth >= scope.tableBreakpoints[i].min && windowWidth < scope.tableBreakpoints[i].max) {
9993 ctrl.maxTableColumns = scope.tableBreakpoints[i].columns;
9997 if (ctrl.maxTableColumns > -1 && ctrl.totalTableColums > ctrl.maxTableColumns) {
10000 ctrl.active = false;
10002 for (var i in ctrl.responsiveRowScopes) {
10003 ctrl.responsiveRowScopes[i].setActive(ctrl.active);
10006 var findHiddenColumn = function () {
10007 var columnDiffenence = ctrl.maxTableColumns > -1 ? ctrl.totalTableColums - ctrl.maxTableColumns : 0;
10008 ctrl.hiddenColumn = [];
10009 if (columnDiffenence > 0) {
10010 var tempHideColumnPriority = angular.copy(ctrl.hideColumnPriority);
10011 for (var i = 0; i < columnDiffenence; i++) {
10012 ctrl.hiddenColumn.push(tempHideColumnPriority.pop());
10016 var resizeListener = function () {
10017 findMaxTableColumns();
10018 findHiddenColumn();
10020 if (ctrl.responsive) {
10021 window.bind('resize', function () {
10025 $timeout(function () {
10032 .directive('b2bTableRow', [function () {
10035 compile: function (elem, attr) {
10036 if (attr.type === 'header') {
10038 } else if (attr.type === 'body') {
10039 var html = elem.children();
10040 if (attr.rowRepeat) {
10041 html.attr('ng-repeat', attr.rowRepeat.concat(" | orderBy : columnIndex : reverse | filter : searchCriteria : false "));
10043 html.attr('ng-class', "{'odd': $odd && zebraStripFlag}");
10044 html.attr('class', 'data-row');
10045 html.attr('b2b-responsive-row', '{{$index}}');
10051 .directive('b2bTableHeader', ['b2bTableConfig', function (b2bTableConfig) {
10061 require: '^b2bTable',
10062 templateUrl: function (elem, attr) {
10063 if (attr.sortable === 'false') {
10064 return 'b2bTemplate/tables/b2bTableHeaderUnsortable.html';
10066 return 'b2bTemplate/tables/b2bTableHeaderSortable.html';
10069 link: function (scope, elem, attr, ctrl) {
10070 var reverse = b2bTableConfig.defaultSortPattern;
10071 scope.headerName = elem.text();
10072 scope.headerId = elem.attr('id');
10073 scope.sortPattern = null;
10074 var priority = parseInt(attr.priority, 10);
10075 scope.columnIndex = ctrl.setIndex(scope, priority);
10077 scope.isHidden = function () {
10078 return (ctrl.hiddenColumn.indexOf(scope.columnIndex) > -1);
10081 scope.$watch(function () {
10082 return elem.text();
10083 }, function (value) {
10084 scope.headerName = value;
10086 scope.sort = function (sortType) {
10087 if (typeof sortType === 'boolean') {
10088 reverse = sortType;
10090 ctrl.sortData(scope.index, reverse, false);
10091 scope.sortPattern = reverse ? 'descending' : 'ascending';
10092 reverse = !reverse;
10094 scope.$watch(function () {
10095 return ctrl.currentSortIndex;
10096 }, function (value) {
10097 if (value !== scope.index) {
10098 scope.sortPattern = null;
10102 if (scope.sortable === undefined || scope.sortable === 'true' || scope.sortable === true) {
10103 scope.sortable = 'true';
10104 } else if (scope.sortable === false || scope.sortable === 'false') {
10105 scope.sortable = 'false';
10108 if (scope.sortable !== 'false') {
10109 if (scope.defaultSort === 'A' || scope.defaultSort === 'a') {
10111 } else if (scope.defaultSort === 'D' || scope.defaultSort === 'd') {
10115 scope.resetSortPattern = function () {
10116 reverse = b2bTableConfig.defaultSortPattern;
10121 .directive('b2bResponsiveRow', ['$templateCache', '$timeout', '$compile', function ($templateCache, $timeout, $compile) {
10124 require: '^b2bTable',
10125 controller: ['$scope', function ($scope) {
10126 this.rowValues = $scope.rowValues = [];
10127 this.setRowValues = function (rowValue) {
10128 this.rowValues.push(rowValue);
10130 var columnIndexCounter = -1;
10131 this.getIndex = function () {
10132 columnIndexCounter++;
10133 return columnIndexCounter;
10136 link: function (scope, elem, attr, ctrl) {
10137 if (ctrl.responsive) {
10138 scope.rowIndex = attr.b2bResponsiveRow;
10139 scope.active = false;
10140 scope.expandFlag = false;
10141 scope.headerValues = ctrl.headers;
10142 ctrl.setResponsiveRow(scope);
10143 var firstTd = elem.find('td').eq(0);
10144 scope.setActive = function (activeFlag) {
10145 scope.active = activeFlag;
10146 if (scope.active) {
10147 elem.addClass('has-button');
10148 firstTd.attr('role', 'rowheader');
10149 firstTd.parent().attr('role', 'row');
10151 elem.removeClass('has-button');
10152 firstTd.removeAttr('role');
10153 firstTd.parent().removeAttr('role');
10156 scope.toggleExpandFlag = function (expandFlag) {
10157 if (angular.isDefined(expandFlag)) {
10158 scope.expandFlag = expandFlag;
10160 scope.expandFlag = !scope.expandFlag;
10162 if (scope.expandFlag) {
10163 elem.addClass('opened');
10165 elem.removeClass('opened');
10169 firstTd.attr('scope', 'row');
10170 firstTd.addClass('col-1');
10171 scope.$on('$destroy', function () {
10172 elem.next().remove();
10174 $timeout(function () {
10175 scope.firstTdId = firstTd.attr('id');
10176 var firstTdContent = firstTd.html();
10177 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>';
10178 toggleButtonTemplate = $compile(toggleButtonTemplate)(scope);
10180 firstTd.prepend(toggleButtonTemplate);
10182 var template = $templateCache.get('b2bTemplate/tables/b2bResponsiveRow.html');
10183 template = $compile(template)(scope);
10184 elem.after(template);
10190 .directive('b2bResponsiveList', ['$templateCache', '$timeout', '$compile', function ($templateCache, $timeout, $compile) {
10193 require: '^b2bTable',
10194 link: function (scope, elem, attr, ctrl) {
10195 scope.columnIndex = parseInt(attr.b2bResponsiveList, 10);
10196 scope.isVisible = function () {
10197 return (ctrl.hiddenColumn.indexOf(scope.columnIndex) > -1);
10202 .directive('b2bTableBody', ['$filter', '$timeout', 'b2bTableConfig', function ($filter, $timeout, b2bTableConfig) {
10205 require: ['^b2bTable', '?^b2bResponsiveRow'],
10209 templateUrl: 'b2bTemplate/tables/b2bTableBody.html',
10210 link: function (scope, elem, attr, ctrl) {
10211 var b2bTableCtrl = ctrl[0];
10212 var b2bResponsiveRowCtrl = ctrl[1];
10213 var highlightSearchStringClass = b2bTableConfig.highlightSearchStringClass;
10214 var searchString = "";
10215 var wrapElement = function (elem) {
10216 var text = elem.text();
10217 elem.html($filter('b2bHighlight')(text, searchString, highlightSearchStringClass));
10219 var traverse = function (elem) {
10220 var innerHtml = elem.children();
10221 if (innerHtml.length > 0) {
10222 for (var i = 0; i < innerHtml.length; i++) {
10223 traverse(innerHtml.eq(i));
10230 var clearWrap = function (elem) {
10231 var elems = elem.find('*');
10232 for (var i = 0; i < elems.length; i++) {
10233 if (elems.eq(i).attr('class') && elems.eq(i).attr('class').indexOf(highlightSearchStringClass) !== -1) {
10234 var text = elems.eq(i).text();
10235 elems.eq(i).replaceWith(text);
10239 if (b2bResponsiveRowCtrl) {
10240 scope.columnIndex = b2bResponsiveRowCtrl.getIndex();
10241 scope.isHidden = function () {
10242 return (b2bTableCtrl.hiddenColumn.indexOf(scope.columnIndex) > -1);
10245 $timeout(function () {
10246 var actualHtml = elem.children();
10247 scope.$watch(function () {
10248 return b2bTableCtrl.getSearchString();
10249 }, function (val) {
10250 searchString = val;
10252 if (actualHtml.length > 0) {
10258 if (b2bResponsiveRowCtrl) {
10259 b2bResponsiveRowCtrl.setRowValues(elem.html());
10265 .directive('b2bTableSort', ['b2bTableConfig','$timeout', function (b2bTableConfig,$timeout) {
10269 require: '^b2bTable',
10270 link: function (scope, elem, attr, ctrl) {
10271 var initialSort = '',
10274 initialSort = attr.initialSort;
10276 scope.sortTable = function (msg) {
10277 $timeout(function(){
10278 if (nextSort.length > 0) {
10280 if (nextSort === 'd' || nextSort === 'D') {
10281 tempsort = nextSort
10282 ctrl.sortData(msg, true, true);
10284 $timeout(function(){
10285 if(!angular.isUndefined(elem[0].querySelector('.sortButton')) || elem[0].querySelector('.sortButton') !== null ){
10286 angular.element(elem[0].querySelector('.sortButton'))[0].focus();
10291 tempsort = nextSort
10292 ctrl.sortData(msg, false, true);
10294 $timeout(function(){
10295 if(!angular.isUndefined(elem[0].querySelector('.sortButton')) || elem[0].querySelector('.sortButton') !== null ){
10296 angular.element(elem[0].querySelector('.sortButton'))[0].focus();
10300 } else if (initialSort.length > 0) {
10302 if (initialSort === 'd' || initialSort === 'D') {
10303 tempsort = nextSort
10304 ctrl.sortData(msg, true, true);
10306 $timeout(function(){
10307 if(!angular.isUndefined(elem[0].querySelector('.sortButton')) || elem[0].querySelector('.sortButton') !== null ){
10308 angular.element(elem[0].querySelector('.sortButton'))[0].focus();
10313 tempsort = nextSort
10314 ctrl.sortData(msg, false, true);
10316 $timeout(function(){
10317 if(!angular.isUndefined(elem[0].querySelector('.sortButton')) || elem[0].querySelector('.sortButton') !== null ){
10318 angular.element(elem[0].querySelector('.sortButton'))[0].focus();
10329 scope.sortDropdown = function(msg) {
10331 if(tempsort==='') {
10335 if(tempsort === 'd' || tempsort === 'D' ) {
10336 ctrl.sortData(msg, true, false);
10338 ctrl.sortData(msg, false, false);
10347 * @name Tabs, tables & accordions.att:tabs
10350 * <file src="src/tabs/docs/readme.md" />
10353 * <b2b-tabset tab-id-selected="activeTabsId">
10354 <b2b-tab ng-repeat="tab in gTabs" tab-item="tab"
10355 id="{{tab.uniqueId}}" aria-controls="{{tab.tabPanelId}}"
10356 ng-disabled="tab.disabled">
10362 * <section id="code">
10363 <example module="b2b.att">
10364 <file src="src/tabs/docs/demo.html" />
10365 <file src="src/tabs/docs/demo.js" />
10371 angular.module('b2b.att.tabs', ['b2b.att.utilities'])
10372 .directive('b2bTabset', function () {
10380 templateUrl: 'b2bTemplate/tabs/b2bTabset.html',
10381 controller: ['$scope', function ($scope) {
10383 this.setTabIdSelected = function (tab) {
10384 $scope.tabIdSelected = tab.id;
10387 this.getTabIdSelected = function () {
10388 return $scope.tabIdSelected;
10393 .directive('b2bTab', ['keymap', function (keymap) {
10398 require: '^b2bTabset',
10402 templateUrl: 'b2bTemplate/tabs/b2bTab.html',
10403 controller: [function(){}],
10404 link: function (scope, element, attr, b2bTabsetCtrl) {
10406 if (scope.tabItem && !scope.tabItem.disabled) {
10407 scope.tabItem.disabled = false;
10410 scope.isTabActive = function () {
10411 return (scope.tabItem.id === b2bTabsetCtrl.getTabIdSelected());
10414 scope.clickTab = function () {
10415 if (attr.disabled) {
10418 b2bTabsetCtrl.setTabIdSelected(scope.tabItem);
10421 scope.nextKey = function () {
10422 var el = angular.element(element[0])[0];
10423 var elementToFocus = null;
10424 while (el && el.nextElementSibling) {
10425 el = el.nextElementSibling;
10426 if (!el.querySelector('a').disabled) {
10427 elementToFocus = el.querySelector('a');
10432 if (!elementToFocus) {
10433 var childTabs = element.parent().children();
10434 for (var i = 0; i < childTabs.length; i++) {
10435 if (!childTabs[i].querySelector('a').disabled) {
10436 elementToFocus = childTabs[i].querySelector('a');
10442 if (elementToFocus) {
10443 elementToFocus.focus();
10447 scope.previousKey = function () {
10448 var el = angular.element(element[0])[0];
10449 var elementToFocus = null;
10451 while (el && el.previousElementSibling) {
10452 el = el.previousElementSibling;
10453 if (!el.querySelector('a').disabled) {
10454 elementToFocus = el.querySelector('a');
10459 if (!elementToFocus) {
10460 var childTabs = element.parent().children();
10461 for (var i = childTabs.length - 1; i > 0; i--) {
10462 if (!childTabs[i].querySelector('a').disabled) {
10463 elementToFocus = childTabs[i].querySelector('a');
10469 if (elementToFocus) {
10470 elementToFocus.focus();
10474 angular.element(element[0].querySelector('a')).bind('keydown', function (evt) {
10476 if (!(evt.keyCode)) {
10477 evt.keyCode = evt.which;
10480 switch (evt.keyCode) {
10481 case keymap.KEY.RIGHT:
10482 evt.preventDefault();
10486 case keymap.KEY.LEFT:
10487 evt.preventDefault();
10488 scope.previousKey();
10499 * @name Messages, modals & alerts.att:tagBadges
10502 * <file src="src/tagBadges/docs/readme.md" />
10505 * <section id="code">
10506 <example module="b2b.att">
10507 <file src="src/tagBadges/docs/demo.html" />
10508 <file src="src/tagBadges/docs/demo.js" />
10513 angular.module('b2b.att.tagBadges', ['b2b.att.utilities'])
10514 .directive('b2bTagBadge',['$timeout',function($timeout){
10517 link: function(scope,elem,attr,ctrl){
10518 elem.addClass('b2b-tags');
10519 if(angular.element(elem[0].querySelector('.icon-primary-close')).length>0) {
10520 var item = angular.element(elem[0].querySelector('.icon-primary-close'));
10521 item.bind('click',function(){
10522 elem.css({'height':'0','width':'0','padding':'0','border':'0'});
10523 elem.attr('tabindex','0');
10525 item.parent().remove();
10526 elem[0].bind('blur',function(){
10540 * @name Forms.att:textArea
10543 * <file src="src/textArea/docs/readme.md" />
10546 * <textarea b2b-reset b2b-reset-textarea ng-model="textAreaModel" ng-disabled="disabled" ng-trim="false" placeholder="{{placeholderText}}" rows="{{textAreaRows}}" maxlength="{{textAreaMaxlength}}" role="textarea"></textarea>
10549 <section id="code">
10550 <b>HTML + AngularJS</b>
10551 <example module="b2b.att">
10552 <file src="src/textArea/docs/demo.html" />
10553 <file src="src/textArea/docs/demo.js" />
10557 angular.module('b2b.att.textArea', ['b2b.att.utilities'])
10559 .directive('b2bResetTextarea', [ function () {
10562 require: 'b2bReset',
10563 link: function (scope, element, attrs, ctrl) {
10565 var resetButton = ctrl.getResetButton();
10567 var computeScrollbarAndAddClass = function () {
10568 if (element.prop('scrollHeight') > element[0].clientHeight) {
10569 element.addClass('hasScrollbar');
10571 element.removeClass('hasScrollbar');
10575 computeScrollbarAndAddClass();
10577 element.on('focus keyup', function(){
10578 computeScrollbarAndAddClass();
10586 * @name Forms.att:tooltipsForForms
10589 * <file src="src/tooltipsForForms/docs/readme.md" />
10592 <example module="b2b.att">
10593 <file src="src/tooltipsForForms/docs/demo.html" />
10594 <file src="src/tooltipsForForms/docs/demo.js" />
10597 angular.module('b2b.att.tooltipsForForms', ['b2b.att.utilities'])
10598 .directive('b2bTooltip', ['$document', '$window', '$isElement', function ($document, $window, $isElement) {
10601 link: function (scope, elem, attr, ctrl) {
10602 var icon = elem[0].querySelector('a.tooltip-element');
10603 var btnIcon = elem[0].querySelector('.btn.tooltip-element');
10604 var tooltipText = elem[0].querySelector('.helpertext');
10605 var tooltipWrapper = elem[0].querySelector('.tooltip-size-control');
10606 if (elem.hasClass('tooltip-onfocus')) {
10607 var inputElm = angular.element(elem[0].querySelector("input"));
10608 var textAreaElm = angular.element(elem[0].querySelector("textarea"));
10610 angular.element(icon).attr({'aria-expanded': false});
10611 angular.element(btnIcon).attr({'aria-expanded': false});
10612 var calcTooltip = function () {
10613 if (!elem.hasClass('tooltip active')) {
10614 if (elem.hasClass('tooltip-onfocus')) {
10615 angular.element(elem[0].querySelector("input")).triggerHandler('focusout');
10617 if (elem.hasClass('tooltip-onclick')) {
10620 angular.element(icon).removeClass('active');
10621 angular.element(icon).attr({'aria-expanded': true});
10622 angular.element(icon).attr({'aria-describedby': angular.element(tooltipText).attr('id')});
10623 angular.element(tooltipText).attr({'aria-hidden': false});
10624 elem.addClass('active');
10626 var tooltipIconPos = angular.element(icon).prop('offsetLeft'),
10627 tooltipPosition = angular.element(tooltipText).prop('offsetWidth') / 2,
10628 tipOffset = (tooltipIconPos - 30) - tooltipPosition,
10629 maxRightPos = (($window.innerWidth - 72) - (tooltipPosition * 2)) - 14.5;
10631 if ($window.innerWidth >= '768') {
10632 if (tipOffset < 0) {// if icon on far left side of page
10635 else if (tooltipIconPos > maxRightPos) {// if icon is far right side of page
10636 tipOffset = maxRightPos;
10638 else {// if tooltip in the middle somewhere
10639 tipOffset = tipOffset;
10641 angular.element(tooltipWrapper).css({left: tipOffset + 'px'});
10646 // TOOLTIP LINK ONCLICK AND FOCUS
10647 angular.element(icon).on('click mouseover mouseout focus blur', function (e) {
10648 if (e.type == 'mouseover') {
10651 else if (e.type == 'mouseout' && elem.hasClass('active')) {
10652 if (!elem.hasClass('activeClick')) {
10653 angular.element(tooltipText).attr({
10654 'aria-hidden': true,
10657 elem.removeClass('active');
10658 } else if (elem.hasClass('activeClick') && navigator.userAgent.match(/iphone/i)) {
10659 elem.removeClass('active activeClick');
10664 if (elem.hasClass('activeClick')) {
10665 angular.element(icon).attr({'aria-expanded': false});
10666 angular.element(tooltipText).attr({'aria-hidden': true});
10667 angular.element(icon).removeAttr('aria-describedby');
10668 elem.removeClass('activeClick active');
10669 e.preventDefault();
10671 else if (e.type == 'click') {
10672 elem.addClass('activeClick');
10674 e.preventDefault();
10677 angular.element(icon).on('keydown', function (e) {
10678 if (e.keyCode == '32') {
10679 (elem.hasClass('active')) ? elem.removeClass('active') : elem.addClass('value');
10680 angular.element(icon).triggerHandler('click');
10681 e.preventDefault();
10682 } else if (e.keyCode == '27') {
10683 (elem.hasClass('active')) ? elem.removeClass('active activeClick') : elem.addClass('value');
10686 e.preventDefault();
10689 e.preventDefault();
10692 // TOOLTIP BUTTON INSIDE A TEXT FIELD
10693 angular.element(btnIcon).on('click', function (e) {
10694 var $this = angular.element(this);
10695 if ($this.hasClass('active') && elem.hasClass('tooltip-onclick')) {
10696 elem.removeClass('active');
10697 $this.removeClass('active');
10698 angular.element(tooltipText).removeAttr('aria-live');
10699 $this.attr({'aria-expanded': 'false'});
10700 $this.removeAttr('aria-describedby');
10702 elem.addClass('active');
10703 $this.addClass('active');
10704 $this.attr({'aria-expanded': 'true', 'aria-describedby': angular.element(tooltipText).attr('id')});
10705 angular.element(tooltipText).attr({'aria-live': 'polite'});
10709 angular.element(btnIcon).on('blur', function (e) {
10710 var $this = angular.element(this);
10711 if ($this.hasClass('active') && elem.hasClass('tooltip-onclick')) {
10712 elem.removeClass('active');
10713 $this.removeClass('active');
10714 angular.element(tooltipText).removeAttr('aria-live');
10715 $this.attr({'aria-expanded': 'false'});
10716 $this.removeAttr('aria-describedby');
10720 angular.element(btnIcon).on('keydown', function (e) {
10721 var $this = angular.element(this);
10722 if (e.keyCode == '27') {
10723 var $this = angular.element(this);
10724 if ($this.hasClass('active') && elem.hasClass('tooltip-onclick')) {
10725 elem.removeClass('active');
10726 $this.removeClass('active');
10727 angular.element(tooltipText).removeAttr('aria-live');
10728 $this.attr({'aria-expanded': 'false'});
10729 $this.removeAttr('aria-describedby');
10734 // close all tooltips if clicking something else
10735 $document.bind('click', function (e) {
10736 var isElement = $isElement(angular.element(e.target), elem, $document);
10738 elem.removeClass('active');
10739 angular.element(elem[0].querySelector('.tooltip-element')).removeClass('active');
10740 angular.element(tooltipText).removeAttr('aria-live');
10741 angular.element(elem[0].querySelector('.tooltip-element')).attr({'aria-expanded': 'false'});
10742 angular.element(elem[0].querySelector('.tooltip-element')).removeAttr('aria-describedby');
10746 angular.element(inputElm).on('keydown', function (e) {
10747 if (e.keyCode == '27'){
10748 elem.removeClass('active');
10749 angular.element(tooltipText).css('display', 'none');
10750 angular.element(tooltipText).removeAttr('aria-live');
10752 if (angular.element(this).attr('aria-describedby') === undefined){
10756 else if ((spaceIndex = angular.element(this).attr('aria-describedby').lastIndexOf(' ')) >= 0){
10758 var describedByValue = angular.element(this).attr('aria-describedby').slice(0, spaceIndex);
10760 angular.element(this).attr('aria-describedby', describedByValue);
10764 angular.element(this).removeAttr('aria-describedby');
10769 angular.element(textAreaElm).on('keydown', function (e) {
10770 if (e.keyCode == '27'){
10771 elem.removeClass('active');
10772 angular.element(tooltipText).css('display', 'none');
10773 angular.element(tooltipText).removeAttr('aria-live');
10774 if (angular.element(this).attr('aria-describedby') === undefined){
10778 else if ((spaceIndex = angular.element(this).attr('aria-describedby').lastIndexOf(' ')) >= 0){
10780 var describedByValue = angular.element(this).attr('aria-describedby').slice(0, spaceIndex);
10782 angular.element(this).attr('aria-describedby', describedByValue);
10786 angular.element(this).removeAttr('aria-describedby');
10791 // TOOLTIP TRIGGERED AUTOMATICALLY INSIDE A TEXT FIELD
10792 angular.element(inputElm).on('focus', function (e) {
10793 var allTooltip = $document[0].querySelectorAll('[class*="tooltip"]');
10794 for (var i = 0; i < allTooltip.length; i++) {
10795 if (angular.element(allTooltip[i]).hasClass('active')) {
10796 angular.element(allTooltip[i]).triggerHandler('click');
10799 angular.element(this).attr({'aria-describedby': angular.element(tooltipText).attr('id')});
10800 angular.element(tooltipText).css('display', 'block');
10801 angular.element(tooltipText).attr({'aria-live': 'polite'});
10802 elem.addClass('active');
10804 angular.element(inputElm).on('blur', function (e) {
10805 elem.removeClass('active');
10806 angular.element(tooltipText).css('display', 'none');
10807 angular.element(tooltipText).removeAttr('aria-live');
10808 angular.element(this).removeAttr('aria-describedby');
10811 // TOOLTIP TRIGGERED AUTOMATICALLY INSIDE A TEXTAREA
10812 angular.element(textAreaElm).on('focus', function (e) {
10813 var allTooltip = $document[0].querySelectorAll('[class*="tooltip"]');
10814 for (var i = 0; i < allTooltip.length; i++) {
10815 if (angular.element(allTooltip[i]).hasClass('active')) {
10816 angular.element(allTooltip[i]).triggerHandler('click');
10819 elem.addClass('active');
10820 angular.element(tooltipText).css('display', 'block');
10821 angular.element(tooltipText).attr({'aria-live': 'polite'});
10822 angular.element(this).attr({'aria-describedby': angular.element(tooltipText).attr('id')});
10824 angular.element(textAreaElm).on('blur', function (e) {
10825 elem.removeClass('active');
10826 angular.element(tooltipText).css('display', 'none');
10827 angular.element(tooltipText).removeAttr('aria-live');
10828 angular.element(this).removeAttr('aria-describedby');
10835 * @name Navigation.att:TreeNavigation
10839 * @param {String} setRole - This value needs to be "tree". This is required to incorporate CATO requirements.
10840 * @param {Boolean} groupIt - This value needs to be "false" for top-level tree rendered.
10843 * <file src="src/treeNav/docs/readme.md" />
10846 * <div class="b2b-tree">
10847 * <b2b-tree-nav collection="treeStructure" set-role="tree" group-it="false"></b2b-tree-nav>
10850 * <section id="code">
10851 <example module="b2b.att">
10852 <file src="src/treeNav/docs/demo.html" />
10853 <file src="src/treeNav/docs/demo.js" />
10858 angular.module('b2b.att.treeNav', ['b2b.att.utilities'])
10859 .directive('b2bTreeNav', function () {
10868 templateUrl: function (element, attrs) {
10869 if (attrs.groupIt === 'true') {
10870 return "b2bTemplate/treeNav/groupedTree.html";
10872 return "b2bTemplate/treeNav/ungroupedTree.html";
10875 link: function (scope) {
10876 if (!(scope.setRole === 'tree')) {
10877 scope.setRole = 'group';
10882 .directive('b2bMember', ['$compile', '$timeout', 'keymap', function ($compile, $timeout, keymap) {
10890 templateUrl: 'b2bTemplate/treeNav/treeMember.html',
10891 link: function (scope, element, attrs) {
10892 scope.elemArr = [];
10893 var removeRootTabIndex = function (elem) {
10894 if (elem.parent().parent().eq(0).hasClass('b2b-tree')) {
10895 elem.attr('tabindex', -1);
10898 removeRootTabIndex(elem.parent());
10900 scope.$watch('member.child', function(newVal, oldVal){
10901 if(newVal !== oldVal){
10905 scope.showChild = function () {
10906 if (!element.hasClass('grouped')) {
10907 if (angular.isArray(scope.member.child) && scope.member.child.length > 0 && (scope.member.divide === undefined || scope.member.child.length < scope.member.divide)) {
10908 scope.groupIt = false;
10909 element.addClass('grouped');
10910 element.append("<b2b-tree-nav collection='member.child' group-it='" + scope.groupIt + "'></b2b-tree-nav>");
10911 $compile(element.contents())(scope);
10912 if(scope.member.active && scope.member.active === true){
10913 element.find('i').eq(0).removeClass('icon-primary-collapsed');
10915 if(scope.member.selected && scope.member.selected === true){
10916 element.attr('aria-selected', true);
10917 element.attr('tabindex', 0);
10918 removeRootTabIndex(element);
10920 if(scope.member.active && scope.member.active == undefined){
10921 element.find('i').eq(0).addClass('icon-primary-collapsed');
10923 } else if (scope.member.child && scope.member.divide && scope.member.child.length > scope.member.divide) {
10924 element.addClass('grouped');
10925 scope.groupIt = true;
10926 // FILTER - GROUPBY - APPROACH
10929 if(scope.member.child[0].groupName !== undefined){
10930 grpName = scope.member.child[0].groupName;
10933 var toSlice = scope.member.child[0].name.search(' ');
10934 grpName = scope.member.child[0].name.slice(0, toSlice);
10937 for (i = 0; i < scope.member.child.length; i += scope.member.divide) {
10939 for (j = j + i; j < (i + scope.member.divide); j++) {
10940 if (j === scope.member.child.length) {
10941 scope.member.child[j - 1].grpChild = grpName + ' ' + (i + 1) + ' - ' + (scope.member.child.length);
10944 if(scope.member.child[j-1].active && scope.member.child[j-1].active===true){
10945 scope.member.child[j-1].activeGrp = true;
10949 if (i + scope.member.divide > scope.member.child.length) {
10950 scope.member.child[j].grpChild = grpName + ' ' + (i + 1) + ' - ' + (scope.member.child.length);
10951 if(scope.member.child[j].active && scope.member.child[j].active===true){
10952 scope.member.child[j].activeGrp = true;
10956 scope.member.child[j].grpChild = grpName + ' ' + (i + 1) + ' - ' + (i + scope.member.divide);
10957 if(scope.member.child[j].active && scope.member.child[j].active===true){
10958 scope.member.child[j].activeGrp = true;
10963 if(scope.member.divide){
10964 element.append("<b2b-tree-nav collection='member.child' group-it='" + scope.groupIt + "'></b2b-tree-nav>");
10966 element.append("<b2b-tree-nav collection='member.child' group-it='" + scope.groupIt + "'></b2b-tree-nav>");
10968 $compile(element.contents())(scope);
10969 if(scope.member.active && scope.member.active === true){
10970 element.find('i').eq(0).removeClass('icon-primary-collapsed');
10972 if(scope.member.selected && scope.member.selected === true){
10973 element.attr('aria-selected', true);
10975 if( scope.member.active && scope.member.active == undefined){
10976 element.find('i').eq(0).addClass('icon-primary-collapsed');
10981 //Below condition opens node for opening on json load.
10982 if(scope.member.active && scope.member.active == true){
10985 if(scope.member.active == undefined && !element.find('a').eq(0).hasClass('active') && scope.member.child !== undefined){
10986 element.find('i').eq(0).addClass('icon-primary-collapsed');
10988 else if(scope.member.child == undefined){
10989 element.find('i').eq(0).addClass('icon-primary-circle');
10991 element.bind('keydown', function (evt) {
10992 switch (evt.keyCode) {
10993 case keymap.KEY.ENTER:
10994 if (element.hasClass('bg') && scope.member.onSelect !== undefined) {
10995 scope.member.onSelect(scope.member);
10997 evt.stopPropagation();
11004 //else getting true in every case .. so better use switch case .. that makes more sense you dumb.
11005 element.bind('click', function (evt) {
11007 var expandFunc = scope.member.onExpand;
11010 if (element.hasClass('bg') && scope.member.onSelect !== undefined) {
11011 scope.member.onSelect(scope.member);
11013 if (element.find('a').eq(0).hasClass('active') && scope.member.onExpand !== undefined) {
11014 var eValue = scope.member.onExpand(scope.member);
11016 if (!element.find('a').eq(0).hasClass('active') && scope.member.onCollapse !== undefined) {
11017 scope.member.onCollapse(scope.member);
11023 .directive('b2bTreeLink', ['keymap', '$timeout', function (keymap, $timeout) {
11026 link: function (scope, element, attr, ctrl) {
11027 var rootE, parentE, upE, downE;
11028 var closeOthersUp = function (elem,isKeyPress,passiveClose) {
11029 //For accordion functionality on sibling nodes
11030 if (elem.find('a').eq(0).hasClass('active')) {
11031 activeToggle(elem,isKeyPress,passiveClose);
11034 if (elem.hasClass('bg') && !isKeyPress) {
11035 elem.removeClass('bg');
11036 if (elem.attr('aria-selected')) {
11037 elem.attr('aria-selected', 'false');
11040 if (elem[0].previousElementSibling !== null) {
11041 closeOthersUp(angular.element(elem[0].previousElementSibling),isKeyPress);
11044 var closeOthersDown = function (elem,isKeyPress,passiveClose) {
11045 //For accordion functionality on sibling nodes
11046 if (elem.find('a').eq(0).hasClass('active')) {
11047 activeToggle(elem,isKeyPress,passiveClose);
11050 if (elem.hasClass('bg') && !isKeyPress) {
11051 elem.removeClass('bg');
11052 if (elem.attr('aria-selected')) {
11053 elem.attr('aria-selected', 'false');
11056 if (elem[0].nextElementSibling !== null) {
11057 closeOthersDown(angular.element(elem[0].nextElementSibling),isKeyPress);
11062 var removeBackground = function(elem){
11064 if(elem.hasClass('b2b-tree')){
11065 angular.element(elem[0].getElementsByClassName('bg')).removeClass('bg');
11068 removeBackground(elem.parent().parent());
11074 * These two functions used for setting heights on parent nodes as the child node closes
11075 * Retaining this code for future reference
11077 var addParentHeight = function(e, h) {
11078 var parentLi = e.parent().parent();
11079 var parentUl = e.parent();
11080 if(!parentLi.hasClass('b2b-tree')) {
11081 var addHeight = parentUl[0].offsetHeight + h;
11082 parentLi.find('ul').eq(0).css({
11083 height: addHeight+'px'
11085 addParentHeight(parentLi, h);
11089 var removeParentHeight = function(e, h) {
11090 var parentLi = e.parent().parent();
11091 var parentUl = e.parent();
11092 if(!parentLi.hasClass('b2b-tree')) {
11093 var addHeight = parentUl[0].offsetHeight - h;
11094 parentLi.find('ul').eq(0).css({
11095 height: addHeight+'px'
11097 removeParentHeight(parentLi, h);
11102 // isKeyPress - to notify that the function is called by Right Key press
11103 // passiveClose - prevents firing of oncollapse events during the action
11104 // of expand function(check the function definition)
11106 var activeToggle = function (elem,isKeyPress,passiveClose) {
11107 var element = elem.find('a').eq(0);
11108 if (element.hasClass('active')) {
11110 elem.removeClass('bg');
11113 if (elem.attr('aria-selected') && !isKeyPress) {
11114 elem.attr('aria-selected', 'false');
11116 if (!element.find('i').eq(0).hasClass('icon-primary-circle')) {
11117 if(isKeyPress && scope.member){
11118 if (scope.member.onCollapse !== undefined && !passiveClose) {
11119 scope.member.onCollapse(scope.member);
11122 element.removeClass('active');
11123 elem.attr('aria-expanded', 'false');
11124 element.find('i').eq(0).removeClass('icon-primary-expanded');
11125 element.find('i').eq(0).addClass('icon-primary-collapsed');
11126 //For Animation: below commented code is used to manually set height of UL to zero
11127 //retaining code for future reference
11129 var totalHeight = elem.find('ul')[0].scrollHeight;
11130 removeParentHeight(elem, totalHeight);
11131 elem.find('ul').eq(0).css({
11137 elem.addClass('bg');
11138 elem.attr('aria-selected', 'true');
11141 if (!element.find('i').eq(0).hasClass('icon-primary-circle')) {
11143 if(typeof scope.showChild === 'function' ){
11147 if (scope.member.onExpand !== undefined) {
11148 scope.member.onExpand(scope.member);
11152 element.addClass('active');
11153 elem.attr('aria-expanded', 'true');
11154 element.find('i').eq(0).removeClass('icon-primary-collapsed');
11155 element.find('i').eq(0).addClass('icon-primary-expanded');
11156 //For Animation: below commented code is used to manually set height of the ul generatedon the click of parent LI.
11157 //retaining code for future reference
11159 var totalHeight = elem.find('ul')[0].scrollHeight;
11160 addParentHeight(elem, totalHeight);
11161 elem.find('ul').eq(0).css({
11162 height: totalHeight+'px'
11168 element.bind('click', function (evt) {
11169 //first we close others and then we open the clicked element
11170 if (element[0].previousElementSibling) {
11171 closeOthersUp(angular.element(element[0].previousElementSibling));
11173 if (element[0].nextElementSibling) {
11174 closeOthersDown(angular.element(element[0].nextElementSibling));
11176 removeBackground(element);
11177 activeToggle(element);
11179 evt.stopPropagation();
11181 //default root tree element tabindex set zero
11182 if (element.parent().parent().hasClass('b2b-tree') && (element.parent()[0].previousElementSibling === null)) {
11183 element.attr('tabindex', 0);
11185 //check root via class
11186 var isRoot = function (elem) {
11187 if (elem.parent().parent().eq(0).hasClass('b2b-tree')) {
11193 var findRoot = function (elem) {
11194 if (isRoot(elem)) {
11198 findRoot(elem.parent());
11201 var findPreActive = function (elem) {
11203 if (!(elem.hasClass("active"))) {
11206 var childElems = angular.element(elem[0].nextElementSibling.children);
11207 lastE = angular.element(childElems[childElems.length - 1]);
11208 if (lastE.find('a').eq(0).hasClass('active')) {
11209 findPreActive(lastE.find('a').eq(0));
11215 var findUp = function (elem) {
11216 if (isRoot(elem)) {
11220 if (elem[0].previousElementSibling !== null && !angular.element(elem[0].previousElementSibling).hasClass('tree-hide')) {
11221 upE = angular.element(elem[0].previousElementSibling);
11222 if (upE.find('a').eq(0).hasClass('active')) {
11223 findPreActive(upE.find('a').eq(0));
11226 upE = elem.parent().parent();
11230 var downElement = function (elem) {
11231 if (elem.next().hasClass('tree-hide')) {
11232 downElement(elem.next());
11234 downE = elem.next();
11237 var isBottomElem = false;
11238 var downParent = function(liElem){
11239 if(liElem.eq(0).parent().parent().eq(0).hasClass('b2b-tree')){
11240 isBottomElem = true;
11243 if(liElem.next().length !== 0){
11244 downE = liElem.next().eq(0);
11248 downParent(liElem.parent().parent());
11252 var findDown = function (elem) {
11253 if (isRoot(elem.parent()) && !elem.hasClass('active')) {
11254 downE = elem.parent();
11257 if (elem.hasClass('active')) {
11258 downE = elem.next().find('li').eq(0);
11259 if (downE.hasClass('tree-hide')) {
11260 downElement(downE);
11264 downParent(elem.parent());
11265 if(isBottomElem === true){
11266 downE = elem.parent();
11267 isBottomElem = false;
11273 var resetTabPosition = function(element){
11275 angular.element(rootE.parent().parent()[0].querySelector("li[tabindex='0']")).attr('tabindex','-1');
11276 var elemToFocus = rootE.parent().parent()[0].querySelector(".bg")|| rootE;
11278 angular.element(elemToFocus).attr('tabindex','0');
11280 // Function to control the expansion of nodes when the user tabs into the tree and
11281 // the slected node is not visible
11282 var expand = function(elemArr){
11283 var elem= elemArr.pop();
11284 var element = elem.find('a').eq(0);
11285 var selectedNode = elem.parent().parent()[0].querySelector(".bg");
11286 if(selectedNode != null){
11288 element = elem.find('a').eq(0);
11289 if(!element.hasClass('active') ){
11292 if (elem[0].previousElementSibling) {
11293 closeOthersUp(angular.element(elem[0].previousElementSibling),true,true);
11295 if (elem[0].nextElementSibling) {
11296 closeOthersDown(angular.element(elem[0].nextElementSibling),true,true);
11299 if (!element.find('i').eq(0).hasClass('icon-primary-circle')) {
11300 if(typeof scope.showChild === 'function' ){
11303 element.addClass('active');
11304 elem.attr('aria-expanded', 'true');
11305 element.find('i').eq(0).removeClass('icon-primary-collapsed');
11306 element.find('i').eq(0).addClass('icon-primary-expanded');
11310 elem = elemArr.pop();
11318 element.find('a').eq(0).bind('mouseenter', function (evt) {
11319 angular.forEach(document.querySelectorAll('.activeTooltip'), function(value, key) {
11320 angular.element(value).removeClass('activeTooltip')
11322 element.addClass('activeTooltip');
11324 element.find('a').eq(0).bind('mouseleave', function (evt) {
11325 element.removeClass('activeTooltip');
11327 element.bind('focus', function (evt) {
11328 angular.forEach(document.querySelectorAll('.activeTooltip'), function(value, key) {
11329 angular.element(value).removeClass('activeTooltip')
11331 element.addClass('activeTooltip');
11333 element.bind('blur', function (evt) {
11334 element.removeClass('activeTooltip');
11336 element.bind('keydown', function (evt) {
11337 switch (evt.keyCode) {
11338 case keymap.KEY.HOME:
11339 evt.preventDefault();
11340 evt.stopPropagation();
11341 element.attr('tabindex', -1);
11343 rootE.eq(0).attr('tabindex', 0);
11346 case keymap.KEY.LEFT:
11347 evt.preventDefault();
11348 evt.stopPropagation();
11350 if(element.find('a').eq(0).hasClass('active')){
11351 if (element[0].previousElementSibling) {
11352 closeOthersUp(angular.element(element[0].previousElementSibling),true);
11354 if (element[0].nextElementSibling) {
11355 closeOthersDown(angular.element(element[0].nextElementSibling),true);
11357 activeToggle(element,true);
11360 element.attr('tabindex', -1);
11361 parentE = element.parent().parent();
11362 parentE.attr('tabindex', 0);
11363 parentE[0].focus();
11365 case keymap.KEY.UP:
11366 evt.preventDefault();
11367 evt.stopPropagation();
11368 element.attr('tabindex', -1);
11370 upE.eq(0).attr('tabindex', 0);
11373 case keymap.KEY.RIGHT:
11374 evt.preventDefault();
11375 evt.stopPropagation();
11376 if(element.find('i').eq(0).hasClass('icon-primary-circle')){
11379 if (!element.find('a').eq(0).hasClass('active')) {
11380 if (element[0].previousElementSibling) {
11381 closeOthersUp(angular.element(element[0].previousElementSibling),true);
11383 if (element[0].nextElementSibling) {
11384 closeOthersDown(angular.element(element[0].nextElementSibling),true);
11386 activeToggle(element,true);
11390 element.attr('tabindex', -1);
11391 findDown(element.find('a').eq(0));
11392 downE.eq(0).attr('tabindex', 0);
11396 case keymap.KEY.DOWN:
11397 evt.preventDefault();
11398 element.attr('tabindex', -1);
11399 findDown(element.find('a').eq(0));
11400 downE.eq(0).attr('tabindex', 0);
11402 evt.stopPropagation();
11404 case keymap.KEY.ENTER:
11405 var isSelectedElem = element.hasClass('bg');
11406 var enterFunc = function(element){
11407 if (isSelectedElem) {
11408 element.removeClass('bg');
11409 if (element.attr('aria-selected')) {
11410 element.attr('aria-selected', 'false');
11414 element.addClass('bg');
11415 element.attr('aria-selected', 'true');
11418 if (element[0].previousElementSibling) {
11419 closeOthersUp(angular.element(element[0].previousElementSibling));
11421 if (element[0].nextElementSibling) {
11422 closeOthersDown(angular.element(element[0].nextElementSibling));
11425 removeBackground(element);
11426 enterFunc(element);
11427 evt.stopPropagation();
11429 case keymap.KEY.TAB:
11430 $timeout(function(){
11431 resetTabPosition(element);
11433 evt.stopPropagation();
11440 element.bind('keyup',function(evt){
11441 if(evt.keyCode === keymap.KEY.TAB){
11443 var tempElem = element;
11445 while(!tempElem.hasClass('b2b-tree')){
11446 elemArr.push(tempElem);
11447 tempElem = tempElem.parent().parent();
11449 elemArr.push(tempElem);
11453 evt.stopPropagation();
11460 * @name Navigation.att:Tree nodes with checkboxes
11462 * @param {String} setRole - The value needs to be "tree". This is required to incorporate CATO requirements.
11463 * @param {boolean} groupIt - The value needs to be "false" for top-level tree rendered.
11464 * @param {Object} collection - The JSON object of tree to be rendered.
11466 * <file src="src/treeNodeCheckbox/docs/readme.md" />
11469 * <div class="b2b-tree-checkbox">
11470 * <b2b-tree-node-checkbox collection="treeStructure" set-role="tree" group-it="false"></b2b-tree-node-checkbox>
11473 * <section id="code">
11474 <example module="b2b.att">
11475 <file src="src/treeNodeCheckbox/docs/demo.html" />
11476 <file src="src/treeNodeCheckbox/docs/demo.js" />
11481 angular.module('b2b.att.treeNodeCheckbox', ['b2b.att.utilities'])
11482 .directive('b2bTreeNodeCheckbox', function () {
11491 templateUrl: function (element, attrs) {
11492 if (attrs.groupIt === 'true') {
11493 return "b2bTemplate/treeNodeCheckbox/groupedTree.html";
11495 return "b2bTemplate/treeNodeCheckbox/ungroupedTree.html";
11498 link: function (scope) {
11499 if (!(scope.setRole === 'tree')) {
11500 scope.setRole = 'group';
11505 .directive('b2bTreeMember', ['$compile', '$timeout', 'keymap', function ($compile, $timeout, keymap) {
11513 templateUrl: 'b2bTemplate/treeNodeCheckbox/treeMember.html',
11514 link: function (scope, element, attrs) {
11515 scope.elemArr = [];
11516 var removeRootTabIndex = function (elem) {
11517 if (elem.parent().parent().eq(0).hasClass('b2b-tree-checkbox')) {
11518 elem.attr('tabindex', -1);
11521 removeRootTabIndex(elem.parent());
11523 scope.$watch('member.child', function(newVal, oldVal){
11524 if(newVal !== oldVal){
11529 var checkedCount = 0;
11530 var nonCheckedCount = 0;
11531 var checkBoxesCount = 0;
11533 if(element.find('a').eq(0).find('input')){
11534 if(scope.member.indeterminate){
11535 element.find('a').eq(0).find('input').prop('indeterminate', true);
11536 element.attr('aria-checked',"mixed");
11538 element.attr('aria-checked',scope.member.isSelected);
11541 element.find('a').eq(0).find('input').bind('change',function(){
11542 scope.member.indeterminate = false;
11543 downwardModalUpdate(scope.member);
11544 downwardSelection(element);
11545 upwardSelection(element);
11546 element.attr('aria-checked',scope.member.isSelected);
11547 if (scope.member.onSelect !== undefined) {
11548 scope.member.onSelect(scope.member);
11552 element.find('a').eq(0).find('input').bind('click',function(){
11553 var elem = angular.element(this);
11554 if(scope.member.indeterminate){
11555 scope.member.indeterminate = false;
11556 scope.member.isSelected = true;
11557 elem.prop('indeterminate', false);
11558 elem.prop('checked', true);
11559 elem.triggerHandler('change');
11563 var groupNode = false;
11564 var checkedTreeNode = false;
11566 var isCheckboxSelected = function(elem){
11567 checkedTreeNode = false;
11568 checkedTreeNode = angular.element(angular.element(elem).find('a').eq(0))[0].querySelector('input.treeCheckBox').checked;
11571 var findCheckbox = function(elem){
11572 return angular.element(angular.element(elem).find('a').eq(0))[0].querySelector('input.treeCheckBox');
11575 var updateGrpNodeCheckboxes = function(elem, checked){
11576 angular.element(angular.element(elem).find('a').eq(0))[0].querySelector('input.treeCheckBox').checked = checked;
11580 var isGroupNode = function(elem){
11582 if(angular.element(angular.element(elem).find('a').eq(0))[0].querySelector('input.grpTreeCheckbox')){
11587 var downwardModalUpdate = function(curMember){
11588 angular.forEach(curMember.child, function(childMember, key) {
11589 childMember.isSelected = curMember.isSelected;
11590 childMember.indeterminate = false;
11591 if(angular.isArray(childMember.child) && scope.member.child.length > 0){
11592 downwardModalUpdate(childMember);
11597 var downwardSelection = function(elem){
11598 if(findCheckbox(elem)){
11599 isCheckboxSelected(elem)
11601 if(angular.element(elem).find('ul').length > 0){
11602 var childNodes = angular.element(elem).find('ul').eq(0).children('li');
11603 for(var i=0; i<childNodes.length; i++){
11604 if(findCheckbox(childNodes[i])){
11605 isGroupNode(childNodes[i]);
11606 angular.element(findCheckbox(childNodes[i])).prop('indeterminate', false);
11607 angular.element(childNodes[i]).attr('aria-checked',checkedTreeNode);
11609 updateGrpNodeCheckboxes(childNodes[i],checkedTreeNode);
11611 angular.element(childNodes[i]).scope().member.isSelected = checkedTreeNode;
11612 angular.element(childNodes[i]).scope().member.indeterminate = false
11613 angular.element(childNodes[i]).scope().$apply();
11615 downwardSelection(childNodes[i]);
11621 var upwardSelection = function(elem){
11622 var childNodes = elem.parent().parent().find('ul').eq(0).children('li');
11624 nonCheckedCount = 0;
11625 checkBoxesCount = 0;
11626 for(i=0; i<childNodes.length; i++){
11627 if(findCheckbox(childNodes[i])){
11628 isGroupNode(childNodes[i]);
11629 isCheckboxSelected(childNodes[i]);
11631 if(checkedTreeNode){
11633 }else if(!angular.element(angular.element(angular.element(childNodes[i]).find('a').eq(0))[0].querySelector('input.treeCheckBox')).prop('indeterminate')){
11638 var parentNodeScope;
11639 parentNodeScope = angular.element(elem.parent().parent()).scope();
11640 if(findCheckbox(elem.parent().parent())){
11641 if(nonCheckedCount == checkBoxesCount){
11642 angular.element(findCheckbox(elem.parent().parent())).prop('indeterminate', false);
11643 if(parentNodeScope && parentNodeScope.member){
11644 parentNodeScope.member.isSelected = false;
11645 parentNodeScope.member.indeterminate = false;
11647 updateGrpNodeCheckboxes(elem.parent().parent(),false);
11649 angular.element(elem.parent().parent()).attr('aria-checked',false);
11650 }else if(checkedCount == checkBoxesCount){
11651 angular.element(findCheckbox(elem.parent().parent())).prop('indeterminate', false);
11652 if(parentNodeScope && parentNodeScope.member){
11653 parentNodeScope.member.isSelected = true;
11654 parentNodeScope.member.indeterminate = false;
11656 updateGrpNodeCheckboxes(elem.parent().parent(),true);
11658 angular.element(elem.parent().parent()).attr('aria-checked',true);
11660 angular.element(findCheckbox(elem.parent().parent())).prop('indeterminate', true);
11661 if(parentNodeScope && parentNodeScope.member){
11662 parentNodeScope.member.isSelected = false;
11663 parentNodeScope.member.indeterminate = true;
11665 updateGrpNodeCheckboxes(elem.parent().parent(),false);
11667 angular.element(elem.parent().parent()).attr('aria-checked',"mixed");
11669 if(parentNodeScope && parentNodeScope.member){
11670 parentNodeScope.$apply();
11676 if(elem.parent().parent().attr('role') == "treeitem"){
11677 upwardSelection(elem.parent().parent());
11681 scope.showChild = function () {
11682 if (!element.hasClass('grouped')) {
11683 if (angular.isArray(scope.member.child) && scope.member.child.length > 0 && (scope.member.divide === undefined || scope.member.child.length < scope.member.divide)) {
11684 scope.groupIt = false;
11685 element.addClass('grouped');
11686 element.append("<b2b-tree-node-checkbox collection='member.child' group-it='" + scope.groupIt + "'></b2b-tree-node-checkbox>");
11687 $compile(element.contents())(scope);
11688 if(scope.member.active && scope.member.active === true){
11689 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).removeClass('icon-primary-collapsed');
11691 if(scope.member.selected && scope.member.selected === true){
11692 element.attr('tabindex', 0);
11693 removeRootTabIndex(element);
11695 if(scope.member.active && scope.member.active == undefined){
11696 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).addClass('icon-primary-collapsed');
11698 } else if (scope.member.child && scope.member.divide && scope.member.child.length > scope.member.divide) {
11699 element.addClass('grouped');
11700 scope.groupIt = true;
11703 if(scope.member.child[0].groupName !== undefined){
11704 grpName = scope.member.child[0].groupName;
11707 var toSlice = scope.member.child[0].name.search(' ');
11708 grpName = scope.member.child[0].name.slice(0, toSlice);
11711 for (i = 0; i < scope.member.child.length; i += scope.member.divide) {
11713 for (j = j + i; j < (i + scope.member.divide); j++) {
11714 if (j === scope.member.child.length) {
11715 scope.member.child[j - 1].grpChild = grpName + ' ' + (i + 1) + ' - ' + (scope.member.child.length);
11718 if(scope.member.child[j-1].active && scope.member.child[j-1].active===true){
11719 scope.member.child[j-1].activeGrp = true;
11723 if (i + scope.member.divide > scope.member.child.length) {
11724 scope.member.child[j].grpChild = grpName + ' ' + (i + 1) + ' - ' + (scope.member.child.length);
11725 if(scope.member.child[j].active && scope.member.child[j].active===true){
11726 scope.member.child[j].activeGrp = true;
11730 scope.member.child[j].grpChild = grpName + ' ' + (i + 1) + ' - ' + (i + scope.member.divide);
11731 if(scope.member.child[j].active && scope.member.child[j].active===true){
11732 scope.member.child[j].activeGrp = true;
11737 if(scope.member.divide){
11738 element.append("<b2b-tree-node-checkbox collection='member.child' group-it='" + scope.groupIt + "'></b2b-tree-node-checkbox>");
11740 element.append("<b2b-tree-node-checkbox collection='member.child' group-it='" + scope.groupIt + "'></b2b-tree-node-checkbox>");
11742 $compile(element.contents())(scope);
11743 if(scope.member.active && scope.member.active === true){
11744 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).removeClass('icon-primary-collapsed');
11747 if( scope.member.active && scope.member.active == undefined){
11748 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).addClass('icon-primary-collapsed');
11752 $timeout(function () {
11753 if(!scope.member.indeterminate){
11754 downwardSelection(element);
11760 if(scope.member.active && scope.member.active == true){
11763 if(scope.member.active == undefined && !element.find('a').eq(0).hasClass('active') && scope.member.child !== undefined){
11764 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).addClass('icon-primary-collapsed');
11766 else if(scope.member.child == undefined){
11767 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).addClass('icon-primary-circle');
11768 if(scope.$parent.$index === 0) {
11769 element.find('a').eq(0).append('<span class="first-link"></span>');
11773 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).bind('click', function (evt) {
11775 var expandFunc = scope.member.onExpand;
11776 if (element.find('a').eq(0).hasClass('active') && scope.member.onExpand !== undefined) {
11777 var eValue = scope.member.onExpand(scope.member);
11779 if (!element.find('a').eq(0).hasClass('active') && scope.member.onCollapse !== undefined) {
11780 scope.member.onCollapse(scope.member);
11784 angular.element(element[0].querySelectorAll('.treeNodeName')).eq(0).bind('click', function (evt) {
11791 .directive('b2bTreeNodeLink', ['keymap', '$timeout', function (keymap, $timeout) {
11794 link: function (scope, element, attr, ctrl) {
11795 var rootE, parentE, upE, downE;
11796 var closeOthersUp = function (elem) {
11798 if (elem.find('a').eq(0).hasClass('active')) {
11799 activeToggle(elem);
11802 if (elem.hasClass('bg')) {
11803 elem.removeClass('bg');
11805 if (elem[0].previousElementSibling !== null) {
11806 closeOthersUp(angular.element(elem[0].previousElementSibling));
11809 var closeOthersDown = function (elem) {
11811 if (elem.find('a').eq(0).hasClass('active')) {
11812 activeToggle(elem);
11815 if (elem.hasClass('bg')) {
11816 elem.removeClass('bg');
11818 if (elem[0].nextElementSibling !== null) {
11819 closeOthersDown(angular.element(elem[0].nextElementSibling));
11823 var removeBackgroundUp = function (elem) {
11825 if (elem.hasClass('b2b-tree-checkbox')) {
11828 elem.parent().parent().removeClass('bg');
11829 removeBackgroundUp(elem.parent().parent());
11833 var removeBackgroundDown = function (elem) {
11835 angular.element(elem[0].querySelector('.bg')).removeClass('bg');
11840 var activeToggle = function (elem) {
11841 var element = elem.find('a').eq(0);
11842 if (element.hasClass('active')) {
11843 elem.removeClass('bg');
11844 if (!angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).hasClass('icon-primary-circle')) {
11845 element.removeClass('active');
11846 elem.attr('aria-expanded', 'false');
11847 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).removeClass('icon-primary-expanded');
11848 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).addClass('icon-primary-collapsed');
11851 elem.addClass('bg');
11852 if (!angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).hasClass('icon-primary-circle')) {
11853 element.addClass('active');
11854 elem.attr('aria-expanded', 'true');
11855 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).removeClass('icon-primary-collapsed');
11856 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).addClass('icon-primary-expanded');
11860 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).bind('click', function (evt) {
11862 if (element[0].previousElementSibling) {
11863 closeOthersUp(angular.element(element[0].previousElementSibling));
11865 if (element[0].nextElementSibling) {
11866 closeOthersDown(angular.element(element[0].nextElementSibling));
11869 activeToggle(element);
11871 removeBackgroundDown(element);
11872 removeBackgroundUp(element);
11873 evt.stopPropagation();
11876 if (element.parent().parent().hasClass('b2b-tree-checkbox') && (element.parent()[0].previousElementSibling === null)) {
11877 element.attr('tabindex', 0);
11880 var isRoot = function (elem) {
11881 if (elem.parent().parent().eq(0).hasClass('b2b-tree-checkbox')) {
11887 var findRoot = function (elem) {
11888 if (isRoot(elem)) {
11892 findRoot(elem.parent());
11895 var findPreActive = function (elem) {
11897 if (!(elem.hasClass("active"))) {
11900 var childElems = angular.element(elem[0].nextElementSibling.children);
11901 lastE = angular.element(childElems[childElems.length - 1]);
11902 if (lastE.find('a').eq(0).hasClass('active')) {
11903 findPreActive(lastE.find('a').eq(0));
11909 var findUp = function (elem) {
11910 if (isRoot(elem)) {
11914 if (elem[0].previousElementSibling !== null && !angular.element(elem[0].previousElementSibling).hasClass('tree-hide')) {
11915 upE = angular.element(elem[0].previousElementSibling);
11916 if (upE.find('a').eq(0).hasClass('active')) {
11917 findPreActive(upE.find('a').eq(0));
11920 upE = elem.parent().parent();
11924 var downElement = function (elem) {
11925 if (elem.next().hasClass('tree-hide')) {
11926 downElement(elem.next());
11928 downE = elem.next();
11931 var isBottomElem = false;
11932 var downParent = function(liElem){
11933 if(liElem.eq(0).parent().parent().eq(0).hasClass('b2b-tree-checkbox')){
11934 isBottomElem = true;
11937 if(liElem.next().length !== 0){
11938 downE = liElem.next().eq(0);
11942 downParent(liElem.parent().parent());
11946 var findDown = function (elem) {
11947 if (isRoot(elem.parent()) && !elem.hasClass('active')) {
11948 downE = elem.parent();
11951 if (elem.hasClass('active')) {
11952 downE = elem.next().find('li').eq(0);
11953 if (downE.hasClass('tree-hide')) {
11954 downElement(downE);
11958 downParent(elem.parent());
11959 if(isBottomElem === true){
11960 downE = elem.parent();
11961 isBottomElem = false;
11965 element.bind('keydown', function (evt) {
11966 switch (evt.keyCode) {
11967 case keymap.KEY.HOME:
11968 evt.preventDefault();
11969 evt.stopPropagation();
11970 element.attr('tabindex', -1);
11972 rootE.eq(0).attr('tabindex', 0);
11975 case keymap.KEY.LEFT:
11976 evt.preventDefault();
11977 evt.stopPropagation();
11978 if (!isRoot(element)) {
11979 if(element.find('a').eq(0).hasClass('active')){
11980 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).triggerHandler('click');
11983 element.attr('tabindex', -1);
11984 parentE = element.parent().parent();
11985 parentE.attr('tabindex', 0);
11986 parentE[0].focus();
11988 if (element.find('a').eq(0).hasClass('active')) {
11989 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).triggerHandler('click');
11993 case keymap.KEY.UP:
11994 evt.preventDefault();
11995 evt.stopPropagation();
11996 element.attr('tabindex', -1);
11998 upE.eq(0).attr('tabindex', 0);
12001 case keymap.KEY.RIGHT:
12002 evt.preventDefault();
12003 evt.stopPropagation();
12004 if(angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).hasClass('icon-primary-circle')){
12007 if (!element.find('a').eq(0).hasClass('active')) {
12008 angular.element(element[0].querySelectorAll('i.expandCollapseIcon')).eq(0).triggerHandler('click');
12011 element.attr('tabindex', -1);
12012 findDown(element.find('a').eq(0));
12013 downE.eq(0).attr('tabindex', 0);
12017 case keymap.KEY.DOWN:
12018 evt.preventDefault();
12019 element.attr('tabindex', -1);
12020 findDown(element.find('a').eq(0));
12021 downE.eq(0).attr('tabindex', 0);
12023 evt.stopPropagation();
12025 case keymap.KEY.SPACE:
12026 case keymap.KEY.ENTER:
12027 evt.preventDefault();
12028 evt.stopPropagation();
12029 if(angular.isDefined(element.scope().member.isSelected)){
12030 element.scope().member.isSelected = !element.scope().member.isSelected;
12031 element.scope().member.indeterminate = false;
12032 element.scope().$apply();
12033 element.find('a').eq(0).find('input').prop('indeterminate', false);
12034 element.find('a').eq(0).find('input').triggerHandler('change');
12047 * UPDATES AND DOCS AT: http://www.greensock.com
12049 * @license Copyright (c) 2008-2014, GreenSock. All rights reserved.
12050 * This work is subject to the terms at http://www.greensock.com/terms_of_use.html or for
12051 * Club GreenSock members, the software agreement that was issued with your membership.
12053 * @author: Jack Doyle, jack@greensock.com
12055 (window._gsQueue || (window._gsQueue = [])).push( function() {
12059 var _doc = document.documentElement,
12061 _max = function(element, axis) {
12062 var dim = (axis === "x") ? "Width" : "Height",
12063 scroll = "scroll" + dim,
12064 client = "client" + dim,
12065 body = document.body;
12066 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];
12069 ScrollToPlugin = window._gsDefine.plugin({
12070 propName: "scrollTo",
12074 //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.
12075 init: function(target, value, tween) {
12076 this._wdw = (target === _window);
12077 this._target = target;
12078 this._tween = tween;
12079 if (typeof(value) !== "object") {
12080 value = {y:value}; //if we don't receive an object as the parameter, assume the user intends "y".
12082 this._autoKill = (value.autoKill !== false);
12083 this.x = this.xPrev = this.getX();
12084 this.y = this.yPrev = this.getY();
12085 if (value.x != null) {
12086 this._addTween(this, "x", this.x, (value.x === "max") ? _max(target, "x") : value.x, "scrollTo_x", true);
12087 this._overwriteProps.push("scrollTo_x");
12091 if (value.y != null) {
12092 this._addTween(this, "y", this.y, (value.y === "max") ? _max(target, "y") : value.y, "scrollTo_y", true);
12093 this._overwriteProps.push("scrollTo_y");
12100 //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.)
12102 this._super.setRatio.call(this, v);
12104 var x = (this._wdw || !this.skipX) ? this.getX() : this.xPrev,
12105 y = (this._wdw || !this.skipY) ? this.getY() : this.yPrev,
12106 yDif = y - this.yPrev,
12107 xDif = x - this.xPrev;
12109 if (this._autoKill) {
12110 //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.
12111 if (!this.skipX && (xDif > 7 || xDif < -7) && x < _max(this._target, "x")) {
12112 this.skipX = true; //if the user scrolls separately, we should stop tweening!
12114 if (!this.skipY && (yDif > 7 || yDif < -7) && y < _max(this._target, "y")) {
12115 this.skipY = true; //if the user scrolls separately, we should stop tweening!
12117 if (this.skipX && this.skipY) {
12118 this._tween.kill();
12122 _window.scrollTo((!this.skipX) ? this.x : x, (!this.skipY) ? this.y : y);
12125 this._target.scrollTop = this.y;
12128 this._target.scrollLeft = this.x;
12131 this.xPrev = this.x;
12132 this.yPrev = this.y;
12136 p = ScrollToPlugin.prototype;
12138 ScrollToPlugin.max = _max;
12140 p.getX = function() {
12141 return (!this._wdw) ? this._target.scrollLeft : (_window.pageXOffset != null) ? _window.pageXOffset : (_doc.scrollLeft != null) ? _doc.scrollLeft : document.body.scrollLeft;
12144 p.getY = function() {
12145 return (!this._wdw) ? this._target.scrollTop : (_window.pageYOffset != null) ? _window.pageYOffset : (_doc.scrollTop != null) ? _doc.scrollTop : document.body.scrollTop;
12148 p._kill = function(lookup) {
12149 if (lookup.scrollTo_x) {
12152 if (lookup.scrollTo_y) {
12155 return this._super._kill.call(this, lookup);
12158 }); if (window._gsDefine) { window._gsQueue.pop()(); }
12162 * UPDATES AND DOCS AT: http://www.greensock.com
12164 * Includes all of the following: TweenLite, TweenMax, TimelineLite, TimelineMax, EasePack, CSSPlugin, RoundPropsPlugin, BezierPlugin, AttrPlugin, DirectionalRotationPlugin
12166 * @license Copyright (c) 2008-2014, GreenSock. All rights reserved.
12167 * This work is subject to the terms at http://www.greensock.com/terms_of_use.html or for
12168 * Club GreenSock members, the software agreement that was issued with your membership.
12170 * @author: Jack Doyle, jack@greensock.com
12173 (window._gsQueue || (window._gsQueue = [])).push( function() {
12177 window._gsDefine("TweenMax", ["core.Animation","core.SimpleTimeline","TweenLite"], function(Animation, SimpleTimeline, TweenLite) {
12179 var _slice = [].slice,
12180 TweenMax = function(target, duration, vars) {
12181 TweenLite.call(this, target, duration, vars);
12183 this._yoyo = (this.vars.yoyo === true);
12184 this._repeat = this.vars.repeat || 0;
12185 this._repeatDelay = this.vars.repeatDelay || 0;
12186 this._dirty = true; //ensures that if there is any repeat, the totalDuration will get recalculated to accurately report it.
12187 this.render = TweenMax.prototype.render; //speed optimization (avoid prototype lookup on this "hot" method)
12189 _tinyNum = 0.0000000001,
12190 TweenLiteInternals = TweenLite._internals,
12191 _isSelector = TweenLiteInternals.isSelector,
12192 _isArray = TweenLiteInternals.isArray,
12193 p = TweenMax.prototype = TweenLite.to({}, 0.1, {}),
12196 TweenMax.version = "1.12.1";
12197 p.constructor = TweenMax;
12198 p.kill()._gc = false;
12199 TweenMax.killTweensOf = TweenMax.killDelayedCallsTo = TweenLite.killTweensOf;
12200 TweenMax.getTweensOf = TweenLite.getTweensOf;
12201 TweenMax.lagSmoothing = TweenLite.lagSmoothing;
12202 TweenMax.ticker = TweenLite.ticker;
12203 TweenMax.render = TweenLite.render;
12205 p.invalidate = function() {
12206 this._yoyo = (this.vars.yoyo === true);
12207 this._repeat = this.vars.repeat || 0;
12208 this._repeatDelay = this.vars.repeatDelay || 0;
12209 this._uncache(true);
12210 return TweenLite.prototype.invalidate.call(this);
12213 p.updateTo = function(vars, resetDuration) {
12214 var curRatio = this.ratio, p;
12215 if (resetDuration && this._startTime < this._timeline._time) {
12216 this._startTime = this._timeline._time;
12217 this._uncache(false);
12219 this._enabled(true, false);
12221 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.
12225 this.vars[p] = vars[p];
12227 if (this._initted) {
12228 if (resetDuration) {
12229 this._initted = false;
12232 this._enabled(true, false);
12234 if (this._notifyPluginsOfEnabled && this._firstPT) {
12235 TweenLite._onPluginEvent("_onDisable", this); //in case a plugin like MotionBlur must perform some cleanup tasks
12237 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.
12238 var prevTime = this._time;
12239 this.render(0, true, false);
12240 this._initted = false;
12241 this.render(prevTime, true, false);
12242 } else if (this._time > 0) {
12243 this._initted = false;
12245 var inv = 1 / (1 - curRatio),
12246 pt = this._firstPT, endValue;
12248 endValue = pt.s + pt.c;
12250 pt.s = endValue - pt.c;
12259 p.render = function(time, suppressEvents, force) {
12260 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.
12263 var totalDur = (!this._dirty) ? this._totalDuration : this.totalDuration(),
12264 prevTime = this._time,
12265 prevTotalTime = this._totalTime,
12266 prevCycle = this._cycle,
12267 duration = this._duration,
12268 prevRawPrevTime = this._rawPrevTime,
12269 isComplete, callback, pt, cycleDuration, r, type, pow, rawPrevTime, i;
12270 if (time >= totalDur) {
12271 this._totalTime = totalDur;
12272 this._cycle = this._repeat;
12273 if (this._yoyo && (this._cycle & 1) !== 0) {
12275 this.ratio = this._ease._calcEnd ? this._ease.getRatio(0) : 0;
12277 this._time = duration;
12278 this.ratio = this._ease._calcEnd ? this._ease.getRatio(1) : 1;
12280 if (!this._reversed) {
12282 callback = "onComplete";
12284 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.
12285 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.
12288 if (time === 0 || prevRawPrevTime < 0 || prevRawPrevTime === _tinyNum) if (prevRawPrevTime !== time) {
12290 if (prevRawPrevTime > _tinyNum) {
12291 callback = "onReverseComplete";
12294 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.
12297 } else if (time < 0.0000001) { //to work around occasional floating point math artifacts, round super small values to 0.
12298 this._totalTime = this._time = this._cycle = 0;
12299 this.ratio = this._ease._calcEnd ? this._ease.getRatio(0) : 0;
12300 if (prevTotalTime !== 0 || (duration === 0 && prevRawPrevTime > 0 && prevRawPrevTime !== _tinyNum)) {
12301 callback = "onReverseComplete";
12302 isComplete = this._reversed;
12305 this._active = false;
12306 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.
12307 if (prevRawPrevTime >= 0) {
12310 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.
12312 } 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.
12316 this._totalTime = this._time = time;
12318 if (this._repeat !== 0) {
12319 cycleDuration = duration + this._repeatDelay;
12320 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!)
12321 if (this._cycle !== 0) if (this._cycle === this._totalTime / cycleDuration) {
12322 this._cycle--; //otherwise when rendered exactly at the end time, it will act as though it is repeating (at the beginning)
12324 this._time = this._totalTime - (this._cycle * cycleDuration);
12325 if (this._yoyo) if ((this._cycle & 1) !== 0) {
12326 this._time = duration - this._time;
12328 if (this._time > duration) {
12329 this._time = duration;
12330 } else if (this._time < 0) {
12335 if (this._easeType) {
12336 r = this._time / duration;
12337 type = this._easeType;
12338 pow = this._easePower;
12339 if (type === 1 || (type === 3 && r >= 0.5)) {
12347 } else if (pow === 2) {
12349 } else if (pow === 3) {
12351 } else if (pow === 4) {
12352 r *= r * r * r * r;
12356 this.ratio = 1 - r;
12357 } else if (type === 2) {
12359 } else if (this._time / duration < 0.5) {
12360 this.ratio = r / 2;
12362 this.ratio = 1 - (r / 2);
12366 this.ratio = this._ease.getRatio(this._time / duration);
12371 if (prevTime === this._time && !force && prevCycle === this._cycle) {
12372 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.
12373 this._onUpdate.apply(this.vars.onUpdateScope || this, this.vars.onUpdateParams || _blankArray);
12376 } else if (!this._initted) {
12378 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.
12380 } 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.
12381 this._time = prevTime;
12382 this._totalTime = prevTotalTime;
12383 this._rawPrevTime = prevRawPrevTime;
12384 this._cycle = prevCycle;
12385 TweenLiteInternals.lazyTweens.push(this);
12389 //_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.
12390 if (this._time && !isComplete) {
12391 this.ratio = this._ease.getRatio(this._time / duration);
12392 } else if (isComplete && this._ease._calcEnd) {
12393 this.ratio = this._ease.getRatio((this._time === 0) ? 0 : 1);
12396 if (this._lazy !== false) {
12397 this._lazy = false;
12400 if (!this._active) if (!this._paused && this._time !== prevTime && time >= 0) {
12401 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.
12403 if (prevTotalTime === 0) {
12404 if (this._initted === 2 && time > 0) {
12405 //this.invalidate();
12406 this._init(); //will just apply overwriting since _initted of (2) means it was a from() tween that had immediateRender:true
12408 if (this._startAt) {
12410 this._startAt.render(time, suppressEvents, force);
12411 } else if (!callback) {
12412 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.
12415 if (this.vars.onStart) if (this._totalTime !== 0 || duration === 0) if (!suppressEvents) {
12416 this.vars.onStart.apply(this.vars.onStartScope || this, this.vars.onStartParams || _blankArray);
12420 pt = this._firstPT;
12423 pt.t[pt.p](pt.c * this.ratio + pt.s);
12425 pt.t[pt.p] = pt.c * this.ratio + pt.s;
12430 if (this._onUpdate) {
12431 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.
12432 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.
12434 if (!suppressEvents) if (this._totalTime !== prevTotalTime || isComplete) {
12435 this._onUpdate.apply(this.vars.onUpdateScope || this, this.vars.onUpdateParams || _blankArray);
12438 if (this._cycle !== prevCycle) if (!suppressEvents) if (!this._gc) if (this.vars.onRepeat) {
12439 this.vars.onRepeat.apply(this.vars.onRepeatScope || this, this.vars.onRepeatParams || _blankArray);
12441 if (callback) if (!this._gc) { //check gc because there's a chance that kill() could be called in an onUpdate
12442 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.
12443 this._startAt.render(time, suppressEvents, force);
12446 if (this._timeline.autoRemoveChildren) {
12447 this._enabled(false, false);
12449 this._active = false;
12451 if (!suppressEvents && this.vars[callback]) {
12452 this.vars[callback].apply(this.vars[callback + "Scope"] || this, this.vars[callback + "Params"] || _blankArray);
12454 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.
12455 this._rawPrevTime = 0;
12460 //---- STATIC FUNCTIONS -----------------------------------------------------------------------------------------------------------
12462 TweenMax.to = function(target, duration, vars) {
12463 return new TweenMax(target, duration, vars);
12466 TweenMax.from = function(target, duration, vars) {
12467 vars.runBackwards = true;
12468 vars.immediateRender = (vars.immediateRender != false);
12469 return new TweenMax(target, duration, vars);
12472 TweenMax.fromTo = function(target, duration, fromVars, toVars) {
12473 toVars.startAt = fromVars;
12474 toVars.immediateRender = (toVars.immediateRender != false && fromVars.immediateRender != false);
12475 return new TweenMax(target, duration, toVars);
12478 TweenMax.staggerTo = TweenMax.allTo = function(targets, duration, vars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
12479 stagger = stagger || 0;
12480 var delay = vars.delay || 0,
12482 finalComplete = function() {
12483 if (vars.onComplete) {
12484 vars.onComplete.apply(vars.onCompleteScope || this, arguments);
12486 onCompleteAll.apply(onCompleteAllScope || this, onCompleteAllParams || _blankArray);
12489 if (!_isArray(targets)) {
12490 if (typeof(targets) === "string") {
12491 targets = TweenLite.selector(targets) || targets;
12493 if (_isSelector(targets)) {
12494 targets = _slice.call(targets, 0);
12497 l = targets.length;
12498 for (i = 0; i < l; i++) {
12503 copy.delay = delay;
12504 if (i === l - 1 && onCompleteAll) {
12505 copy.onComplete = finalComplete;
12507 a[i] = new TweenMax(targets[i], duration, copy);
12513 TweenMax.staggerFrom = TweenMax.allFrom = function(targets, duration, vars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
12514 vars.runBackwards = true;
12515 vars.immediateRender = (vars.immediateRender != false);
12516 return TweenMax.staggerTo(targets, duration, vars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope);
12519 TweenMax.staggerFromTo = TweenMax.allFromTo = function(targets, duration, fromVars, toVars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
12520 toVars.startAt = fromVars;
12521 toVars.immediateRender = (toVars.immediateRender != false && fromVars.immediateRender != false);
12522 return TweenMax.staggerTo(targets, duration, toVars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope);
12525 TweenMax.delayedCall = function(delay, callback, params, scope, useFrames) {
12526 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});
12529 TweenMax.set = function(target, vars) {
12530 return new TweenMax(target, 0, vars);
12533 TweenMax.isTweening = function(target) {
12534 return (TweenLite.getTweensOf(target, true).length > 0);
12537 var _getChildrenOf = function(timeline, includeTimelines) {
12540 tween = timeline._first;
12542 if (tween instanceof TweenLite) {
12545 if (includeTimelines) {
12548 a = a.concat(_getChildrenOf(tween, includeTimelines));
12551 tween = tween._next;
12555 getAllTweens = TweenMax.getAllTweens = function(includeTimelines) {
12556 return _getChildrenOf(Animation._rootTimeline, includeTimelines).concat( _getChildrenOf(Animation._rootFramesTimeline, includeTimelines) );
12559 TweenMax.killAll = function(complete, tweens, delayedCalls, timelines) {
12560 if (tweens == null) {
12563 if (delayedCalls == null) {
12564 delayedCalls = true;
12566 var a = getAllTweens((timelines != false)),
12568 allTrue = (tweens && delayedCalls && timelines),
12570 for (i = 0; i < l; i++) {
12572 if (allTrue || (tween instanceof SimpleTimeline) || ((isDC = (tween.target === tween.vars.onComplete)) && delayedCalls) || (tweens && !isDC)) {
12574 tween.totalTime(tween._reversed ? 0 : tween.totalDuration());
12576 tween._enabled(false, false);
12582 TweenMax.killChildTweensOf = function(parent, complete) {
12583 if (parent == null) {
12586 var tl = TweenLiteInternals.tweenLookup,
12587 a, curParent, p, i, l;
12588 if (typeof(parent) === "string") {
12589 parent = TweenLite.selector(parent) || parent;
12591 if (_isSelector(parent)) {
12592 parent = _slice.call(parent, 0);
12594 if (_isArray(parent)) {
12597 TweenMax.killChildTweensOf(parent[i], complete);
12603 curParent = tl[p].target.parentNode;
12604 while (curParent) {
12605 if (curParent === parent) {
12606 a = a.concat(tl[p].tweens);
12608 curParent = curParent.parentNode;
12612 for (i = 0; i < l; i++) {
12614 a[i].totalTime(a[i].totalDuration());
12616 a[i]._enabled(false, false);
12620 var _changePause = function(pause, tweens, delayedCalls, timelines) {
12621 tweens = (tweens !== false);
12622 delayedCalls = (delayedCalls !== false);
12623 timelines = (timelines !== false);
12624 var a = getAllTweens(timelines),
12625 allTrue = (tweens && delayedCalls && timelines),
12630 if (allTrue || (tween instanceof SimpleTimeline) || ((isDC = (tween.target === tween.vars.onComplete)) && delayedCalls) || (tweens && !isDC)) {
12631 tween.paused(pause);
12636 TweenMax.pauseAll = function(tweens, delayedCalls, timelines) {
12637 _changePause(true, tweens, delayedCalls, timelines);
12640 TweenMax.resumeAll = function(tweens, delayedCalls, timelines) {
12641 _changePause(false, tweens, delayedCalls, timelines);
12644 TweenMax.globalTimeScale = function(value) {
12645 var tl = Animation._rootTimeline,
12646 t = TweenLite.ticker.time;
12647 if (!arguments.length) {
12648 return tl._timeScale;
12650 value = value || _tinyNum; //can't allow zero because it'll throw the math off
12651 tl._startTime = t - ((t - tl._startTime) * tl._timeScale / value);
12652 tl = Animation._rootFramesTimeline;
12653 t = TweenLite.ticker.frame;
12654 tl._startTime = t - ((t - tl._startTime) * tl._timeScale / value);
12655 tl._timeScale = Animation._rootTimeline._timeScale = value;
12660 //---- GETTERS / SETTERS ----------------------------------------------------------------------------------------------------------
12662 p.progress = function(value) {
12663 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);
12666 p.totalProgress = function(value) {
12667 return (!arguments.length) ? this._totalTime / this.totalDuration() : this.totalTime( this.totalDuration() * value, false);
12670 p.time = function(value, suppressEvents) {
12671 if (!arguments.length) {
12675 this.totalDuration();
12677 if (value > this._duration) {
12678 value = this._duration;
12680 if (this._yoyo && (this._cycle & 1) !== 0) {
12681 value = (this._duration - value) + (this._cycle * (this._duration + this._repeatDelay));
12682 } else if (this._repeat !== 0) {
12683 value += this._cycle * (this._duration + this._repeatDelay);
12685 return this.totalTime(value, suppressEvents);
12688 p.duration = function(value) {
12689 if (!arguments.length) {
12690 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.
12692 return Animation.prototype.duration.call(this, value);
12695 p.totalDuration = function(value) {
12696 if (!arguments.length) {
12698 //instead of Infinity, we use 999999999999 so that we can accommodate reverses
12699 this._totalDuration = (this._repeat === -1) ? 999999999999 : this._duration * (this._repeat + 1) + (this._repeatDelay * this._repeat);
12700 this._dirty = false;
12702 return this._totalDuration;
12704 return (this._repeat === -1) ? this : this.duration( (value - (this._repeat * this._repeatDelay)) / (this._repeat + 1) );
12707 p.repeat = function(value) {
12708 if (!arguments.length) {
12709 return this._repeat;
12711 this._repeat = value;
12712 return this._uncache(true);
12715 p.repeatDelay = function(value) {
12716 if (!arguments.length) {
12717 return this._repeatDelay;
12719 this._repeatDelay = value;
12720 return this._uncache(true);
12723 p.yoyo = function(value) {
12724 if (!arguments.length) {
12727 this._yoyo = value;
12744 * ----------------------------------------------------------------
12746 * ----------------------------------------------------------------
12748 window._gsDefine("TimelineLite", ["core.Animation","core.SimpleTimeline","TweenLite"], function(Animation, SimpleTimeline, TweenLite) {
12750 var TimelineLite = function(vars) {
12751 SimpleTimeline.call(this, vars);
12753 this.autoRemoveChildren = (this.vars.autoRemoveChildren === true);
12754 this.smoothChildTiming = (this.vars.smoothChildTiming === true);
12755 this._sortChildren = true;
12756 this._onUpdate = this.vars.onUpdate;
12761 if (_isArray(val)) if (val.join("").indexOf("{self}") !== -1) {
12762 v[p] = this._swapSelfInParams(val);
12765 if (_isArray(v.tweens)) {
12766 this.add(v.tweens, 0, v.align, v.stagger);
12769 _tinyNum = 0.0000000001,
12770 _isSelector = TweenLite._internals.isSelector,
12771 _isArray = TweenLite._internals.isArray,
12773 _globals = window._gsDefine.globals,
12774 _copy = function(vars) {
12781 _pauseCallback = function(tween, callback, params, scope) {
12782 tween._timeline.pause(tween._startTime);
12784 callback.apply(scope || tween._timeline, params || _blankArray);
12787 _slice = _blankArray.slice,
12788 p = TimelineLite.prototype = new SimpleTimeline();
12790 TimelineLite.version = "1.12.1";
12791 p.constructor = TimelineLite;
12792 p.kill()._gc = false;
12794 p.to = function(target, duration, vars, position) {
12795 var Engine = (vars.repeat && _globals.TweenMax) || TweenLite;
12796 return duration ? this.add( new Engine(target, duration, vars), position) : this.set(target, vars, position);
12799 p.from = function(target, duration, vars, position) {
12800 return this.add( ((vars.repeat && _globals.TweenMax) || TweenLite).from(target, duration, vars), position);
12803 p.fromTo = function(target, duration, fromVars, toVars, position) {
12804 var Engine = (toVars.repeat && _globals.TweenMax) || TweenLite;
12805 return duration ? this.add( Engine.fromTo(target, duration, fromVars, toVars), position) : this.set(target, toVars, position);
12808 p.staggerTo = function(targets, duration, vars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
12809 var tl = new TimelineLite({onComplete:onCompleteAll, onCompleteParams:onCompleteAllParams, onCompleteScope:onCompleteAllScope, smoothChildTiming:this.smoothChildTiming}),
12811 if (typeof(targets) === "string") {
12812 targets = TweenLite.selector(targets) || targets;
12814 if (_isSelector(targets)) { //senses if the targets object is a selector. If it is, we should translate it into an array.
12815 targets = _slice.call(targets, 0);
12817 stagger = stagger || 0;
12818 for (i = 0; i < targets.length; i++) {
12819 if (vars.startAt) {
12820 vars.startAt = _copy(vars.startAt);
12822 tl.to(targets[i], duration, _copy(vars), i * stagger);
12824 return this.add(tl, position);
12827 p.staggerFrom = function(targets, duration, vars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
12828 vars.immediateRender = (vars.immediateRender != false);
12829 vars.runBackwards = true;
12830 return this.staggerTo(targets, duration, vars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope);
12833 p.staggerFromTo = function(targets, duration, fromVars, toVars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
12834 toVars.startAt = fromVars;
12835 toVars.immediateRender = (toVars.immediateRender != false && fromVars.immediateRender != false);
12836 return this.staggerTo(targets, duration, toVars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope);
12839 p.call = function(callback, params, scope, position) {
12840 return this.add( TweenLite.delayedCall(0, callback, params, scope), position);
12843 p.set = function(target, vars, position) {
12844 position = this._parseTimeOrLabel(position, 0, true);
12845 if (vars.immediateRender == null) {
12846 vars.immediateRender = (position === this._time && !this._paused);
12848 return this.add( new TweenLite(target, 0, vars), position);
12851 TimelineLite.exportRoot = function(vars, ignoreDelayedCalls) {
12853 if (vars.smoothChildTiming == null) {
12854 vars.smoothChildTiming = true;
12856 var tl = new TimelineLite(vars),
12857 root = tl._timeline,
12859 if (ignoreDelayedCalls == null) {
12860 ignoreDelayedCalls = true;
12862 root._remove(tl, true);
12864 tl._rawPrevTime = tl._time = tl._totalTime = root._time;
12865 tween = root._first;
12867 next = tween._next;
12868 if (!ignoreDelayedCalls || !(tween instanceof TweenLite && tween.target === tween.vars.onComplete)) {
12869 tl.add(tween, tween._startTime - tween._delay);
12877 p.add = function(value, position, align, stagger) {
12878 var curTime, l, i, child, tl, beforeRawTime;
12879 if (typeof(position) !== "number") {
12880 position = this._parseTimeOrLabel(position, 0, true, value);
12882 if (!(value instanceof Animation)) {
12883 if ((value instanceof Array) || (value && value.push && _isArray(value))) {
12884 align = align || "normal";
12885 stagger = stagger || 0;
12886 curTime = position;
12888 for (i = 0; i < l; i++) {
12889 if (_isArray(child = value[i])) {
12890 child = new TimelineLite({tweens:child});
12892 this.add(child, curTime);
12893 if (typeof(child) !== "string" && typeof(child) !== "function") {
12894 if (align === "sequence") {
12895 curTime = child._startTime + (child.totalDuration() / child._timeScale);
12896 } else if (align === "start") {
12897 child._startTime -= child.delay();
12900 curTime += stagger;
12902 return this._uncache(true);
12903 } else if (typeof(value) === "string") {
12904 return this.addLabel(value, position);
12905 } else if (typeof(value) === "function") {
12906 value = TweenLite.delayedCall(0, value);
12908 throw("Cannot add " + value + " into the timeline; it is not a tween, timeline, function, or string.");
12912 SimpleTimeline.prototype.add.call(this, value, position);
12914 //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.
12915 if (this._gc || this._time === this._duration) if (!this._paused) if (this._duration < this.duration()) {
12916 //in case any of the ancestors had completed but should now be enabled...
12918 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.
12919 while (tl._timeline) {
12920 if (beforeRawTime && tl._timeline.smoothChildTiming) {
12921 tl.totalTime(tl._totalTime, true); //moves the timeline (shifts its startTime) if necessary, and also enables it.
12922 } else if (tl._gc) {
12923 tl._enabled(true, false);
12932 p.remove = function(value) {
12933 if (value instanceof Animation) {
12934 return this._remove(value, false);
12935 } else if (value instanceof Array || (value && value.push && _isArray(value))) {
12936 var i = value.length;
12938 this.remove(value[i]);
12941 } else if (typeof(value) === "string") {
12942 return this.removeLabel(value);
12944 return this.kill(null, value);
12947 p._remove = function(tween, skipDisable) {
12948 SimpleTimeline.prototype._remove.call(this, tween, skipDisable);
12949 var last = this._last;
12951 this._time = this._totalTime = this._duration = this._totalDuration = 0;
12952 } else if (this._time > last._startTime + last._totalDuration / last._timeScale) {
12953 this._time = this.duration();
12954 this._totalTime = this._totalDuration;
12959 p.append = function(value, offsetOrLabel) {
12960 return this.add(value, this._parseTimeOrLabel(null, offsetOrLabel, true, value));
12963 p.insert = p.insertMultiple = function(value, position, align, stagger) {
12964 return this.add(value, position || 0, align, stagger);
12967 p.appendMultiple = function(tweens, offsetOrLabel, align, stagger) {
12968 return this.add(tweens, this._parseTimeOrLabel(null, offsetOrLabel, true, tweens), align, stagger);
12971 p.addLabel = function(label, position) {
12972 this._labels[label] = this._parseTimeOrLabel(position);
12976 p.addPause = function(position, callback, params, scope) {
12977 return this.call(_pauseCallback, ["{self}", callback, params, scope], this, position);
12980 p.removeLabel = function(label) {
12981 delete this._labels[label];
12985 p.getLabelTime = function(label) {
12986 return (this._labels[label] != null) ? this._labels[label] : -1;
12989 p._parseTimeOrLabel = function(timeOrLabel, offsetOrLabel, appendIfAbsent, ignore) {
12991 //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().
12992 if (ignore instanceof Animation && ignore.timeline === this) {
12993 this.remove(ignore);
12994 } else if (ignore && ((ignore instanceof Array) || (ignore.push && _isArray(ignore)))) {
12997 if (ignore[i] instanceof Animation && ignore[i].timeline === this) {
12998 this.remove(ignore[i]);
13002 if (typeof(offsetOrLabel) === "string") {
13003 return this._parseTimeOrLabel(offsetOrLabel, (appendIfAbsent && typeof(timeOrLabel) === "number" && this._labels[offsetOrLabel] == null) ? timeOrLabel - this.duration() : 0, appendIfAbsent);
13005 offsetOrLabel = offsetOrLabel || 0;
13006 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).
13007 i = timeOrLabel.indexOf("=");
13009 if (this._labels[timeOrLabel] == null) {
13010 return appendIfAbsent ? (this._labels[timeOrLabel] = this.duration() + offsetOrLabel) : offsetOrLabel;
13012 return this._labels[timeOrLabel] + offsetOrLabel;
13014 offsetOrLabel = parseInt(timeOrLabel.charAt(i-1) + "1", 10) * Number(timeOrLabel.substr(i+1));
13015 timeOrLabel = (i > 1) ? this._parseTimeOrLabel(timeOrLabel.substr(0, i-1), 0, appendIfAbsent) : this.duration();
13016 } else if (timeOrLabel == null) {
13017 timeOrLabel = this.duration();
13019 return Number(timeOrLabel) + offsetOrLabel;
13022 p.seek = function(position, suppressEvents) {
13023 return this.totalTime((typeof(position) === "number") ? position : this._parseTimeOrLabel(position), (suppressEvents !== false));
13026 p.stop = function() {
13027 return this.paused(true);
13030 p.gotoAndPlay = function(position, suppressEvents) {
13031 return this.play(position, suppressEvents);
13034 p.gotoAndStop = function(position, suppressEvents) {
13035 return this.pause(position, suppressEvents);
13038 p.render = function(time, suppressEvents, force) {
13040 this._enabled(true, false);
13042 var totalDur = (!this._dirty) ? this._totalDuration : this.totalDuration(),
13043 prevTime = this._time,
13044 prevStart = this._startTime,
13045 prevTimeScale = this._timeScale,
13046 prevPaused = this._paused,
13047 tween, isComplete, next, callback, internalForce;
13048 if (time >= totalDur) {
13049 this._totalTime = this._time = totalDur;
13050 if (!this._reversed) if (!this._hasPausedChild()) {
13052 callback = "onComplete";
13053 if (this._duration === 0) if (time === 0 || this._rawPrevTime < 0 || this._rawPrevTime === _tinyNum) if (this._rawPrevTime !== time && this._first) {
13054 internalForce = true;
13055 if (this._rawPrevTime > _tinyNum) {
13056 callback = "onReverseComplete";
13060 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.
13061 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.
13063 } else if (time < 0.0000001) { //to work around occasional floating point math artifacts, round super small values to 0.
13064 this._totalTime = this._time = 0;
13065 if (prevTime !== 0 || (this._duration === 0 && this._rawPrevTime !== _tinyNum && (this._rawPrevTime > 0 || (time < 0 && this._rawPrevTime >= 0)))) {
13066 callback = "onReverseComplete";
13067 isComplete = this._reversed;
13070 this._active = false;
13071 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.
13072 internalForce = true;
13074 this._rawPrevTime = time;
13076 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.
13078 time = 0; //to avoid occasional floating point rounding errors (could cause problems especially with zero-duration tweens at the very beginning of the timeline)
13079 if (!this._initted) {
13080 internalForce = true;
13085 this._totalTime = this._time = this._rawPrevTime = time;
13087 if ((this._time === prevTime || !this._first) && !force && !internalForce) {
13089 } else if (!this._initted) {
13090 this._initted = true;
13093 if (!this._active) if (!this._paused && this._time !== prevTime && time > 0) {
13094 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.
13097 if (prevTime === 0) if (this.vars.onStart) if (this._time !== 0) if (!suppressEvents) {
13098 this.vars.onStart.apply(this.vars.onStartScope || this, this.vars.onStartParams || _blankArray);
13101 if (this._time >= prevTime) {
13102 tween = this._first;
13104 next = tween._next; //record it here because the value could change after rendering...
13105 if (this._paused && !prevPaused) { //in case a tween pauses the timeline when rendering
13107 } else if (tween._active || (tween._startTime <= this._time && !tween._paused && !tween._gc)) {
13108 if (!tween._reversed) {
13109 tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
13111 tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
13117 tween = this._last;
13119 next = tween._prev; //record it here because the value could change after rendering...
13120 if (this._paused && !prevPaused) { //in case a tween pauses the timeline when rendering
13122 } else if (tween._active || (tween._startTime <= prevTime && !tween._paused && !tween._gc)) {
13123 if (!tween._reversed) {
13124 tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
13126 tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
13133 if (this._onUpdate) if (!suppressEvents) {
13134 this._onUpdate.apply(this.vars.onUpdateScope || this, this.vars.onUpdateParams || _blankArray);
13137 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
13139 if (this._timeline.autoRemoveChildren) {
13140 this._enabled(false, false);
13142 this._active = false;
13144 if (!suppressEvents && this.vars[callback]) {
13145 this.vars[callback].apply(this.vars[callback + "Scope"] || this, this.vars[callback + "Params"] || _blankArray);
13150 p._hasPausedChild = function() {
13151 var tween = this._first;
13153 if (tween._paused || ((tween instanceof TimelineLite) && tween._hasPausedChild())) {
13156 tween = tween._next;
13161 p.getChildren = function(nested, tweens, timelines, ignoreBeforeTime) {
13162 ignoreBeforeTime = ignoreBeforeTime || -9999999999;
13164 tween = this._first,
13167 if (tween._startTime < ignoreBeforeTime) {
13169 } else if (tween instanceof TweenLite) {
13170 if (tweens !== false) {
13174 if (timelines !== false) {
13177 if (nested !== false) {
13178 a = a.concat(tween.getChildren(true, tweens, timelines));
13182 tween = tween._next;
13187 p.getTweensOf = function(target, nested) {
13188 var disabled = this._gc,
13193 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.
13195 tweens = TweenLite.getTweensOf(target);
13198 if (tweens[i].timeline === this || (nested && this._contains(tweens[i]))) {
13199 a[cnt++] = tweens[i];
13203 this._enabled(false, true);
13208 p._contains = function(tween) {
13209 var tl = tween.timeline;
13219 p.shiftChildren = function(amount, adjustLabels, ignoreBeforeTime) {
13220 ignoreBeforeTime = ignoreBeforeTime || 0;
13221 var tween = this._first,
13222 labels = this._labels,
13225 if (tween._startTime >= ignoreBeforeTime) {
13226 tween._startTime += amount;
13228 tween = tween._next;
13230 if (adjustLabels) {
13231 for (p in labels) {
13232 if (labels[p] >= ignoreBeforeTime) {
13233 labels[p] += amount;
13237 return this._uncache(true);
13240 p._kill = function(vars, target) {
13241 if (!vars && !target) {
13242 return this._enabled(false, false);
13244 var tweens = (!target) ? this.getChildren(true, true, false) : this.getTweensOf(target),
13248 if (tweens[i]._kill(vars, target)) {
13255 p.clear = function(labels) {
13256 var tweens = this.getChildren(false, true, true),
13258 this._time = this._totalTime = 0;
13260 tweens[i]._enabled(false, false);
13262 if (labels !== false) {
13265 return this._uncache(true);
13268 p.invalidate = function() {
13269 var tween = this._first;
13271 tween.invalidate();
13272 tween = tween._next;
13277 p._enabled = function(enabled, ignoreTimeline) {
13278 if (enabled === this._gc) {
13279 var tween = this._first;
13281 tween._enabled(enabled, true);
13282 tween = tween._next;
13285 return SimpleTimeline.prototype._enabled.call(this, enabled, ignoreTimeline);
13288 p.duration = function(value) {
13289 if (!arguments.length) {
13291 this.totalDuration(); //just triggers recalculation
13293 return this._duration;
13295 if (this.duration() !== 0 && value !== 0) {
13296 this.timeScale(this._duration / value);
13301 p.totalDuration = function(value) {
13302 if (!arguments.length) {
13305 tween = this._last,
13306 prevStart = 999999999999,
13309 prev = tween._prev; //record it here in case the tween changes position in the sequence...
13310 if (tween._dirty) {
13311 tween.totalDuration(); //could change the tween._startTime, so make sure the tween's cache is clean before analyzing it.
13313 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
13314 this.add(tween, tween._startTime - tween._delay);
13316 prevStart = tween._startTime;
13318 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.
13319 max -= tween._startTime;
13320 if (this._timeline.smoothChildTiming) {
13321 this._startTime += tween._startTime / this._timeScale;
13323 this.shiftChildren(-tween._startTime, false, -9999999999);
13326 end = tween._startTime + (tween._totalDuration / tween._timeScale);
13332 this._duration = this._totalDuration = max;
13333 this._dirty = false;
13335 return this._totalDuration;
13337 if (this.totalDuration() !== 0) if (value !== 0) {
13338 this.timeScale(this._totalDuration / value);
13343 p.usesFrames = function() {
13344 var tl = this._timeline;
13345 while (tl._timeline) {
13348 return (tl === Animation._rootFramesTimeline);
13351 p.rawTime = function() {
13352 return this._paused ? this._totalTime : (this._timeline.rawTime() - this._startTime) * this._timeScale;
13355 return TimelineLite;
13372 * ----------------------------------------------------------------
13374 * ----------------------------------------------------------------
13376 window._gsDefine("TimelineMax", ["TimelineLite","TweenLite","easing.Ease"], function(TimelineLite, TweenLite, Ease) {
13378 var TimelineMax = function(vars) {
13379 TimelineLite.call(this, vars);
13380 this._repeat = this.vars.repeat || 0;
13381 this._repeatDelay = this.vars.repeatDelay || 0;
13383 this._yoyo = (this.vars.yoyo === true);
13384 this._dirty = true;
13386 _tinyNum = 0.0000000001,
13388 _easeNone = new Ease(null, null, 1, 0),
13389 p = TimelineMax.prototype = new TimelineLite();
13391 p.constructor = TimelineMax;
13392 p.kill()._gc = false;
13393 TimelineMax.version = "1.12.1";
13395 p.invalidate = function() {
13396 this._yoyo = (this.vars.yoyo === true);
13397 this._repeat = this.vars.repeat || 0;
13398 this._repeatDelay = this.vars.repeatDelay || 0;
13399 this._uncache(true);
13400 return TimelineLite.prototype.invalidate.call(this);
13403 p.addCallback = function(callback, position, params, scope) {
13404 return this.add( TweenLite.delayedCall(0, callback, params, scope), position);
13407 p.removeCallback = function(callback, position) {
13409 if (position == null) {
13410 this._kill(null, callback);
13412 var a = this.getTweensOf(callback, false),
13414 time = this._parseTimeOrLabel(position);
13416 if (a[i]._startTime === time) {
13417 a[i]._enabled(false, false);
13425 p.tweenTo = function(position, vars) {
13427 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.
13432 copy.time = this._parseTimeOrLabel(position);
13433 duration = (Math.abs(Number(copy.time) - this._time) / this._timeScale) || 0.001;
13434 t = new TweenLite(this, duration, copy);
13435 copy.onStart = function() {
13436 t.target.paused(true);
13437 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.
13438 t.duration( Math.abs( t.vars.time - t.target.time()) / t.target._timeScale );
13440 if (vars.onStart) { //in case the user had an onStart in the vars - we don't want to overwrite it.
13441 vars.onStart.apply(vars.onStartScope || t, vars.onStartParams || _blankArray);
13447 p.tweenFromTo = function(fromPosition, toPosition, vars) {
13449 fromPosition = this._parseTimeOrLabel(fromPosition);
13450 vars.startAt = {onComplete:this.seek, onCompleteParams:[fromPosition], onCompleteScope:this};
13451 vars.immediateRender = (vars.immediateRender !== false);
13452 var t = this.tweenTo(toPosition, vars);
13453 return t.duration((Math.abs( t.vars.time - fromPosition) / this._timeScale) || 0.001);
13456 p.render = function(time, suppressEvents, force) {
13458 this._enabled(true, false);
13460 var totalDur = (!this._dirty) ? this._totalDuration : this.totalDuration(),
13461 dur = this._duration,
13462 prevTime = this._time,
13463 prevTotalTime = this._totalTime,
13464 prevStart = this._startTime,
13465 prevTimeScale = this._timeScale,
13466 prevRawPrevTime = this._rawPrevTime,
13467 prevPaused = this._paused,
13468 prevCycle = this._cycle,
13469 tween, isComplete, next, callback, internalForce, cycleDuration;
13470 if (time >= totalDur) {
13471 if (!this._locked) {
13472 this._totalTime = totalDur;
13473 this._cycle = this._repeat;
13475 if (!this._reversed) if (!this._hasPausedChild()) {
13477 callback = "onComplete";
13478 if (this._duration === 0) if (time === 0 || prevRawPrevTime < 0 || prevRawPrevTime === _tinyNum) if (prevRawPrevTime !== time && this._first) {
13479 internalForce = true;
13480 if (prevRawPrevTime > _tinyNum) {
13481 callback = "onReverseComplete";
13485 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.
13486 if (this._yoyo && (this._cycle & 1) !== 0) {
13487 this._time = time = 0;
13490 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.
13493 } else if (time < 0.0000001) { //to work around occasional floating point math artifacts, round super small values to 0.
13494 if (!this._locked) {
13495 this._totalTime = this._cycle = 0;
13498 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)
13499 callback = "onReverseComplete";
13500 isComplete = this._reversed;
13503 this._active = false;
13504 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.
13505 internalForce = true;
13507 this._rawPrevTime = time;
13509 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.
13510 time = 0; //to avoid occasional floating point rounding errors (could cause problems especially with zero-duration tweens at the very beginning of the timeline)
13511 if (!this._initted) {
13512 internalForce = true;
13517 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.
13518 internalForce = true;
13520 this._time = this._rawPrevTime = time;
13521 if (!this._locked) {
13522 this._totalTime = time;
13523 if (this._repeat !== 0) {
13524 cycleDuration = dur + this._repeatDelay;
13525 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!)
13526 if (this._cycle !== 0) if (this._cycle === this._totalTime / cycleDuration) {
13527 this._cycle--; //otherwise when rendered exactly at the end time, it will act as though it is repeating (at the beginning)
13529 this._time = this._totalTime - (this._cycle * cycleDuration);
13530 if (this._yoyo) if ((this._cycle & 1) !== 0) {
13531 this._time = dur - this._time;
13533 if (this._time > dur) {
13535 time = dur + 0.0001; //to avoid occasional floating point rounding error
13536 } else if (this._time < 0) {
13537 this._time = time = 0;
13545 if (this._cycle !== prevCycle) if (!this._locked) {
13547 make sure children at the end/beginning of the timeline are rendered properly. If, for example,
13548 a 3-second long timeline rendered at 2.9 seconds previously, and now renders at 3.2 seconds (which
13549 would get transated to 2.8 seconds if the timeline yoyos or 0.2 seconds if it just repeats), there
13550 could be a callback or a short tween that's at 2.95 or 3 seconds in which wouldn't render. So
13551 we need to push the timeline to the end (and/or beginning depending on its yoyo value). Also we must
13552 ensure that zero-duration tweens at the very beginning or end of the TimelineMax work.
13554 var backwards = (this._yoyo && (prevCycle & 1) !== 0),
13555 wrap = (backwards === (this._yoyo && (this._cycle & 1) !== 0)),
13556 recTotalTime = this._totalTime,
13557 recCycle = this._cycle,
13558 recRawPrevTime = this._rawPrevTime,
13559 recTime = this._time;
13561 this._totalTime = prevCycle * dur;
13562 if (this._cycle < prevCycle) {
13563 backwards = !backwards;
13565 this._totalTime += dur;
13567 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.
13569 this._rawPrevTime = (dur === 0) ? prevRawPrevTime - 0.0001 : prevRawPrevTime;
13570 this._cycle = prevCycle;
13571 this._locked = true; //prevents changes to totalTime and skips repeat/yoyo behavior when we recursively call render()
13572 prevTime = (backwards) ? 0 : dur;
13573 this.render(prevTime, suppressEvents, (dur === 0));
13574 if (!suppressEvents) if (!this._gc) {
13575 if (this.vars.onRepeat) {
13576 this.vars.onRepeat.apply(this.vars.onRepeatScope || this, this.vars.onRepeatParams || _blankArray);
13580 prevTime = (backwards) ? dur + 0.0001 : -0.0001;
13581 this.render(prevTime, true, false);
13583 this._locked = false;
13584 if (this._paused && !prevPaused) { //if the render() triggered callback that paused this timeline, we should abort (very rare, but possible)
13587 this._time = recTime;
13588 this._totalTime = recTotalTime;
13589 this._cycle = recCycle;
13590 this._rawPrevTime = recRawPrevTime;
13593 if ((this._time === prevTime || !this._first) && !force && !internalForce) {
13594 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.
13595 this._onUpdate.apply(this.vars.onUpdateScope || this, this.vars.onUpdateParams || _blankArray);
13598 } else if (!this._initted) {
13599 this._initted = true;
13602 if (!this._active) if (!this._paused && this._totalTime !== prevTotalTime && time > 0) {
13603 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.
13606 if (prevTotalTime === 0) if (this.vars.onStart) if (this._totalTime !== 0) if (!suppressEvents) {
13607 this.vars.onStart.apply(this.vars.onStartScope || this, this.vars.onStartParams || _blankArray);
13610 if (this._time >= prevTime) {
13611 tween = this._first;
13613 next = tween._next; //record it here because the value could change after rendering...
13614 if (this._paused && !prevPaused) { //in case a tween pauses the timeline when rendering
13616 } else if (tween._active || (tween._startTime <= this._time && !tween._paused && !tween._gc)) {
13617 if (!tween._reversed) {
13618 tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
13620 tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
13627 tween = this._last;
13629 next = tween._prev; //record it here because the value could change after rendering...
13630 if (this._paused && !prevPaused) { //in case a tween pauses the timeline when rendering
13632 } else if (tween._active || (tween._startTime <= prevTime && !tween._paused && !tween._gc)) {
13633 if (!tween._reversed) {
13634 tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
13636 tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
13643 if (this._onUpdate) if (!suppressEvents) {
13644 this._onUpdate.apply(this.vars.onUpdateScope || this, this.vars.onUpdateParams || _blankArray);
13646 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
13648 if (this._timeline.autoRemoveChildren) {
13649 this._enabled(false, false);
13651 this._active = false;
13653 if (!suppressEvents && this.vars[callback]) {
13654 this.vars[callback].apply(this.vars[callback + "Scope"] || this, this.vars[callback + "Params"] || _blankArray);
13659 p.getActive = function(nested, tweens, timelines) {
13660 if (nested == null) {
13663 if (tweens == null) {
13666 if (timelines == null) {
13670 all = this.getChildren(nested, tweens, timelines),
13674 for (i = 0; i < l; i++) {
13676 if (tween.isActive()) {
13684 p.getLabelAfter = function(time) {
13685 if (!time) if (time !== 0) { //faster than isNan()
13688 var labels = this.getLabelsArray(),
13691 for (i = 0; i < l; i++) {
13692 if (labels[i].time > time) {
13693 return labels[i].name;
13699 p.getLabelBefore = function(time) {
13700 if (time == null) {
13703 var labels = this.getLabelsArray(),
13706 if (labels[i].time < time) {
13707 return labels[i].name;
13713 p.getLabelsArray = function() {
13717 for (p in this._labels) {
13718 a[cnt++] = {time:this._labels[p], name:p};
13720 a.sort(function(a,b) {
13721 return a.time - b.time;
13727 //---- GETTERS / SETTERS -------------------------------------------------------------------------------------------------------
13729 p.progress = function(value) {
13730 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);
13733 p.totalProgress = function(value) {
13734 return (!arguments.length) ? this._totalTime / this.totalDuration() : this.totalTime( this.totalDuration() * value, false);
13737 p.totalDuration = function(value) {
13738 if (!arguments.length) {
13740 TimelineLite.prototype.totalDuration.call(this); //just forces refresh
13741 //Instead of Infinity, we use 999999999999 so that we can accommodate reverses.
13742 this._totalDuration = (this._repeat === -1) ? 999999999999 : this._duration * (this._repeat + 1) + (this._repeatDelay * this._repeat);
13744 return this._totalDuration;
13746 return (this._repeat === -1) ? this : this.duration( (value - (this._repeat * this._repeatDelay)) / (this._repeat + 1) );
13749 p.time = function(value, suppressEvents) {
13750 if (!arguments.length) {
13754 this.totalDuration();
13756 if (value > this._duration) {
13757 value = this._duration;
13759 if (this._yoyo && (this._cycle & 1) !== 0) {
13760 value = (this._duration - value) + (this._cycle * (this._duration + this._repeatDelay));
13761 } else if (this._repeat !== 0) {
13762 value += this._cycle * (this._duration + this._repeatDelay);
13764 return this.totalTime(value, suppressEvents);
13767 p.repeat = function(value) {
13768 if (!arguments.length) {
13769 return this._repeat;
13771 this._repeat = value;
13772 return this._uncache(true);
13775 p.repeatDelay = function(value) {
13776 if (!arguments.length) {
13777 return this._repeatDelay;
13779 this._repeatDelay = value;
13780 return this._uncache(true);
13783 p.yoyo = function(value) {
13784 if (!arguments.length) {
13787 this._yoyo = value;
13791 p.currentLabel = function(value) {
13792 if (!arguments.length) {
13793 return this.getLabelBefore(this._time + 0.00000001);
13795 return this.seek(value, true);
13798 return TimelineMax;
13814 * ----------------------------------------------------------------
13816 * ----------------------------------------------------------------
13820 var _RAD2DEG = 180 / Math.PI,
13825 Segment = function(a, b, c, d) {
13834 _correlate = ",x,y,z,left,top,right,bottom,marginTop,marginLeft,marginRight,marginBottom,paddingLeft,paddingTop,paddingRight,paddingBottom,backgroundPosition,backgroundPosition_y,",
13835 cubicToQuadratic = function(a, b, c, d) {
13843 mabc = (mab + mbc) / 2,
13844 mbcd = (mbc + mcd) / 2,
13845 m8 = (mbcd - mabc) / 8;
13846 q1.b = mab + (a - mab) / 4;
13848 q1.c = q2.a = (q1.b + q2.b) / 2;
13849 q2.c = q3.a = (mabc + mbcd) / 2;
13851 q4.b = mcd + (d - mcd) / 4;
13852 q3.c = q4.a = (q3.b + q4.b) / 2;
13853 return [q1, q2, q3, q4];
13855 _calculateControlPoints = function(a, curviness, quad, basic, correlate) {
13856 var l = a.length - 1,
13859 i, p1, p2, p3, seg, m1, m2, mm, cp2, qb, r1, r2, tl;
13860 for (i = 0; i < l; i++) {
13869 tl = ((r2 + r1) * curviness * 0.25) / (basic ? 0.5 : _r3[i] || 0.5);
13870 m1 = p2 - (p2 - p1) * (basic ? curviness * 0.5 : (r1 !== 0 ? tl / r1 : 0));
13871 m2 = p2 + (p3 - p2) * (basic ? curviness * 0.5 : (r2 !== 0 ? tl / r2 : 0));
13872 mm = p2 - (m1 + (((m2 - m1) * ((r1 * 3 / (r1 + r2)) + 0.5) / 4) || 0));
13874 m1 = p2 - (p2 - p1) * curviness * 0.5;
13875 m2 = p2 + (p3 - p2) * curviness * 0.5;
13876 mm = p2 - (m1 + m2) / 2;
13885 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.
13893 qb = cubicToQuadratic(p1, cp1, cp2, p2);
13894 a.splice(ii, 1, qb[0], qb[1], qb[2], qb[3]);
13904 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.
13905 seg.da = seg.d - seg.a;
13906 seg.ca = seg.c - seg.a;
13907 seg.ba = cp1 - seg.a;
13909 qb = cubicToQuadratic(seg.a, cp1, seg.c, seg.d);
13910 a.splice(ii, 1, qb[0], qb[1], qb[2], qb[3]);
13913 _parseAnchors = function(values, p, correlate, prepend) {
13915 l, i, p1, p2, p3, tmp;
13917 values = [prepend].concat(values);
13920 if (typeof( (tmp = values[i][p]) ) === "string") if (tmp.charAt(1) === "=") {
13921 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
13925 l = values.length - 2;
13927 a[0] = new Segment(values[0][p], 0, 0, values[(l < -1) ? 0 : 1][p]);
13930 for (i = 0; i < l; i++) {
13932 p2 = values[i+1][p];
13933 a[i] = new Segment(p1, 0, 0, p2);
13935 p3 = values[i+2][p];
13936 _r1[i] = (_r1[i] || 0) + (p2 - p1) * (p2 - p1);
13937 _r2[i] = (_r2[i] || 0) + (p3 - p2) * (p3 - p2);
13940 a[i] = new Segment(values[i][p], 0, 0, values[i+1][p]);
13943 bezierThrough = function(values, curviness, quadratic, basic, correlate, prepend) {
13946 first = prepend || values[0],
13947 i, p, a, j, r, l, seamless, last;
13948 correlate = (typeof(correlate) === "string") ? ","+correlate+"," : _correlate;
13949 if (curviness == null) {
13952 for (p in values[0]) {
13955 //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)
13956 if (values.length > 1) {
13957 last = values[values.length - 1];
13962 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
13968 values = values.concat(); //duplicate the array to avoid contaminating the original which the user may be reusing for other tweens
13970 values.unshift(prepend);
13972 values.push(values[1]);
13973 prepend = values[values.length - 3];
13976 _r1.length = _r2.length = _r3.length = 0;
13980 _corProps[p] = (correlate.indexOf(","+p+",") !== -1);
13981 obj[p] = _parseAnchors(values, p, _corProps[p], prepend);
13985 _r1[i] = Math.sqrt(_r1[i]);
13986 _r2[i] = Math.sqrt(_r2[i]);
13991 if (_corProps[p]) {
13994 for (j = 0; j < l; j++) {
13995 r = a[j+1].da / _r2[j] + a[j].da / _r1[j];
13996 _r3[j] = (_r3[j] || 0) + r * r;
14002 _r3[i] = Math.sqrt(_r3[i]);
14006 j = quadratic ? 4 : 1;
14010 _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
14013 a.splice(a.length - j, j);
14018 _parseBezierData = function(values, type, prepend) {
14019 type = type || "soft";
14021 inc = (type === "cubic") ? 3 : 2,
14022 soft = (type === "soft"),
14024 a, b, c, d, cur, i, j, l, p, cnt, tmp;
14025 if (soft && prepend) {
14026 values = [prepend].concat(values);
14028 if (values == null || values.length < inc + 1) { throw "invalid Bezier data"; }
14029 for (p in values[0]) {
14038 for (j = 0; j < l; j++) {
14039 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);
14040 if (soft) if (j > 1) if (j < l - 1) {
14041 cur[cnt++] = (a + cur[cnt-2]) / 2;
14047 for (j = 0; j < l; j += inc) {
14051 d = (inc === 2) ? 0 : cur[j+3];
14052 cur[cnt++] = tmp = (inc === 3) ? new Segment(a, b, c, d) : new Segment(a, (2 * b + a) / 3, (2 * b + c) / 3, c);
14058 _addCubicLengths = function(a, steps, resolution) {
14059 var inc = 1 / resolution,
14061 d, d1, s, da, ca, ba, p, i, inv, bez, index;
14069 for (i = 1; i <= resolution; i++) {
14072 d = d1 - (d1 = (p * p * da + 3 * inv * (p * ca + inv * ba)) * p);
14073 index = j * resolution + i - 1;
14074 steps[index] = (steps[index] || 0) + d * d;
14078 _parseLengthData = function(obj, resolution) {
14079 resolution = resolution >> 0 || 6;
14084 threshold = resolution - 1,
14086 curLS = [], //current length segments array
14089 _addCubicLengths(obj[p], a, resolution);
14092 for (i = 0; i < l; i++) {
14093 d += Math.sqrt(a[i]);
14094 index = i % resolution;
14096 if (index === threshold) {
14098 index = (i / resolution) >> 0;
14099 segments[index] = curLS;
14100 lengths[index] = total;
14105 return {length:total, lengths:lengths, segments:segments};
14110 BezierPlugin = window._gsDefine.plugin({
14111 propName: "bezier",
14117 //gets called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.
14118 init: function(target, vars, tween) {
14119 this._target = target;
14120 if (vars instanceof Array) {
14121 vars = {values:vars};
14126 this._timeRes = (vars.timeResolution == null) ? 6 : parseInt(vars.timeResolution, 10);
14127 var values = vars.values || [],
14129 second = values[0],
14130 autoRotate = vars.autoRotate || tween.vars.orientToBezier,
14131 p, isFunc, i, j, prepend;
14133 this._autoRotate = autoRotate ? (autoRotate instanceof Array) ? autoRotate : [["x","y","rotation",((autoRotate === true) ? 0 : Number(autoRotate) || 0)]] : null;
14134 for (p in second) {
14135 this._props.push(p);
14138 i = this._props.length;
14140 p = this._props[i];
14142 this._overwriteProps.push(p);
14143 isFunc = this._func[p] = (typeof(target[p]) === "function");
14144 first[p] = (!isFunc) ? parseFloat(target[p]) : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]();
14145 if (!prepend) if (first[p] !== values[0][p]) {
14149 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);
14150 this._segCount = this._beziers[p].length;
14152 if (this._timeRes) {
14153 var ld = _parseLengthData(this._beziers, this._timeRes);
14154 this._length = ld.length;
14155 this._lengths = ld.lengths;
14156 this._segments = ld.segments;
14157 this._l1 = this._li = this._s1 = this._si = 0;
14158 this._l2 = this._lengths[0];
14159 this._curSeg = this._segments[0];
14160 this._s2 = this._curSeg[0];
14161 this._prec = 1 / this._curSeg.length;
14164 if ((autoRotate = this._autoRotate)) {
14165 this._initialRotations = [];
14166 if (!(autoRotate[0] instanceof Array)) {
14167 this._autoRotate = autoRotate = [autoRotate];
14169 i = autoRotate.length;
14171 for (j = 0; j < 3; j++) {
14172 p = autoRotate[i][j];
14173 this._func[p] = (typeof(target[p]) === "function") ? target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ] : false;
14175 p = autoRotate[i][2];
14176 this._initialRotations[i] = this._func[p] ? this._func[p].call(this._target) : this._target[p];
14179 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.
14183 //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.)
14185 var segments = this._segCount,
14187 target = this._target,
14188 notStart = (v !== this._startRatio),
14189 curIndex, inv, i, p, b, t, val, l, lengths, curSeg;
14190 if (!this._timeRes) {
14191 curIndex = (v < 0) ? 0 : (v >= 1) ? segments - 1 : (segments * v) >> 0;
14192 t = (v - (curIndex * (1 / segments))) * segments;
14194 lengths = this._lengths;
14195 curSeg = this._curSeg;
14198 //find the appropriate segment (if the currently cached one isn't correct)
14199 if (v > this._l2 && i < segments - 1) {
14201 while (i < l && (this._l2 = lengths[++i]) <= v) { }
14202 this._l1 = lengths[i-1];
14204 this._curSeg = curSeg = this._segments[i];
14205 this._s2 = curSeg[(this._s1 = this._si = 0)];
14206 } else if (v < this._l1 && i > 0) {
14207 while (i > 0 && (this._l1 = lengths[--i]) >= v) { }
14208 if (i === 0 && v < this._l1) {
14213 this._l2 = lengths[i];
14215 this._curSeg = curSeg = this._segments[i];
14216 this._s1 = curSeg[(this._si = curSeg.length - 1) - 1] || 0;
14217 this._s2 = curSeg[this._si];
14220 //now find the appropriate sub-segment (we split it into the number of pieces that was defined by "precision" and measured each one)
14223 if (v > this._s2 && i < curSeg.length - 1) {
14224 l = curSeg.length - 1;
14225 while (i < l && (this._s2 = curSeg[++i]) <= v) { }
14226 this._s1 = curSeg[i-1];
14228 } else if (v < this._s1 && i > 0) {
14229 while (i > 0 && (this._s1 = curSeg[--i]) >= v) { }
14230 if (i === 0 && v < this._s1) {
14235 this._s2 = curSeg[i];
14238 t = (i + (v - this._s1) / (this._s2 - this._s1)) * this._prec;
14242 i = this._props.length;
14244 p = this._props[i];
14245 b = this._beziers[p][curIndex];
14246 val = (t * t * b.da + 3 * inv * (t * b.ca + inv * b.ba)) * t + b.a;
14247 if (this._round[p]) {
14248 val = Math.round(val);
14257 if (this._autoRotate) {
14258 var ar = this._autoRotate,
14259 b2, x1, y1, x2, y2, add, conv;
14263 add = ar[i][3] || 0;
14264 conv = (ar[i][4] === true) ? 1 : _RAD2DEG;
14265 b = this._beziers[ar[i][0]];
14266 b2 = this._beziers[ar[i][1]];
14268 if (b && b2) { //in case one of the properties got overwritten.
14272 x1 = b.a + (b.b - b.a) * t;
14273 x2 = b.b + (b.c - b.b) * t;
14274 x1 += (x2 - x1) * t;
14275 x2 += ((b.c + (b.d - b.c) * t) - x2) * t;
14277 y1 = b2.a + (b2.b - b2.a) * t;
14278 y2 = b2.b + (b2.c - b2.b) * t;
14279 y1 += (y2 - y1) * t;
14280 y2 += ((b2.c + (b2.d - b2.c) * t) - y2) * t;
14282 val = notStart ? Math.atan2(y2 - y1, x2 - x1) * conv + add : this._initialRotations[i];
14294 p = BezierPlugin.prototype;
14297 BezierPlugin.bezierThrough = bezierThrough;
14298 BezierPlugin.cubicToQuadratic = cubicToQuadratic;
14299 BezierPlugin._autoCSS = true; //indicates that this plugin can be inserted into the "css" object using the autoCSS feature of TweenLite
14300 BezierPlugin.quadraticToCubic = function(a, b, c) {
14301 return new Segment(a, (2 * b + a) / 3, (2 * b + c) / 3, c);
14304 BezierPlugin._cssRegister = function() {
14305 var CSSPlugin = window._gsDefine.globals.CSSPlugin;
14309 var _internals = CSSPlugin._internals,
14310 _parseToProxy = _internals._parseToProxy,
14311 _setPluginRatio = _internals._setPluginRatio,
14312 CSSPropTween = _internals.CSSPropTween;
14313 _internals._registerComplexSpecialProp("bezier", {parser:function(t, e, prop, cssp, pt, plugin) {
14314 if (e instanceof Array) {
14317 plugin = new BezierPlugin();
14318 var values = e.values,
14319 l = values.length - 1,
14326 for (i = 0; i <= l; i++) {
14327 data = _parseToProxy(t, values[i], cssp, pt, plugin, (l !== i));
14328 pluginValues[i] = data.end;
14331 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.
14333 v.values = pluginValues;
14334 pt = new CSSPropTween(t, "bezier", 0, 0, data.pt, 2);
14336 pt.plugin = plugin;
14337 pt.setRatio = _setPluginRatio;
14338 if (v.autoRotate === 0) {
14339 v.autoRotate = true;
14341 if (v.autoRotate && !(v.autoRotate instanceof Array)) {
14342 i = (v.autoRotate === true) ? 0 : Number(v.autoRotate);
14343 v.autoRotate = (data.end.left != null) ? [["left","top","rotation",i,false]] : (data.end.x != null) ? [["x","y","rotation",i,false]] : false;
14345 if (v.autoRotate) {
14346 if (!cssp._transform) {
14347 cssp._enableTransforms(false);
14349 data.autoRotate = cssp._target._gsTransform;
14351 plugin._onInitTween(data.proxy, v, cssp._tween);
14356 p._roundProps = function(lookup, value) {
14357 var op = this._overwriteProps,
14360 if (lookup[op[i]] || lookup.bezier || lookup.bezierThrough) {
14361 this._round[op[i]] = value;
14366 p._kill = function(lookup) {
14367 var a = this._props,
14369 for (p in this._beziers) {
14371 delete this._beziers[p];
14372 delete this._func[p];
14381 return this._super._kill.call(this, lookup);
14400 * ----------------------------------------------------------------
14402 * ----------------------------------------------------------------
14404 window._gsDefine("plugins.CSSPlugin", ["plugins.TweenPlugin","TweenLite"], function(TweenPlugin, TweenLite) {
14406 /** @constructor **/
14407 var CSSPlugin = function() {
14408 TweenPlugin.call(this, "css");
14409 this._overwriteProps.length = 0;
14410 this.setRatio = CSSPlugin.prototype.setRatio; //speed optimization (avoid prototype lookup on this "hot" method)
14412 _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.
14413 _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
14414 _cs, //computed style (we store this in a shared variable to conserve memory and make minification tighter
14415 _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.
14416 _specialProps = {},
14417 p = CSSPlugin.prototype = new TweenPlugin("css");
14419 p.constructor = CSSPlugin;
14420 CSSPlugin.version = "1.12.1";
14422 CSSPlugin.defaultTransformPerspective = 0;
14423 CSSPlugin.defaultSkewType = "compensated";
14424 p = "px"; //we'll reuse the "p" variable to keep file size down
14425 CSSPlugin.suffixMap = {top:p, right:p, bottom:p, left:p, width:p, height:p, fontSize:p, padding:p, margin:p, perspective:p, lineHeight:""};
14428 var _numExp = /(?:\d|\-\d|\.\d|\-\.\d)+/g,
14429 _relNumExp = /(?:\d|\-\d|\.\d|\-\.\d|\+=\d|\-=\d|\+=.\d|\-=\.\d)+/g,
14430 _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)"
14431 _NaNExp = /[^\d\-\.]/g,
14432 _suffixExp = /(?:\d|\-|\+|=|#|\.)*/g,
14433 _opacityExp = /opacity *= *([^)]*)/i,
14434 _opacityValExp = /opacity:([^;]*)/i,
14435 _alphaFilterExp = /alpha\(opacity *=.+?\)/i,
14436 _rgbhslExp = /^(rgb|hsl)/,
14437 _capsExp = /([A-Z])/g,
14438 _camelExp = /-([a-z])/gi,
14439 _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)
14440 _camelFunc = function(s, g) { return g.toUpperCase(); },
14441 _horizExp = /(?:Left|Right|Width)/i,
14442 _ieGetMatrixExp = /(M11|M12|M21|M22)=[\d\-\.e]+/gi,
14443 _ieSetMatrixExp = /progid\:DXImageTransform\.Microsoft\.Matrix\(.+?\)/i,
14444 _commasOutsideParenExp = /,(?=[^\)]*(?:\(|$))/gi, //finds any commas that are not within parenthesis
14445 _DEG2RAD = Math.PI / 180,
14446 _RAD2DEG = 180 / Math.PI,
14449 _tempDiv = _doc.createElement("div"),
14450 _tempImg = _doc.createElement("img"),
14451 _internals = CSSPlugin._internals = {_specialProps:_specialProps}, //provides a hook to a few internal methods that we need to access from inside other plugins
14452 _agent = navigator.userAgent,
14454 _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).
14457 _isFirefox, //Firefox has a bug that causes 3D transformed elements to randomly disappear unless a repaint is forced after each update on each element.
14458 _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!)
14460 _supportsOpacity = (function() { //we set _isSafari, _ieVers, _isFirefox, and _supportsOpacity all in one function here to reduce file size slightly, especially in the minified version.
14461 var i = _agent.indexOf("Android"),
14462 d = _doc.createElement("div"), a;
14464 _isSafari = (_agent.indexOf("Safari") !== -1 && _agent.indexOf("Chrome") === -1 && (i === -1 || Number(_agent.substr(i+8, 1)) > 3));
14465 _isSafariLT6 = (_isSafari && (Number(_agent.substr(_agent.indexOf("Version/")+8, 1)) < 6));
14466 _isFirefox = (_agent.indexOf("Firefox") !== -1);
14468 if ((/MSIE ([0-9]{1,}[\.0-9]{0,})/).exec(_agent)) {
14469 _ieVers = parseFloat( RegExp.$1 );
14472 d.innerHTML = "<a title='' style='top:1px;opacity:.55;'>a</a>";
14473 a = d.getElementsByTagName("a")[0];
14474 return a ? /^0.55/.test(a.style.opacity) : false;
14476 _getIEOpacity = function(v) {
14477 return (_opacityExp.test( ((typeof(v) === "string") ? v : (v.currentStyle ? v.currentStyle.filter : v.style.filter) || "") ) ? ( parseFloat( RegExp.$1 ) / 100 ) : 1);
14479 _log = function(s) {//for logging messages, but in a way that won't throw errors in old versions of IE.
14480 if (window.console) {
14484 _prefixCSS = "", //the non-camelCase vendor prefix like "-o-", "-moz-", "-ms-", or "-webkit-"
14485 _prefix = "", //camelCase vendor prefix like "O", "ms", "Webkit", or "Moz".
14487 // @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)
14488 _checkPropPrefix = function(p, e) {
14492 if (s[p] !== undefined) {
14495 p = p.charAt(0).toUpperCase() + p.substr(1);
14496 a = ["O","Moz","ms","Ms","Webkit"];
14498 while (--i > -1 && s[a[i]+p] === undefined) { }
14500 _prefix = (i === 3) ? "ms" : a[i];
14501 _prefixCSS = "-" + _prefix.toLowerCase() + "-";
14502 return _prefix + p;
14507 _getComputedStyle = _doc.defaultView ? _doc.defaultView.getComputedStyle : function() {},
14510 * @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:
14511 * var currentLeft = CSSPlugin.getStyle( document.getElementById("myElement"), "left");
14513 * @param {!Object} t Target element whose style property you want to query
14514 * @param {!string} p Property name (like "left" or "top" or "marginTop", etc.)
14515 * @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.
14516 * @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.
14517 * @param {string=} dflt Default value that should be returned in the place of null, "none", "auto" or "auto auto".
14518 * @return {?string} The current property value
14520 _getStyle = CSSPlugin.getStyle = function(t, p, cs, calc, dflt) {
14522 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.
14523 return _getIEOpacity(t);
14525 if (!calc && t.style[p]) {
14527 } else if ((cs = cs || _getComputedStyle(t))) {
14528 rv = cs[p] || cs.getPropertyValue(p) || cs.getPropertyValue(p.replace(_capsExp, "-$1").toLowerCase());
14529 } else if (t.currentStyle) {
14530 rv = t.currentStyle[p];
14532 return (dflt != null && (!rv || rv === "none" || rv === "auto" || rv === "auto auto")) ? dflt : rv;
14536 * @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.
14537 * @param {!Object} t Target element
14538 * @param {!string} p Property name (like "left", "top", "marginLeft", etc.)
14539 * @param {!number} v Value
14540 * @param {string=} sfx Suffix (like "px" or "%" or "em")
14541 * @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.
14542 * @return {number} value in pixels
14544 _convertToPixels = _internals.convertToPixels = function(t, p, v, sfx, recurse) {
14545 if (sfx === "px" || !sfx) { return v; }
14546 if (sfx === "auto" || !v) { return 0; }
14547 var horiz = _horizExp.test(p),
14549 style = _tempDiv.style,
14555 if (sfx === "%" && p.indexOf("border") !== -1) {
14556 pix = (v / 100) * (horiz ? t.clientWidth : t.clientHeight);
14558 style.cssText = "border:0 solid red;position:" + _getStyle(t, "position") + ";line-height:0;";
14559 if (sfx === "%" || !node.appendChild) {
14560 node = t.parentNode || _doc.body;
14561 cache = node._gsCache;
14562 time = TweenLite.ticker.frame;
14563 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)
14564 return cache.width * v / 100;
14566 style[(horiz ? "width" : "height")] = v + sfx;
14568 style[(horiz ? "borderLeftWidth" : "borderTopWidth")] = v + sfx;
14570 node.appendChild(_tempDiv);
14571 pix = parseFloat(_tempDiv[(horiz ? "offsetWidth" : "offsetHeight")]);
14572 node.removeChild(_tempDiv);
14573 if (horiz && sfx === "%" && CSSPlugin.cacheWidths !== false) {
14574 cache = node._gsCache = node._gsCache || {};
14576 cache.width = pix / v * 100;
14578 if (pix === 0 && !recurse) {
14579 pix = _convertToPixels(t, p, v, sfx, true);
14582 return neg ? -pix : pix;
14584 _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
14585 if (_getStyle(t, "position", cs) !== "absolute") { return 0; }
14586 var dim = ((p === "left") ? "Left" : "Top"),
14587 v = _getStyle(t, "margin" + dim, cs);
14588 return t["offset" + dim] - (_convertToPixels(t, p, parseFloat(v), v.replace(_suffixExp, "")) || 0);
14591 // @private returns at object containing ALL of the style properties in camelCase and their associated values.
14592 _getAllStyles = function(t, cs) {
14595 if ((cs = cs || _getComputedStyle(t, null))) {
14596 if ((i = cs.length)) {
14598 s[cs[i].replace(_camelExp, _camelFunc)] = cs.getPropertyValue(cs[i]);
14600 } else { //Opera behaves differently - cs.length is always 0, so we must do a for...in loop.
14605 } else if ((cs = t.currentStyle || t.style)) {
14607 if (typeof(i) === "string" && s[i] === undefined) {
14608 s[i.replace(_camelExp, _camelFunc)] = cs[i];
14612 if (!_supportsOpacity) {
14613 s.opacity = _getIEOpacity(t);
14615 tr = _getTransform(t, cs, false);
14616 s.rotation = tr.rotation;
14617 s.skewX = tr.skewX;
14618 s.scaleX = tr.scaleX;
14619 s.scaleY = tr.scaleY;
14624 s.rotationX = tr.rotationX;
14625 s.rotationY = tr.rotationY;
14626 s.scaleZ = tr.scaleZ;
14634 // @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.
14635 _cssDif = function(t, s1, s2, vars, forceLookup) {
14640 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") {
14641 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.
14642 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.
14643 mpt = new MiniPropTween(style, p, style[p], mpt);
14648 for (p in vars) { //copy properties (except className)
14649 if (p !== "className") {
14654 return {difs:difs, firstMPT:mpt};
14656 _dimensions = {width:["Left","Right"], height:["Top","Bottom"]},
14657 _margins = ["marginLeft","marginRight","marginTop","marginBottom"],
14660 * @private Gets the width or height of an element
14661 * @param {!Object} t Target element
14662 * @param {!string} p Property name ("width" or "height")
14663 * @param {Object=} cs Computed style object (if one exists). Just a speed optimization.
14664 * @return {number} Dimension (in pixels)
14666 _getDimension = function(t, p, cs) {
14667 var v = parseFloat((p === "width") ? t.offsetWidth : t.offsetHeight),
14668 a = _dimensions[p],
14670 cs = cs || _getComputedStyle(t, null);
14672 v -= parseFloat( _getStyle(t, "padding" + a[i], cs, true) ) || 0;
14673 v -= parseFloat( _getStyle(t, "border" + a[i] + "Width", cs, true) ) || 0;
14678 // @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)
14679 _parsePosition = function(v, recObj) {
14680 if (v == null || v === "" || v === "auto" || v === "auto auto") { //note: Firefox uses "auto auto" as default whereas Chrome uses "auto".
14683 var a = v.split(" "),
14684 x = (v.indexOf("left") !== -1) ? "0%" : (v.indexOf("right") !== -1) ? "100%" : a[0],
14685 y = (v.indexOf("top") !== -1) ? "0%" : (v.indexOf("bottom") !== -1) ? "100%" : a[1];
14688 } else if (y === "center") {
14691 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.
14695 recObj.oxp = (x.indexOf("%") !== -1);
14696 recObj.oyp = (y.indexOf("%") !== -1);
14697 recObj.oxr = (x.charAt(1) === "=");
14698 recObj.oyr = (y.charAt(1) === "=");
14699 recObj.ox = parseFloat(x.replace(_NaNExp, ""));
14700 recObj.oy = parseFloat(y.replace(_NaNExp, ""));
14702 return x + " " + y + ((a.length > 2) ? " " + a[2] : "");
14706 * @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!)
14707 * @param {(number|string)} e End value which is typically a string, but could be a number
14708 * @param {(number|string)} b Beginning value which is typically a string but could be a number
14709 * @return {number} Amount of change between the beginning and ending values (relative values that have a "+=" or "-=" are recognized)
14711 _parseChange = function(e, b) {
14712 return (typeof(e) === "string" && e.charAt(1) === "=") ? parseInt(e.charAt(0) + "1", 10) * parseFloat(e.substr(2)) : parseFloat(e) - parseFloat(b);
14716 * @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.
14717 * @param {Object} v Value to be parsed
14718 * @param {!number} d Default value (which is also used for relative calculations if "+=" or "-=" is found in the first parameter)
14719 * @return {number} Parsed value
14721 _parseVal = function(v, d) {
14722 return (v == null) ? d : (typeof(v) === "string" && v.charAt(1) === "=") ? parseInt(v.charAt(0) + "1", 10) * Number(v.substr(2)) + d : parseFloat(v);
14726 * @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.
14727 * @param {Object} v Value to be parsed
14728 * @param {!number} d Default value (which is also used for relative calculations if "+=" or "-=" is found in the first parameter)
14729 * @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"
14730 * @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.
14731 * @return {number} parsed angle in radians
14733 _parseAngle = function(v, d, p, directionalEnd) {
14734 var min = 0.000001,
14735 cap, split, dif, result;
14738 } else if (typeof(v) === "number") {
14742 split = v.split("_");
14743 dif = Number(split[0].replace(_NaNExp, "")) * ((v.indexOf("rad") === -1) ? 1 : _RAD2DEG) - ((v.charAt(1) === "=") ? 0 : d);
14744 if (split.length) {
14745 if (directionalEnd) {
14746 directionalEnd[p] = d + dif;
14748 if (v.indexOf("short") !== -1) {
14750 if (dif !== dif % (cap / 2)) {
14751 dif = (dif < 0) ? dif + cap : dif - cap;
14754 if (v.indexOf("_cw") !== -1 && dif < 0) {
14755 dif = ((dif + cap * 9999999999) % cap) - ((dif / cap) | 0) * cap;
14756 } else if (v.indexOf("ccw") !== -1 && dif > 0) {
14757 dif = ((dif - cap * 9999999999) % cap) - ((dif / cap) | 0) * cap;
14762 if (result < min && result > -min) {
14768 _colorLookup = {aqua:[0,255,255],
14770 silver:[192,192,192],
14776 white:[255,255,255],
14777 fuchsia:[255,0,255],
14779 yellow:[255,255,0],
14780 orange:[255,165,0],
14781 gray:[128,128,128],
14782 purple:[128,0,128],
14785 pink:[255,192,203],
14787 transparent:[255,255,255,0]},
14789 _hue = function(h, m1, m2) {
14790 h = (h < 0) ? h + 1 : (h > 1) ? h - 1 : h;
14791 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;
14795 * @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)
14796 * @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.
14797 * @return {Array.<number>} An array containing red, green, and blue (and optionally alpha) in that order.
14799 _parseColor = function(v) {
14800 var c1, c2, c3, h, s, l;
14801 if (!v || v === "") {
14802 return _colorLookup.black;
14804 if (typeof(v) === "number") {
14805 return [v >> 16, (v >> 8) & 255, v & 255];
14807 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.
14808 v = v.substr(0, v.length - 1);
14810 if (_colorLookup[v]) {
14811 return _colorLookup[v];
14813 if (v.charAt(0) === "#") {
14814 if (v.length === 4) { //for shorthand like #9F0
14818 v = "#" + c1 + c1 + c2 + c2 + c3 + c3;
14820 v = parseInt(v.substr(1), 16);
14821 return [v >> 16, (v >> 8) & 255, v & 255];
14823 if (v.substr(0, 3) === "hsl") {
14824 v = v.match(_numExp);
14825 h = (Number(v[0]) % 360) / 360;
14826 s = Number(v[1]) / 100;
14827 l = Number(v[2]) / 100;
14828 c2 = (l <= 0.5) ? l * (s + 1) : l + s - l * s;
14830 if (v.length > 3) {
14831 v[3] = Number(v[3]);
14833 v[0] = _hue(h + 1 / 3, c1, c2);
14834 v[1] = _hue(h, c1, c2);
14835 v[2] = _hue(h - 1 / 3, c1, c2);
14838 v = v.match(_numExp) || _colorLookup.transparent;
14839 v[0] = Number(v[0]);
14840 v[1] = Number(v[1]);
14841 v[2] = Number(v[2]);
14842 if (v.length > 3) {
14843 v[3] = Number(v[3]);
14847 _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.
14849 for (p in _colorLookup) {
14850 _colorExp += "|" + p + "\\b";
14852 _colorExp = new RegExp(_colorExp+")", "gi");
14855 * @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.
14856 * @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.
14857 * @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.
14858 * @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.
14859 * @return {Function} formatter function
14861 var _getFormatter = function(dflt, clr, collapsible, multi) {
14862 if (dflt == null) {
14863 return function(v) {return v;};
14865 var dColor = clr ? (dflt.match(_colorExp) || [""])[0] : "",
14866 dVals = dflt.split(dColor).join("").match(_valuesExp) || [],
14867 pfx = dflt.substr(0, dflt.indexOf(dVals[0])),
14868 sfx = (dflt.charAt(dflt.length - 1) === ")") ? ")" : "",
14869 delim = (dflt.indexOf(" ") !== -1) ? " " : ",",
14870 numVals = dVals.length,
14871 dSfx = (numVals > 0) ? dVals[0].replace(_numExp, "") : "",
14874 return function(v) {return v;};
14877 formatter = function(v) {
14878 var color, vals, i, a;
14879 if (typeof(v) === "number") {
14881 } else if (multi && _commasOutsideParenExp.test(v)) {
14882 a = v.replace(_commasOutsideParenExp, "|").split("|");
14883 for (i = 0; i < a.length; i++) {
14884 a[i] = formatter(a[i]);
14886 return a.join(",");
14888 color = (v.match(_colorExp) || [dColor])[0];
14889 vals = v.split(color).join("").match(_valuesExp) || [];
14891 if (numVals > i--) {
14892 while (++i < numVals) {
14893 vals[i] = collapsible ? vals[(((i - 1) / 2) | 0)] : dVals[i];
14896 return pfx + vals.join(delim) + delim + color + sfx + (v.indexOf("inset") !== -1 ? " inset" : "");
14901 formatter = function(v) {
14903 if (typeof(v) === "number") {
14905 } else if (multi && _commasOutsideParenExp.test(v)) {
14906 a = v.replace(_commasOutsideParenExp, "|").split("|");
14907 for (i = 0; i < a.length; i++) {
14908 a[i] = formatter(a[i]);
14910 return a.join(",");
14912 vals = v.match(_valuesExp) || [];
14914 if (numVals > i--) {
14915 while (++i < numVals) {
14916 vals[i] = collapsible ? vals[(((i - 1) / 2) | 0)] : dVals[i];
14919 return pfx + vals.join(delim) + sfx;
14925 * @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.
14926 * @param {!string} props a comma-delimited list of property names in order from top to left, like "marginTop,marginRight,marginBottom,marginLeft"
14927 * @return {Function} a formatter function
14929 _getEdgeParser = function(props) {
14930 props = props.split(",");
14931 return function(t, e, p, cssp, pt, plugin, vars) {
14932 var a = (e + "").split(" "),
14935 for (i = 0; i < 4; i++) {
14936 vars[props[i]] = a[i] = a[i] || a[(((i - 1) / 2) >> 0)];
14938 return cssp.parse(t, vars, pt, plugin);
14942 // @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.
14943 _setPluginRatio = _internals._setPluginRatio = function(v) {
14944 this.plugin.setRatio(v);
14951 val = proxy[mpt.v];
14953 val = Math.round(val);
14954 } else if (val < min && val > -min) {
14957 mpt.t[mpt.p] = val;
14960 if (d.autoRotate) {
14961 d.autoRotate.rotation = proxy.rotation;
14963 //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.
14969 pt.e = pt.s + pt.xs0;
14970 } else if (pt.type === 1) {
14971 str = pt.xs0 + pt.s + pt.xs1;
14972 for (i = 1; i < pt.l; i++) {
14973 str += pt["xn"+i] + pt["xs"+(i+1)];
14983 * @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.
14984 * @param {!Object} t target object whose property we're tweening (often a CSSPropTween)
14985 * @param {!string} p property name
14986 * @param {(number|string|object)} v value
14987 * @param {MiniPropTween=} next next MiniPropTween in the linked list
14988 * @param {boolean=} r if true, the tweened value should be rounded to the nearest integer
14990 MiniPropTween = function(t, p, v, next, r) {
15002 * @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.
15003 * This method returns an object that has the following properties:
15004 * - 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
15005 * - 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
15006 * - firstMPT: the first MiniPropTween in the linked list
15007 * - 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.
15008 * @param {!Object} t target object to be tweened
15009 * @param {!(Object|string)} vars the object containing the information about the tweening values (typically the end/destination values) that should be parsed
15010 * @param {!CSSPlugin} cssp The CSSPlugin instance
15011 * @param {CSSPropTween=} pt the next CSSPropTween in the linked list
15012 * @param {TweenPlugin=} plugin the external TweenPlugin instance that will be handling tweening the numeric values
15013 * @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.
15014 * @return An object containing the following properties: proxy, end, firstMPT, and pt (see above for descriptions)
15016 _parseToProxy = _internals._parseToProxy = function(t, vars, cssp, pt, plugin, shallow) {
15020 transform = cssp._transform,
15021 oldForce = _forcePT,
15022 i, p, xp, mpt, firstPT;
15023 cssp._transform = null;
15025 pt = firstPT = cssp.parse(t, vars, pt, plugin);
15026 _forcePT = oldForce;
15027 //break off from the linked list so the new ones are isolated.
15029 cssp._transform = transform;
15033 bpt._prev._next = null;
15037 while (pt && pt !== bpt) {
15038 if (pt.type <= 1) {
15040 end[p] = pt.s + pt.c;
15043 mpt = new MiniPropTween(pt, "s", p, mpt, pt.r);
15046 if (pt.type === 1) {
15050 p = pt.p + "_" + xp;
15051 end[p] = pt.data[xp];
15054 mpt = new MiniPropTween(pt, xp, p, mpt, pt.rxp[xp]);
15061 return {proxy:start, end:end, firstMPT:mpt, pt:firstPT};
15067 * @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.
15068 * CSSPropTweens have the following optional properties as well (not defined through the constructor):
15069 * - 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.
15070 * - 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)
15071 * - 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.
15072 * - 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.
15073 * - 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.
15074 * @param {!Object} t Target object whose property will be tweened. Often a DOM element, but not always. It could be anything.
15075 * @param {string} p Property to tween (name). For example, to tween element.width, p would be "width".
15076 * @param {number} s Starting numeric value
15077 * @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.
15078 * @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.
15079 * @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.
15080 * @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"
15081 * @param {boolean=} r If true, the value(s) should be rounded
15082 * @param {number=} pr Priority in the linked list order. Higher priority CSSPropTweens will be updated before lower priority ones. The default priority is 0.
15083 * @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.
15084 * @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.
15086 CSSPropTween = _internals.CSSPropTween = function(t, p, s, c, next, type, n, r, pr, b, e) {
15087 this.t = t; //target
15088 this.p = p; //property
15089 this.s = s; //starting value
15090 this.c = c; //change value
15091 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)
15092 if (!(t instanceof CSSPropTween)) {
15093 _overwriteProps.push(this.n);
15095 this.r = r; //round (boolean)
15096 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
15099 _hasPriority = true;
15101 this.b = (b === undefined) ? s : b;
15102 this.e = (e === undefined) ? s + c : e;
15110 * 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:
15111 * 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);
15112 * 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().
15113 * 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.
15115 * @param {!Object} t Target whose property will be tweened
15116 * @param {!string} p Property that will be tweened (its name, like "left" or "backgroundColor" or "boxShadow")
15117 * @param {string} b Beginning value
15118 * @param {string} e Ending value
15119 * @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)
15120 * @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
15121 * @param {?CSSPropTween} pt CSSPropTween instance that is the current head of the linked list (we'll prepend to this).
15122 * @param {number=} pr Priority in the linked list order. Higher priority properties will be updated before lower priority ones. The default priority is 0.
15123 * @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}
15124 * @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.
15125 * @return {CSSPropTween} The first CSSPropTween in the linked list which includes the new one(s) added by the parseComplex() call.
15127 _parseComplex = CSSPlugin.parseComplex = function(t, p, b, e, clrs, dflt, pt, pr, plugin, setRatio) {
15128 //DEBUG: _log("parseComplex: "+p+", b: "+b+", e: "+e);
15129 b = b || dflt || "";
15130 pt = new CSSPropTween(t, p, 0, 0, pt, (setRatio ? 2 : 1), null, false, pr, b, e);
15131 e += ""; //ensures it's a string
15132 var ba = b.split(", ").join(",").split(" "), //beginning array
15133 ea = e.split(", ").join(",").split(" "), //ending array
15135 autoRound = (_autoRound !== false),
15136 i, xi, ni, bv, ev, bnums, enums, bn, rgba, temp, cv, str;
15137 if (e.indexOf(",") !== -1 || b.indexOf(",") !== -1) {
15138 ba = ba.join(" ").replace(_commasOutsideParenExp, ", ").split(" ");
15139 ea = ea.join(" ").replace(_commasOutsideParenExp, ", ").split(" ");
15142 if (l !== ea.length) {
15143 //DEBUG: _log("mismatched formatting detected on " + p + " (" + b + " vs " + e + ")");
15144 ba = (dflt || "").split(" ");
15147 pt.plugin = plugin;
15148 pt.setRatio = setRatio;
15149 for (i = 0; i < l; i++) {
15152 bn = parseFloat(bv);
15154 //if the value begins with a number (most common). It's fine if it has a suffix like px
15155 if (bn || bn === 0) {
15156 pt.appendXtra("", bn, _parseChange(ev, bn), ev.replace(_relNumExp, ""), (autoRound && ev.indexOf("px") !== -1), true);
15158 //if the value is a color
15159 } else if (clrs && (bv.charAt(0) === "#" || _colorLookup[bv] || _rgbhslExp.test(bv))) {
15160 str = ev.charAt(ev.length - 1) === "," ? ")," : ")"; //if there's a comma at the end, retain it.
15161 bv = _parseColor(bv);
15162 ev = _parseColor(ev);
15163 rgba = (bv.length + ev.length > 6);
15164 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
15165 pt["xs" + pt.l] += pt.l ? " transparent" : "transparent";
15166 pt.e = pt.e.split(ea[i]).join("transparent");
15168 if (!_supportsOpacity) { //old versions of IE don't support rgba().
15171 pt.appendXtra((rgba ? "rgba(" : "rgb("), bv[0], ev[0] - bv[0], ",", true, true)
15172 .appendXtra("", bv[1], ev[1] - bv[1], ",", true)
15173 .appendXtra("", bv[2], ev[2] - bv[2], (rgba ? "," : str), true);
15175 bv = (bv.length < 4) ? 1 : bv[3];
15176 pt.appendXtra("", bv, ((ev.length < 4) ? 1 : ev[3]) - bv, str, false);
15181 bnums = bv.match(_numExp); //gets each group of numbers in the beginning value string and drops them into an array
15183 //if no number is found, treat it as a non-tweening value and just append the string to the current xs.
15185 pt["xs" + pt.l] += pt.l ? " " + bv : bv;
15187 //loop through all the numbers that are found and construct the extra values on the pt.
15189 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
15190 if (!enums || enums.length !== bnums.length) {
15191 //DEBUG: _log("mismatched formatting detected on " + p + " (" + b + " vs " + e + ")");
15195 for (xi = 0; xi < bnums.length; xi++) {
15197 temp = bv.indexOf(cv, ni);
15198 pt.appendXtra(bv.substr(ni, temp - ni), Number(cv), _parseChange(enums[xi], cv), "", (autoRound && bv.substr(temp + cv.length, 2) === "px"), (xi === 0));
15199 ni = temp + cv.length;
15201 pt["xs" + pt.l] += bv.substr(ni);
15205 //if there are relative values ("+=" or "-=" prefix), we need to adjust the ending value to eliminate the prefixes and combine the values properly.
15206 if (e.indexOf("=") !== -1) if (pt.data) {
15207 str = pt.xs0 + pt.data.s;
15208 for (i = 1; i < pt.l; i++) {
15209 str += pt["xs" + i] + pt.data["xn" + i];
15211 pt.e = str + pt["xs" + i];
15217 return pt.xfirst || pt;
15222 p = CSSPropTween.prototype;
15223 p.l = p.pr = 0; //length (number of extra properties like xn1, xn2, xn3, etc.
15229 p._next = p._prev = p.xfirst = p.data = p.plugin = p.setRatio = p.rxp = null;
15233 * 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:
15234 * xs0:"rect(", s:10, xs1:"px, ", xn1:5, xs2:"px, ", xn2:0, xs3:"px, ", xn3:20, xn4:"px)"
15235 * And they'd all get joined together when the CSSPlugin renders (in the setRatio() method).
15236 * @param {string=} pfx Prefix (if any)
15237 * @param {!number} s Starting value
15238 * @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.
15239 * @param {string=} sfx Suffix (if any)
15240 * @param {boolean=} r Round (if true).
15241 * @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.
15242 * @return {CSSPropTween} returns itself so that multiple methods can be chained together.
15244 p.appendXtra = function(pfx, s, c, sfx, r, pad) {
15247 pt["xs" + l] += (pad && l) ? " " + pfx : pfx || "";
15248 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!
15249 pt["xs" + l] += s + (sfx || "");
15253 pt.type = pt.setRatio ? 2 : 1;
15254 pt["xs" + pt.l] = sfx || "";
15256 pt.data["xn" + l] = s + c;
15257 pt.rxp["xn" + l] = r; //round extra property (we need to tap into this in the _parseToProxy() method)
15260 pt.xfirst = new CSSPropTween(pt, "xn" + l, s, c, pt.xfirst || pt, 0, pt.n, r, pt.pr);
15261 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.
15265 pt.data = {s:s + c};
15274 * @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.
15275 * @param {!string} p Property name (like "boxShadow" or "throwProps")
15276 * @param {Object=} options An object containing any of the following configuration options:
15277 * - defaultValue: the default value
15278 * - 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)
15279 * - 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.)
15280 * - prefix: if true, we'll determine whether or not this property requires a vendor prefix (like Webkit or Moz or ms or O)
15281 * - color: set this to true if the value for this SpecialProp may contain color-related values like rgb(), rgba(), etc.
15282 * - priority: priority in the linked list order. Higher priority SpecialProps will be updated before lower priority ones. The default priority is 0.
15283 * - multi: if true, the formatter should accommodate a comma-delimited list of values, like boxShadow could have multiple boxShadows listed out.
15284 * - 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.
15285 * - 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).
15287 var SpecialProp = function(p, options) {
15288 options = options || {};
15289 this.p = options.prefix ? _checkPropPrefix(p) || p : p;
15290 _specialProps[p] = _specialProps[this.p] = this;
15291 this.format = options.formatter || _getFormatter(options.defaultValue, options.color, options.collapsible, options.multi);
15292 if (options.parser) {
15293 this.parse = options.parser;
15295 this.clrs = options.color;
15296 this.multi = options.multi;
15297 this.keyword = options.keyword;
15298 this.dflt = options.defaultValue;
15299 this.pr = options.priority || 0;
15302 //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.
15303 _registerComplexSpecialProp = _internals._registerComplexSpecialProp = function(p, options, defaults) {
15304 if (typeof(options) !== "object") {
15305 options = {parser:defaults}; //to make backwards compatible with older versions of BezierPlugin and ThrowPropsPlugin
15307 var a = p.split(","),
15308 d = options.defaultValue,
15310 defaults = defaults || [d];
15311 for (i = 0; i < a.length; i++) {
15312 options.prefix = (i === 0 && options.prefix);
15313 options.defaultValue = defaults[i] || d;
15314 temp = new SpecialProp(a[i], options);
15318 //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.
15319 _registerPluginProp = function(p) {
15320 if (!_specialProps[p]) {
15321 var pluginName = p.charAt(0).toUpperCase() + p.substr(1) + "Plugin";
15322 _registerComplexSpecialProp(p, {parser:function(t, e, p, cssp, pt, plugin, vars) {
15323 var pluginClass = (window.GreenSockGlobals || window).com.greensock.plugins[pluginName];
15324 if (!pluginClass) {
15325 _log("Error: " + pluginName + " js file not loaded.");
15328 pluginClass._cssRegister();
15329 return _specialProps[p].parse(t, e, p, cssp, pt, plugin, vars);
15335 p = SpecialProp.prototype;
15338 * 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)
15339 * @param {!Object} t target element
15340 * @param {(string|number|object)} b beginning value
15341 * @param {(string|number|object)} e ending (destination) value
15342 * @param {CSSPropTween=} pt next CSSPropTween in the linked list
15343 * @param {TweenPlugin=} plugin If another plugin will be tweening the complex value, that TweenPlugin instance goes here.
15344 * @param {function=} setRatio If a custom setRatio() method should be used to handle this complex value, that goes here.
15345 * @return {CSSPropTween=} First CSSPropTween in the linked list
15347 p.parseComplex = function(t, b, e, pt, plugin, setRatio) {
15348 var kwd = this.keyword,
15349 i, ba, ea, l, bi, ei;
15350 //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)
15351 if (this.multi) if (_commasOutsideParenExp.test(e) || _commasOutsideParenExp.test(b)) {
15352 ba = b.replace(_commasOutsideParenExp, "|").split("|");
15353 ea = e.replace(_commasOutsideParenExp, "|").split("|");
15359 l = (ea.length > ba.length) ? ea.length : ba.length;
15360 for (i = 0; i < l; i++) {
15361 b = ba[i] = ba[i] || this.dflt;
15362 e = ea[i] = ea[i] || this.dflt;
15364 bi = b.indexOf(kwd);
15365 ei = e.indexOf(kwd);
15367 e = (ei === -1) ? ea : ba;
15375 return _parseComplex(t, this.p, b, e, this.clrs, this.dflt, pt, this.pr, plugin, setRatio);
15379 * 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:
15380 * this._firstPT = sp.parse(element, "5px 10px 20px rgb(2550,102,51)", "boxShadow", this);
15381 * 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).
15382 * @param {!Object} t Target object whose property is being tweened
15383 * @param {Object} e End value as provided in the vars object (typically a string, but not always - like a throwProps would be an object).
15384 * @param {!string} p Property name
15385 * @param {!CSSPlugin} cssp The CSSPlugin instance that should be associated with this tween.
15386 * @param {?CSSPropTween} pt The CSSPropTween that is the current head of the linked list (we'll prepend to it)
15387 * @param {TweenPlugin=} plugin If a plugin will be used to tween the parsed value, this is the plugin instance.
15388 * @param {Object=} vars Original vars object that contains the data for parsing.
15389 * @return {CSSPropTween} The first CSSPropTween in the linked list which includes the new one(s) added by the parse() call.
15391 p.parse = function(t, e, p, cssp, pt, plugin, vars) {
15392 return this.parseComplex(t.style, this.format(_getStyle(t, this.p, _cs, false, this.dflt)), this.format(e), pt, plugin);
15396 * 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:
15397 * 1) Target object whose property should be tweened (typically a DOM element)
15398 * 2) The end/destination value (could be a string, number, object, or whatever you want)
15399 * 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)
15401 * 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:
15403 * CSSPlugin.registerSpecialProp("myCustomProp", function(target, value, tween) {
15404 * var start = target.style.width;
15405 * return function(ratio) {
15406 * target.style.width = (start + value * ratio) + "px";
15407 * console.log("set width to " + target.style.width);
15411 * Then, when I do this tween, it will trigger my special property:
15413 * TweenLite.to(element, 1, {css:{myCustomProp:100}});
15415 * In the example, of course, we're just changing the width, but you can do anything you want.
15417 * @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}})
15418 * @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.
15419 * @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.
15421 CSSPlugin.registerSpecialProp = function(name, onInitTween, priority) {
15422 _registerComplexSpecialProp(name, {parser:function(t, e, p, cssp, pt, plugin, vars) {
15423 var rv = new CSSPropTween(t, p, 0, 0, pt, 2, p, false, priority);
15424 rv.plugin = plugin;
15425 rv.setRatio = onInitTween(t, e, cssp._tween, p);
15427 }, priority:priority});
15437 //transform-related methods and properties
15438 var _transformProps = ("scaleX,scaleY,scaleZ,x,y,z,skewX,skewY,rotation,rotationX,rotationY,perspective").split(","),
15439 _transformProp = _checkPropPrefix("transform"), //the Javascript (camelCase) transform property, like msTransform, WebkitTransform, MozTransform, or OTransform.
15440 _transformPropCSS = _prefixCSS + "transform",
15441 _transformOriginProp = _checkPropPrefix("transformOrigin"),
15442 _supports3D = (_checkPropPrefix("perspective") !== null),
15443 Transform = _internals.Transform = function() {
15448 * 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.
15449 * @param {!Object} t target element
15450 * @param {Object=} cs computed style object (optional)
15451 * @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...}
15452 * @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)
15453 * @return {object} object containing all of the transform properties/values like {x:0, y:0, z:0, scaleX:1...}
15455 _getTransform = _internals.getTransform = function(t, cs, rec, parse) {
15456 if (t._gsTransform && rec && !parse) {
15457 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.
15459 var tm = rec ? t._gsTransform || new Transform() : new Transform(),
15460 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.
15464 minPI = minAngle * _DEG2RAD,
15465 zOrigin = _supports3D ? parseFloat(_getStyle(t, _transformOriginProp, cs, false, "0 0 0").split(" ")[2]) || tm.zOrigin || 0 : 0,
15466 s, m, i, n, dec, scaleX, scaleY, rotation, skewX, difX, difY, difR, difS;
15467 if (_transformProp) {
15468 s = _getStyle(t, _transformPropCSS, cs, true);
15469 } else if (t.currentStyle) {
15470 //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.
15471 s = t.currentStyle.filter.match(_ieGetMatrixExp);
15472 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(",") : "";
15474 //split the matrix values out into an array (m for matrix)
15475 m = (s || "").match(/(?:\-|\b)[\d\-\.e]+\b/gi) || [];
15479 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).
15481 if (m.length === 16) {
15483 //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)
15484 var a13 = m[8], a23 = m[9], a33 = m[10],
15485 a14 = m[12], a24 = m[13], a34 = m[14];
15487 //we manually compensate for non-zero z component of transformOrigin to work around bugs in Safari
15490 a14 = a13*a34-m[12];
15491 a24 = a23*a34-m[13];
15492 a34 = a33*a34+tm.zOrigin-m[14];
15495 //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.
15496 if (!rec || parse || tm.rotationX == null) {
15497 var a11 = m[0], a21 = m[1], a31 = m[2], a41 = m[3],
15498 a12 = m[4], a22 = m[5], a32 = m[6], a42 = m[7],
15500 angle = Math.atan2(a32, a33),
15501 xFlip = (angle < -minPI || angle > minPI),
15502 t1, t2, t3, cos, sin, yFlip, zFlip;
15503 tm.rotationX = angle * _RAD2DEG;
15506 cos = Math.cos(-angle);
15507 sin = Math.sin(-angle);
15508 t1 = a12*cos+a13*sin;
15509 t2 = a22*cos+a23*sin;
15510 t3 = a32*cos+a33*sin;
15511 a13 = a12*-sin+a13*cos;
15512 a23 = a22*-sin+a23*cos;
15513 a33 = a32*-sin+a33*cos;
15514 a43 = a42*-sin+a43*cos;
15520 angle = Math.atan2(a13, a11);
15521 tm.rotationY = angle * _RAD2DEG;
15523 yFlip = (angle < -minPI || angle > minPI);
15524 cos = Math.cos(-angle);
15525 sin = Math.sin(-angle);
15526 t1 = a11*cos-a13*sin;
15527 t2 = a21*cos-a23*sin;
15528 t3 = a31*cos-a33*sin;
15529 a23 = a21*sin+a23*cos;
15530 a33 = a31*sin+a33*cos;
15531 a43 = a41*sin+a43*cos;
15537 angle = Math.atan2(a21, a22);
15538 tm.rotation = angle * _RAD2DEG;
15540 zFlip = (angle < -minPI || angle > minPI);
15541 cos = Math.cos(-angle);
15542 sin = Math.sin(-angle);
15543 a11 = a11*cos+a12*sin;
15544 t2 = a21*cos+a22*sin;
15545 a22 = a21*-sin+a22*cos;
15546 a32 = a31*-sin+a32*cos;
15550 if (zFlip && xFlip) {
15551 tm.rotation = tm.rotationX = 0;
15552 } else if (zFlip && yFlip) {
15553 tm.rotation = tm.rotationY = 0;
15554 } else if (yFlip && xFlip) {
15555 tm.rotationY = tm.rotationX = 0;
15558 tm.scaleX = ((Math.sqrt(a11 * a11 + a21 * a21) * rnd + 0.5) | 0) / rnd;
15559 tm.scaleY = ((Math.sqrt(a22 * a22 + a23 * a23) * rnd + 0.5) | 0) / rnd;
15560 tm.scaleZ = ((Math.sqrt(a32 * a32 + a33 * a33) * rnd + 0.5) | 0) / rnd;
15562 tm.perspective = a43 ? 1 / ((a43 < 0) ? -a43 : a43) : 0;
15568 } 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.
15569 var k = (m.length >= 6),
15576 scaleX = Math.sqrt(a * a + b * b);
15577 scaleY = Math.sqrt(d * d + c * c);
15578 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).
15579 skewX = (c || d) ? Math.atan2(c, d) * _RAD2DEG + rotation : tm.skewX || 0;
15580 difX = scaleX - Math.abs(tm.scaleX || 0);
15581 difY = scaleY - Math.abs(tm.scaleY || 0);
15582 if (Math.abs(skewX) > 90 && Math.abs(skewX) < 270) {
15585 skewX += (rotation <= 0) ? 180 : -180;
15586 rotation += (rotation <= 0) ? 180 : -180;
15589 skewX += (skewX <= 0) ? 180 : -180;
15592 difR = (rotation - tm.rotation) % 180; //note: matching ranges would be very small (+/-0.0001) or very close to 180.
15593 difS = (skewX - tm.skewX) % 180;
15594 //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.
15595 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)) {
15596 tm.scaleX = scaleX;
15597 tm.scaleY = scaleY;
15598 tm.rotation = rotation;
15602 tm.rotationX = tm.rotationY = tm.z = 0;
15603 tm.perspective = parseFloat(CSSPlugin.defaultTransformPerspective) || 0;
15607 tm.zOrigin = zOrigin;
15609 //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.
15611 if (tm[i] < min) if (tm[i] > -min) {
15615 //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);
15617 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)
15622 //for setting 2D transforms in IE6, IE7, and IE8 (must use a "filter" to emulate the behavior of modern day browser transforms)
15623 _setIETransformRatio = function(v) {
15624 var t = this.data, //refers to the element's _gsTransform object
15625 ang = -t.rotation * _DEG2RAD,
15626 skew = ang + t.skewX * _DEG2RAD,
15628 a = ((Math.cos(ang) * t.scaleX * rnd) | 0) / rnd,
15629 b = ((Math.sin(ang) * t.scaleX * rnd) | 0) / rnd,
15630 c = ((Math.sin(skew) * -t.scaleY * rnd) | 0) / rnd,
15631 d = ((Math.cos(skew) * t.scaleY * rnd) | 0) / rnd,
15632 style = this.t.style,
15633 cs = this.t.currentStyle,
15638 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)
15641 filters = cs.filter;
15642 style.filter = ""; //remove filters so that we can accurately measure offsetWidth/offsetHeight
15643 var w = this.t.offsetWidth,
15644 h = this.t.offsetHeight,
15645 clip = (cs.position !== "absolute"),
15646 m = "progid:DXImageTransform.Microsoft.Matrix(M11=" + a + ", M12=" + b + ", M21=" + c + ", M22=" + d,
15651 //if transformOrigin is being used, adjust the offset x and y
15652 if (t.ox != null) {
15653 dx = ((t.oxp) ? w * t.ox * 0.01 : t.ox) - w / 2;
15654 dy = ((t.oyp) ? h * t.oy * 0.01 : t.oy) - h / 2;
15655 ox += dx - (dx * a + dy * b);
15656 oy += dy - (dx * c + dy * d);
15660 m += ", sizingMethod='auto expand')";
15664 //translate to ensure that transformations occur around the correct origin (default is center).
15665 m += ", Dx=" + (dx - (dx * a + dy * b) + ox) + ", Dy=" + (dy - (dx * c + dy * d) + oy) + ")";
15667 if (filters.indexOf("DXImageTransform.Microsoft.Matrix(") !== -1) {
15668 style.filter = filters.replace(_ieSetMatrixExp, m);
15670 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.
15673 //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.
15674 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) {
15675 style.removeAttribute("filter");
15678 //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).
15680 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
15682 dx = t.ieOffsetX || 0;
15683 dy = t.ieOffsetY || 0;
15684 t.ieOffsetX = Math.round((w - ((a < 0 ? -a : a) * w + (b < 0 ? -b : b) * h)) / 2 + ox);
15685 t.ieOffsetY = Math.round((h - ((d < 0 ? -d : d) * h + (c < 0 ? -c : c) * w)) / 2 + oy);
15686 for (i = 0; i < 4; i++) {
15687 prop = _margins[i];
15689 //we need to get the current margin in case it is being tweened separately (we want to respect that tween's changes)
15690 val = (marg.indexOf("px") !== -1) ? parseFloat(marg) : _convertToPixels(this.t, prop, parseFloat(marg), marg.replace(_suffixExp, "")) || 0;
15691 if (val !== t[prop]) {
15692 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.
15694 dif = (i < 2) ? dx - t.ieOffsetX : dy - t.ieOffsetY;
15696 style[prop] = (t[prop] = Math.round( val - dif * ((i === 0 || i === 2) ? 1 : mult) )) + "px";
15701 _set3DTransformRatio = _internals.set3DTransformRatio = function(v) {
15702 var t = this.data, //refers to the element's _gsTransform object
15703 style = this.t.style,
15704 angle = t.rotation * _DEG2RAD,
15708 perspective = t.perspective,
15709 a11, a12, a13, a14, a21, a22, a23, a24, a31, a32, a33, a34, a41, a42, a43,
15710 zOrigin, rnd, cos, sin, t1, t2, t3, t4;
15711 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
15712 _set2DTransformRatio.call(this, v);
15717 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.
15720 if (sy < n && sy > -n) {
15723 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).
15727 if (angle || t.skewX) {
15728 cos = Math.cos(angle);
15729 sin = Math.sin(angle);
15733 angle -= t.skewX * _DEG2RAD;
15734 cos = Math.cos(angle);
15735 sin = Math.sin(angle);
15736 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
15737 t1 = Math.tan(t.skewX * _DEG2RAD);
15738 t1 = Math.sqrt(1 + t1 * t1);
15746 } else if (!t.rotationY && !t.rotationX && sz === 1 && !perspective) { //if we're only translating and/or 2D scaling, this is faster...
15747 style[_transformProp] = "translate3d(" + t.x + "px," + t.y + "px," + t.z +"px)" + ((sx !== 1 || sy !== 1) ? " scale(" + sx + "," + sy + ")" : "");
15754 a13 = a14 = a23 = a24 = a31 = a32 = a34 = a41 = a42 = 0;
15755 a43 = (perspective) ? -1 / perspective : 0;
15756 zOrigin = t.zOrigin;
15758 angle = t.rotationY * _DEG2RAD;
15760 cos = Math.cos(angle);
15761 sin = Math.sin(angle);
15771 angle = t.rotationX * _DEG2RAD;
15773 cos = Math.cos(angle);
15774 sin = Math.sin(angle);
15775 t1 = a12*cos+a13*sin;
15776 t2 = a22*cos+a23*sin;
15777 t3 = a32*cos+a33*sin;
15778 t4 = a42*cos+a43*sin;
15779 a13 = a12*-sin+a13*cos;
15780 a23 = a22*-sin+a23*cos;
15781 a33 = a32*-sin+a33*cos;
15782 a43 = a42*-sin+a43*cos;
15810 a34 = a33*a34+zOrigin;
15812 //we round the x, y, and z slightly differently to allow even larger values.
15813 a14 = (t1 = (a14 += t.x) - (a14 |= 0)) ? ((t1 * rnd + (t1 < 0 ? -0.5 : 0.5)) | 0) / rnd + a14 : a14;
15814 a24 = (t1 = (a24 += t.y) - (a24 |= 0)) ? ((t1 * rnd + (t1 < 0 ? -0.5 : 0.5)) | 0) / rnd + a24 : a24;
15815 a34 = (t1 = (a34 += t.z) - (a34 |= 0)) ? ((t1 * rnd + (t1 < 0 ? -0.5 : 0.5)) | 0) / rnd + a34 : a34;
15816 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(",") + ")";
15819 _set2DTransformRatio = _internals.set2DTransformRatio = function(v) {
15820 var t = this.data, //refers to the element's _gsTransform object
15822 style = targ.style,
15823 ang, skew, rnd, sx, sy;
15824 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.
15825 this.setRatio = _set3DTransformRatio;
15826 _set3DTransformRatio.call(this, v);
15829 if (!t.rotation && !t.skewX) {
15830 style[_transformProp] = "matrix(" + t.scaleX + ",0,0," + t.scaleY + "," + t.x + "," + t.y + ")";
15832 ang = t.rotation * _DEG2RAD;
15833 skew = ang - t.skewX * _DEG2RAD;
15835 sx = t.scaleX * rnd;
15836 sy = t.scaleY * rnd;
15837 //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.
15838 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 + ")";
15842 _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) {
15843 if (cssp._transform) { return pt; } //only need to parse the transform once, and only if the browser supports it.
15844 var m1 = cssp._transform = _getTransform(t, _cs, true, vars.parseTransform),
15847 i = _transformProps.length,
15850 m2, skewY, copy, orig, has3D, hasChange, dr;
15851 if (typeof(v.transform) === "string" && _transformProp) { //for values like transform:"rotate(60deg) scale(0.5, 0.8)"
15852 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.
15853 copy[_transformProp] = v.transform;
15854 copy.display = "block"; //if display is "none", the browser often refuses to report the transform properties correctly.
15855 copy.position = "absolute";
15856 _doc.body.appendChild(_tempDiv);
15857 m2 = _getTransform(_tempDiv, null, false);
15858 _doc.body.removeChild(_tempDiv);
15859 } else if (typeof(v) === "object") { //for values like scaleX, scaleY, rotation, x, y, skewX, and skewY or transform:{...} (object)
15860 m2 = {scaleX:_parseVal((v.scaleX != null) ? v.scaleX : v.scale, m1.scaleX),
15861 scaleY:_parseVal((v.scaleY != null) ? v.scaleY : v.scale, m1.scaleY),
15862 scaleZ:_parseVal(v.scaleZ, m1.scaleZ),
15863 x:_parseVal(v.x, m1.x),
15864 y:_parseVal(v.y, m1.y),
15865 z:_parseVal(v.z, m1.z),
15866 perspective:_parseVal(v.transformPerspective, m1.perspective)};
15867 dr = v.directionalRotation;
15869 if (typeof(dr) === "object") {
15871 v[copy] = dr[copy];
15877 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);
15879 m2.rotationX = _parseAngle(("rotationX" in v) ? v.rotationX : ("shortRotationX" in v) ? v.shortRotationX + "_short" : m1.rotationX || 0, m1.rotationX, "rotationX", endRotations);
15880 m2.rotationY = _parseAngle(("rotationY" in v) ? v.rotationY : ("shortRotationY" in v) ? v.shortRotationY + "_short" : m1.rotationY || 0, m1.rotationY, "rotationY", endRotations);
15882 m2.skewX = (v.skewX == null) ? m1.skewX : _parseAngle(v.skewX, m1.skewX);
15884 //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.
15885 m2.skewY = (v.skewY == null) ? m1.skewY : _parseAngle(v.skewY, m1.skewY);
15886 if ((skewY = m2.skewY - m1.skewY)) {
15888 m2.rotation += skewY;
15892 if (_supports3D && v.force3D != null) {
15893 m1.force3D = v.force3D;
15897 m1.skewType = v.skewType || m1.skewType || CSSPlugin.defaultSkewType;
15899 has3D = (m1.force3D || m1.z || m1.rotationX || m1.rotationY || m2.z || m2.rotationX || m2.rotationY || m2.perspective);
15900 if (!has3D && v.scale != null) {
15901 m2.scaleZ = 1; //no need to tween scaleZ.
15905 p = _transformProps[i];
15906 orig = m2[p] - m1[p];
15907 if (orig > min || orig < -min || _forcePT[p] != null) {
15909 pt = new CSSPropTween(m1, p, m1[p], orig, pt);
15910 if (p in endRotations) {
15911 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
15913 pt.xs0 = 0; //ensures the value stays numeric in setRatio()
15914 pt.plugin = plugin;
15915 cssp._overwriteProps.push(pt.n);
15919 orig = v.transformOrigin;
15920 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).
15921 if (_transformProp) {
15923 p = _transformOriginProp;
15924 orig = (orig || _getStyle(t, p, _cs, false, "50% 50%")) + ""; //cast as string to avoid errors
15925 pt = new CSSPropTween(style, p, 0, 0, pt, -1, "transformOrigin");
15927 pt.plugin = plugin;
15930 orig = orig.split(" ");
15931 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.
15932 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)!
15933 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)
15935 pt.xs0 = pt.e = m1.zOrigin;
15937 pt.xs0 = pt.e = orig;
15940 //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).
15942 _parsePosition(orig + "", m1);
15947 cssp._transformType = (has3D || this._transformType === 3) ? 3 : 2; //quicker than calling cssp._enableTransforms();
15952 _registerComplexSpecialProp("boxShadow", {defaultValue:"0px 0px 0px 0px #999", prefix:true, color:true, multi:true, keyword:"inset"});
15954 _registerComplexSpecialProp("borderRadius", {defaultValue:"0px", parser:function(t, e, p, cssp, pt, plugin) {
15955 e = this.format(e);
15956 var props = ["borderTopLeftRadius","borderTopRightRadius","borderBottomRightRadius","borderBottomLeftRadius"],
15958 ea1, i, es2, bs2, bs, es, bn, en, w, h, esfx, bsfx, rel, hn, vn, em;
15959 w = parseFloat(t.offsetWidth);
15960 h = parseFloat(t.offsetHeight);
15961 ea1 = e.split(" ");
15962 for (i = 0; i < props.length; i++) { //if we're dealing with percentages, we must convert things separately for the horizontal and vertical axis!
15963 if (this.p.indexOf("border")) { //older browsers used a prefix
15964 props[i] = _checkPropPrefix(props[i]);
15966 bs = bs2 = _getStyle(t, props[i], _cs, false, "0px");
15967 if (bs.indexOf(" ") !== -1) {
15968 bs2 = bs.split(" ");
15973 bn = parseFloat(bs);
15974 bsfx = bs.substr((bn + "").length);
15975 rel = (es.charAt(1) === "=");
15977 en = parseInt(es.charAt(0)+"1", 10);
15979 en *= parseFloat(es);
15980 esfx = es.substr((en + "").length - (en < 0 ? 1 : 0)) || "";
15982 en = parseFloat(es);
15983 esfx = es.substr((en + "").length);
15986 esfx = _suffixMap[p] || bsfx;
15988 if (esfx !== bsfx) {
15989 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.
15990 vn = _convertToPixels(t, "borderTop", bn, bsfx); //vertical number
15991 if (esfx === "%") {
15992 bs = (hn / w * 100) + "%";
15993 bs2 = (vn / h * 100) + "%";
15994 } else if (esfx === "em") {
15995 em = _convertToPixels(t, "borderLeft", 1, "em");
15996 bs = (hn / em) + "em";
15997 bs2 = (vn / em) + "em";
16003 es = (parseFloat(bs) + en) + esfx;
16004 es2 = (parseFloat(bs2) + en) + esfx;
16007 pt = _parseComplex(style, props[i], bs + " " + bs2, es + " " + es2, false, "0px", pt);
16010 }, prefix:true, formatter:_getFormatter("0px 0px 0px 0px", false, true)});
16011 _registerComplexSpecialProp("backgroundPosition", {defaultValue:"0 0", parser:function(t, e, p, cssp, pt, plugin) {
16012 var bp = "background-position",
16013 cs = (_cs || _getComputedStyle(t, null)),
16014 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
16015 es = this.format(e),
16016 ba, ea, i, pct, overlap, src;
16017 if ((bs.indexOf("%") !== -1) !== (es.indexOf("%") !== -1)) {
16018 src = _getStyle(t, "backgroundImage").replace(_urlExp, "");
16019 if (src && src !== "none") {
16020 ba = bs.split(" ");
16021 ea = es.split(" ");
16022 _tempImg.setAttribute("src", src); //set the temp <img>'s src to the background-image so that we can measure its width/height
16026 pct = (bs.indexOf("%") !== -1);
16027 if (pct !== (ea[i].indexOf("%") !== -1)) {
16028 overlap = (i === 0) ? t.offsetWidth - _tempImg.width : t.offsetHeight - _tempImg.height;
16029 ba[i] = pct ? (parseFloat(bs) / 100 * overlap) + "px" : (parseFloat(bs) / overlap * 100) + "%";
16035 return this.parseComplex(t.style, bs, es, pt, plugin);
16036 }, formatter:_parsePosition});
16037 _registerComplexSpecialProp("backgroundSize", {defaultValue:"0 0", formatter:_parsePosition});
16038 _registerComplexSpecialProp("perspective", {defaultValue:"0px", prefix:true});
16039 _registerComplexSpecialProp("perspectiveOrigin", {defaultValue:"50% 50%", prefix:true});
16040 _registerComplexSpecialProp("transformStyle", {prefix:true});
16041 _registerComplexSpecialProp("backfaceVisibility", {prefix:true});
16042 _registerComplexSpecialProp("userSelect", {prefix:true});
16043 _registerComplexSpecialProp("margin", {parser:_getEdgeParser("marginTop,marginRight,marginBottom,marginLeft")});
16044 _registerComplexSpecialProp("padding", {parser:_getEdgeParser("paddingTop,paddingRight,paddingBottom,paddingLeft")});
16045 _registerComplexSpecialProp("clip", {defaultValue:"rect(0px,0px,0px,0px)", parser:function(t, e, p, cssp, pt, plugin){
16047 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.
16048 cs = t.currentStyle;
16049 delim = _ieVers < 8 ? " " : ",";
16050 b = "rect(" + cs.clipTop + delim + cs.clipRight + delim + cs.clipBottom + delim + cs.clipLeft + ")";
16051 e = this.format(e).split(",").join(delim);
16053 b = this.format(_getStyle(t, this.p, _cs, false, this.dflt));
16054 e = this.format(e);
16056 return this.parseComplex(t.style, b, e, pt, plugin);
16058 _registerComplexSpecialProp("textShadow", {defaultValue:"0px 0px 0px #999", color:true, multi:true});
16059 _registerComplexSpecialProp("autoRound,strictUnits", {parser:function(t, e, p, cssp, pt) {return pt;}}); //just so that we can ignore these properties (not tween them)
16060 _registerComplexSpecialProp("border", {defaultValue:"0px solid #000", parser:function(t, e, p, cssp, pt, plugin) {
16061 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);
16062 }, color:true, formatter:function(v) {
16063 var a = v.split(" ");
16064 return a[0] + " " + (a[1] || "solid") + " " + (v.match(_colorExp) || ["#000"])[0];
16066 _registerComplexSpecialProp("borderWidth", {parser:_getEdgeParser("borderTopWidth,borderRightWidth,borderBottomWidth,borderLeftWidth")}); //Firefox doesn't pick up on borderWidth set in style sheets (only inline).
16067 _registerComplexSpecialProp("float,cssFloat,styleFloat", {parser:function(t, e, p, cssp, pt, plugin) {
16069 prop = ("cssFloat" in s) ? "cssFloat" : "styleFloat";
16070 return new CSSPropTween(s, prop, 0, 0, pt, -1, p, false, 0, s[prop], e);
16074 var _setIEOpacityRatio = function(v) {
16075 var t = this.t, //refers to the element's style property
16076 filters = t.filter || _getStyle(this.data, "filter"),
16077 val = (this.s + this.c * v) | 0,
16079 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.
16080 if (filters.indexOf("atrix(") === -1 && filters.indexOf("radient(") === -1 && filters.indexOf("oader(") === -1) {
16081 t.removeAttribute("filter");
16082 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.
16084 t.filter = filters.replace(_alphaFilterExp, "");
16090 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.
16092 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
16093 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)
16094 t.filter = filters + " alpha(opacity=" + val + ")"; //we round the value because otherwise, bugs in IE7/8 can prevent "visibility" changes from being applied properly.
16097 t.filter = filters.replace(_opacityExp, "opacity=" + val);
16101 _registerComplexSpecialProp("opacity,alpha,autoAlpha", {defaultValue:"1", parser:function(t, e, p, cssp, pt, plugin) {
16102 var b = parseFloat(_getStyle(t, "opacity", _cs, false, "1")),
16104 isAutoAlpha = (p === "autoAlpha");
16105 if (typeof(e) === "string" && e.charAt(1) === "=") {
16106 e = ((e.charAt(0) === "-") ? -1 : 1) * parseFloat(e.substr(2)) + b;
16108 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)
16111 if (_supportsOpacity) {
16112 pt = new CSSPropTween(style, "opacity", b, e - b, pt);
16114 pt = new CSSPropTween(style, "opacity", b * 100, (e - b) * 100, pt);
16115 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.
16116 style.zoom = 1; //helps correct an IE issue.
16118 pt.b = "alpha(opacity=" + pt.s + ")";
16119 pt.e = "alpha(opacity=" + (pt.s + pt.c) + ")";
16121 pt.plugin = plugin;
16122 pt.setRatio = _setIEOpacityRatio;
16124 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
16125 pt = new CSSPropTween(style, "visibility", 0, 0, pt, -1, null, false, 0, ((b !== 0) ? "inherit" : "hidden"), ((e === 0) ? "hidden" : "inherit"));
16126 pt.xs0 = "inherit";
16127 cssp._overwriteProps.push(pt.n);
16128 cssp._overwriteProps.push(p);
16134 var _removeProp = function(s, p) {
16136 if (s.removeProperty) {
16137 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)
16138 p = "M" + p.substr(1);
16140 s.removeProperty(p.replace(_capsExp, "-$1").toLowerCase());
16141 } else { //note: old versions of IE use "removeAttribute()" instead of "removeProperty()"
16142 s.removeAttribute(p);
16146 _setClassNameRatio = function(v) {
16147 this.t._gsClassPT = this;
16148 if (v === 1 || v === 0) {
16149 this.t.setAttribute("class", (v === 0) ? this.b : this.e);
16150 var mpt = this.data, //first MiniPropTween
16154 _removeProp(s, mpt.p);
16160 if (v === 1 && this.t._gsClassPT === this) {
16161 this.t._gsClassPT = null;
16163 } else if (this.t.getAttribute("class") !== this.e) {
16164 this.t.setAttribute("class", this.e);
16167 _registerComplexSpecialProp("className", {parser:function(t, e, p, cssp, pt, plugin, vars) {
16168 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.
16169 cssText = t.style.cssText,
16170 difData, bs, cnpt, cnptLookup, mpt;
16171 pt = cssp._classNamePT = new CSSPropTween(t, p, 0, 0, pt, 2);
16172 pt.setRatio = _setClassNameRatio;
16174 _hasPriority = true;
16176 bs = _getAllStyles(t, _cs);
16177 //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)
16178 cnpt = t._gsClassPT;
16181 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.
16183 cnptLookup[mpt.p] = 1;
16189 pt.e = (e.charAt(1) !== "=") ? e : b.replace(new RegExp("\\s*\\b" + e.substr(2) + "\\b"), "") + ((e.charAt(0) === "+") ? " " + e.substr(2) : "");
16190 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.
16191 t.setAttribute("class", pt.e);
16192 difData = _cssDif(t, bs, _getAllStyles(t), vars, cnptLookup);
16193 t.setAttribute("class", b);
16194 pt.data = difData.firstMPT;
16195 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).
16196 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)
16202 var _setClearPropsRatio = function(v) {
16203 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).
16204 var s = this.t.style,
16205 transformParse = _specialProps.transform.parse,
16206 a, p, i, clearTransform;
16207 if (this.e === "all") {
16209 clearTransform = true;
16211 a = this.e.split(",");
16215 if (_specialProps[p]) {
16216 if (_specialProps[p].parse === transformParse) {
16217 clearTransform = true;
16219 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"
16225 if (clearTransform) {
16226 _removeProp(s, _transformProp);
16227 if (this.t._gsTransform) {
16228 delete this.t._gsTransform;
16234 _registerComplexSpecialProp("clearProps", {parser:function(t, e, p, cssp, pt) {
16235 pt = new CSSPropTween(t, p, 0, 0, pt, 2);
16236 pt.setRatio = _setClearPropsRatio;
16239 pt.data = cssp._tween;
16240 _hasPriority = true;
16244 p = "bezier,throwProps,physicsProps,physics2D".split(",");
16247 _registerPluginProp(p[i]);
16257 p = CSSPlugin.prototype;
16260 //gets called when the tween renders for the first time. This kicks everything off, recording start/end values, etc.
16261 p._onInitTween = function(target, vars, tween) {
16262 if (!target.nodeType) { //css is only for dom elements
16265 this._target = target;
16266 this._tween = tween;
16268 _autoRound = vars.autoRound;
16269 _hasPriority = false;
16270 _suffixMap = vars.suffixMap || CSSPlugin.suffixMap;
16271 _cs = _getComputedStyle(target, "");
16272 _overwriteProps = this._overwriteProps;
16273 var style = target.style,
16274 v, pt, pt2, first, last, next, zIndex, tpt, threeD;
16275 if (_reqSafariFix) if (style.zIndex === "") {
16276 v = _getStyle(target, "zIndex", _cs);
16277 if (v === "auto" || v === "") {
16278 //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.
16279 this._addLazySet(style, "zIndex", 0);
16283 if (typeof(vars) === "string") {
16284 first = style.cssText;
16285 v = _getAllStyles(target, _cs);
16286 style.cssText = first + ";" + vars;
16287 v = _cssDif(target, v, _getAllStyles(target)).difs;
16288 if (!_supportsOpacity && _opacityValExp.test(vars)) {
16289 v.opacity = parseFloat( RegExp.$1 );
16292 style.cssText = first;
16294 this._firstPT = pt = this.parse(target, vars, null);
16296 if (this._transformType) {
16297 threeD = (this._transformType === 3);
16298 if (!_transformProp) {
16299 style.zoom = 1; //helps correct an IE issue.
16300 } else if (_isSafari) {
16301 _reqSafariFix = true;
16302 //if zIndex isn't set, iOS Safari doesn't repaint things correctly sometimes (seemingly at random).
16303 if (style.zIndex === "") {
16304 zIndex = _getStyle(target, "zIndex", _cs);
16305 if (zIndex === "auto" || zIndex === "") {
16306 this._addLazySet(style, "zIndex", 0);
16309 //Setting WebkitBackfaceVisibility corrects 3 bugs:
16310 // 1) [non-Android] Safari skips rendering changes to "top" and "left" that are made on the same frame/render as a transform update.
16311 // 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.
16312 // 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.
16313 //Note: we allow the user to override the auto-setting by defining WebkitBackfaceVisibility in the vars of the tween.
16314 if (_isSafariLT6) {
16315 this._addLazySet(style, "WebkitBackfaceVisibility", this._vars.WebkitBackfaceVisibility || (threeD ? "visible" : "hidden"));
16319 while (pt2 && pt2._next) {
16322 tpt = new CSSPropTween(target, "transform", 0, 0, null, 2);
16323 this._linkCSSP(tpt, null, pt2);
16324 tpt.setRatio = (threeD && _supports3D) ? _set3DTransformRatio : _transformProp ? _set2DTransformRatio : _setIETransformRatio;
16325 tpt.data = this._transform || _getTransform(target, _cs, true);
16326 _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.
16329 if (_hasPriority) {
16330 //reorders the linked list in order of pr (priority)
16334 while (pt2 && pt2.pr > pt.pr) {
16337 if ((pt._prev = pt2 ? pt2._prev : last)) {
16338 pt._prev._next = pt;
16342 if ((pt._next = pt2)) {
16349 this._firstPT = first;
16355 p.parse = function(target, vars, pt, plugin) {
16356 var style = target.style,
16357 p, sp, bn, en, bs, es, bsfx, esfx, isStr, rel;
16359 es = vars[p]; //ending value string
16360 sp = _specialProps[p]; //SpecialProp lookup.
16362 pt = sp.parse(target, es, p, this, pt, plugin, vars);
16365 bs = _getStyle(target, p, _cs) + "";
16366 isStr = (typeof(es) === "string");
16367 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:
16369 es = _parseColor(es);
16370 es = ((es.length > 3) ? "rgba(" : "rgb(") + es.join(",") + ")";
16372 pt = _parseComplex(style, p, bs, es, true, "transparent", pt, 0, plugin);
16374 } else if (isStr && (es.indexOf(" ") !== -1 || es.indexOf(",") !== -1)) {
16375 pt = _parseComplex(style, p, bs, es, true, null, pt, 0, plugin);
16378 bn = parseFloat(bs);
16379 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.
16381 if (bs === "" || bs === "auto") {
16382 if (p === "width" || p === "height") {
16383 bn = _getDimension(target, p, _cs);
16385 } else if (p === "left" || p === "top") {
16386 bn = _calculateOffset(target, p, _cs);
16389 bn = (p !== "opacity") ? 0 : 1;
16394 rel = (isStr && es.charAt(1) === "=");
16396 en = parseInt(es.charAt(0) + "1", 10);
16398 en *= parseFloat(es);
16399 esfx = es.replace(_suffixExp, "");
16401 en = parseFloat(es);
16402 esfx = isStr ? es.substr((en + "").length) || "" : "";
16406 esfx = (p in _suffixMap) ? _suffixMap[p] : bsfx; //populate the end suffix, prioritizing the map, then if none is found, use the beginning suffix.
16409 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.
16411 //if the beginning/ending suffixes don't match, normalize them...
16412 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!
16413 bn = _convertToPixels(target, p, bn, bsfx);
16414 if (esfx === "%") {
16415 bn /= _convertToPixels(target, p, 100, "%") / 100;
16416 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.
16420 } else if (esfx === "em") {
16421 bn /= _convertToPixels(target, p, 1, "em");
16423 //otherwise convert to pixels.
16424 } else if (esfx !== "px") {
16425 en = _convertToPixels(target, p, en, esfx);
16426 esfx = "px"; //we don't use bsfx after this, so we don't need to set it to px too.
16428 if (rel) if (en || en === 0) {
16429 es = (en + bn) + esfx; //the changes we made affect relative calculations, so adjust the end value here.
16437 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.
16438 pt = new CSSPropTween(style, p, bn, en - bn, pt, 0, p, (_autoRound !== false && (esfx === "px" || p === "zIndex")), 0, bs, es);
16440 //DEBUG: _log("tween "+p+" from "+pt.b+" ("+bn+esfx+") to "+pt.e+" with suffix: "+pt.xs0);
16441 } else if (style[p] === undefined || !es && (es + "" === "NaN" || es == null)) {
16442 _log("invalid " + p + " tween value: " + vars[p]);
16444 pt = new CSSPropTween(style, p, en || bn || 0, 0, pt, -1, p, false, 0, bs, es);
16445 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.
16446 //DEBUG: _log("non-tweening value "+p+": "+pt.xs0);
16450 if (plugin) if (pt && !pt.plugin) {
16451 pt.plugin = plugin;
16458 //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.
16459 p.setRatio = function(v) {
16460 var pt = this._firstPT,
16464 //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).
16465 if (v === 1 && (this._tween._time === this._tween._duration || this._tween._time === 0)) {
16467 if (pt.type !== 2) {
16475 } else if (v || !(this._tween._time === this._tween._duration || this._tween._time === 0) || this._tween._rawPrevTime === -0.000001) {
16477 val = pt.c * v + pt.s;
16479 val = Math.round(val);
16480 } else if (val < min) if (val > -min) {
16484 pt.t[pt.p] = val + pt.xs0;
16485 } else if (pt.type === 1) { //complex value (one that typically has multiple numbers inside a string, like "rect(5px,10px,20px,25px)"
16488 pt.t[pt.p] = pt.xs0 + val + pt.xs1 + pt.xn1 + pt.xs2;
16489 } else if (i === 3) {
16490 pt.t[pt.p] = pt.xs0 + val + pt.xs1 + pt.xn1 + pt.xs2 + pt.xn2 + pt.xs3;
16491 } else if (i === 4) {
16492 pt.t[pt.p] = pt.xs0 + val + pt.xs1 + pt.xn1 + pt.xs2 + pt.xn2 + pt.xs3 + pt.xn3 + pt.xs4;
16493 } else if (i === 5) {
16494 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;
16496 str = pt.xs0 + val + pt.xs1;
16497 for (i = 1; i < pt.l; i++) {
16498 str += pt["xn"+i] + pt["xs"+(i+1)];
16503 } else if (pt.type === -1) { //non-tweening value
16504 pt.t[pt.p] = pt.xs0;
16506 } else if (pt.setRatio) { //custom setRatio() for things like SpecialProps, external plugins, etc.
16512 //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).
16515 if (pt.type !== 2) {
16527 * Forces rendering of the target's transforms (rotation, scale, etc.) whenever the CSSPlugin's setRatio() is called.
16528 * Basically, this tells the CSSPlugin to create a CSSPropTween (type 2) after instantiation that runs last in the linked
16529 * list and calls the appropriate (3D or 2D) rendering function. We separate this into its own method so that we can call
16530 * it from other plugins like BezierPlugin if, for example, it needs to apply an autoRotation and this CSSPlugin
16531 * doesn't have any transform-related properties of its own. You can call this method as many times as you
16532 * want and it won't create duplicate CSSPropTweens.
16534 * @param {boolean} threeD if true, it should apply 3D tweens (otherwise, just 2D ones are fine and typically faster)
16536 p._enableTransforms = function(threeD) {
16537 this._transformType = (threeD || this._transformType === 3) ? 3 : 2;
16538 this._transform = this._transform || _getTransform(this._target, _cs, true); //ensures that the element has a _gsTransform property with the appropriate values.
16541 var lazySet = function(v) {
16542 this.t[this.p] = this.e;
16543 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.
16545 /** @private Gives us a way to set a value on the first render (and only the first render). **/
16546 p._addLazySet = function(t, p, v) {
16547 var pt = this._firstPT = new CSSPropTween(t, p, 0, 0, this._firstPT, 2);
16549 pt.setRatio = lazySet;
16554 p._linkCSSP = function(pt, next, prev, remove) {
16560 pt._next._prev = pt._prev;
16563 pt._prev._next = pt._next;
16564 } else if (this._firstPT === pt) {
16565 this._firstPT = pt._next;
16566 remove = true; //just to prevent resetting this._firstPT 5 lines down in case pt._next is null. (optimized for speed)
16570 } else if (!remove && this._firstPT === null) {
16571 this._firstPT = pt;
16579 //we need to make sure that if alpha or autoAlpha is killed, opacity is too. And autoAlpha affects the "visibility" property.
16580 p._kill = function(lookup) {
16583 if (lookup.autoAlpha || lookup.alpha) {
16585 for (p in lookup) { //copy the lookup so that we're not changing the original which may be passed elsewhere.
16586 copy[p] = lookup[p];
16589 if (copy.autoAlpha) {
16590 copy.visibility = 1;
16593 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".
16594 xfirst = pt.xfirst;
16595 if (xfirst && xfirst._prev) {
16596 this._linkCSSP(xfirst._prev, pt._next, xfirst._prev._prev); //break off the prev
16597 } else if (xfirst === this._firstPT) {
16598 this._firstPT = pt._next;
16601 this._linkCSSP(pt._next, pt._next._next, xfirst._prev);
16603 this._classNamePT = null;
16605 return TweenPlugin.prototype._kill.call(this, copy);
16610 //used by cascadeTo() for gathering all the style properties of each child element into an array for comparison.
16611 var _getChildStyles = function(e, props, targets) {
16612 var children, i, child, type;
16616 _getChildStyles(e[i], props, targets);
16620 children = e.childNodes;
16621 i = children.length;
16623 child = children[i];
16626 props.push(_getAllStyles(child));
16628 targets.push(child);
16631 if ((type === 1 || type === 9 || type === 11) && child.childNodes.length) {
16632 _getChildStyles(child, props, targets);
16638 * Typically only useful for className tweens that may affect child elements, this method creates a TweenLite
16639 * and then compares the style properties of all the target's child elements at the tween's start and end, and
16640 * if any are different, it also creates tweens for those and returns an array containing ALL of the resulting
16641 * tweens (so that you can easily add() them to a TimelineLite, for example). The reason this functionality is
16642 * wrapped into a separate static method of CSSPlugin instead of being integrated into all regular className tweens
16643 * is because it creates entirely new tweens that may have completely different targets than the original tween,
16644 * so if they were all lumped into the original tween instance, it would be inconsistent with the rest of the API
16645 * and it would create other problems. For example:
16646 * - 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)
16647 * - 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.
16648 * - 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.
16650 * @param {Object} target object to be tweened
16651 * @param {number} Duration in seconds (or frames for frames-based tweens)
16652 * @param {Object} Object containing the end values, like {className:"newClass", ease:Linear.easeNone}
16653 * @return {Array} An array of TweenLite instances
16655 CSSPlugin.cascadeTo = function(target, duration, vars) {
16656 var tween = TweenLite.to(target, duration, vars),
16661 _reservedProps = TweenLite._internals.reservedProps,
16663 target = tween._targets || tween.target;
16664 _getChildStyles(target, b, targets);
16665 tween.render(duration, true);
16666 _getChildStyles(target, e);
16667 tween.render(0, true);
16668 tween._enabled(true);
16669 i = targets.length;
16671 difs = _cssDif(targets[i], b[i], e[i]);
16672 if (difs.firstMPT) {
16675 if (_reservedProps[p]) {
16679 results.push( TweenLite.to(targets[i], duration, difs) );
16685 TweenPlugin.activate([CSSPlugin]);
16701 * ----------------------------------------------------------------
16703 * ----------------------------------------------------------------
16707 var RoundPropsPlugin = window._gsDefine.plugin({
16708 propName: "roundProps",
16712 //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.
16713 init: function(target, value, tween) {
16714 this._tween = tween;
16719 p = RoundPropsPlugin.prototype;
16721 p._onInitAllProps = function() {
16722 var tween = this._tween,
16723 rp = (tween.vars.roundProps instanceof Array) ? tween.vars.roundProps : tween.vars.roundProps.split(","),
16726 rpt = tween._propLookup.roundProps,
16734 pt = tween._firstPT;
16736 next = pt._next; //record here, because it may get removed
16738 pt.t._roundProps(lookup, true);
16739 } else if (pt.n === prop) {
16740 this._add(pt.t, prop, pt.s, pt.c);
16741 //remove from linked list
16743 next._prev = pt._prev;
16746 pt._prev._next = next;
16747 } else if (tween._firstPT === pt) {
16748 tween._firstPT = next;
16750 pt._next = pt._prev = null;
16751 tween._propLookup[prop] = rpt;
16759 p._add = function(target, p, s, c) {
16760 this._addTween(target, p, s, s + c, p, true);
16761 this._overwriteProps.push(p);
16776 * ----------------------------------------------------------------
16778 * ----------------------------------------------------------------
16780 window._gsDefine.plugin({
16785 //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.
16786 init: function(target, value, tween) {
16788 if (typeof(target.setAttribute) !== "function") {
16791 this._target = target;
16793 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.
16796 this._start[p] = this._proxy[p] = start = target.getAttribute(p);
16797 end = this._addTween(this._proxy, p, parseFloat(start), value[p], p);
16798 this._end[p] = end ? end.s + end.c : value[p];
16799 this._overwriteProps.push(p);
16804 //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.)
16805 set: function(ratio) {
16806 this._super.setRatio.call(this, ratio);
16807 var props = this._overwriteProps,
16809 lookup = (ratio === 1) ? this._end : ratio ? this._proxy : this._start,
16813 this._target.setAttribute(p, lookup[p] + "");
16829 * ----------------------------------------------------------------
16830 * DirectionalRotationPlugin
16831 * ----------------------------------------------------------------
16833 window._gsDefine.plugin({
16834 propName: "directionalRotation",
16838 //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.
16839 init: function(target, value, tween) {
16840 if (typeof(value) !== "object") {
16841 value = {rotation:value};
16844 var cap = (value.useRadians === true) ? Math.PI * 2 : 360,
16846 p, v, start, end, dif, split;
16848 if (p !== "useRadians") {
16849 split = (value[p] + "").split("_");
16851 start = parseFloat( (typeof(target[p]) !== "function") ? target[p] : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]() );
16852 end = this.finals[p] = (typeof(v) === "string" && v.charAt(1) === "=") ? start + parseInt(v.charAt(0) + "1", 10) * Number(v.substr(2)) : Number(v) || 0;
16854 if (split.length) {
16855 v = split.join("_");
16856 if (v.indexOf("short") !== -1) {
16858 if (dif !== dif % (cap / 2)) {
16859 dif = (dif < 0) ? dif + cap : dif - cap;
16862 if (v.indexOf("_cw") !== -1 && dif < 0) {
16863 dif = ((dif + cap * 9999999999) % cap) - ((dif / cap) | 0) * cap;
16864 } else if (v.indexOf("ccw") !== -1 && dif > 0) {
16865 dif = ((dif - cap * 9999999999) % cap) - ((dif / cap) | 0) * cap;
16868 if (dif > min || dif < -min) {
16869 this._addTween(target, p, start, start + dif, p);
16870 this._overwriteProps.push(p);
16877 //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.)
16878 set: function(ratio) {
16881 this._super.setRatio.call(this, ratio);
16883 pt = this._firstPT;
16886 pt.t[pt.p](this.finals[pt.p]);
16888 pt.t[pt.p] = this.finals[pt.p];
16895 })._autoCSS = true;
16908 * ----------------------------------------------------------------
16910 * ----------------------------------------------------------------
16912 window._gsDefine("easing.Back", ["easing.Ease"], function(Ease) {
16914 var w = (window.GreenSockGlobals || window),
16915 gs = w.com.greensock,
16916 _2PI = Math.PI * 2,
16917 _HALF_PI = Math.PI / 2,
16918 _class = gs._class,
16919 _create = function(n, f) {
16920 var C = _class("easing." + n, function(){}, true),
16921 p = C.prototype = new Ease();
16926 _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.
16927 _wrap = function(name, EaseOut, EaseIn, EaseInOut, aliases) {
16928 var C = _class("easing."+name, {
16929 easeOut:new EaseOut(),
16930 easeIn:new EaseIn(),
16931 easeInOut:new EaseInOut()
16936 EasePoint = function(time, value, next) {
16942 this.c = next.v - value;
16943 this.gap = next.t - time;
16948 _createBack = function(n, f) {
16949 var C = _class("easing." + n, function(overshoot) {
16950 this._p1 = (overshoot || overshoot === 0) ? overshoot : 1.70158;
16951 this._p2 = this._p1 * 1.525;
16953 p = C.prototype = new Ease();
16956 p.config = function(overshoot) {
16957 return new C(overshoot);
16962 Back = _wrap("Back",
16963 _createBack("BackOut", function(p) {
16964 return ((p = p - 1) * p * ((this._p1 + 1) * p + this._p1) + 1);
16966 _createBack("BackIn", function(p) {
16967 return p * p * ((this._p1 + 1) * p - this._p1);
16969 _createBack("BackInOut", function(p) {
16970 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);
16976 SlowMo = _class("easing.SlowMo", function(linearRatio, power, yoyoMode) {
16977 power = (power || power === 0) ? power : 0.7;
16978 if (linearRatio == null) {
16980 } else if (linearRatio > 1) {
16983 this._p = (linearRatio !== 1) ? power : 0;
16984 this._p1 = (1 - linearRatio) / 2;
16985 this._p2 = linearRatio;
16986 this._p3 = this._p1 + this._p2;
16987 this._calcEnd = (yoyoMode === true);
16989 p = SlowMo.prototype = new Ease(),
16990 SteppedEase, RoughEase, _createElastic;
16992 p.constructor = SlowMo;
16993 p.getRatio = function(p) {
16994 var r = p + (0.5 - p) * this._p;
16995 if (p < this._p1) {
16996 return this._calcEnd ? 1 - ((p = 1 - (p / this._p1)) * p) : r - ((p = 1 - (p / this._p1)) * p * p * p * r);
16997 } else if (p > this._p3) {
16998 return this._calcEnd ? 1 - (p = (p - this._p3) / this._p1) * p : r + ((p - r) * (p = (p - this._p3) / this._p1) * p * p * p);
17000 return this._calcEnd ? 1 : r;
17002 SlowMo.ease = new SlowMo(0.7, 0.7);
17004 p.config = SlowMo.config = function(linearRatio, power, yoyoMode) {
17005 return new SlowMo(linearRatio, power, yoyoMode);
17010 SteppedEase = _class("easing.SteppedEase", function(steps) {
17011 steps = steps || 1;
17012 this._p1 = 1 / steps;
17013 this._p2 = steps + 1;
17015 p = SteppedEase.prototype = new Ease();
17016 p.constructor = SteppedEase;
17017 p.getRatio = function(p) {
17020 } else if (p >= 1) {
17023 return ((this._p2 * p) >> 0) * this._p1;
17025 p.config = SteppedEase.config = function(steps) {
17026 return new SteppedEase(steps);
17031 RoughEase = _class("easing.RoughEase", function(vars) {
17033 var taper = vars.taper || "none",
17036 points = (vars.points || 20) | 0,
17038 randomize = (vars.randomize !== false),
17039 clamp = (vars.clamp === true),
17040 template = (vars.template instanceof Ease) ? vars.template : null,
17041 strength = (typeof(vars.strength) === "number") ? vars.strength * 0.4 : 0.4,
17042 x, y, bump, invX, obj, pnt;
17044 x = randomize ? Math.random() : (1 / points) * i;
17045 y = template ? template.getRatio(x) : x;
17046 if (taper === "none") {
17048 } else if (taper === "out") {
17050 bump = invX * invX * strength;
17051 } else if (taper === "in") {
17052 bump = x * x * strength;
17053 } else if (x < 0.5) { //"both" (start)
17055 bump = invX * invX * 0.5 * strength;
17056 } else { //"both" (end)
17057 invX = (1 - x) * 2;
17058 bump = invX * invX * 0.5 * strength;
17061 y += (Math.random() * bump) - (bump * 0.5);
17062 } else if (i % 2) {
17070 } else if (y < 0) {
17074 a[cnt++] = {x:x, y:y};
17076 a.sort(function(a, b) {
17080 pnt = new EasePoint(1, 1, null);
17084 pnt = new EasePoint(obj.x, obj.y, pnt);
17087 this._prev = new EasePoint(0, 0, (pnt.t !== 0) ? pnt : pnt.next);
17089 p = RoughEase.prototype = new Ease();
17090 p.constructor = RoughEase;
17091 p.getRatio = function(p) {
17092 var pnt = this._prev;
17094 while (pnt.next && p >= pnt.t) {
17099 while (pnt.prev && p <= pnt.t) {
17104 return (pnt.v + ((p - pnt.t) / pnt.gap) * pnt.c);
17106 p.config = function(vars) {
17107 return new RoughEase(vars);
17109 RoughEase.ease = new RoughEase();
17114 _create("BounceOut", function(p) {
17115 if (p < 1 / 2.75) {
17116 return 7.5625 * p * p;
17117 } else if (p < 2 / 2.75) {
17118 return 7.5625 * (p -= 1.5 / 2.75) * p + 0.75;
17119 } else if (p < 2.5 / 2.75) {
17120 return 7.5625 * (p -= 2.25 / 2.75) * p + 0.9375;
17122 return 7.5625 * (p -= 2.625 / 2.75) * p + 0.984375;
17124 _create("BounceIn", function(p) {
17125 if ((p = 1 - p) < 1 / 2.75) {
17126 return 1 - (7.5625 * p * p);
17127 } else if (p < 2 / 2.75) {
17128 return 1 - (7.5625 * (p -= 1.5 / 2.75) * p + 0.75);
17129 } else if (p < 2.5 / 2.75) {
17130 return 1 - (7.5625 * (p -= 2.25 / 2.75) * p + 0.9375);
17132 return 1 - (7.5625 * (p -= 2.625 / 2.75) * p + 0.984375);
17134 _create("BounceInOut", function(p) {
17135 var invert = (p < 0.5);
17141 if (p < 1 / 2.75) {
17142 p = 7.5625 * p * p;
17143 } else if (p < 2 / 2.75) {
17144 p = 7.5625 * (p -= 1.5 / 2.75) * p + 0.75;
17145 } else if (p < 2.5 / 2.75) {
17146 p = 7.5625 * (p -= 2.25 / 2.75) * p + 0.9375;
17148 p = 7.5625 * (p -= 2.625 / 2.75) * p + 0.984375;
17150 return invert ? (1 - p) * 0.5 : p * 0.5 + 0.5;
17157 _create("CircOut", function(p) {
17158 return Math.sqrt(1 - (p = p - 1) * p);
17160 _create("CircIn", function(p) {
17161 return -(Math.sqrt(1 - (p * p)) - 1);
17163 _create("CircInOut", function(p) {
17164 return ((p*=2) < 1) ? -0.5 * (Math.sqrt(1 - p * p) - 1) : 0.5 * (Math.sqrt(1 - (p -= 2) * p) + 1);
17170 _createElastic = function(n, f, def) {
17171 var C = _class("easing." + n, function(amplitude, period) {
17172 this._p1 = amplitude || 1;
17173 this._p2 = period || def;
17174 this._p3 = this._p2 / _2PI * (Math.asin(1 / this._p1) || 0);
17176 p = C.prototype = new Ease();
17179 p.config = function(amplitude, period) {
17180 return new C(amplitude, period);
17185 _createElastic("ElasticOut", function(p) {
17186 return this._p1 * Math.pow(2, -10 * p) * Math.sin( (p - this._p3) * _2PI / this._p2 ) + 1;
17188 _createElastic("ElasticIn", function(p) {
17189 return -(this._p1 * Math.pow(2, 10 * (p -= 1)) * Math.sin( (p - this._p3) * _2PI / this._p2 ));
17191 _createElastic("ElasticInOut", function(p) {
17192 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;
17199 _create("ExpoOut", function(p) {
17200 return 1 - Math.pow(2, -10 * p);
17202 _create("ExpoIn", function(p) {
17203 return Math.pow(2, 10 * (p - 1)) - 0.001;
17205 _create("ExpoInOut", function(p) {
17206 return ((p *= 2) < 1) ? 0.5 * Math.pow(2, 10 * (p - 1)) : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
17213 _create("SineOut", function(p) {
17214 return Math.sin(p * _HALF_PI);
17216 _create("SineIn", function(p) {
17217 return -Math.cos(p * _HALF_PI) + 1;
17219 _create("SineInOut", function(p) {
17220 return -0.5 * (Math.cos(Math.PI * p) - 1);
17224 _class("easing.EaseLookup", {
17226 return Ease.map[s];
17230 //register the non-standard eases
17231 _easeReg(w.SlowMo, "SlowMo", "ease,");
17232 _easeReg(RoughEase, "RoughEase", "ease,");
17233 _easeReg(SteppedEase, "SteppedEase", "ease,");
17253 * ----------------------------------------------------------------
17254 * Base classes like TweenLite, SimpleTimeline, Ease, Ticker, etc.
17255 * ----------------------------------------------------------------
17257 (function(window) {
17260 var _globals = window.GreenSockGlobals || window;
17261 if (_globals.TweenLite) {
17262 return; //in case the core set of classes is already loaded, don't instantiate twice.
17264 var _namespace = function(ns) {
17265 var a = ns.split("."),
17267 for (i = 0; i < a.length; i++) {
17268 p[a[i]] = p = p[a[i]] || {};
17272 gs = _namespace("com.greensock"),
17273 _tinyNum = 0.0000000001,
17275 _emptyFunc = function() {},
17276 _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)
17277 var toString = Object.prototype.toString,
17278 array = toString.call([]);
17279 return function(obj) {
17280 return obj != null && (obj instanceof Array || (typeof(obj) === "object" && !!obj.push && toString.call(obj) === array));
17283 a, i, p, _ticker, _tickerActive,
17288 * Defines a GreenSock class, optionally with an array of dependencies that must be instantiated first and passed into the definition.
17289 * This allows users to load GreenSock JS files in any order even if they have interdependencies (like CSSPlugin extends TweenPlugin which is
17290 * inside TweenLite.js, but if CSSPlugin is loaded first, it should wait to run its code until TweenLite.js loads and instantiates TweenPlugin
17291 * and then pass TweenPlugin to CSSPlugin's definition). This is all done automatically and internally.
17293 * Every definition will be added to a "com.greensock" global object (typically window, but if a window.GreenSockGlobals object is found,
17294 * 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,
17295 * it is ALSO referenced at window.TweenLite. However some classes aren't considered global, like the base com.greensock.core.Animation class, so
17296 * those will only be at the package like window.com.greensock.core.Animation. Again, if you define a GreenSockGlobals object on the window, everything
17297 * gets tucked neatly inside there instead of on the window directly. This allows you to do advanced things like load multiple versions of GreenSock
17298 * 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
17299 * sandbox the banner one like:
17302 * 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.
17304 * <script src="js/greensock/v1.7/TweenMax.js"></script>
17306 * 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(...)
17308 * <script src="js/greensock/v1.6/TweenMax.js"></script>
17310 * gs.TweenLite.to(...); //would use v1.7
17311 * TweenLite.to(...); //would use v1.6
17314 * @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".
17315 * @param {!Array.<string>} dependencies An array of dependencies (described as their namespaces minus "com.greensock." prefix). For example ["TweenLite","plugins.TweenPlugin","core.Animation"]
17316 * @param {!function():Object} func The function that should be called and passed the resolved dependencies which will return the actual class for this definition.
17317 * @param {boolean=} global If true, the class will be added to the global scope (typically window unless you define a window.GreenSockGlobals object)
17319 Definition = function(ns, dependencies, func, global) {
17320 this.sc = (_defLookup[ns]) ? _defLookup[ns].sc : []; //subclasses
17321 _defLookup[ns] = this;
17322 this.gsClass = null;
17325 this.check = function(init) {
17326 var i = dependencies.length,
17330 if ((cur = _defLookup[dependencies[i]] || new Definition(dependencies[i], [])).gsClass) {
17331 _classes[i] = cur.gsClass;
17337 if (missing === 0 && func) {
17338 a = ("com.greensock." + ns).split(".");
17340 cl = _namespace(a.join("."))[n] = this.gsClass = func.apply(func, _classes);
17342 //exports to multiple environments
17344 _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.)
17345 if (typeof(define) === "function" && define.amd){ //AMD
17346 define((window.GreenSockAMDPath ? window.GreenSockAMDPath + "/" : "") + ns.split(".").join("/"), [], function() { return cl; });
17347 } else if (typeof(module) !== "undefined" && module.exports){ //node
17348 module.exports = cl;
17351 for (i = 0; i < this.sc.length; i++) {
17352 this.sc[i].check();
17359 //used to create Definition instances (which basically registers a class that has dependencies).
17360 _gsDefine = window._gsDefine = function(ns, dependencies, func, global) {
17361 return new Definition(ns, dependencies, func, global);
17364 //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).
17365 _class = gs._class = function(ns, func, global) {
17366 func = func || function() {};
17367 _gsDefine(ns, [], function(){ return func; }, global);
17371 _gsDefine.globals = _globals;
17376 * ----------------------------------------------------------------
17378 * ----------------------------------------------------------------
17380 var _baseParams = [0, 0, 1, 1],
17382 Ease = _class("easing.Ease", function(func, extraParams, type, power) {
17384 this._type = type || 0;
17385 this._power = power || 0;
17386 this._params = extraParams ? _baseParams.concat(extraParams) : _baseParams;
17388 _easeMap = Ease.map = {},
17389 _easeReg = Ease.register = function(ease, names, types, create) {
17390 var na = names.split(","),
17392 ta = (types || "easeIn,easeOut,easeInOut").split(","),
17396 e = create ? _class("easing."+name, null, true) : gs.easing[name] || {};
17400 _easeMap[name + "." + type] = _easeMap[type + name] = e[type] = ease.getRatio ? ease : ease[type] || new ease();
17405 p = Ease.prototype;
17406 p._calcEnd = false;
17407 p.getRatio = function(p) {
17409 this._params[0] = p;
17410 return this._func.apply(null, this._params);
17412 var t = this._type,
17414 r = (t === 1) ? 1 - p : (t === 2) ? p : (p < 0.5) ? p * 2 : (1 - p) * 2;
17417 } else if (pw === 2) {
17419 } else if (pw === 3) {
17421 } else if (pw === 4) {
17422 r *= r * r * r * r;
17424 return (t === 1) ? 1 - r : (t === 2) ? r : (p < 0.5) ? r / 2 : 1 - (r / 2);
17427 //create all the standard eases like Linear, Quad, Cubic, Quart, Quint, Strong, Power0, Power1, Power2, Power3, and Power4 (each with easeIn, easeOut, and easeInOut)
17428 a = ["Linear","Quad","Cubic","Quart","Quint,Strong"];
17431 p = a[i]+",Power"+i;
17432 _easeReg(new Ease(null,null,1,i), p, "easeOut", true);
17433 _easeReg(new Ease(null,null,2,i), p, "easeIn" + ((i === 0) ? ",easeNone" : ""));
17434 _easeReg(new Ease(null,null,3,i), p, "easeInOut");
17436 _easeMap.linear = gs.easing.Linear.easeIn;
17437 _easeMap.swing = gs.easing.Quad.easeInOut; //for jQuery folks
17441 * ----------------------------------------------------------------
17443 * ----------------------------------------------------------------
17445 var EventDispatcher = _class("events.EventDispatcher", function(target) {
17446 this._listeners = {};
17447 this._eventTarget = target || this;
17449 p = EventDispatcher.prototype;
17451 p.addEventListener = function(type, callback, scope, useParam, priority) {
17452 priority = priority || 0;
17453 var list = this._listeners[type],
17456 if (list == null) {
17457 this._listeners[type] = list = [];
17461 listener = list[i];
17462 if (listener.c === callback && listener.s === scope) {
17464 } else if (index === 0 && listener.pr < priority) {
17468 list.splice(index, 0, {c:callback, s:scope, up:useParam, pr:priority});
17469 if (this === _ticker && !_tickerActive) {
17474 p.removeEventListener = function(type, callback) {
17475 var list = this._listeners[type], i;
17479 if (list[i].c === callback) {
17487 p.dispatchEvent = function(type) {
17488 var list = this._listeners[type],
17492 t = this._eventTarget;
17494 listener = list[i];
17496 listener.c.call(listener.s || t, {type:type, target:t});
17498 listener.c.call(listener.s || t);
17506 * ----------------------------------------------------------------
17508 * ----------------------------------------------------------------
17510 var _reqAnimFrame = window.requestAnimationFrame,
17511 _cancelAnimFrame = window.cancelAnimationFrame,
17512 _getTime = Date.now || function() {return new Date().getTime();},
17513 _lastUpdate = _getTime();
17515 //now try to determine the requestAnimationFrame and cancelAnimationFrame functions and if none are found, we'll use a setTimeout()/clearTimeout() polyfill.
17516 a = ["ms","moz","webkit","o"];
17518 while (--i > -1 && !_reqAnimFrame) {
17519 _reqAnimFrame = window[a[i] + "RequestAnimationFrame"];
17520 _cancelAnimFrame = window[a[i] + "CancelAnimationFrame"] || window[a[i] + "CancelRequestAnimationFrame"];
17523 _class("Ticker", function(fps, useRAF) {
17525 _startTime = _getTime(),
17526 _useRAF = (useRAF !== false && _reqAnimFrame),
17527 _lagThreshold = 500,
17529 _fps, _req, _id, _gap, _nextTime,
17530 _tick = function(manual) {
17531 var elapsed = _getTime() - _lastUpdate,
17533 if (elapsed > _lagThreshold) {
17534 _startTime += elapsed - _adjustedLag;
17536 _lastUpdate += elapsed;
17537 _self.time = (_lastUpdate - _startTime) / 1000;
17538 overlap = _self.time - _nextTime;
17539 if (!_fps || overlap > 0 || manual === true) {
17541 _nextTime += overlap + (overlap >= _gap ? 0.004 : _gap - overlap);
17544 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.
17548 _self.dispatchEvent("tick");
17552 EventDispatcher.call(_self);
17553 _self.time = _self.frame = 0;
17554 _self.tick = function() {
17558 _self.lagSmoothing = function(threshold, adjustedLag) {
17559 _lagThreshold = threshold || (1 / _tinyNum); //zero should be interpreted as basically unlimited
17560 _adjustedLag = Math.min(adjustedLag, _lagThreshold, 0);
17563 _self.sleep = function() {
17567 if (!_useRAF || !_cancelAnimFrame) {
17570 _cancelAnimFrame(_id);
17574 if (_self === _ticker) {
17575 _tickerActive = false;
17579 _self.wake = function() {
17580 if (_id !== null) {
17582 } 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().
17583 _lastUpdate = _getTime() - _lagThreshold + 5;
17585 _req = (_fps === 0) ? _emptyFunc : (!_useRAF || !_reqAnimFrame) ? function(f) { return setTimeout(f, ((_nextTime - _self.time) * 1000 + 1) | 0); } : _reqAnimFrame;
17586 if (_self === _ticker) {
17587 _tickerActive = true;
17592 _self.fps = function(value) {
17593 if (!arguments.length) {
17597 _gap = 1 / (_fps || 60);
17598 _nextTime = this.time + _gap;
17602 _self.useRAF = function(value) {
17603 if (!arguments.length) {
17612 //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.
17613 setTimeout(function() {
17614 if (_useRAF && (!_id || _self.frame < 5)) {
17615 _self.useRAF(false);
17620 p = gs.Ticker.prototype = new gs.events.EventDispatcher();
17621 p.constructor = gs.Ticker;
17625 * ----------------------------------------------------------------
17627 * ----------------------------------------------------------------
17629 var Animation = _class("core.Animation", function(duration, vars) {
17630 this.vars = vars = vars || {};
17631 this._duration = this._totalDuration = duration || 0;
17632 this._delay = Number(vars.delay) || 0;
17633 this._timeScale = 1;
17634 this._active = (vars.immediateRender === true);
17635 this.data = vars.data;
17636 this._reversed = (vars.reversed === true);
17638 if (!_rootTimeline) {
17641 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.
17645 var tl = this.vars.useFrames ? _rootFramesTimeline : _rootTimeline;
17646 tl.add(this, tl._time);
17648 if (this.vars.paused) {
17653 _ticker = Animation.ticker = new gs.Ticker();
17654 p = Animation.prototype;
17655 p._dirty = p._gc = p._initted = p._paused = false;
17656 p._totalTime = p._time = 0;
17657 p._rawPrevTime = -1;
17658 p._next = p._last = p._onUpdate = p._timeline = p.timeline = null;
17662 //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.
17663 var _checkTimeout = function() {
17664 if (_tickerActive && _getTime() - _lastUpdate > 2000) {
17667 setTimeout(_checkTimeout, 2000);
17672 p.play = function(from, suppressEvents) {
17673 if (from != null) {
17674 this.seek(from, suppressEvents);
17676 return this.reversed(false).paused(false);
17679 p.pause = function(atTime, suppressEvents) {
17680 if (atTime != null) {
17681 this.seek(atTime, suppressEvents);
17683 return this.paused(true);
17686 p.resume = function(from, suppressEvents) {
17687 if (from != null) {
17688 this.seek(from, suppressEvents);
17690 return this.paused(false);
17693 p.seek = function(time, suppressEvents) {
17694 return this.totalTime(Number(time), suppressEvents !== false);
17697 p.restart = function(includeDelay, suppressEvents) {
17698 return this.reversed(false).paused(false).totalTime(includeDelay ? -this._delay : 0, (suppressEvents !== false), true);
17701 p.reverse = function(from, suppressEvents) {
17702 if (from != null) {
17703 this.seek((from || this.totalDuration()), suppressEvents);
17705 return this.reversed(true).paused(false);
17708 p.render = function(time, suppressEvents, force) {
17709 //stub - we override this method in subclasses.
17712 p.invalidate = function() {
17716 p.isActive = function() {
17717 var tl = this._timeline, //the 2 root timelines won't have a _timeline; they're always active.
17718 startTime = this._startTime,
17720 return (!tl || (!this._gc && !this._paused && tl.isActive() && (rawTime = tl.rawTime()) >= startTime && rawTime < startTime + this.totalDuration() / this._timeScale));
17723 p._enabled = function (enabled, ignoreTimeline) {
17724 if (!_tickerActive) {
17727 this._gc = !enabled;
17728 this._active = this.isActive();
17729 if (ignoreTimeline !== true) {
17730 if (enabled && !this.timeline) {
17731 this._timeline.add(this, this._startTime - this._delay);
17732 } else if (!enabled && this.timeline) {
17733 this._timeline._remove(this, true);
17740 p._kill = function(vars, target) {
17741 return this._enabled(false, false);
17744 p.kill = function(vars, target) {
17745 this._kill(vars, target);
17749 p._uncache = function(includeSelf) {
17750 var tween = includeSelf ? this : this.timeline;
17752 tween._dirty = true;
17753 tween = tween.timeline;
17758 p._swapSelfInParams = function(params) {
17759 var i = params.length,
17760 copy = params.concat();
17762 if (params[i] === "{self}") {
17769 //----Animation getters/setters --------------------------------------------------------
17771 p.eventCallback = function(type, callback, params, scope) {
17772 if ((type || "").substr(0,2) === "on") {
17774 if (arguments.length === 1) {
17777 if (callback == null) {
17780 v[type] = callback;
17781 v[type + "Params"] = (_isArray(params) && params.join("").indexOf("{self}") !== -1) ? this._swapSelfInParams(params) : params;
17782 v[type + "Scope"] = scope;
17784 if (type === "onUpdate") {
17785 this._onUpdate = callback;
17791 p.delay = function(value) {
17792 if (!arguments.length) {
17793 return this._delay;
17795 if (this._timeline.smoothChildTiming) {
17796 this.startTime( this._startTime + value - this._delay );
17798 this._delay = value;
17802 p.duration = function(value) {
17803 if (!arguments.length) {
17804 this._dirty = false;
17805 return this._duration;
17807 this._duration = this._totalDuration = value;
17808 this._uncache(true); //true in case it's a TweenMax or TimelineMax that has a repeat - we'll need to refresh the totalDuration.
17809 if (this._timeline.smoothChildTiming) if (this._time > 0) if (this._time < this._duration) if (value !== 0) {
17810 this.totalTime(this._totalTime * (value / this._duration), true);
17815 p.totalDuration = function(value) {
17816 this._dirty = false;
17817 return (!arguments.length) ? this._totalDuration : this.duration(value);
17820 p.time = function(value, suppressEvents) {
17821 if (!arguments.length) {
17825 this.totalDuration();
17827 return this.totalTime((value > this._duration) ? this._duration : value, suppressEvents);
17830 p.totalTime = function(time, suppressEvents, uncapped) {
17831 if (!_tickerActive) {
17834 if (!arguments.length) {
17835 return this._totalTime;
17837 if (this._timeline) {
17838 if (time < 0 && !uncapped) {
17839 time += this.totalDuration();
17841 if (this._timeline.smoothChildTiming) {
17843 this.totalDuration();
17845 var totalDuration = this._totalDuration,
17846 tl = this._timeline;
17847 if (time > totalDuration && !uncapped) {
17848 time = totalDuration;
17850 this._startTime = (this._paused ? this._pauseTime : tl._time) - ((!this._reversed ? time : totalDuration - time) / this._timeScale);
17851 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.
17852 this._uncache(false);
17854 //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.
17855 if (tl._timeline) {
17856 while (tl._timeline) {
17857 if (tl._timeline._time !== (tl._startTime + tl._totalTime) / tl._timeScale) {
17858 tl.totalTime(tl._totalTime, true);
17865 this._enabled(true, false);
17867 if (this._totalTime !== time || this._duration === 0) {
17868 this.render(time, suppressEvents, false);
17869 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.
17877 p.progress = p.totalProgress = function(value, suppressEvents) {
17878 return (!arguments.length) ? this._time / this.duration() : this.totalTime(this.duration() * value, suppressEvents);
17881 p.startTime = function(value) {
17882 if (!arguments.length) {
17883 return this._startTime;
17885 if (value !== this._startTime) {
17886 this._startTime = value;
17887 if (this.timeline) if (this.timeline._sortChildren) {
17888 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.
17894 p.timeScale = function(value) {
17895 if (!arguments.length) {
17896 return this._timeScale;
17898 value = value || _tinyNum; //can't allow zero because it'll throw the math off
17899 if (this._timeline && this._timeline.smoothChildTiming) {
17900 var pauseTime = this._pauseTime,
17901 t = (pauseTime || pauseTime === 0) ? pauseTime : this._timeline.totalTime();
17902 this._startTime = t - ((t - this._startTime) * this._timeScale / value);
17904 this._timeScale = value;
17905 return this._uncache(false);
17908 p.reversed = function(value) {
17909 if (!arguments.length) {
17910 return this._reversed;
17912 if (value != this._reversed) {
17913 this._reversed = value;
17914 this.totalTime(((this._timeline && !this._timeline.smoothChildTiming) ? this.totalDuration() - this._totalTime : this._totalTime), true);
17919 p.paused = function(value) {
17920 if (!arguments.length) {
17921 return this._paused;
17923 if (value != this._paused) if (this._timeline) {
17924 if (!_tickerActive && !value) {
17927 var tl = this._timeline,
17928 raw = tl.rawTime(),
17929 elapsed = raw - this._pauseTime;
17930 if (!value && tl.smoothChildTiming) {
17931 this._startTime += elapsed;
17932 this._uncache(false);
17934 this._pauseTime = value ? raw : null;
17935 this._paused = value;
17936 this._active = this.isActive();
17937 if (!value && elapsed !== 0 && this._initted && this.duration()) {
17938 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.
17941 if (this._gc && !value) {
17942 this._enabled(true, false);
17949 * ----------------------------------------------------------------
17951 * ----------------------------------------------------------------
17953 var SimpleTimeline = _class("core.SimpleTimeline", function(vars) {
17954 Animation.call(this, 0, vars);
17955 this.autoRemoveChildren = this.smoothChildTiming = true;
17958 p = SimpleTimeline.prototype = new Animation();
17959 p.constructor = SimpleTimeline;
17960 p.kill()._gc = false;
17961 p._first = p._last = null;
17962 p._sortChildren = false;
17964 p.add = p.insert = function(child, position, align, stagger) {
17966 child._startTime = Number(position || 0) + child._delay;
17967 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).
17968 child._pauseTime = child._startTime + ((this.rawTime() - child._startTime) / child._timeScale);
17970 if (child.timeline) {
17971 child.timeline._remove(child, true); //removes from existing timeline so that it can be properly added to this one.
17973 child.timeline = child._timeline = this;
17975 child._enabled(true, true);
17977 prevTween = this._last;
17978 if (this._sortChildren) {
17979 st = child._startTime;
17980 while (prevTween && prevTween._startTime > st) {
17981 prevTween = prevTween._prev;
17985 child._next = prevTween._next;
17986 prevTween._next = child;
17988 child._next = this._first;
17989 this._first = child;
17992 child._next._prev = child;
17994 this._last = child;
17996 child._prev = prevTween;
17997 if (this._timeline) {
17998 this._uncache(true);
18003 p._remove = function(tween, skipDisable) {
18004 if (tween.timeline === this) {
18005 if (!skipDisable) {
18006 tween._enabled(false, true);
18008 tween.timeline = null;
18011 tween._prev._next = tween._next;
18012 } else if (this._first === tween) {
18013 this._first = tween._next;
18016 tween._next._prev = tween._prev;
18017 } else if (this._last === tween) {
18018 this._last = tween._prev;
18021 if (this._timeline) {
18022 this._uncache(true);
18028 p.render = function(time, suppressEvents, force) {
18029 var tween = this._first,
18031 this._totalTime = this._time = this._rawPrevTime = time;
18033 next = tween._next; //record it here because the value could change after rendering...
18034 if (tween._active || (time >= tween._startTime && !tween._paused)) {
18035 if (!tween._reversed) {
18036 tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
18038 tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
18045 p.rawTime = function() {
18046 if (!_tickerActive) {
18049 return this._totalTime;
18053 * ----------------------------------------------------------------
18055 * ----------------------------------------------------------------
18057 var TweenLite = _class("TweenLite", function(target, duration, vars) {
18058 Animation.call(this, duration, vars);
18059 this.render = TweenLite.prototype.render; //speed optimization (avoid prototype lookup on this "hot" method)
18061 if (target == null) {
18062 throw "Cannot tween a null target.";
18065 this.target = target = (typeof(target) !== "string") ? target : TweenLite.selector(target) || target;
18067 var isSelector = (target.jquery || (target.length && target !== window && target[0] && (target[0] === window || (target[0].nodeType && target[0].style && !target.nodeType)))),
18068 overwrite = this.vars.overwrite,
18071 this._overwrite = overwrite = (overwrite == null) ? _overwriteLookup[TweenLite.defaultOverwrite] : (typeof(overwrite) === "number") ? overwrite >> 0 : _overwriteLookup[overwrite];
18073 if ((isSelector || target instanceof Array || (target.push && _isArray(target))) && typeof(target[0]) !== "number") {
18074 this._targets = targets = _slice.call(target, 0);
18075 this._propLookup = [];
18076 this._siblings = [];
18077 for (i = 0; i < targets.length; i++) {
18080 targets.splice(i--, 1);
18082 } else if (typeof(targ) === "string") {
18083 targ = targets[i--] = TweenLite.selector(targ); //in case it's an array of strings
18084 if (typeof(targ) === "string") {
18085 targets.splice(i+1, 1); //to avoid an endless loop (can't imagine why the selector would return a string, but just in case)
18088 } 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.
18089 targets.splice(i--, 1);
18090 this._targets = targets = targets.concat(_slice.call(targ, 0));
18093 this._siblings[i] = _register(targ, this, false);
18094 if (overwrite === 1) if (this._siblings[i].length > 1) {
18095 _applyOverwrite(targ, this, null, 1, this._siblings[i]);
18100 this._propLookup = {};
18101 this._siblings = _register(target, this, false);
18102 if (overwrite === 1) if (this._siblings.length > 1) {
18103 _applyOverwrite(target, this, null, 1, this._siblings);
18106 if (this.vars.immediateRender || (duration === 0 && this._delay === 0 && this.vars.immediateRender !== false)) {
18107 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)
18108 this.render(-this._delay);
18111 _isSelector = function(v) {
18112 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.
18114 _autoCSS = function(vars, target) {
18118 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.
18126 p = TweenLite.prototype = new Animation();
18127 p.constructor = TweenLite;
18128 p.kill()._gc = false;
18130 //----TweenLite defaults, overwrite management, and root updates ----------------------------------------------------
18133 p._firstPT = p._targets = p._overwrittenProps = p._startAt = null;
18134 p._notifyPluginsOfEnabled = p._lazy = false;
18136 TweenLite.version = "1.12.1";
18137 TweenLite.defaultEase = p._ease = new Ease(null, null, 1, 1);
18138 TweenLite.defaultOverwrite = "auto";
18139 TweenLite.ticker = _ticker;
18140 TweenLite.autoSleep = true;
18141 TweenLite.lagSmoothing = function(threshold, adjustedLag) {
18142 _ticker.lagSmoothing(threshold, adjustedLag);
18144 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; };
18146 var _lazyTweens = [],
18148 _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.
18149 _plugins = TweenLite._plugins = {},
18150 _tweenLookup = _internals.tweenLookup = {},
18151 _tweenLookupNum = 0,
18152 _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},
18153 _overwriteLookup = {none:0, all:1, auto:2, concurrent:3, allOnStart:4, preexisting:5, "true":1, "false":0},
18154 _rootFramesTimeline = Animation._rootFramesTimeline = new SimpleTimeline(),
18155 _rootTimeline = Animation._rootTimeline = new SimpleTimeline(),
18156 _lazyRender = function() {
18157 var i = _lazyTweens.length;
18160 a = _lazyTweens[i];
18161 if (a && a._lazy !== false) {
18162 a.render(a._lazy, false, true);
18166 _lazyTweens.length = 0;
18169 _rootTimeline._startTime = _ticker.time;
18170 _rootFramesTimeline._startTime = _ticker.frame;
18171 _rootTimeline._active = _rootFramesTimeline._active = true;
18172 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".
18174 Animation._updateRoot = TweenLite.render = function() {
18176 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.
18179 _rootTimeline.render((_ticker.time - _rootTimeline._startTime) * _rootTimeline._timeScale, false, false);
18180 _rootFramesTimeline.render((_ticker.frame - _rootFramesTimeline._startTime) * _rootFramesTimeline._timeScale, false, false);
18181 if (_lazyTweens.length) {
18184 if (!(_ticker.frame % 120)) { //dump garbage every 120 frames...
18185 for (p in _tweenLookup) {
18186 a = _tweenLookup[p].tweens;
18193 if (a.length === 0) {
18194 delete _tweenLookup[p];
18197 //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
18198 p = _rootTimeline._first;
18199 if (!p || p._paused) if (TweenLite.autoSleep && !_rootFramesTimeline._first && _ticker._listeners.tick.length === 1) {
18200 while (p && p._paused) {
18210 _ticker.addEventListener("tick", Animation._updateRoot);
18212 var _register = function(target, tween, scrub) {
18213 var id = target._gsTweenID, a, i;
18214 if (!_tweenLookup[id || (target._gsTweenID = id = "t" + (_tweenLookupNum++))]) {
18215 _tweenLookup[id] = {target:target, tweens:[]};
18218 a = _tweenLookup[id].tweens;
18219 a[(i = a.length)] = tween;
18222 if (a[i] === tween) {
18228 return _tweenLookup[id].tweens;
18231 _applyOverwrite = function(target, tween, props, mode, siblings) {
18232 var i, changed, curTween, l;
18233 if (mode === 1 || mode >= 4) {
18234 l = siblings.length;
18235 for (i = 0; i < l; i++) {
18236 if ((curTween = siblings[i]) !== tween) {
18237 if (!curTween._gc) if (curTween._enabled(false, false)) {
18240 } else if (mode === 5) {
18246 //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)
18247 var startTime = tween._startTime + _tinyNum,
18250 zeroDur = (tween._duration === 0),
18252 i = siblings.length;
18254 if ((curTween = siblings[i]) === tween || curTween._gc || curTween._paused) {
18256 } else if (curTween._timeline !== tween._timeline) {
18257 globalStart = globalStart || _checkOverlap(tween, 0, zeroDur);
18258 if (_checkOverlap(curTween, globalStart, zeroDur) === 0) {
18259 overlaps[oCount++] = curTween;
18261 } else if (curTween._startTime <= startTime) if (curTween._startTime + curTween.totalDuration() / curTween._timeScale > startTime) if (!((zeroDur || !curTween._initted) && startTime - curTween._startTime <= 0.0000000002)) {
18262 overlaps[oCount++] = curTween;
18268 curTween = overlaps[i];
18269 if (mode === 2) if (curTween._kill(props, target)) {
18272 if (mode !== 2 || (!curTween._firstPT && curTween._initted)) {
18273 if (curTween._enabled(false, false)) { //if all property tweens have been overwritten, kill the tween.
18281 _checkOverlap = function(tween, reference, zeroDur) {
18282 var tl = tween._timeline,
18283 ts = tl._timeScale,
18284 t = tween._startTime;
18285 while (tl._timeline) {
18286 t += tl._startTime;
18287 ts *= tl._timeScale;
18294 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;
18298 //---- TweenLite instance methods -----------------------------------------------------------------------------
18300 p._init = function() {
18302 op = this._overwrittenProps,
18303 dur = this._duration,
18304 immediate = !!v.immediateRender,
18306 i, initPlugins, pt, p, startVars;
18308 if (this._startAt) {
18309 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.
18310 this._startAt.kill();
18313 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);
18314 startVars[p] = v.startAt[p];
18316 startVars.overwrite = false;
18317 startVars.immediateRender = true;
18318 startVars.lazy = (immediate && v.lazy !== false);
18319 startVars.startAt = startVars.delay = null; //no nesting of startAt objects allowed (otherwise it could cause an infinite loop).
18320 this._startAt = TweenLite.to(this.target, 0, startVars);
18322 if (this._time > 0) {
18323 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()).
18324 } else if (dur !== 0) {
18325 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.
18328 } else if (v.runBackwards && dur !== 0) {
18329 //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)
18330 if (this._startAt) {
18331 this._startAt.render(-1, true);
18332 this._startAt.kill();
18333 this._startAt = null;
18336 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.
18337 if (!_reservedProps[p] || p === "autoCSS") {
18342 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.
18343 pt.lazy = (immediate && v.lazy !== false);
18344 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)
18345 this._startAt = TweenLite.to(this.target, 0, pt);
18347 this._startAt._init(); //ensures that the initial values are recorded
18348 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.
18349 } else if (this._time === 0) {
18355 this._ease = TweenLite.defaultEase;
18356 } else if (ease instanceof Ease) {
18357 this._ease = (v.easeParams instanceof Array) ? ease.config.apply(ease, v.easeParams) : ease;
18359 this._ease = (typeof(ease) === "function") ? new Ease(ease, v.easeParams) : _easeMap[ease] || TweenLite.defaultEase;
18361 this._easeType = this._ease._type;
18362 this._easePower = this._ease._power;
18363 this._firstPT = null;
18365 if (this._targets) {
18366 i = this._targets.length;
18368 if ( this._initProps( this._targets[i], (this._propLookup[i] = {}), this._siblings[i], (op ? op[i] : null)) ) {
18369 initPlugins = true;
18373 initPlugins = this._initProps(this.target, this._propLookup, this._siblings, op);
18377 TweenLite._onPluginEvent("_onInitAllProps", this); //reorders the array in order of priority. Uses a static TweenPlugin method in order to minimize file size in TweenLite
18379 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.
18380 this._enabled(false, false);
18382 if (v.runBackwards) {
18383 pt = this._firstPT;
18390 this._onUpdate = v.onUpdate;
18391 this._initted = true;
18394 p._initProps = function(target, propLookup, siblings, overwrittenProps) {
18395 var p, i, initPlugins, plugin, pt, v;
18396 if (target == null) {
18400 if (_lazyLookup[target._gsTweenID]) {
18401 _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)
18404 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.
18405 _autoCSS(this.vars, target);
18407 for (p in this.vars) {
18409 if (_reservedProps[p]) {
18410 if (v) if ((v instanceof Array) || (v.push && _isArray(v))) if (v.join("").indexOf("{self}") !== -1) {
18411 this.vars[p] = v = this._swapSelfInParams(v, this);
18414 } else if (_plugins[p] && (plugin = new _plugins[p]())._onInitTween(target, this.vars[p], this)) {
18416 //t - target [object]
18417 //p - property [string]
18418 //s - start [number]
18419 //c - change [number]
18420 //f - isFunction [boolean]
18421 //n - name [string]
18422 //pg - isPlugin [boolean]
18423 //pr - priority [number]
18424 this._firstPT = pt = {_next:this._firstPT, t:plugin, p:"setRatio", s:0, c:1, f:true, n:p, pg:true, pr:plugin._priority};
18425 i = plugin._overwriteProps.length;
18427 propLookup[plugin._overwriteProps[i]] = this._firstPT;
18429 if (plugin._priority || plugin._onInitAllProps) {
18430 initPlugins = true;
18432 if (plugin._onDisable || plugin._onEnable) {
18433 this._notifyPluginsOfEnabled = true;
18437 this._firstPT = propLookup[p] = pt = {_next:this._firstPT, t:target, p:p, f:(typeof(target[p]) === "function"), n:p, pg:false, pr:0};
18438 pt.s = (!pt.f) ? parseFloat(target[p]) : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]();
18439 pt.c = (typeof(v) === "string" && v.charAt(1) === "=") ? parseInt(v.charAt(0) + "1", 10) * Number(v.substr(2)) : (Number(v) - pt.s) || 0;
18441 if (pt) if (pt._next) {
18442 pt._next._prev = pt;
18446 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)
18447 return this._initProps(target, propLookup, siblings, overwrittenProps);
18449 if (this._overwrite > 1) if (this._firstPT) if (siblings.length > 1) if (_applyOverwrite(target, this, propLookup, this._overwrite, siblings)) {
18450 this._kill(propLookup, target);
18451 return this._initProps(target, propLookup, siblings, overwrittenProps);
18453 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.
18454 _lazyLookup[target._gsTweenID] = true;
18456 return initPlugins;
18459 p.render = function(time, suppressEvents, force) {
18460 var prevTime = this._time,
18461 duration = this._duration,
18462 prevRawPrevTime = this._rawPrevTime,
18463 isComplete, callback, pt, rawPrevTime;
18464 if (time >= duration) {
18465 this._totalTime = this._time = duration;
18466 this.ratio = this._ease._calcEnd ? this._ease.getRatio(1) : 1;
18467 if (!this._reversed ) {
18469 callback = "onComplete";
18471 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.
18472 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.
18475 if (time === 0 || prevRawPrevTime < 0 || prevRawPrevTime === _tinyNum) if (prevRawPrevTime !== time) {
18477 if (prevRawPrevTime > _tinyNum) {
18478 callback = "onReverseComplete";
18481 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.
18484 } else if (time < 0.0000001) { //to work around occasional floating point math artifacts, round super small values to 0.
18485 this._totalTime = this._time = 0;
18486 this.ratio = this._ease._calcEnd ? this._ease.getRatio(0) : 0;
18487 if (prevTime !== 0 || (duration === 0 && prevRawPrevTime > 0 && prevRawPrevTime !== _tinyNum)) {
18488 callback = "onReverseComplete";
18489 isComplete = this._reversed;
18492 this._active = false;
18493 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.
18494 if (prevRawPrevTime >= 0) {
18497 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.
18499 } 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.
18503 this._totalTime = this._time = time;
18505 if (this._easeType) {
18506 var r = time / duration, type = this._easeType, pow = this._easePower;
18507 if (type === 1 || (type === 3 && r >= 0.5)) {
18515 } else if (pow === 2) {
18517 } else if (pow === 3) {
18519 } else if (pow === 4) {
18520 r *= r * r * r * r;
18524 this.ratio = 1 - r;
18525 } else if (type === 2) {
18527 } else if (time / duration < 0.5) {
18528 this.ratio = r / 2;
18530 this.ratio = 1 - (r / 2);
18534 this.ratio = this._ease.getRatio(time / duration);
18538 if (this._time === prevTime && !force) {
18540 } else if (!this._initted) {
18542 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.
18544 } else if (!force && this._firstPT && ((this.vars.lazy !== false && this._duration) || (this.vars.lazy && !this._duration))) {
18545 this._time = this._totalTime = prevTime;
18546 this._rawPrevTime = prevRawPrevTime;
18547 _lazyTweens.push(this);
18551 //_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.
18552 if (this._time && !isComplete) {
18553 this.ratio = this._ease.getRatio(this._time / duration);
18554 } else if (isComplete && this._ease._calcEnd) {
18555 this.ratio = this._ease.getRatio((this._time === 0) ? 0 : 1);
18558 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.
18559 this._lazy = false;
18561 if (!this._active) if (!this._paused && this._time !== prevTime && time >= 0) {
18562 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.
18564 if (prevTime === 0) {
18565 if (this._startAt) {
18567 this._startAt.render(time, suppressEvents, force);
18568 } else if (!callback) {
18569 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.
18572 if (this.vars.onStart) if (this._time !== 0 || duration === 0) if (!suppressEvents) {
18573 this.vars.onStart.apply(this.vars.onStartScope || this, this.vars.onStartParams || _blankArray);
18577 pt = this._firstPT;
18580 pt.t[pt.p](pt.c * this.ratio + pt.s);
18582 pt.t[pt.p] = pt.c * this.ratio + pt.s;
18587 if (this._onUpdate) {
18588 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.
18589 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.
18591 if (!suppressEvents) if (this._time !== prevTime || isComplete) {
18592 this._onUpdate.apply(this.vars.onUpdateScope || this, this.vars.onUpdateParams || _blankArray);
18596 if (callback) if (!this._gc) { //check _gc because there's a chance that kill() could be called in an onUpdate
18597 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.
18598 this._startAt.render(time, suppressEvents, force);
18601 if (this._timeline.autoRemoveChildren) {
18602 this._enabled(false, false);
18604 this._active = false;
18606 if (!suppressEvents && this.vars[callback]) {
18607 this.vars[callback].apply(this.vars[callback + "Scope"] || this, this.vars[callback + "Params"] || _blankArray);
18609 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.
18610 this._rawPrevTime = 0;
18616 p._kill = function(vars, target) {
18617 if (vars === "all") {
18620 if (vars == null) if (target == null || target === this.target) {
18621 this._lazy = false;
18622 return this._enabled(false, false);
18624 target = (typeof(target) !== "string") ? (target || this._targets || this.target) : TweenLite.selector(target) || target;
18625 var i, overwrittenProps, p, pt, propLookup, changed, killProps, record;
18626 if ((_isArray(target) || _isSelector(target)) && typeof(target[0]) !== "number") {
18629 if (this._kill(vars, target[i])) {
18634 if (this._targets) {
18635 i = this._targets.length;
18637 if (target === this._targets[i]) {
18638 propLookup = this._propLookup[i] || {};
18639 this._overwrittenProps = this._overwrittenProps || [];
18640 overwrittenProps = this._overwrittenProps[i] = vars ? this._overwrittenProps[i] || {} : "all";
18644 } else if (target !== this.target) {
18647 propLookup = this._propLookup;
18648 overwrittenProps = this._overwrittenProps = vars ? this._overwrittenProps || {} : "all";
18652 killProps = vars || propLookup;
18653 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)
18654 for (p in killProps) {
18655 if ((pt = propLookup[p])) {
18656 if (pt.pg && pt.t._kill(killProps)) {
18657 changed = true; //some plugins need to be notified so they can perform cleanup tasks first
18659 if (!pt.pg || pt.t._overwriteProps.length === 0) {
18661 pt._prev._next = pt._next;
18662 } else if (pt === this._firstPT) {
18663 this._firstPT = pt._next;
18666 pt._next._prev = pt._prev;
18668 pt._next = pt._prev = null;
18670 delete propLookup[p];
18673 overwrittenProps[p] = 1;
18676 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.
18677 this._enabled(false, false);
18684 p.invalidate = function() {
18685 if (this._notifyPluginsOfEnabled) {
18686 TweenLite._onPluginEvent("_onDisable", this);
18688 this._firstPT = null;
18689 this._overwrittenProps = null;
18690 this._onUpdate = null;
18691 this._startAt = null;
18692 this._initted = this._active = this._notifyPluginsOfEnabled = this._lazy = false;
18693 this._propLookup = (this._targets) ? {} : [];
18697 p._enabled = function(enabled, ignoreTimeline) {
18698 if (!_tickerActive) {
18701 if (enabled && this._gc) {
18702 var targets = this._targets,
18705 i = targets.length;
18707 this._siblings[i] = _register(targets[i], this, true);
18710 this._siblings = _register(this.target, this, true);
18713 Animation.prototype._enabled.call(this, enabled, ignoreTimeline);
18714 if (this._notifyPluginsOfEnabled) if (this._firstPT) {
18715 return TweenLite._onPluginEvent((enabled ? "_onEnable" : "_onDisable"), this);
18721 //----TweenLite static methods -----------------------------------------------------
18723 TweenLite.to = function(target, duration, vars) {
18724 return new TweenLite(target, duration, vars);
18727 TweenLite.from = function(target, duration, vars) {
18728 vars.runBackwards = true;
18729 vars.immediateRender = (vars.immediateRender != false);
18730 return new TweenLite(target, duration, vars);
18733 TweenLite.fromTo = function(target, duration, fromVars, toVars) {
18734 toVars.startAt = fromVars;
18735 toVars.immediateRender = (toVars.immediateRender != false && fromVars.immediateRender != false);
18736 return new TweenLite(target, duration, toVars);
18739 TweenLite.delayedCall = function(delay, callback, params, scope, useFrames) {
18740 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});
18743 TweenLite.set = function(target, vars) {
18744 return new TweenLite(target, 0, vars);
18747 TweenLite.getTweensOf = function(target, onlyActive) {
18748 if (target == null) { return []; }
18749 target = (typeof(target) !== "string") ? target : TweenLite.selector(target) || target;
18751 if ((_isArray(target) || _isSelector(target)) && typeof(target[0]) !== "number") {
18755 a = a.concat(TweenLite.getTweensOf(target[i], onlyActive));
18758 //now get rid of any duplicates (tweens of arrays of objects could cause duplicates)
18769 a = _register(target).concat();
18772 if (a[i]._gc || (onlyActive && !a[i].isActive())) {
18780 TweenLite.killTweensOf = TweenLite.killDelayedCallsTo = function(target, onlyActive, vars) {
18781 if (typeof(onlyActive) === "object") {
18782 vars = onlyActive; //for backwards compatibility (before "onlyActive" parameter was inserted)
18783 onlyActive = false;
18785 var a = TweenLite.getTweensOf(target, onlyActive),
18788 a[i]._kill(vars, target);
18795 * ----------------------------------------------------------------
18796 * 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)
18797 * ----------------------------------------------------------------
18799 var TweenPlugin = _class("plugins.TweenPlugin", function(props, priority) {
18800 this._overwriteProps = (props || "").split(",");
18801 this._propName = this._overwriteProps[0];
18802 this._priority = priority || 0;
18803 this._super = TweenPlugin.prototype;
18806 p = TweenPlugin.prototype;
18807 TweenPlugin.version = "1.10.1";
18808 TweenPlugin.API = 2;
18811 p._addTween = function(target, prop, start, end, overwriteProp, round) {
18813 if (end != null && (c = (typeof(end) === "number" || end.charAt(1) !== "=") ? Number(end) - start : parseInt(end.charAt(0) + "1", 10) * Number(end.substr(2)))) {
18814 this._firstPT = pt = {_next:this._firstPT, t:target, p:prop, s:start, c:c, f:(typeof(target[prop]) === "function"), n:overwriteProp || prop, r:round};
18816 pt._next._prev = pt;
18822 p.setRatio = function(v) {
18823 var pt = this._firstPT,
18827 val = pt.c * v + pt.s;
18829 val = Math.round(val);
18830 } else if (val < min) if (val > -min) { //prevents issues with converting very small numbers to strings in the browser
18842 p._kill = function(lookup) {
18843 var a = this._overwriteProps,
18844 pt = this._firstPT,
18846 if (lookup[this._propName] != null) {
18847 this._overwriteProps = [];
18851 if (lookup[a[i]] != null) {
18857 if (lookup[pt.n] != null) {
18859 pt._next._prev = pt._prev;
18862 pt._prev._next = pt._next;
18864 } else if (this._firstPT === pt) {
18865 this._firstPT = pt._next;
18873 p._roundProps = function(lookup, value) {
18874 var pt = this._firstPT;
18876 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.
18883 TweenLite._onPluginEvent = function(type, tween) {
18884 var pt = tween._firstPT,
18885 changed, pt2, first, last, next;
18886 if (type === "_onInitAllProps") {
18887 //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.
18891 while (pt2 && pt2.pr > pt.pr) {
18894 if ((pt._prev = pt2 ? pt2._prev : last)) {
18895 pt._prev._next = pt;
18899 if ((pt._next = pt2)) {
18906 pt = tween._firstPT = first;
18909 if (pt.pg) if (typeof(pt.t[type]) === "function") if (pt.t[type]()) {
18917 TweenPlugin.activate = function(plugins) {
18918 var i = plugins.length;
18920 if (plugins[i].API === TweenPlugin.API) {
18921 _plugins[(new plugins[i]())._propName] = plugins[i];
18927 //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.
18928 _gsDefine.plugin = function(config) {
18929 if (!config || !config.propName || !config.init || !config.API) { throw "illegal plugin definition."; }
18930 var propName = config.propName,
18931 priority = config.priority || 0,
18932 overwriteProps = config.overwriteProps,
18933 map = {init:"_onInitTween", set:"setRatio", kill:"_kill", round:"_roundProps", initAll:"_onInitAllProps"},
18934 Plugin = _class("plugins." + propName.charAt(0).toUpperCase() + propName.substr(1) + "Plugin",
18936 TweenPlugin.call(this, propName, priority);
18937 this._overwriteProps = overwriteProps || [];
18938 }, (config.global === true)),
18939 p = Plugin.prototype = new TweenPlugin(propName),
18941 p.constructor = Plugin;
18942 Plugin.API = config.API;
18943 for (prop in map) {
18944 if (typeof(config[prop]) === "function") {
18945 p[map[prop]] = config[prop];
18948 Plugin.version = config.version;
18949 TweenPlugin.activate([Plugin]);
18954 //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.
18955 a = window._gsQueue;
18957 for (i = 0; i < a.length; i++) {
18960 for (p in _defLookup) {
18961 if (!_defLookup[p].func) {
18962 //window.console.log("GSAP encountered missing dependency: com.greensock." + p);
18967 _tickerActive = false; //ensures that the first official animation forces a ticker.tick() to update the time when it is instantiated
18971 angular.module('b2b.att.collapse', ['b2b.att.transition'])
18973 // The collapsible directive indicates a block of html that will expand and collapse
18974 .directive('b2bCollapse', ['$transition', function($transition) {
18975 // CSS transitions don't work with height: auto, so we have to manually change the height to a
18976 // specific value and then once the animation completes, we can reset the height to auto.
18977 // Unfortunately if you do this while the CSS transitions are specified (i.e. in the CSS class
18978 // "collapse") then you trigger a change to height 0 in between.
18979 // The fix is to remove the "collapse" CSS class while changing the height back to auto - phew!
18984 marginBottom: null,
18986 paddingBottom: null,
18998 var fixUpHeight = function(scope, element, height) {
18999 // We remove the collapse CSS class to prevent a transition when we change to height: auto
19000 element.removeClass('b2bCollapse');
19001 element.css({height: height});
19002 //adjusting for any margin or padding
19003 if (height === 0) {
19004 element.css(props.closed);
19006 element.css(props.open);
19008 // It appears that reading offsetWidth makes the browser realise that we have changed the
19009 // height already :-/
19010 var x = element[0].offsetWidth;
19011 element.addClass('b2bCollapse');
19015 link: function(scope, element, attrs) {
19017 var initialAnimSkip = true;
19018 scope.$watch(function() {
19019 return element[0].scrollHeight;
19020 }, function(value) {
19021 //The listener is called when scrollHeight changes
19022 //It actually does on 2 scenarios:
19023 // 1. Parent is set to display none
19024 // 2. angular bindings inside are resolved
19025 //When we have a change of scrollHeight we are setting again the correct height if the group is opened
19026 if (element[0].scrollHeight !== 0) {
19027 if (!isCollapsed) {
19028 if (initialAnimSkip) {
19029 fixUpHeight(scope, element, element[0].scrollHeight + 'px');
19031 fixUpHeight(scope, element, 'auto');
19032 element.css({overflow: 'visible'});
19038 scope.$watch(attrs.b2bCollapse, function(value) {
19047 var currentTransition;
19048 var doTransition = function(change) {
19049 if (currentTransition) {
19050 currentTransition.cancel();
19052 currentTransition = $transition(element, change);
19053 currentTransition.then(
19055 currentTransition = undefined;
19058 currentTransition = undefined;
19061 return currentTransition;
19064 var expand = function() {
19065 scope.postTransition = true;
19066 if (initialAnimSkip) {
19067 initialAnimSkip = false;
19068 if (!isCollapsed) {
19069 fixUpHeight(scope, element, 'auto');
19072 //doTransition({ height : element[0].scrollHeight + 'px' })
19073 doTransition(angular.extend({height: element[0].scrollHeight + 'px'}, props.open))
19075 // This check ensures that we don't accidentally update the height if the user has closed
19076 // the group while the animation was still running
19077 if (!isCollapsed) {
19078 fixUpHeight(scope, element, 'auto');
19082 isCollapsed = false;
19085 var collapse = function() {
19086 isCollapsed = true;
19087 if (initialAnimSkip) {
19088 initialAnimSkip = false;
19089 fixUpHeight(scope, element, 0);
19091 fixUpHeight(scope, element, element[0].scrollHeight + 'px');
19092 doTransition(angular.extend({height: 0}, props.closed)).then(function() {
19093 scope.postTransition = false;
19095 element.css({overflow: 'hidden'});
19101 angular.module('b2b.att.position', [])
19103 .factory('$position', ['$document', '$window', function ($document, $window) {
19104 function getStyle(el, cssprop) {
19105 if (el.currentStyle) { //IE
19106 return el.currentStyle[cssprop];
19107 } else if ($window.getComputedStyle) {
19108 return $window.getComputedStyle(el)[cssprop];
19110 // finally try and get inline style
19111 return el.style[cssprop];
19115 * Checks if a given element is statically positioned
19116 * @param element - raw DOM element
19118 function isStaticPositioned(element) {
19119 return (getStyle(element, "position") || 'static') === 'static';
19123 * returns the closest, non-statically positioned parentOffset of a given element
19126 var parentOffsetEl = function (element) {
19127 var docDomEl = $document[0];
19128 var offsetParent = element.offsetParent || docDomEl;
19129 while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent)) {
19130 offsetParent = offsetParent.offsetParent;
19132 return offsetParent || docDomEl;
19137 * Provides read-only equivalent of jQuery's position function:
19138 * http://api.jquery.com/position/
19140 position: function (element) {
19141 var elBCR = this.offset(element);
19142 var offsetParentBCR = {
19146 var offsetParentEl = parentOffsetEl(element[0]);
19147 if (offsetParentEl !== $document[0]) {
19148 offsetParentBCR = this.offset(angular.element(offsetParentEl));
19149 offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
19150 offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
19154 width: element.prop('offsetWidth'),
19155 height: element.prop('offsetHeight'),
19156 top: elBCR.top - offsetParentBCR.top,
19157 left: elBCR.left - offsetParentBCR.left
19162 * Provides read-only equivalent of jQuery's offset function:
19163 * http://api.jquery.com/offset/
19165 offset: function (element) {
19166 var boundingClientRect = element[0].getBoundingClientRect();
19168 width: element.prop('offsetWidth'),
19169 height: element.prop('offsetHeight'),
19170 top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
19171 left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)
19176 * Provides functionality to check whether an element is in view port.
19178 isElementInViewport: function (element) {
19180 var rect = element[0].getBoundingClientRect();
19184 rect.bottom <= ($window.innerHeight || $document[0].documentElement.clientHeight) &&
19185 rect.right <= ($window.innerWidth || $document[0].documentElement.clientWidth)
19194 .factory('$isElement', [function () {
19195 var isElement = function (currentElem, targetElem, alternateElem) {
19196 if (currentElem[0] === targetElem[0]) {
19198 } else if (currentElem[0] === alternateElem[0]) {
19201 return isElement((currentElem.parent()[0] && currentElem.parent()) || targetElem, targetElem, alternateElem);
19208 .directive('attPosition', ['$position', function ($position) {
19211 link: function (scope, elem, attr) {
19212 scope.$watchCollection(function () {
19213 return $position.position(elem);
19214 }, function (value) {
19215 scope[attr.attPosition] = value;
19221 angular.module('b2b.att.transition', [])
19223 .factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
19225 var $transition = function(element, trigger, options) {
19226 options = options || {};
19227 var deferred = $q.defer();
19228 var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
19230 var transitionEndHandler = function() {
19231 $rootScope.$apply(function() {
19232 element.unbind(endEventName, transitionEndHandler);
19233 deferred.resolve(element);
19237 if (endEventName) {
19238 element.bind(endEventName, transitionEndHandler);
19241 // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
19242 $timeout(function() {
19243 if ( angular.isString(trigger) ) {
19244 element.addClass(trigger);
19245 } else if ( angular.isFunction(trigger) ) {
19247 } else if ( angular.isObject(trigger) ) {
19248 element.css(trigger);
19250 //If browser does not support transitions, instantly resolve
19251 if ( !endEventName ) {
19252 deferred.resolve(element);
19256 // Add our custom cancel function to the promise that is returned
19257 // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
19258 // i.e. it will therefore never raise a transitionEnd event for that transition
19259 deferred.promise.cancel = function() {
19260 if ( endEventName ) {
19261 element.unbind(endEventName, transitionEndHandler);
19263 deferred.reject('Transition cancelled');
19266 return deferred.promise;
19269 // Work out the name of the transitionEnd event
19270 var transElement = document.createElement('trans');
19271 var transitionEndEventNames = {
19272 'WebkitTransition': 'webkitTransitionEnd',
19273 'MozTransition': 'transitionend',
19274 'OTransition': 'oTransitionEnd',
19275 'transition': 'transitionend'
19277 var animationEndEventNames = {
19278 'WebkitTransition': 'webkitAnimationEnd',
19279 'MozTransition': 'animationend',
19280 'OTransition': 'oAnimationEnd',
19281 'transition': 'animationend'
19283 function findEndEventName(endEventNames) {
19284 for (var name in endEventNames){
19285 if (transElement.style[name] !== undefined) {
19286 return endEventNames[name];
19290 $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
19291 $transition.animationEndEventName = findEndEventName(animationEndEventNames);
19292 return $transition;
19295 .factory('$scrollTo', ['$window', function($window) {
19296 var $scrollTo = function(offsetLeft, offsetTop, duration) {
19297 TweenMax.to($window, duration || 1, {scrollTo: {y: offsetTop, x: offsetLeft}, ease: Power4.easeOut});
19301 .factory('animation', function(){
19304 .factory('$progressBar', function(){
19306 //Provides a function to pass in code for closure purposes
19307 var loadingAnimationCreator = function(onUpdateCallback){
19309 //Use closure to setup some resuable code
19310 var loadingAnimation = function(callback, duration){
19311 TweenMax.to({}, duration, {
19312 onUpdateParams: ["{self}"],
19313 onUpdate: onUpdateCallback,
19314 onComplete: callback
19317 //Returns a function that takes a callback function and a duration for the animation
19318 return (function(){
19319 return loadingAnimation;
19323 return loadingAnimationCreator;
19325 .factory('$height', function(){
19326 var heightAnimation = function(element,duration,height,alpha){
19327 TweenMax.to(element,
19329 {height:height, autoAlpha:alpha},
19332 return heightAnimation;
19334 angular.module('b2b.att.utilities', ['ngSanitize'])
19335 .constant('b2bUtilitiesConfig', {
19342 enableSearch: false,
19344 circularTraversal: false
19346 .constant('b2bWhenScrollEndsConstants', {
19351 // All breakpoints ranges from >= min and < max
19352 .constant('b2bAwdBreakpoints', {
19368 .filter('groupBy', function ($timeout) {
19369 //Custom GroupBy Filter for treeNav, returns key string and value.childarray as set of grouped elements
19370 return function (data, key) {
19371 if (!key) return data;
19372 var outputPropertyName = '__groupBy__' + key;
19373 if (!data[outputPropertyName]) {
19375 for (var i = 0; i < data.length; i++) {
19376 if (!result[data[i][key]])
19377 result[data[i][key]] = {};
19378 if (!result[data[i][key]].childArray) {
19379 result[data[i][key]].childArray = [];
19381 result[data[i][key]].childArray.push(data[i]);
19382 if (data[i].activeGrp && data[i].activeGrp == true) {
19383 console.log('make ' + data[i].grpChild + ' active');
19384 result[data[i][key]].showGroup = true;
19387 Object.defineProperty(result, 'length', {enumerable: false,value: Object.keys(result).length});
19388 Object.defineProperty(data, outputPropertyName, {enumerable: false,configurable: true,writable:false,value:result});
19389 $timeout(function(){delete data[outputPropertyName];},0,false);
19391 return data[outputPropertyName];
19394 .filter('searchObjectPropertiesFilter', [function() {
19395 return function(items, searchText, attrs) {
19400 searchText = searchText.toLowerCase();
19401 angular.forEach(items, function(item) {
19402 angular.forEach(attrs, function(attr) {
19403 if (item.hasOwnProperty(attr) && item[attr].toLowerCase().includes(searchText)) {
19404 filtered.push(item);
19412 .filter('unsafe',[ '$sce', function ($sce) {
19413 return function(val){
19414 return $sce.trustAsHtml(val);
19417 .filter('b2bHighlight', function () {
19418 function escapeRegexp(queryToEscape) {
19419 return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
19421 return function (matchItem, query, className) {
19422 return query && matchItem ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<span class=\"' + className + '\">$&</span>') : matchItem;
19426 Copyright © 2013 Matt Diamond
19427 https://github.com/cwilso/AudioRecorder/blob/master/js/recorderjs/recorder.js
19429 .factory('b2bRecorder', function() {
19431 var Recorder = function(source, cfg) {
19432 var WORKER_PATH = 'recorderWorker.js';
19433 var config = cfg || {};
19434 var bufferLen = config.bufferLen || 4096;
19435 this.context = source.context;
19436 if(!this.context.createScriptProcessor) {
19437 this.node = this.context.createJavacriptProcessor(bufferLen, 2, 2);
19439 this.node = this.context.createScriptProcessor(bufferLen, 2, 2);
19441 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()}};';
19442 var blob = new Blob([workerCode]);
19444 var worker = new Worker(window.URL.createObjectURL(blob)); //TODO: Use a blob instead
19445 worker.postMessage({
19448 sampleRate: this.context.sampleRate
19451 var recording = false,
19454 this.node.onaudioprocess = function(e) {
19455 if (!recording) return;
19456 worker.postMessage({
19459 e.inputBuffer.getChannelData(0),
19460 e.inputBuffer.getChannelData(1)
19465 this.configure = function(cfg) {
19466 for (var prop in cfg) {//TODO: look into using angular.extend() here
19467 if (cfg.hasOwnProperty(prop)) {
19468 config[prop] = cfg[prop];
19473 this.record = function() {
19477 this.stop = function() {
19481 this.clear = function() {
19482 worker.postMessage({ command: 'clear' });
19483 window.URL.revokeObjectURL(blob);
19486 this.getBuffers = function(cb) {
19487 currCallback = cb || config.callback;
19488 worker.postMessage({ command: 'getBuffers' });
19491 this.exportWAV = function(cb, type) {
19492 currCallback = cb || config.callback;
19493 type = type || config.type || 'audio/wav';
19494 if (!currCallback) throw new Error('[b2bRecorder]: Callback not set!');
19495 worker.postMessage({
19496 command: 'exportWAV',
19501 this.exportMonoWAV = function(cb, type) {
19502 currCallback = cb || config.callback;
19503 type = type || config.type || 'audio/wav';
19504 if (!currCallback) throw new Error('[b2bRecorder]: Callback not set!');
19505 worker.postMessage({
19506 command: 'exportMonoWAV',
19511 worker.onmessage = function(e) {
19513 currCallback(blob);
19516 source.connect(this.node);
19517 this.node.connect(this.context.destination); // if the script node is not connected to an output the "onaudioprocess" event is not triggerd in Chrome
19524 .factory('b2bViewport', function() {
19525 /* Source: https://gist.github.com/bjankord/2399828 */
19526 var _viewportWidth = function() {
19528 var webkit = (!(window.webkitConvertPointFromNodeToPage == null));
19532 var vpwtest = document.createElement( "div" );
19533 // Sets test div to width 100%, !important overrides any other misc. box model styles that may be set in the CSS
19534 vpwtest.style.cssText = "width:100% !important; margin:0 !important; padding:0 !important; border:none !important;";
19535 document.documentElement.insertBefore( vpwtest, document.documentElement.firstChild );
19536 vpw = vpwtest.offsetWidth;
19537 document.documentElement.removeChild( vpwtest );
19540 else if ( window.innerWidth === undefined ) {
19541 vpw = document.documentElement.clientWidth;
19545 vpw = window.innerWidth;
19551 viewportWidth: _viewportWidth
19554 .directive('b2bWhenScrollEnds', function(b2bWhenScrollEndsConstants, $log) {
19557 link: function (scope, element, attrs) {
19559 * Exposed Attributes:
19560 * threshold - integer - number of pixels before scrollbar hits end that callback is called
19561 * width - integer - override for element's width (px)
19562 * height - integer - override for element's height (px)
19563 * axis - string - x/y for scroll bar axis
19565 var threshold = parseInt(attrs.threshold, 10) || b2bWhenScrollEndsConstants.threshold;
19567 if (!attrs.axis || attrs.axis === '') {
19568 $log.warn('axis attribute must be defined for b2bWhenScrollEnds.');
19572 if (attrs.axis === 'x') {
19573 visibleWidth = parseInt(attrs.width, 10) || b2bWhenScrollEndsConstants.width;
19574 if (element.css('width')) {
19575 visibleWidth = element.css('width').split('px')[0];
19578 element[0].addEventListener('scroll', function() {
19579 var scrollableWidth = element.prop('scrollWidth');
19580 if (scrollableWidth === undefined) {
19581 scrollableWidth = 1;
19583 var hiddenContentWidth = scrollableWidth - visibleWidth;
19585 if (hiddenContentWidth - element[0].scrollLeft <= threshold) {
19586 /* Scroll almost at bottom, load more rows */
19587 scope.$apply(attrs.b2bWhenScrollEnds);
19590 } else if (attrs.axis === 'y') {
19591 visibleHeight = parseInt(attrs.height, 10) || b2bWhenScrollEndsConstants.height;
19592 if (element.css('width')) {
19593 visibleHeight = element.css('height').split('px')[0];
19596 element[0].addEventListener('scroll', function() {
19597 var scrollableHeight = element.prop('scrollHeight');
19598 if (scrollableHeight === undefined) {
19599 scrollableHeight = 1;
19601 var hiddenContentHeight = scrollableHeight - visibleHeight;
19603 if (hiddenContentHeight - element[0].scrollTop <= threshold) {
19604 /* Scroll almost at bottom, load more rows */
19605 scope.$apply(attrs.b2bWhenScrollEnds);
19613 .factory('$windowBind', ['$window', '$timeout', function($window, $timeout) {
19614 var win = angular.element($window);
19615 var _scroll = function (flag, callbackFunc, scope) {
19616 scope.$watch(flag, function (val) {
19617 $timeout(function () {
19619 win.bind('scroll', callbackFunc);
19621 win.unbind('scroll', callbackFunc);
19627 var throttle = function(type, name, obj) {
19628 obj = obj || window;
19629 var running = false;
19630 var func = function() {
19631 if (running) { return; }
19633 requestAnimationFrame(function() {
19634 obj.dispatchEvent(new CustomEvent(name));
19638 obj.addEventListener(type, func);
19641 var _resize = function(callbackFunc, scope) {
19642 throttle("resize", "optimizedResize");
19643 window.addEventListener("optimizedResize", function(event) {
19645 //win.bind(event, callbackFunc);
19646 if (!scope.$$phase) {
19652 var _click = function (flag, callbackFunc, scope) {
19653 scope.$watch(flag, function (val) {
19654 $timeout(function () {
19656 win.bind('click', callbackFunc);
19658 win.unbind('click', callbackFunc);
19664 var _event = function (event, flag, callbackFunc, scope, timeoutFlag, timeoutValue) {
19666 if (!(timeoutValue)) {
19669 scope.$watch(flag, function (newVal, oldVal) {
19670 if (newVal !== oldVal) {
19671 $timeout(function () {
19673 win.bind(event, callbackFunc);
19675 win.unbind(event, callbackFunc);
19681 scope.$watch(flag, function (newVal, oldVal) {
19682 if (newVal !== oldVal) {
19684 win.bind(event, callbackFunc);
19686 win.unbind(event, callbackFunc);
19701 .factory('keymap', function () {
19723 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 : "'"
19725 isControl: function (e) {
19728 case this.KEY.COMMAND:
19729 case this.KEY.SHIFT:
19730 case this.KEY.CTRL:
19742 isFunctionKey: function (k) {
19743 k = k.keyCode ? k.keyCode : k;
19744 return k >= 112 && k <= 123;
19746 isVerticalMovement: function (k) {
19747 return ~[this.KEY.UP, this.KEY.DOWN].indexOf(k);
19749 isHorizontalMovement: function (k) {
19750 return ~[this.KEY.LEFT, this.KEY.RIGHT, this.KEY.BACKSPACE, this.KEY.DELETE].indexOf(k);
19752 isAllowedKey: function (k) {
19753 return (~[this.KEY.SPACE, this.KEY.ESC, this.KEY.ENTER].indexOf(k)) || this.isHorizontalMovement(k) || this.isVerticalMovement(k);
19755 isNumericKey: function (e) {
19757 if ((k >= 48 && k <= 57) || (k >= 96 && k <= 105)) {
19763 isAlphaNumericKey: function (e) {
19765 if ((k >= 48 && k <= 57) || (k >= 96 && k <= 105) || (k >= 65 && k <= 90)) {
19774 .factory('$isElement', [function () {
19775 var isElement = function (currentElem, targetElem, alternateElem) {
19776 if (currentElem[0] === targetElem[0]) {
19778 } else if (currentElem[0] === alternateElem[0]) {
19781 return isElement((currentElem.parent()[0] && currentElem.parent()) || targetElem, targetElem, alternateElem);
19788 .factory('events', function () {
19789 var _stopPropagation = function (evt) {
19790 if (evt.stopPropagation) {
19791 evt.stopPropagation();
19793 evt.returnValue = false;
19796 var _preventDefault = function (evt) {
19797 if (evt.preventDefault) {
19798 evt.preventDefault();
19800 evt.returnValue = false;
19804 stopPropagation: _stopPropagation,
19805 preventDefault: _preventDefault
19810 .factory('$documentBind', ['$document', '$timeout', function ($document, $timeout) {
19811 var _click = function (flag, callbackFunc, scope) {
19812 scope.$watch(flag, function (val) {
19813 $timeout(function () {
19815 $document.bind('click', callbackFunc);
19817 $document.unbind('click', callbackFunc);
19823 var _scroll = function (flag, callbackFunc, scope) {
19824 scope.$watch(flag, function (val) {
19825 $timeout(function () {
19827 $document.bind('scroll', callbackFunc);
19829 $document.unbind('scroll', callbackFunc);
19835 var _event = function (event, flag, callbackFunc, scope, timeoutFlag, timeoutValue) {
19837 if (!(timeoutValue)) {
19840 scope.$watch(flag, function (newVal, oldVal) {
19841 if (newVal !== oldVal) {
19842 $timeout(function () {
19844 $document.bind(event, callbackFunc);
19846 $document.unbind(event, callbackFunc);
19852 scope.$watch(flag, function (newVal, oldVal) {
19853 if (newVal !== oldVal) {
19855 $document.bind(event, callbackFunc);
19857 $document.unbind(event, callbackFunc);
19871 .directive('b2bOnlyNums', function (keymap) {
19874 require: 'ngModel',
19875 link: function (scope, elm, attrs, ctrl) {
19876 var maxChars = attrs.b2bOnlyNums ? attrs.b2bOnlyNums : 4;
19877 elm.on('keydown', function (event) {
19878 if ((event.which >= 48 && event.which <= 57) || (event.which >= 96 && event.which <= 105)) {
19879 // check for maximum characters allowed
19880 if (elm.val().length < maxChars){
19883 event.preventDefault();
19886 } else if ([8, 9, 13, 27, 37, 38, 39, 40].indexOf(event.which) > -1) {
19887 // to allow backspace, tab, enter, escape, arrows
19889 } else if (event.altKey || event.ctrlKey) {
19890 // to allow alter, control, and shift keys
19894 event.preventDefault();
19899 var validateString = function (value) {
19900 if (angular.isUndefined(value) || value === null || value === '') {
19901 return ctrl.$modelValue;
19905 ctrl.$parsers.unshift(validateString);
19910 .directive('b2bKeyupClick', [ function () {
19913 link: function (scope, elem, attr) {
19915 attr.$observe('b2bKeyupClick', function (value) {
19917 keyCode = value.split(',');
19920 elem.bind('keydown keyup', function (ev) {
19921 var keyCodeCondition = function () {
19923 if (!(ev.keyCode)) {
19925 ev.keyCode = ev.which;
19926 } else if (ev.charCode) {
19927 ev.keyCode = ev.charCode;
19930 if ((ev.keyCode && keyCode.indexOf(ev.keyCode.toString()) > -1)) {
19935 if (ev.type === 'keydown' && keyCodeCondition()) {
19936 ev.preventDefault();
19938 else if (ev.type === 'keyup' && keyCodeCondition()) {
19946 .factory('b2bDOMHelper', function() {
19948 var _isTabable = function(node) {
19949 var element = angular.element(node);
19950 var tagName = element[0].tagName.toUpperCase();
19952 if (isHidden(element)) {
19955 if (element.attr('tabindex') !== undefined) {
19956 return (parseInt(element.attr('tabindex'), 10) >= 0);
19958 if (tagName === 'A' || tagName === 'AREA' || tagName === 'BUTTON' || tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT') {
19959 if (tagName === 'A' || tagName === 'AREA') {
19960 // anchors/areas without href are not focusable
19961 return (element[0].href !== '');
19963 return !(element[0].disabled || element[0].readOnly);
19968 function isValidChild(child) {
19969 return child.nodeType == 1 && child.nodeName != 'SCRIPT' && child.nodeName != 'STYLE';
19972 function isHidden(obj) {
19973 var elem = angular.element(obj);
19974 var elemStyle = undefined;
19975 if(obj instanceof HTMLElement){
19976 elemStyle = window.getComputedStyle(obj);
19979 elemStyle = window.getComputedStyle(obj[0]);
19981 return elem.hasClass('ng-hide') || elem.css('display') === 'none' || elemStyle.display === 'none' || elemStyle.visibility === 'hidden' || elem.css('visibility') === 'hidden';
19984 function hasValidParent(obj) {
19985 return (isValidChild(obj) && obj.parentElement.nodeName !== 'BODY');
19988 function traverse(obj, fromTop) {
19989 var obj = obj || document.getElementsByTagName('body')[0];
19990 if (isValidChild(obj) && _isTabable(obj)) {
19993 // If object is hidden, skip it's children
19994 if (isValidChild(obj) && isHidden(obj)) {
19997 // If object is hidden, skip it's children
19998 if (angular.element(obj).hasClass('ng-hide')) {
20001 if (obj.hasChildNodes()) {
20004 child = obj.firstChild;
20006 child = obj.lastChild;
20009 var res = traverse(child, fromTop);
20015 child = child.nextSibling;
20017 child = child.previousSibling;
20027 var _previousElement = function(el, isFocusable){
20030 if (el.hasOwnProperty('length')) {
20034 var parent = elem.parentElement;
20035 var previousElem = undefined;
20038 if (hasValidParent(elem)) {
20039 var siblings = angular.element(parent).children();
20040 if (siblings.length > 0) {
20041 // Good practice to splice out the elem from siblings if there, saving some time.
20042 // We allow for a quick check for jumping to parent first before removing.
20043 if (siblings[0] === elem) {
20044 // If we are looking at immidiate parent and elem is first child, we need to go higher
20045 var e = _previousElement(angular.element(elem).parent(), isFocusable);
20046 if (_isTabable(e)) {
20050 // I need to filter myself and any nodes next to me from the siblings
20051 var indexOfElem = Array.prototype.indexOf.call(siblings, elem);
20052 siblings = Array.prototype.filter.call(siblings, function(item, itemIndex) {
20053 if (!angular.equals(elem, item) && itemIndex < indexOfElem) {
20058 // We need to search backwards
20059 for (var i = 0; i <= siblings.length-1; i++) {//for (var i = siblings.length-1; i >= 0; i--) {
20060 var ret = traverse(siblings[i], false);
20061 if (ret !== undefined) {
20066 var e = _previousElement(angular.element(elem).parent(), isFocusable);
20067 if (_isTabable(e)) {
20073 var siblings = angular.element(parent).children();
20074 if (siblings.length > 1) {
20075 // Since indexOf is on Array.prototype and parent.children is a NodeList, we have to use call()
20076 var index = Array.prototype.indexOf.call(siblings, elem);
20077 previousElem = siblings[index-1];
20080 return previousElem;
20083 var _lastTabableElement = function(el) {
20084 /* This will return the first tabable element from the parent el */
20086 if (el.hasOwnProperty('length')) {
20090 return traverse(elem, false);
20093 var _firstTabableElement = function(el) {
20094 /* This will return the first tabable element from the parent el */
20096 if (el.hasOwnProperty('length')) {
20100 return traverse(elem, true);
20103 var _isInDOM = function(obj) {
20104 return document.documentElement.contains(obj);
20108 firstTabableElement: _firstTabableElement,
20109 lastTabableElement: _lastTabableElement,
20110 previousElement: _previousElement,
20112 isTabable: _isTabable,
20117 .factory('trapFocusInElement', ['$document', '$isElement', 'DOMHelper', 'keymap', function ($document, $isElement, DOMHelper, keymap) {
20118 var elementStack = [];
20119 var stackHead = undefined;
20120 var trapFocusInElement = function (flag) {
20121 var bodyElements = $document.find('body').children();
20123 var firstTabableElement = angular.element(DOMHelper.firstTabableElement(stackHead));
20124 var lastTabableElement = angular.element(DOMHelper.lastTabableElement(stackHead));
20126 var trapKeyboardFocusInFirstElement = function (e) {
20128 e.keyCode = e.which;
20131 if (e.shiftKey === true && e.keyCode === keymap.KEY.TAB) {
20132 lastTabableElement[0].focus();
20133 e.preventDefault(e);
20134 e.stopPropagation(e);
20139 var trapKeyboardFocusInLastElement = function (e) {
20141 e.keyCode = e.which;
20144 if (e.shiftKey === false && e.keyCode === keymap.KEY.TAB) {
20145 firstTabableElement[0].focus();
20146 e.preventDefault(e);
20147 e.stopPropagation(e);
20152 for (var i = 0; i < bodyElements.length; i++) {
20153 if (bodyElements[i] !== stackHead[0]) {
20154 bodyElements.eq(i).attr('aria-hidden', true);
20157 firstTabableElement.bind('keydown', trapKeyboardFocusInFirstElement);
20158 lastTabableElement.bind('keydown', trapKeyboardFocusInLastElement);
20160 for (var j = 0; j < bodyElements.length; j++) {
20161 if (bodyElements[j] !== stackHead[0]) {
20162 bodyElements.eq(j).removeAttr('aria-hidden');
20165 firstTabableElement.unbind('keydown', trapKeyboardFocusInFirstElement);
20166 lastTabableElement.unbind('keydown', trapKeyboardFocusInLastElement);
20169 var toggleTrapFocusInElement = function (flag, element) {
20170 if (angular.isDefined(flag) && angular.isDefined(element)) {
20171 if (angular.isUndefined(stackHead)) {
20172 stackHead = element;
20173 trapFocusInElement(flag);
20176 trapFocusInElement(false);
20177 elementStack.push(stackHead);
20178 stackHead = element;
20179 trapFocusInElement(true);
20181 if (stackHead.prop('$$hashKey') === element.prop('$$hashKey')) {
20182 trapFocusInElement(false);
20183 stackHead = elementStack.pop();
20184 if (angular.isDefined(stackHead)) {
20185 trapFocusInElement(true);
20193 return toggleTrapFocusInElement;
20196 .factory('DOMHelper', function () {
20198 var _isTabable = function (node) {
20199 var element = angular.element(node);
20200 var tagName = element[0].tagName.toUpperCase();
20202 if (isHidden(element)) {
20205 if (element.attr('tabindex') !== undefined) {
20206 return (parseInt(element.attr('tabindex'), 10) >= 0);
20208 if (tagName === 'A' || tagName === 'AREA' || tagName === 'BUTTON' || tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT') {
20209 if (tagName === 'A' || tagName === 'AREA') {
20210 // anchors/areas without href are not focusable
20211 return (element[0].href !== '');
20213 return !(element[0].disabled || element[0].readOnly);
20218 function isValidChild(child) {
20219 return child.nodeType == 1 && child.nodeName != 'SCRIPT' && child.nodeName != 'STYLE';
20222 function isHidden(obj) {
20223 var elem = angular.element(obj);
20226 style = window.getComputedStyle(obj);
20229 style = window.getComputedStyle(obj[0]);
20232 // getComputedStyle() for Zepto object returns null
20233 if (style === null){
20234 return elem.hasClass('ng-hide') || elem.css('display') === 'none';
20237 return elem.hasClass('ng-hide') || elem.css('display') === 'none' || style.display === 'none' || style.display === 'hidden';
20240 function traverse(obj, fromTop) {
20241 var obj = obj || document.getElementsByTagName('body')[0];
20243 if (isValidChild(obj) && _isTabable(obj)) {
20247 // If object is hidden, skip it's children
20248 if (isValidChild(obj) && isHidden(obj)) {
20251 // If object is hidden, skip it's children
20252 if (angular.element(obj).hasClass('ng-hide')) {
20256 if (obj.hasChildNodes()) {
20259 child = obj.firstChild;
20261 child = obj.lastChild;
20264 var res = traverse(child, fromTop);
20269 child = child.nextSibling;
20271 child = child.previousSibling;
20280 var _lastTabableElement = function (el) {
20281 /* This will return the last tabable element from the parent el */
20283 if (el.hasOwnProperty('length')) {
20287 return traverse(elem, false);
20290 var _firstTabableElement = function (el) {
20291 /* This will return the first tabable element from the parent el */
20293 if (el.hasOwnProperty('length')) {
20297 return traverse(elem, true);
20301 firstTabableElement: _firstTabableElement,
20302 lastTabableElement: _lastTabableElement,
20303 isTabable: _isTabable
20307 .factory('windowOrientation', ['$window', function ($window) {
20308 var _isPotrait = function () {
20309 if ($window.innerHeight > $window.innerWidth) {
20315 var _isLandscape = function () {
20316 if ($window.innerHeight < $window.innerWidth) {
20324 isPotrait: _isPotrait,
20325 isLandscape: _isLandscape
20329 .directive('b2bNextElement', function() {
20333 link: function (scope, elem, attr, ctrls) {
20335 var keys = attr.b2bNextElement.split(',');
20337 elem.bind('keydown', function (e) {
20338 var nextElement = elem.next();
20339 if(e.keyCode == 39 || e.keyCode == 40){ // if e.keyCode in keys
20340 if(nextElement.length) {
20341 e.preventDefault();
20342 nextElement[0].focus();
20350 .directive('b2bAccessibilityClick', [function () {
20353 link: function (scope, elem, attr, ctrl) {
20355 attr.$observe('b2bAccessibilityClick', function (value) {
20357 keyCode = value.split(',');
20360 elem.bind('keydown keypress', function (ev) {
20361 var keyCodeCondition = function () {
20363 if (!(ev.keyCode)) {
20365 ev.keyCode = ev.which;
20366 } else if (ev.charCode) {
20367 ev.keyCode = ev.charCode;
20370 if ((ev.keyCode && keyCode.indexOf(ev.keyCode.toString()) > -1)) {
20375 if (keyCode.length > 0 && keyCodeCondition()) {
20377 ev.preventDefault();
20384 .directive('b2bReset', ['$compile', function ($compile) {
20387 require: ['?ngModel', 'b2bReset'],
20388 controller: ['$scope', function ($scope) {
20389 var resetButton = angular.element('<button type="button" class="reset-field" tabindex="-1" aria-label="Click to reset" aria-hidden="true" role="button"></button>');
20391 this.getResetButton = function () {
20392 return resetButton;
20395 link: function (scope, element, attrs, ctrls) {
20397 var ngModelCtrl = ctrls[0];
20398 var ctrl = ctrls[1];
20400 var resetButton = ctrl.getResetButton();
20403 resetButton.on('click', function () {
20404 element[0].value = '';
20407 if (attrs.b2bReset) {
20408 ngModelCtrl.$setViewValue(attrs.b2bReset);
20410 ngModelCtrl.$setViewValue('');
20412 element[0].value = ngModelCtrl.$viewValue;
20413 ngModelCtrl.$render();
20416 element[0].focus();
20417 element[0].select();
20420 var addResetButton = function () {
20421 element.after(resetButton);
20422 element.unbind('focus input', addResetButton);
20425 element.bind('focus input', addResetButton);
20430 .directive('b2bPrevElement', ['b2bDOMHelper', 'keymap', function (b2bDOMHelper, keymap) {
20434 link: function (scope, elem, attr) {
20436 elem.bind('keydown', function (e) {
20437 if(e.keyCode == 37 || e.keyCode == 38){
20438 var prev = b2bDOMHelper.previousElement(elem, false);
20439 if(prev !== undefined) {
20440 e.preventDefault();
20449 * @param {integer} delay - Timeout before first and last focusable elements are found
20450 * @param {boolean} trigger - A variable on scope that will trigger refinding first/last focusable elements
20452 .directive('b2bTrapFocusInsideElement', ['$timeout', 'b2bDOMHelper', 'keymap', 'events', function ($timeout, b2bDOMHelper, keymap, events) {
20456 link: function (scope, elem, attr) {
20458 var delay = parseInt(attr.delay, 10) || 10;
20460 /* Before opening modal, find the focused element */
20461 var firstTabableElement = undefined,
20462 lastTabableElement = undefined;
20465 $timeout(function () {
20466 firstTabableElement = b2bDOMHelper.firstTabableElement(elem);
20467 lastTabableElement = b2bDOMHelper.lastTabableElement(elem);
20468 angular.element(firstTabableElement).bind('keydown', firstTabableElementKeyhandler);
20469 angular.element(lastTabableElement).bind('keydown', lastTabableElementKeyhandler);
20473 if (attr.trigger !== undefined) {
20474 scope.$watch('trigger', function() {
20475 if (scope.trigger) {
20481 var firstTabableElementKeyhandler = function(e) {
20483 e.keyCode = e.which;
20485 if (e.keyCode === keymap.KEY.TAB && e.shiftKey) {
20486 if (attr.trapFocusInsideElement === 'true') {
20487 var temp = b2bDOMHelper.lastTabableElement(elem);
20488 if (lastTabableElement !== temp) {
20489 // Unbind keydown handler on lastTabableElement
20490 angular.element(lastTabableElement).unbind('keydown', lastTabableElementKeyhandler);
20491 lastTabableElement = temp;
20492 angular.element(lastTabableElement).bind('keydown', lastTabableElementKeyhandler);
20495 lastTabableElement.focus();
20496 events.preventDefault(e);
20497 events.stopPropagation(e);
20501 var lastTabableElementKeyhandler = function(e) {
20503 e.keyCode = e.which;
20505 if (e.keyCode === keymap.KEY.TAB && !e.shiftKey) {
20506 if (attr.trapFocusInsideElement === 'true') {
20507 var temp = b2bDOMHelper.firstTabableElement(elem);
20508 if (firstTabableElement !== temp) {
20509 // Unbind keydown handler on firstTabableElement
20510 angular.element(firstTabableElement).unbind('keydown', firstTabableElementKeyhandler);
20511 firstTabableElement = temp;
20512 angular.element(firstTabableElement).bind('keydown', firstTabableElementKeyhandler);
20515 firstTabableElement.focus();
20516 events.preventDefault(e);
20517 events.stopPropagation(e);
20526 .factory('trapFocusInElement', ['$document', '$isElement', 'DOMHelper', 'keymap', '$interval', function ($document, $isElement, DOMHelper, keymap, $interval) {
20527 var elementStack = [];
20528 var stackHead = undefined;
20529 var stopInterval = undefined;
20530 var intervalRequired = false;
20531 var interval = 1000;
20532 var firstTabableElement, lastTabableElement;
20534 var trapKeyboardFocusInFirstElement = function (e) {
20536 e.keyCode = e.which;
20539 if (e.shiftKey === true && e.keyCode === keymap.KEY.TAB) {
20540 lastTabableElement[0].focus();
20541 e.preventDefault(e);
20542 e.stopPropagation(e);
20547 var trapKeyboardFocusInLastElement = function (e) {
20549 e.keyCode = e.which;
20552 if (e.shiftKey === false && e.keyCode === keymap.KEY.TAB) {
20553 firstTabableElement[0].focus();
20554 e.preventDefault(e);
20555 e.stopPropagation(e);
20559 var trapFocusInElement = function (flag, firstTabableElementParam, lastTabableElementParam) {
20560 var bodyElements = $document.find('body').children();
20562 firstTabableElement = firstTabableElementParam ? firstTabableElementParam : angular.element(DOMHelper.firstTabableElement(stackHead));
20563 lastTabableElement = lastTabableElementParam ? lastTabableElementParam : angular.element(DOMHelper.lastTabableElement(stackHead));
20566 for (var i = 0; i < bodyElements.length; i++) {
20567 if (bodyElements[i] !== stackHead[0]) {
20568 bodyElements.eq(i).attr('aria-hidden', true);
20571 firstTabableElement.bind('keydown', trapKeyboardFocusInFirstElement);
20572 lastTabableElement.bind('keydown', trapKeyboardFocusInLastElement);
20574 for (var j = 0; j < bodyElements.length; j++) {
20575 if (bodyElements[j] !== stackHead[0]) {
20576 bodyElements.eq(j).removeAttr('aria-hidden');
20579 firstTabableElement.unbind('keydown', trapKeyboardFocusInFirstElement);
20580 lastTabableElement.unbind('keydown', trapKeyboardFocusInLastElement);
20583 if (intervalRequired && flag) {
20584 stopInterval = $interval(function () {
20585 var firstTabableElementTemp = angular.element(DOMHelper.firstTabableElement(stackHead));
20586 var lastTabableElementTemp = angular.element(DOMHelper.lastTabableElement(stackHead));
20587 if (firstTabableElementTemp[0] !== firstTabableElement[0] || lastTabableElementTemp[0] !== lastTabableElement[0]) {
20588 $interval.cancel(stopInterval);
20589 stopInterval = undefined;
20590 trapFocusInElement(false, firstTabableElement, lastTabableElement);
20591 trapFocusInElement(true, firstTabableElementTemp, lastTabableElementTemp);
20595 if (stopInterval) {
20596 $interval.cancel(stopInterval);
20597 stopInterval = undefined;
20601 var toggleTrapFocusInElement = function (flag, element, intervalRequiredParam, intervalParam) {
20602 intervalRequired = intervalRequiredParam ? intervalRequiredParam : intervalRequired;
20603 interval = intervalParam ? intervalParam : interval;
20604 if (angular.isDefined(flag) && angular.isDefined(element)) {
20605 if (flag && angular.isUndefined(stackHead)) {
20606 stackHead = element;
20607 trapFocusInElement(flag);
20610 trapFocusInElement(false);
20611 elementStack.push(stackHead);
20612 stackHead = element;
20613 trapFocusInElement(true);
20615 if (angular.isDefined(stackHead) && (stackHead[0] === element[0])) {
20616 trapFocusInElement(false);
20617 stackHead = elementStack.pop();
20618 if (angular.isDefined(stackHead)) {
20619 trapFocusInElement(true);
20625 if (angular.isDefined(stackHead)) {
20626 trapFocusInElement(false, firstTabableElement, lastTabableElement);
20627 trapFocusInElement(true);
20632 return toggleTrapFocusInElement;
20635 .directive('b2bSetNextFocusOn', ['b2bDOMHelper', '$timeout', function(b2bDOMHelper, $timeout) {
20639 link: function (scope, elem, attr) {
20640 elem.bind('click', function(){
20641 var firstFocusableElement = undefined;
20642 var containerElem = undefined;
20643 var containerArray = [];
20644 var timeout = parseInt(attr.setNextFocusTimeout, 0) | 100;
20645 var index = parseInt(attr.b2bSetNextFocusIndex, 0) | 0;
20648 *Fix for IE7 and lower
20649 *polyfill src: https://github.com/HubSpot/pace/issues/102
20651 if (!document.querySelectorAll) {
20652 document.querySelectorAll = function (selectors) {
20653 var style = document.createElement('style'), elements = [], element;
20654 document.documentElement.firstChild.appendChild(style);
20655 document._qsa = [];
20657 style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}';
20658 window.scrollBy(0, 0);
20659 style.parentNode.removeChild(style);
20661 while (document._qsa.length) {
20662 element = document._qsa.shift();
20663 element.style.removeAttribute('x-qsa');
20664 elements.push(element);
20666 document._qsa = null;
20671 if (attr.b2bSetNextFocusOn === '') {
20674 containerArray = attr.b2bSetNextFocusOn.split(' ');
20676 $timeout(function(){
20678 do { // cycles thru containerArray until finds a match in DOM to set focus to
20679 containerElem = document.querySelectorAll(containerArray[i])[index];
20681 } while ( (!containerElem) && (i < containerArray.length) );
20683 if (!angular.isDefined(firstFocusableElement)) {
20684 firstFocusableElement = b2bDOMHelper.firstTabableElement(containerElem);
20686 firstFocusableElement.focus();
20695 .directive('b2bInputAllow', [function() {
20698 require: 'ngModel',
20699 link: function (scope, elem, attr, ctrl) {
20700 var regexExpression = null;
20701 attr.$observe('b2bInputAllow', function (value) {
20703 regexExpression = new RegExp(value);
20706 var isValid = function(str) {
20707 if (regexExpression !== null) {
20708 return regexExpression.test(str);
20712 elem.bind('keypress', function($event) {
20713 var charcode = String.fromCharCode($event.which || $event.keyCode);
20714 if (!isValid(charcode)) {
20715 $event.preventDefault();
20716 $event.stopPropagation();
20719 elem.bind('input', function (evt) {
20720 var inputString = ctrl.$viewValue;
20721 if (isValid(inputString)) {
20722 ctrl.$setViewValue(inputString);
20731 .directive('b2bInputDeny', [function() {
20734 require: 'ngModel',
20735 link: function (scope, elem, attr, ctrl) {
20736 var regexExpression = null;
20737 attr.$observe('b2bInputDeny', function (value) {
20739 regexExpression = new RegExp(value, 'g');
20742 elem.bind('input', function () {
20743 var inputString = ctrl.$viewValue && ctrl.$viewValue.replace(regexExpression, '');
20744 if (inputString !== ctrl.$viewValue) {
20745 ctrl.$setViewValue(inputString);
20754 .directive('b2bDragonInput', [function() {
20757 require: 'ngModel',
20758 link: function (scope, elem, attr, ctrl) {
20759 elem.on('focus keyup', function(){
20760 elem.triggerHandler('change');
20766 .directive('b2bKey', ['b2bUtilitiesConfig', '$timeout', 'keymap', function (b2bUtilitiesConfig, $timeout, keymap) {
20769 controller: ['$scope', '$element', '$attrs', function ($scope, $element,attr) {
20770 this.childElements = [];
20771 this.disableNodes = {};
20772 this.enableSearch = attr.enableSearch !== undefined ? true : b2bUtilitiesConfig.enableSearch;
20773 this.circularTraversal = attr.circularTraversal !== undefined ? true : b2bUtilitiesConfig.circularTraversal;
20775 if (this.enableSearch) {
20776 this.searchKeys = [];
20778 var searchString = '';
20780 var selfCtrl = this;
20782 this.childElementsList = [];
20784 this.b2bKeyID = "";
20786 if (angular.isDefined(attr.b2bKey)) {
20787 this.b2bKeyID = attr.b2bKey;
20790 this.calculateChildElementsList = function () {
20791 return $element[0].querySelectorAll("[b2b-key-item='" + this.b2bKeyID + "']:not([disabled])");
20794 this.resetChildElementsList = function () {
20795 return $timeout(function () {
20796 selfCtrl.childElementsList = selfCtrl.calculateChildElementsList();
20800 this.resetChildElementsList();
20802 $scope.$on('b2b-key-reset-child-elements-list', function () {
20803 selfCtrl.resetChildElementsList();
20807 this.registerElement = function (childElement, searchKey) {
20808 this.childElements.push(childElement);
20809 if (this.enableSearch) {
20810 this.searchKeys.push(searchKey);
20812 var count = this.childElements.length - 1;
20813 this.maxLength = count + 1;
20816 this.toggleDisable = function (count, state) {
20817 this.disableNodes[count] = state;
20819 this.searchElement = function (searchExp) {
20820 var regex = new RegExp("\\b" + searchExp, "gi");
20821 var position = this.searchKeys.regexIndexOf(regex, this.counter + 1, true);
20822 if (position > -1) {
20823 this.counter = position;
20824 this.moveFocus(this.counter);
20827 this.startTimer = function (time) {
20828 if (searchString === '') {
20829 $timeout(function () {
20834 this.resetCounter = function (count) {
20835 this.counter = count;
20837 this.moveNext = function (count) {
20838 this.counter = (this.counter + count) < this.maxLength ? this.counter + count : (this.circularTraversal ? 0 : this.counter);
20839 if (this.disableNodes[this.counter]) {
20840 if ((this.counter + count) < this.maxLength) {
20841 this.moveNext(count);
20844 this.moveFocus(this.counter);
20847 this.movePrev = function (count) {
20848 this.counter = (this.counter - count) > -1 ? this.counter - count : (this.circularTraversal ? this.maxLength-1 : this.counter);
20849 if (this.disableNodes[this.counter]) {
20850 if ((this.counter - count) > -1) {
20851 this.movePrev(count);
20854 this.moveFocus(this.counter);
20857 this.moveFocus = function (index) {
20858 this.childElements[index][0].focus();
20861 this.keyDownHandler = function (ev, count) {
20862 if (angular.isDefined(count) && !isNaN(count) && count !== this.counter) {
20863 this.resetCounter(count);
20867 ev.keyCode = ev.which;
20868 } else if (ev.charCode) {
20869 ev.keyCode = ev.charCode;
20873 if (this.prev && this.prev.indexOf(ev.keyCode.toString()) > -1) {
20875 ev.preventDefault();
20876 ev.stopPropagation();
20877 } else if (this.next && this.next.indexOf(ev.keyCode.toString()) > -1) {
20879 ev.preventDefault();
20880 ev.stopPropagation();
20881 } else if (this.up && this.up.indexOf(ev.keyCode.toString()) > -1) {
20882 if (this.type === 'table') {
20883 this.movePrev(this.columns);
20884 ev.preventDefault();
20885 ev.stopPropagation();
20887 } else if (this.down && this.down.indexOf(ev.keyCode.toString()) > -1) {
20888 if (this.type === 'table') {
20889 this.moveNext(this.columns);
20890 ev.preventDefault();
20891 ev.stopPropagation();
20893 } else if (ev.keyCode === keymap.KEY.HOME) {
20894 var firstIndex = 0;
20895 while (this.disableNodes[firstIndex] !== false) {
20898 var count = this.counter - firstIndex;
20899 this.movePrev(count);
20900 ev.preventDefault();
20901 ev.stopPropagation();
20902 } else if (ev.keyCode === keymap.KEY.END) {
20903 var lastIndex = this.childElements.length - 1;
20904 while (this.disableNodes[lastIndex] !== false) {
20907 var count = lastIndex - this.counter;
20908 this.moveNext(count);
20909 ev.preventDefault();
20910 ev.stopPropagation();
20911 } else if (ev.keyCode >= 48 && ev.keyCode <= 105) {
20912 if (this.enableSearch) {
20913 this.startTimer(b2bUtilitiesConfig.searchTimer);
20914 searchString = searchString + (keymap.MAP[ev.keyCode] || '');
20915 this.searchElement(searchString);
20916 ev.preventDefault();
20917 ev.stopPropagation();
20923 link: function (scope, elem, attr, ctrl) {
20924 ctrl.prev = attr.prev ? attr.prev.split(',') : b2bUtilitiesConfig.prev.split(',');
20925 ctrl.next = attr.next ? attr.next.split(',') : b2bUtilitiesConfig.next.split(',');
20926 ctrl.type = attr.type ? attr.type : b2bUtilitiesConfig.type;
20927 if (ctrl.type === 'table') {
20928 ctrl.up = attr.up ? attr.up.split(',') : b2bUtilitiesConfig.up.split(',');
20929 ctrl.down = attr.down ? attr.down.split(',') : b2bUtilitiesConfig.down.split(',');
20930 ctrl.columns = attr.columns ? parseInt(attr.columns, 10) : b2bUtilitiesConfig.columns;
20933 elem.bind('keydown', function (ev) {
20934 ctrl.keyDownHandler(ev);
20940 .directive('b2bKeyItem', [function () {
20943 link: function (scope, elem, attr, ctrl) {
20944 var parentCtrl = (elem.parent() && elem.parent().controller('b2bKey')) || undefined;
20945 if (angular.isDefined(parentCtrl)) {
20946 var count = parentCtrl.registerElement(elem, attr.searchKey);
20947 elem.bind('keydown', function (ev) {
20948 parentCtrl.keyDownHandler(ev, count);
20950 scope.$watch(attr.b2bKeyItem, function (value) {
20951 value = value === undefined ? true : value;
20952 parentCtrl.toggleDisable(count, !value);
20954 scope.$on('$destroy', function () {
20955 parentCtrl.toggleDisable(count, true);
20962 .directive('b2bElementFocus', [function () {
20965 link: function (scope, elem, attr, ctrl) {
20966 scope.$watch(attr.b2bElementFocus, function (value) {
20967 if (value === true) {
20976 .directive('b2bAppendElement', ['$compile', function ($compile) {
20979 link: function (scope, elem, attr, ctrl) {
20980 var parameters = attr.b2bAppendElement.split(':');
20981 if (parameters.length === 1) {
20982 elem.append(scope.$eval(parameters[0]));
20983 } else if (parameters.length === 2) {
20984 if (parameters[1] === 'compile') {
20985 var element = angular.element('<span>' + scope.$eval(parameters[0]) + '</span>');
20986 elem.append($compile(element)(scope));
20994 .directive('b2bKeyItemRefreshInNgRepeat', [function () {
20997 require: '^^b2bKey',
20998 link: function (scope, elem, attr, parentCtrl) {
20999 if (angular.isDefined(parentCtrl)) {
21001 var attrToObserve = 'attrToObserve';
21003 if (attr.b2bKeyItemRefreshInNgRepeat) {
21004 attrToObserve = 'b2bKeyItemRefreshInNgRepeat';
21007 attr.$observe(attrToObserve, function (newVal, oldVal) {
21008 if (newVal && newVal !== oldVal) {
21009 parentCtrl.resetChildElementsList();
21017 .filter('b2bMultiSepartorHighlight', function($sce) {
21018 return function(text, searchText, searchSeperator) {
21019 var splitText = function(string) {
21020 if(angular.isDefined(searchSeperator)){
21021 if (string.indexOf(searchSeperator) > -1) {
21022 return string.split(searchSeperator);
21031 var newText = splitText(text);
21032 var newPhrase = splitText(searchText);
21033 if (angular.isArray(newPhrase)) {
21034 for (var i = 0; i < newText.length; i++) {
21036 text = newText[i].replace(new RegExp('(' + newPhrase[i] + ')', 'gi'),
21037 '<span class="b2b-search-hightlight">$1</span>');
21039 text = text + searchSeperator + ' ' + (newPhrase[i] ? newText[i].replace(new RegExp('(' + newPhrase[i] + ')', 'gi'),
21040 '<span class="b2b-search-hightlight">$1</span>') : newText[i]);
21044 text = text.replace(new RegExp('(' + searchText + ')', 'gi'),
21045 '<span class="b2b-search-hightlight">$1</span>');
21048 return $sce.trustAsHtml(text)
21052 .factory('b2bUserAgent', [function() {
21053 var _isMobile = function() {
21054 return /Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
21056 var _notMobile = function() {
21057 return !/Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
21059 var _isIE = function() {
21060 return /msie|trident/i.test(navigator.userAgent);
21062 var _isFF = function() {
21063 return /Firefox/.test(navigator.userAgent);
21065 var _isChrome = function() {
21066 return /Google Inc/.test(navigator.vendor);
21068 var _isSafari = function() {
21069 return /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
21073 isMobile: _isMobile,
21074 notMobile: _notMobile,
21077 isChrome: _isChrome,
21078 isSafari: _isSafari
21081 .run(['$document', 'b2bUserAgent', function($document, b2bUserAgent) {
21082 var html = $document.find('html').eq(0);
21083 if (b2bUserAgent.isIE()) {
21084 html.addClass('isIE');
21086 html.removeClass('isIE');
21092 String.prototype.toSnakeCase = function () {
21093 return this.replace(/([A-Z])/g, function ($1) {
21094 return "-" + $1.toLowerCase();
21097 var concat = function (character, times) {
21098 character = character || '';
21099 times = (!isNaN(times) && times) || 0;
21100 var finalChar = '';
21101 for (var i = 0; i < times; i++) {
21102 finalChar += character;
21107 // direction: true for left and false for right
21108 var pad = function (actualString, width, character, direction) {
21109 actualString = actualString || '';
21110 width = (!isNaN(width) && width) || 0;
21111 character = character || '';
21112 if (width > actualString.length) {
21114 return concat(character, (width - actualString.length)) + actualString;
21116 return actualString + concat(character, (width - actualString.length));
21119 return actualString;
21122 String.prototype.lPad = function (width, character) {
21123 return pad(this, width, character, true);
21126 String.prototype.rPad = function (width, character) {
21127 return pad(this, width, character, false);
21130 if (!Array.prototype.indexOf) {
21131 Array.prototype.indexOf = function (val) {
21132 for (var index = 0; index < this.length; index++) {
21133 if (this[index] === val) {
21141 if (!Array.prototype.regexIndexOf) {
21142 Object.defineProperty(Array.prototype, 'regexIndexOf', {
21144 value: function (regex, startIndex, loop) {
21145 startIndex = startIndex && startIndex > -1 ? startIndex : 0;
21146 for (var index = startIndex; index < this.length; index++) {
21147 if (this[index].toString().match(regex)) {
21152 for (var index = 0; index < startIndex; index++) {
21153 if (this[index].toString().match(regex)) {
21163 angular.module("b2bTemplate/audioPlayer/audioPlayer.html", []).run(["$templateCache", function($templateCache) {
21164 $templateCache.put("b2bTemplate/audioPlayer/audioPlayer.html",
21165 "<div class=\"b2b-audio\">\n" +
21166 " <audio preload=\"auto\">\n" +
21167 " <source ng-src=\"{{audio.mp3 | trustedAudioUrl}}\" type=\"audio/mp3\"></source>\n" +
21168 " <i>Your browser does not support the audio element.</i>\n" +
21171 " <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" +
21172 " <i class=\"icoControls-pointer\" ng-show='!isPlayInProgress'></i>\n" +
21173 " <i class=\"icoControls-pause\" ng-show='isPlayInProgress'></i>\n" +
21176 " <div class=\"seek-bar-container-wrapper\">\n" +
21177 " <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" +
21178 " <div class=\"timing-container\">\n" +
21179 " <span class=\"timing-container-left\">{{timeFormatter(audio.currentTime)}}</span>\n" +
21180 " <span class=\"timing-container-right\">{{timeFormatter(audio.duration)}}</span>\n" +
21181 " <div class=\"timing-container-spacer\"></div>\n" +
21185 " <b2b-flyout>\n" +
21186 " <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" +
21187 " <i class=\"icoControls-mutespeakers\" ng-show=\"audio.currentVolume === 0\"></i>\n" +
21188 " <i class=\"icoControls-volumedown\" ng-show=\"audio.currentVolume > 0 && audio.currentVolume <= 50\"></i>\n" +
21189 " <i class=\"icoControls-volumeup\" ng-show=\"audio.currentVolume > 50\"></i>\n" +
21192 " <b2b-flyout-content horizontal-placement=\"center\" flyout-style=\"width:70px; height:190px;\" vertical-placement=\"above\">\n" +
21193 " <div class=\"b2b-audio-popover text-center\">\n" +
21194 " <span>Max</span>\n" +
21195 " <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" +
21196 " <div class=\"min-label\">Min</div>\n" +
21198 " </b2b-flyout-content>\n" +
21199 " </b2b-flyout>\n" +
21203 angular.module("b2bTemplate/audioRecorder/audioRecorder.html", []).run(["$templateCache", function($templateCache) {
21204 $templateCache.put("b2bTemplate/audioRecorder/audioRecorder.html",
21205 "<div class=\"b2b-audio-recorder row\">\n" +
21206 " <div class=\"b2b-elapsed-time span11\">\n" +
21207 " <div ng-if=\"isRecording\">\n" +
21208 " <span style=\"padding-right: 25px;\">{{config.whileRecordingMessage}}</span>\n" +
21209 " <span>{{timeFormatter(elapsedTime)}}</span>\n" +
21211 " <span ng-if=\"!isRecording\">{{config.startRecordingMessage}}</span>\n" +
21213 " <div class=\"b2b-controls\" title=\"{{isRecording ? 'Stop' : 'REC'}}\" b2b-accessibility-click=\"13,32\" ng-click=\"toggleRecording()\" role=\"button\">\n" +
21214 " <i ng-if=\"isRecording\" class=\"icoControls-stop\" ></i>\n" +
21215 " <i ng-if=\"!isRecording\" class=\"icoControls-record\"></i>\n" +
21220 angular.module("b2bTemplate/backToTop/backToTop.html", []).run(["$templateCache", function($templateCache) {
21221 $templateCache.put("b2bTemplate/backToTop/backToTop.html",
21222 "<button class=\"btn-arrow b2b-backtotop-button\" type=\"button\" aria-label=\"Back to top\">\n" +
21223 " <div class=\"btn-secondary b2b-top-btn\">\n" +
21224 " <i class=\"icoControls-upPRIMARY\" role=\"img\"></i>\n" +
21230 angular.module("b2bTemplate/boardstrip/b2bAddBoard.html", []).run(["$templateCache", function($templateCache) {
21231 $templateCache.put("b2bTemplate/boardstrip/b2bAddBoard.html",
21232 "<div tabindex=\"0\" role=\"menuitem\" b2b-accessibility-click=\"13,32\" ng-click=\"addBoard()\" aria-label=\"Add Board\" class=\"boardstrip-item--add\">\n" +
21233 " <div class=\"centered\"><i aria-hidden=\"true\" class=\"icoControls-add-maximize\"></i> Add board</div>\n" +
21237 angular.module("b2bTemplate/boardstrip/b2bBoard.html", []).run(["$templateCache", function($templateCache) {
21238 $templateCache.put("b2bTemplate/boardstrip/b2bBoard.html",
21239 "<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" +
21240 " <div ng-transclude></div>\n" +
21241 " <div class=\"board-caret\" ng-if=\"getCurrentIndex()===boardIndex\">\n" +
21242 " <div class=\"board-caret-indicator\"></div>\n" +
21243 " <div class=\"board-caret-arrow-up\"></div>\n" +
21248 angular.module("b2bTemplate/boardstrip/b2bBoardstrip.html", []).run(["$templateCache", function($templateCache) {
21249 $templateCache.put("b2bTemplate/boardstrip/b2bBoardstrip.html",
21250 "<div class=\"b2b-boardstrip\">\n" +
21251 " <div class=\"boardstrip-reel\" role=\"menu\">\n" +
21252 " <div class=\"prev-items\">\n" +
21253 " <!-- <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" +
21254 " <button class=\"btn-arrow arrow\" b2b-accessibility-click=\"13,32\" ng-click=\"prevBoard()\" ng-disabled=\"!isPrevBoard()\">\n" +
21255 " <div class=\"btn btn-small btn-alt\"><i class=\"icon-primary-left\"></i>\n" +
21257 " <span class=\"offscreen-text\">Previous boards</span>\n" +
21260 " <div b2b-add-board on-add-board=\"onAddBoard()\"></div>\n" +
21261 " <div class=\"board-viewport\"><ul role=\"menu\" class=\"boardstrip-container\" ng-transclude></ul></div>\n" +
21262 " <div class=\"next-items\">\n" +
21263 " <!-- <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" +
21264 " <button class=\"btn-arrow arrow\" b2b-accessibility-click=\"13,32\" ng-click=\"nextBoard()\" ng-disabled=\"!isNextBoard()\">\n" +
21265 " <div class=\"btn btn-small btn-alt\"><i class=\"icon-primary-right\"></i>\n" +
21267 " <span class=\"offscreen-text\">Next boards</span>\n" +
21275 angular.module("b2bTemplate/calendar/datepicker-popup.html", []).run(["$templateCache", function($templateCache) {
21276 $templateCache.put("b2bTemplate/calendar/datepicker-popup.html",
21277 "<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" +
21278 " <div class=\"datepicker-days\" style=\"display: block;\">\n" +
21279 " <div ng-repeat=\"header in headers\" class=\"text-left\" style=\"width: 100%;\" b2b-append-element=\"header\"></div>\n" +
21280 " <table class=\"table-condensed\">\n" +
21283 " <th id=\"prev\" class=\"prev\" tabindex=\"0\" b2b-accessibility-click=\"13\" 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" +
21284 " <th id=\"month\" tabindex=\"-1\" aria-label=\"{{title}}\" class=\"datepicker-switch\" colspan=\"{{rows[0].length - 2}}\">{{title}}</th>\n" +
21285 " <th id=\"next\" class=\"next\" tabindex=\"0\" b2b-accessibility-click=\"13\" 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" +
21287 " <tr ng-show=\"labels.length > 0\">\n" +
21288 " <th id=\"{{label.post}}\" class=\"dow\" ng-repeat=\"label in labels\" aria-hidden=\"true\"><span aria-hidden=\"true\">{{label.pre}}</span></th>\n" +
21292 " <tr ng-repeat=\"row in rows\">\n" +
21293 " <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" +
21294 " <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" +
21298 " <tr ng-repeat=\"footer in footers\">\n" +
21299 " <th colspan=\"7\" class=\"text-left\" style=\"width: 278px;\" b2b-append-element=\"footer\"></th>\n" +
21307 angular.module("b2bTemplate/calendar/datepicker.html", []).run(["$templateCache", function($templateCache) {
21308 $templateCache.put("b2bTemplate/calendar/datepicker.html",
21310 " <span class=\"icon-primary-calendar span12\" ng-class=\"{'disabled': ngDisabled}\" ng-transclude></span>\n" +
21314 angular.module("b2bTemplate/coachmark/coachmark.html", []).run(["$templateCache", function($templateCache) {
21315 $templateCache.put("b2bTemplate/coachmark/coachmark.html",
21316 "<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" +
21317 " <i class=\"b2b-coachmark-caret\"></i>\n" +
21318 " <div class=\"b2b-coachmark-header\">\n" +
21319 " <div class=\"b2b-coachmark-countlabel\"><span ng-if=\"coachmarkIndex !== 0\">{{coachmarkIndex}} of {{(coachmarks.length-1)}}<span></div>\n" +
21320 " <div class=\"corner-button\">\n" +
21321 " <button type=\"button\" ng-focus=\"closeButtonFocus()\" class=\"close\" title=\"close\" aria-label=\"Close\" ng-click=\"closeCoachmark()\"></button>\n" +
21324 " <div class=\"b2b-coachmark-content\"> \n" +
21325 " <i class=\"icon-misc-dimmer\"></i>\n" +
21326 " <div class=\"b2b-coachmark-content-header\"><span class=\"offscreen-text\">{{currentCoachmark.offscreenText}}</span>{{currentCoachmark.contentHeader}}</div>\n" +
21327 " <div class=\"b2b-coachmark-description\">{{currentCoachmark.content}}</div>\n" +
21328 " <div class=\"b2b-coachmark-btn-group\">\n" +
21329 " <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" +
21330 " <button class=\"btn btn-alt\" ng-if=\"currentCoachmark.buttonLabel !== '' && currentCoachmark.buttonLabel !== undefined\" ng-click=\"actionCoachmark(currentCoachmark.buttonLabel)\">{{currentCoachmark.buttonLabel}}</button>\n" +
21336 angular.module("b2bTemplate/dropdowns/b2bDropdownDesktop.html", []).run(["$templateCache", function($templateCache) {
21337 $templateCache.put("b2bTemplate/dropdowns/b2bDropdownDesktop.html",
21338 "<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" +
21339 " <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-focus=\"focused=true\" ng-blur=\"setBlur(); focused=false\" ng-class=\"{'active': toggleFlag, 'closed': !toggleFlag, 'large': (dropdownSize === 'large'), 'focused':focused}\" style=\"width:100%;\" value=\"{{currentSelected.text}}\" ng-show=\"isInputDropdown\" aria-describedby=\"{{dropdownDescribedBy}}\" readonly=\"readonly\">\n" +
21340 " <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" +
21341 " <div ng-class=\"{'selectWrapper': (isInputDropdown), 'moduleWrapper': (!isInputDropdown)}\">\n" +
21342 " <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" +
21343 " <ul class=\"module-optinalcta\" ng-if=\"toggleFlag && optionalCta\" tabindex=\"-1\" aria-hidden=\"false\">\n" +
21344 " <li class=\"module-list-item\" tabindex=\"-1\" role=\"menuitem\" value=\"\" aria-label=\"Optinal CTA\" aria-selected=\"true\">\n" +
21345 " <span class=\"module-data\" b2b-append-element=\"optionalCta\"></span>\n" +
21349 "<i class=\"icon-primary-down\" aria-hidden=\"true\"></i>\n" +
21353 angular.module("b2bTemplate/dropdowns/b2bDropdownGroupDesktop.html", []).run(["$templateCache", function($templateCache) {
21354 $templateCache.put("b2bTemplate/dropdowns/b2bDropdownGroupDesktop.html",
21355 "<li b2b-dropdown-group-desktop class=\"optgroup-wrapper\">{{groupHeader}}\n" +
21356 " <ul class=\"optgroup\" role=\"group\" aria-label=\"{{groupHeader}}\"></ul>\n" +
21360 angular.module("b2bTemplate/dropdowns/b2bDropdownListDesktop.html", []).run(["$templateCache", function($templateCache) {
21361 $templateCache.put("b2bTemplate/dropdowns/b2bDropdownListDesktop.html",
21362 "<li b2b-dropdown-list-desktop b2b-key-item b2b-accessibility-click=\"13\" aria-selected=\"{{currentSelected.value === dropdownListValue}}\" ng-class=\"{'awd-select-list-item': (isInputDropdown), 'module-list-item': (!isInputDropdown)}\" tabindex=\"0\" role=\"{{isInputDropdown?'option':'menuitem'}}\" ng-click=\"selectDropdownItem()\"></li>");
21365 angular.module("b2bTemplate/fileUpload/fileUpload.html", []).run(["$templateCache", function($templateCache) {
21366 $templateCache.put("b2bTemplate/fileUpload/fileUpload.html",
21367 "<label class=\"b2b-file-container\">\n" +
21368 " <span class=\"b2b-upload-link\" ng-transclude></span>\n" +
21369 " <input type=\"file\" b2b-file-change>\n" +
21373 angular.module("b2bTemplate/flyout/flyout.html", []).run(["$templateCache", function($templateCache) {
21374 $templateCache.put("b2bTemplate/flyout/flyout.html",
21375 "<span class=\"b2b-flyout\" b2b-flyout-trap-focus-inside>\n" +
21376 " <span ng-transclude></span>\n" +
21380 angular.module("b2bTemplate/flyout/flyoutContent.html", []).run(["$templateCache", function($templateCache) {
21381 $templateCache.put("b2bTemplate/flyout/flyoutContent.html",
21382 "<div class=\"b2b-flyout-container\" aria-live=\"polite\" aria-atomic=\"false\" ng-class=\"{'b2b-flyout-left':horizontalPlacement==='left',\n" +
21383 " 'b2b-flyout-center':horizontalPlacement==='center', \n" +
21384 " 'b2b-flyout-right':horizontalPlacement==='right',\n" +
21385 " 'b2b-flyout-centerLeft':horizontalPlacement==='centerLeft',\n" +
21386 " 'b2b-flyout-centerRight':horizontalPlacement==='centerRight', \n" +
21387 " 'b2b-flyout-above':verticalPlacement==='above', \n" +
21388 " 'b2b-flyout-below':verticalPlacement==='below',\n" +
21389 " 'open-flyout': openFlyout,\n" +
21390 " 'b2b-close-flyout': !openFlyout}\">\n" +
21391 " <i class=\"b2b-flyout-caret\" ng-class=\"{'open-flyout': openFlyout, \n" +
21392 " 'b2b-flyout-caret-above':verticalPlacement==='above',\n" +
21393 " 'b2b-flyout-caret-below':verticalPlacement==='below'}\"></i>\n" +
21394 " <span ng-transclude></span>\n" +
21398 angular.module("b2bTemplate/footer/footer_column_switch_tpl.html", []).run(["$templateCache", function($templateCache) {
21399 $templateCache.put("b2bTemplate/footer/footer_column_switch_tpl.html",
21400 "<div class=\"footer-columns \" ng-class=\"{'five-column':footerColumns===5, 'four-column':footerColumns===4, 'three-column':footerColumns===3}\" ng-repeat=\"item in footerLinkItems\">\n" +
21401 " <h3 class=\"b2b-footer-header\">{{item.categoryName}}</h3>\n" +
21403 " <li ng-repeat=\"i in item.values\">\n" +
21404 " <a href=\"{{i.href}}\" title=\"{{item.categoryName}} - {{i.displayLink}}\">{{i.displayLink}}</a> \n" +
21410 "<div ng-transclude></div>\n" +
21414 angular.module("b2bTemplate/horizontalTable/horizontalTable.html", []).run(["$templateCache", function($templateCache) {
21415 $templateCache.put("b2bTemplate/horizontalTable/horizontalTable.html",
21416 "<div class=\"b2b-horizontal-table\">\n" +
21417 " <div class=\"b2b-horizontal-table-arrows row span12\">\n" +
21418 " <div class=\"span4\">\n" +
21419 " <button class=\"btn-arrow left\" type=\"button\" ng-click=\"moveViewportLeft()\" ddh-accessibility-click=\"13,32\" ng-disabled=\"disableLeft\"><div class=\"btn btn-alt\"><i class=\"icon-primary-left\"></i></div>Previous Set</button>\n" +
21422 " <span class=\"span5 b2b-horizontal-table-column-info\" aria-live=\"polite\" tabindex=\"-1\">\n" +
21423 " Displaying {{getColumnSet()[0]}} - {{getColumnSet()[1]}} of {{numOfCols}} (total) columns\n" +
21426 " <div class=\"span3\">\n" +
21427 " <button class=\"btn-arrow right\" ng-click=\"moveViewportRight()\" ddh-accessibility-click=\"13,32\" ng-disabled=\"disableRight\" type=\"button\">Next Set<div class=\"btn btn-alt\"><i class=\"icon-primary-right\"></i></div></button>\n" +
21430 " <div class=\"b2b-horizontal-table-inner-container\">\n" +
21431 " <span ng-transclude></span>\n" +
21438 angular.module("b2bTemplate/hourPicker/b2bHourpicker.html", []).run(["$templateCache", function($templateCache) {
21439 $templateCache.put("b2bTemplate/hourPicker/b2bHourpicker.html",
21440 "<div class=\"hp-container\">\n" +
21441 " <div class=\"hp-selected\">\n" +
21442 " <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" +
21444 " <div b2b-hourpicker-panel></div>\n" +
21448 angular.module("b2bTemplate/hourPicker/b2bHourpickerPanel.html", []).run(["$templateCache", function($templateCache) {
21449 $templateCache.put("b2bTemplate/hourPicker/b2bHourpickerPanel.html",
21450 "<form name=\"{{'hourpickerForm' + $id}}\">\n" +
21451 " <div class=\"hp-checkbox\" role=\"group\">\n" +
21452 " <label class=\"checkbox\" for=\"checkbox_{{dayOption.title}}_{{$id}}\" aria-label=\"{{dayOption.title}}\" ng-repeat=\"dayOption in hourpicker.dayOptions\">\n" +
21453 " <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" +
21456 " <div class=\"row hp-dropdowns\">\n" +
21457 " <div class=\"span4\">\n" +
21458 " <label for=\"{{'hourpickerStartTime' + $id}}\">From</label>\n" +
21459 " <select id=\"{{'hourpickerStartTime' + $parent.$id}}\" b2b-dropdown type=\"\" ng-model=\"hourpickerPanelValue.startTime\">\n" +
21460 " <option b2b-dropdown-list value=\"\">From</option>\n" +
21461 " <option b2b-dropdown-list option-repeat=\"startTimeOption in hourpicker.startTimeOptions\" value=\"{{startTimeOption}}\">{{startTimeOption}}</option>\n" +
21464 " <div class=\"span8 radio-buttons\" role=\"radiogroup\" aria-label=\"Select AM or PM\">\n" +
21465 " <label class=\"radio\" for=\"hourpickerStartMeridiem_AM_{{$id}}\">\n" +
21466 " <input type=\"radio\" id=\"hourpickerStartMeridiem_AM_{{$id}}\" name=\"{{'hourpickerStartMeridiem' + $id}}\" value=\"am\" ng-model=\"hourpickerPanelValue.startMeridiem\" /><i class=\"skin\"></i><span>AM</span>\n" +
21468 " <label class=\"radio\" for=\"hourpickerStartMeridiem_PM_{{$id}}\">\n" +
21469 " <input type=\"radio\" id=\"hourpickerStartMeridiem_PM_{{$id}}\" name=\"{{'hourpickerStartMeridiem' + $id}}\" value=\"pm\" ng-model=\"hourpickerPanelValue.startMeridiem\" /><i class=\"skin\"></i><span>PM</span>\n" +
21473 " <div class=\"row hp-dropdowns\">\n" +
21474 " <div class=\"span4\">\n" +
21475 " <label for=\"{{'hourpickerEndTime' + $id}}\">To</label>\n" +
21476 " <select id=\"{{'hourpickerEndTime' + $parent.$id}}\" b2b-dropdown ng-model=\"hourpickerPanelValue.endTime\">\n" +
21477 " <option b2b-dropdown-list value=\"\">To</option>\n" +
21478 " <option b2b-dropdown-list option-repeat=\"endTimeOption in hourpicker.endTimeOptions\" value=\"{{endTimeOption}}\">{{endTimeOption}}</option>\n" +
21481 " <div class=\"span8 radio-buttons\" role=\"radiogroup\" aria-label=\"Select AM or PM\">\n" +
21482 " <label class=\"radio\" for=\"hourpickerEndMeridiem_AM_{{$id}}\">\n" +
21483 " <input type=\"radio\" id=\"hourpickerEndMeridiem_AM_{{$id}}\" name=\"{{'hourpickerEndMeridiem' + $id}}\" value=\"am\" ng-model=\"hourpickerPanelValue.endMeridiem\" /><i class=\"skin\"></i><span>AM</span>\n" +
21485 " <label class=\"radio\" for=\"hourpickerEndMeridiem_PM_{{$id}}\">\n" +
21486 " <input type=\"radio\" id=\"hourpickerEndMeridiem_PM_{{$id}}\" name=\"{{'hourpickerEndMeridiem' + $id}}\" value=\"pm\" ng-model=\"hourpickerPanelValue.endMeridiem\" /><i class=\"skin\"></i><span>PM</span>\n" +
21490 " <div class=\"row hp-buttons\">\n" +
21491 " <div class=\"span12\">\n" +
21492 " <div style=\"float:right\">\n" +
21493 " <button class=\"btn btn-secondary btn-small\" ng-click=\"resetHourpickerPanelValue()\">Clear</button>\n" +
21494 " <button class=\"btn btn-alt btn-small\" ng-disabled = \"disableAddBtn\" ng-click=\"updateHourpickerValue()\">{{hourpicker.editMode > -1 ? 'Update' : 'Add'}}</button>\n" +
21501 angular.module("b2bTemplate/hourPicker/b2bHourpickerValue.html", []).run(["$templateCache", function($templateCache) {
21502 $templateCache.put("b2bTemplate/hourPicker/b2bHourpickerValue.html",
21503 "<div class=\"selected-days\">\n" +
21504 " <span class=\"day\">{{hourpickerValue.days}} {{hourpickerValue.startTime}} {{hourpickerValue.startMeridiem}} - {{hourpickerValue.endTime}} {{hourpickerValue.endMeridiem}}</span>\n" +
21505 " <span style=\"float:right\">\n" +
21506 " <i class=\"icon-misc-pen\" role=\"button\" title=\"Edit {{hourpickerValue.days}} {{hourpickerValue.startTime}} {{hourpickerValue.startMeridiem}} - {{hourpickerValue.endTime}} {{hourpickerValue.endMeridiem}}\" tabindex=\"0\" b2b-accessibility-click=\"13,32\" ng-click=\"editHourpickerValue(hourpickerValue.index)\"></i>\n" +
21507 " <i class=\"icon-misc-trash\" role=\"button\" title=\"Delete {{hourpickerValue.days}} {{hourpickerValue.startTime}} {{hourpickerValue.startMeridiem}} - {{hourpickerValue.endTime}} {{hourpickerValue.endMeridiem}}\" tabindex=\"0\" b2b-accessibility-click=\"13,32\" ng-click=\"deleteHourpickerValue(hourpickerValue.index)\"></i>\n" +
21509 " <div style=\"clear:both\"></div>\n" +
21513 angular.module("b2bTemplate/leftNavigation/leftNavigation.html", []).run(["$templateCache", function($templateCache) {
21514 $templateCache.put("b2bTemplate/leftNavigation/leftNavigation.html",
21515 "<div class=\"b2b-nav-menu\" ng-init=\"(showmenu = true)\" ng-class=\"{false: 'left-menu-collapsed'}[showmenu]\">\n" +
21516 " <div class=\"b2b-subnav-container\">\n" +
21517 " <ul class=\"b2b-subnav-content\">\n" +
21519 " <div ng-class=\"{true: 'leftmenu-arrow-expand', false: 'leftmenu-arrow-collapse'}[showmenu]\">"+
21520 " <a ng-click=\"toggleDrawer(showmenu);(showmenu = !showmenu) \" class=\"text-right\">" +
21521 " <i ng-class=\"{true: 'icon-controls-left', false: 'icon-controls-right'}[showmenu]\"> </i></a>" +
21524 " <li ng-repeat=\"menu in menuData\" ng-click=\"!showmenu||toggleNav($index,menu.href)\">" +
21525 " <span ng-class=\"{true: 'menu-icon', false: 'menu-icon-collapse'}[showmenu]\"><span class=\"{{menu.imageSrc}}\"></span> </span>" +
21526 " <a ng-class=\"{expand: isOpen($index)}\" ng-if=\"showmenu\" aria-label=\"{{menu.name}}\" title=\" \" aria-expanded=\"{{(idx==$index)?true:false;}}\" href=\"javascript:void(0);\">" +
21528 " <i aria-hidden=\"true\" ng-if=\"(menu.menuItems.length > 0)\" class=\"b2b-icon-primary-plus-minus\"ng-class=\"idx==$index ? 'icon-primary-expanded' : 'icon-primary-collapsed'\"></i>" +
21530 " <div role=\"region\" aria-hidden=\"{{(isOpen($index))?false:true;}}\">\n" +
21531 " <ul ng-class=\"{expand: idx==$index}\">\n" +
21532 " <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" +
21541 angular.module("b2bTemplate/listbox/listbox.html", []).run(["$templateCache", function($templateCache) {
21542 $templateCache.put("b2bTemplate/listbox/listbox.html",
21543 "<div class=\"b2b-list-box\" tabindex=\"0\" role=\"listbox\" ng-transclude>\n" +
21547 angular.module("b2bTemplate/modalsAndAlerts/b2b-backdrop.html", []).run(["$templateCache", function($templateCache) {
21548 $templateCache.put("b2bTemplate/modalsAndAlerts/b2b-backdrop.html",
21549 "<div class=\"b2b-modal-backdrop fade in hidden-by-modal\" aria-hidden=\"true\" tabindex=\"-1\"></div>");
21552 angular.module("b2bTemplate/modalsAndAlerts/b2b-window.html", []).run(["$templateCache", function($templateCache) {
21553 $templateCache.put("b2bTemplate/modalsAndAlerts/b2b-window.html",
21554 "<div class=\"modalwrapper active {{windowClass}}\" ng-class=\"{'modal-landscape': isModalLandscape}\" role=\"{{isNotifDialog?'alertdialog':'dialog'}}\" tabindex=\"-1\" aria-labelledby=\"{{title}}\" aria-describedby=\"{{content}}\">\n" +
21555 " <div class=\"modal fade in {{sizeClass}}\" ng-transclude></div>\n" +
21559 angular.module("b2bTemplate/monthSelector/monthSelector-popup.html", []).run(["$templateCache", function($templateCache) {
21560 $templateCache.put("b2bTemplate/monthSelector/monthSelector-popup.html",
21561 "<div id=\"monthselector{{$id}}\" class=\"datepicker dropdown-menu monthselector\" \n" +
21562 " ng-class=\"{'datepicker-dropdown datepicker-orient-top': !inline, 'datepicker-orient-left': !inline && orientation === 'left', 'datepicker-orient-right': !inline && orientation === 'right'}\" \n" +
21563 " ng-style=\"{position: inline && 'relative', 'z-index': inline && '0', top: !inline && position.top + 'px' || 0, left: !inline && position.left + 'px'}\" \n" +
21564 " style=\"display: block;\" aria-hidden=\"false\" role=\"dialog presentation\" tabindex=\"-1\">\n" +
21565 " <div class=\"datepicker-days\" style=\"display: block;\">\n" +
21566 " <span class=\"offscreen-text\" aria-live=\"polite\" aria-atomic=\"true\">{{title}} displaying</span>\n" +
21567 " <table class=\"table-condensed\" role=\"grid\">\n" +
21569 " <tr ng-repeat=\"header in headers\">\n" +
21570 " <th colspan=\"7\" class=\"text-left\" style=\"width: 278px;\" b2b-append-element=\"header\"></th>\n" +
21573 " <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" +
21574 " <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" +
21575 " <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" +
21577 " <tr ng-show=\"labels.length > 0\">\n" +
21578 " <th id=\"{{label.post}}\" class=\"dow\" ng-repeat=\"label in labels\" aria-hidden=\"true\"><span aria-hidden=\"true\">{{label.pre}}</span></th>\n" +
21581 " <tbody b2b-key type=\"table\" columns=\"4\" >\n" +
21582 " <tr ng-repeat=\"row in rows\">\n" +
21583 " <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" +
21584 " <div aria-hidden=\"true\" tabindex=\"-1\" class=\"show-date\" ng-if=\"!(dt.oldMonth || dt.nextMonth)\">{{dt.label | limitTo: 3}}</div>\n" +
21589 " <tr ng-repeat=\"footer in footers\">\n" +
21590 " <th colspan=\"7\" class=\"text-left\" b2b-append-element=\"footer\"></th>\n" +
21598 angular.module("b2bTemplate/monthSelector/monthSelector.html", []).run(["$templateCache", function($templateCache) {
21599 $templateCache.put("b2bTemplate/monthSelector/monthSelector.html",
21601 " <span class=\"icon-primary-calendar span12\" ng-class=\"{'disabled': ngDisabled}\" ng-transclude></span>\n" +
21605 angular.module("b2bTemplate/monthSelector/monthSelectorLink.html", []).run(["$templateCache", function($templateCache) {
21606 $templateCache.put("b2bTemplate/monthSelector/monthSelectorLink.html",
21608 " <span class=\"span12\" ng-transclude></span>\n" +
21612 angular.module("b2bTemplate/pagination/b2b-pagination.html", []).run(["$templateCache", function($templateCache) {
21613 $templateCache.put("b2bTemplate/pagination/b2b-pagination.html",
21614 "<div class=\"b2b-pager\">\n" +
21615 " <div ng-if=\"notMobile && totalPages > 1\">\n" +
21616 " <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" +
21617 " <i class=\"icon-primary-left\"></i>\n" +
21619 " <a tabindex=\"{{currentPage === 1 ? -1 : 0}}\" ng-class=\"{'b2b-pager__item--noclick': currentPage === 1}\" aria-selected=\"{{checkSelectedPage(page)}}\" 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)\">\n" +
21620 " 1<span class=\"offscreen-text\" ng-if=\"currentPage === 1\"> is selected</span>\n" +
21622 " <a tabindex=\"{{currentPage === 2 ? -1 : 0}}\" aria-selected=\"{{checkSelectedPage(page)}}\" ng-class=\"{'b2b-pager__item--noclick': currentPage === 2}\" class=\"b2b-pager__item b2b-pager__item--link\" title=\"Page 2{{checkSelectedPage(page) ? ' selected' : '' }}\" ng-if=\"totalPages > 10 && currentPage > 6\" b2b-accessibility-click=\"13,32\" ng-click=\"selectPage(2, $event)\">2<span class=\"offscreen-text\" ng-if=\"currentPage === 2\"> is selected</span></a>\n" +
21624 " <span class=\"b2b-pager__item\" ng-if=\"totalPages > 10 && currentPage > 6\">...</span>\n" +
21626 " <a 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-accessibility-click=\"13,32\" ng-click=\"!checkSelectedPage(page) && selectPage(page, $event)\">{{page}}<span class=\"offscreen-text\" ng-if=\"currentPage === page\"> is selected</span></a>\n" +
21628 " <span class=\"b2b-pager__item\" ng-if=\"totalPages > 10 && currentPage <= totalPages - 5\">...</span>\n" +
21630 " <a tabindex=\"{{checkSelectedPage(page) ? -1 : 0}}\" href=\"javascript:void(0)\" ng-class=\"{'b2b-pager__item--noclick': checkSelectedPage(page)}\" aria-selected=\"{{checkSelectedPage(page)}}\" class=\"b2b-pager__item b2b-pager__item--link\" title=\"Page {{totalPages-1}}\" ng-if=\"totalPages > 10 && currentPage <= totalPages - 5\" b2b-accessibility-click=\"13,32\" ng-click=\"selectPage(totalPages-1, $event)\">{{totalPages-1}}<span class=\"offscreen-text\" ng-if=\"currentPage === totalPages-1\"> is selected</span></a>\n" +
21632 " <a tabindex=\"{{currentPage === totalPages ? -1 : 0}}\" href=\"javascript:void(0)\" ng-class=\"{'b2b-pager__item--noclick': currentPage === totalPages}\" aria-selected=\"{{checkSelectedPage(page)}}\" class=\"b2b-pager__item b2b-pager__item--link\" title=\"Page {{totalPages}}\" ng-if=\"totalPages > 10 && currentPage <= totalPages - 5\" b2b-accessibility-click=\"13,32\" ng-click=\"selectPage(totalPages, $event)\">{{totalPages}}<span class=\"offscreen-text\" ng-if=\"currentPage === totalPages\"> is selected</span></a>\n" +
21634 " <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" +
21635 " <i class=\"icon-primary-right\"></i>\n" +
21638 " <span class=\"fieldLabel\" ng-show=\"totalPages > 10 && showInput === true\"> \n" +
21639 " <label for=\"{{inputId}}\">Go to Page:</label>\n" +
21640 " <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" +
21641 " <button class=\"btn-arrow\" ng-click=\"gotoBtnClick()\" ng-disabled=\"$parent.gotoPage !== 0 || $parent.gotoPage !== undefined\" aria-label=\"Go to\">\n" +
21642 " <div class=\"btn btn-small btn-secondary\">\n" +
21643 " <i class=\"icon-primary-right\"></i>\n" +
21648 " <div ng-if=\"isMobile && totalPages > 1\" ng-swipe-right=\"prev()\" ng-swipe-left=\"next()\" ng-class=\"isMobile ? 'b2b-mobile-view': '' \">\n" +
21649 " <a 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" +
21655 angular.module("b2bTemplate/paneSelector/paneSelector.html", []).run(["$templateCache", function($templateCache) {
21656 $templateCache.put("b2bTemplate/paneSelector/paneSelector.html",
21657 "<div class=\"panes\" ng-transclude></div>");
21660 angular.module("b2bTemplate/paneSelector/paneSelectorPane.html", []).run(["$templateCache", function($templateCache) {
21661 $templateCache.put("b2bTemplate/paneSelector/paneSelectorPane.html",
21662 "<div class=\"pane-block\" ng-transclude></div>");
21665 angular.module("b2bTemplate/profileCard/profileCard-addUser.html", []).run(["$templateCache", function($templateCache) {
21666 $templateCache.put("b2bTemplate/profileCard/profileCard-addUser.html",
21667 "<div class=\"span3 b2b-profile-card b2b-add-user\">\n" +
21668 " <div class=\"atcenter\">\n" +
21669 " <div class=\"circle\"><i class=\"icon-primary-add-maximize\"></i></div>\n" +
21670 " <div>Create new user</div>\n" +
21675 angular.module("b2bTemplate/profileCard/profileCard.html", []).run(["$templateCache", function($templateCache) {
21676 $templateCache.put("b2bTemplate/profileCard/profileCard.html",
21677 "<div class=\"span3 b2b-profile-card\">\n" +
21678 " <div class=\"top-block\">\n" +
21679 " <div class=\"profile-image\">\n" +
21680 " <img ng-if=\"image\" profile-name=\"{{profile.name}}\" ng-src=\"{{profile.img}}\" alt=\"{{profile.name}}\">\n" +
21681 " <span ng-if=\"!image\" class=\"default-img\">{{initials}}</span>\n" +
21683 " <h4 class=\"name\">{{profile.name}}</h4>\n" +
21685 " <p class=\"status\">\n" +
21686 " <span class=\"status-icon\" ng-class=\"{'status-green':colorIcon==='green','status-red':colorIcon==='red','status-blue':colorIcon==='blue','status-yellow':colorIcon==='yellow'}\"> \n" +
21688 " <span>{{profile.state}}<span ng-if=\"badge\" class=\"status-badge\">Admin</span></span>\n" +
21692 " <div class=\"bottom-block\">\n" +
21693 " <div class=\"profile-details\">\n" +
21694 " <label>Username</label>\n" +
21695 " <div ng-if=\"shouldClip(profile.userName)\" ng-mouseover=\"showUserNameTooltip = true;\" ng-mouseleave=\"showUserNameTooltip = false;\">\n" +
21696 " <div ng-if=\"shouldClip(profile.userName)\" class=\"tooltip\" b2b-tooltip show-tooltip=\"showUserNameTooltip\">\n" +
21697 " {{profile.userName.slice(0, 25)+'...'}}\n" +
21698 " <div class=\"arrow\"></div>\n" +
21699 " <div class=\"tooltip-wrapper\" role=\"tooltip\" aria-live=\"polite\" aria-atomic=\"false\" style=\"z-index:1111\">\n" +
21700 " <div class=\"tooltip-size-control\">\n" +
21701 " <div class=\"helpertext\" tabindex=\"-1\" role=\"tooltip\">\n" +
21702 " {{profile.userName}}\n" +
21708 " <div ng-if=\"!shouldClip(profile.userName)\">\n" +
21709 " {{profile.userName}}\n" +
21711 " <label>Email</label>\n" +
21712 " <div ng-if=\"shouldClip(profile.email)\" ng-mouseover=\"showEmailTooltip = true;\" ng-mouseleave=\"showEmailTooltip = false;\">\n" +
21713 " <div ng-if=\"shouldClip(profile.email)\" class=\"tooltip\" data-placement=\"bottom\" b2b-tooltip show-tooltip=\"showEmailTooltip\">\n" +
21714 " {{profile.email.slice(0, 25)+'...'}}\n" +
21715 " <div class=\"arrow\"></div>\n" +
21716 " <div class=\"tooltip-wrapper\" role=\"tooltip\" aria-live=\"polite\" aria-atomic=\"false\" style=\"z-index:1111\">\n" +
21717 " <div class=\"tooltip-size-control\">\n" +
21718 " <div class=\"helpertext\" tabindex=\"-1\" role=\"tooltip\">\n" +
21719 " {{profile.email}}\n" +
21725 " <div ng-if=\"!shouldClip(profile.email)\">\n" +
21726 " {{profile.email}}\n" +
21728 " <label>Role</label>\n" +
21729 " <div ng-if=\"shouldClip(profile.role)\" ng-mouseover=\"showRoleTooltip = true;\" ng-mouseleave=\"showRoleTooltip = false;\">\n" +
21730 " <div ng-if=\"shouldClip(profile.role)\" class=\"tooltip\" b2b-tooltip show-tooltip=\"showRoleTooltip\">\n" +
21731 " {{profile.role.slice(0, 25)+'...'}}\n" +
21732 " <div class=\"tooltip-wrapper\" role=\"tooltip\" aria-live=\"polite\" aria-atomic=\"false\" style=\"z-index:1111\">\n" +
21733 " <div class=\"tooltip-size-control\">\n" +
21734 " <div class=\"helpertext\" tabindex=\"-1\" role=\"tooltip\">\n" +
21735 " {{profile.role}}\n" +
21741 " <div ng-if=\"!shouldClip(profile.role)\">\n" +
21742 " {{profile.role}}\n" +
21744 " <label>Last login</label>\n" +
21745 " <div ng-if=\"shouldClip(profile.lastLogin)\" ng-mouseover=\"showLastLoginTooltip = true;\" ng-mouseleave=\"showLastLoginTooltip = false;\">\n" +
21746 " <div ng-if=\"shouldClip(profile.lastLogin)\" class=\"tooltip\" b2b-tooltip show-tooltip=\"showLastLoginTooltip\">\n" +
21747 " {{profile.lastLogin.slice(0, 25)+'...'}}\n" +
21748 " <div class=\"tooltip-wrapper\" role=\"tooltip\" aria-live=\"polite\" aria-atomic=\"false\" style=\"z-index:1111\">\n" +
21749 " <div class=\"tooltip-size-control\">\n" +
21750 " <div class=\"helpertext\" tabindex=\"-1\" role=\"tooltip\">\n" +
21751 " {{profile.lastLogin}}\n" +
21757 " <div ng-if=\"!shouldClip(profile.lastLogin)\">\n" +
21758 " {{profile.lastLogin}}\n" +
21765 angular.module("b2bTemplate/searchField/searchField.html", []).run(["$templateCache", function($templateCache) {
21766 $templateCache.put("b2bTemplate/searchField/searchField.html",
21767 "<div class=\"search-bar\">\n" +
21768 " <div class='input-container' ng-blur=\"blurInput()\">\n" +
21769 " <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}}\" aria-live=\"assertive\"/>\n" +
21770 " <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" +
21772 " <div class=\"search-suggestion-wrapper\" ng-if=\"filterList.length >=0\" ng-show=\"showListFlag\">\n" +
21773 " <ul class=\"search-suggestion-list\" role=\"listbox\"> \n" +
21774 " <li class=\"no-result\" ng-if=\"filterList.length == 0 && configObj.display\">{{configObj.resultText}}</li>\n" +
21775 " <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" +
21776 " {{item.title}} \n" +
21783 angular.module("b2bTemplate/seekBar/seekBar.html", []).run(["$templateCache", function($templateCache) {
21784 $templateCache.put("b2bTemplate/seekBar/seekBar.html",
21785 "<div class=\"b2b-seek-bar-container\" ng-class=\"{vertical:verticalSeekBar}\">\n" +
21786 " <div class=\"b2b-seek-bar-track-container\">\n" +
21787 " <div class=\"b2b-seek-bar-track\"></div>\n" +
21788 " <div class=\"b2b-seek-bar-track-fill\"></div>\n" +
21790 " <div class=\"b2b-seek-bar-knob-container\" role=\"menu\" >\n" +
21791 " <div class=\"b2b-seek-bar-knob\" tabindex=\"0\" role=\"menuitem\"></div>\n" +
21796 angular.module("b2bTemplate/slider/slider.html", []).run(["$templateCache", function($templateCache) {
21797 $templateCache.put("b2bTemplate/slider/slider.html",
21798 "<div class=\"b2b-slider-container\" ng-class=\"{'vertical':verticalSlider}\">\n" +
21799 " <div class=\"slider-track-container\">\n" +
21800 " <div class=\"slider-track\"></div>\n" +
21801 " <div class=\"slider-track-fill\" ng-style=\"{backgroundColor:trackFillColor}\"></div>\n" +
21803 " <div class=\"slider-knob-container\" ng-class=\"{'slider-knob-hidden':hideKnob}\">\n" +
21804 " <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" +
21809 angular.module("b2bTemplate/spinButton/spinButton.html", []).run(["$templateCache", function($templateCache) {
21810 $templateCache.put("b2bTemplate/spinButton/spinButton.html",
21811 "<div class=\"btn-group btn-spinbutton-toggle\" ng-class=\"{'disabled': disabledFlag}\">\n" +
21812 " <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" +
21813 " <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" +
21814 " <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" +
21818 angular.module("b2bTemplate/statusTracker/statusTracker.html", []).run(["$templateCache", function($templateCache) {
21819 $templateCache.put("b2bTemplate/statusTracker/statusTracker.html",
21820 "<div class=\"b2b-status-tracker row\">\n" +
21821 " <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" +
21822 " <div class=\"btn btn-small btn-secondary\">\n" +
21823 " <i class=\"icon-primary-left\"></i>\n" +
21826 " <div ng-repeat=\"status in statuses\" class=\"b2b-status-tracker-step\" ng-class=\"{ 'complete' : status.complete, 'current' : currentStatus($index)}\" ng-show=\"isInViewport($index)\">\n" +
21827 " <p class=\"b2b-status-tracker-heading\">{{status.heading}}</p>\n" +
21828 " <div class=\"progress\">\n" +
21829 " <div class=\"progress-bar\">\n" +
21830 " <span class=\"hidden-spoken\">\n" +
21831 " {{status.complete ? 'Complete' : 'Incomplete'}}\n" +
21835 " <div class=\"b2b-status-tracker-estimate\">\n" +
21836 " <i ng-show=\"status.estimate !== '' && status.estimate !== undefined\" ng-class=\"status.complete ? 'icoControls-approval' : 'icon-misc-time'\"></i> {{status.complete ? 'Complete' : status.estimate}}\n" +
21840 " <div class=\"b2b-status-tracker-description\">\n" +
21841 " {{status.description}}\n" +
21844 " <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" +
21845 " <div class=\"btn btn-small btn-secondary\">\n" +
21846 " <i class=\"icon-primary-right\"></i>\n" +
21852 angular.module("b2bTemplate/stepTracker/stepTracker.html", []).run(["$templateCache", function($templateCache) {
21853 $templateCache.put("b2bTemplate/stepTracker/stepTracker.html",
21854 "<div class=\"b2b-step-tracker\">\n" +
21855 " <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" +
21856 " <div class=\"btn btn-left btn-small btn-secondary\"><i class=\"icon-primary-left\"></i></div>\n" +
21858 " <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" +
21859 " <div class=\"btn btn-small btn-right btn-secondary\"><i class=\"icon-primary-right\"></i></div>\n" +
21861 " <ul class=\"b2b-steps\">\n" +
21862 " <li ng-class=\"{'b2b-step-done':$index < currentIndex - 1 ,'b2b-step-on':$index == currentIndex - 1}\" \n" +
21863 " ng-repeat=\"stepsItem in stepsItemsObject\" ng-show=\"isInViewport($index)\">\n" +
21864 " <p class=\"b2b-step-text\" data-sm-text=\"{{stepsItem.dataMobile}}\" data-large-text=\"{{stepsItem.dataDesktop}}\">{{stepsItem.text}}</p>\n" +
21865 " <span class=\"hidden-spoken\">\n" +
21866 " {{($index < currentIndex - 1)? 'Complete. '+stepsItem.text+' '+stepsItem.dataMobile:''}} \n" +
21867 " {{($index == currentIndex - 1)? 'In Progress. '+stepsItem.text+' '+stepsItem.dataMobile:''}} \n" +
21868 " {{($index > currentIndex - 1)? 'Incomplete. '+stepsItem.text+' '+stepsItem.dataMobile:''}}\n" +
21875 angular.module("b2bTemplate/switches/switches-spanish.html", []).run(["$templateCache", function($templateCache) {
21876 $templateCache.put("b2bTemplate/switches/switches-spanish.html",
21877 "<div class=\"switch-overlay\" aria-hidden=\"true\">\n" +
21878 " <span class=\"btn-slider-on\"><span class=\"hidden-spoken\">seleccione para hacer </span><i class=\"activo\">activo</i></span>\n" +
21879 " <span class=\"switch-handle\"></span>\n" +
21880 " <span class=\"btn-slider-off\"><span class=\"hidden-spoken\">seleccione para hacer </span><i class=\"inactivo\">inactivo</i></span>\n" +
21884 angular.module("b2bTemplate/switches/switches.html", []).run(["$templateCache", function($templateCache) {
21885 $templateCache.put("b2bTemplate/switches/switches.html",
21886 "<div class=\"switch-overlay\" aria-hidden=\"true\">\n" +
21887 " <span class=\"btn-slider-on\">On</span>\n" +
21888 " <span class=\"switch-handle\"></span>\n" +
21889 " <span class=\"btn-slider-off\">Off</span>\n" +
21893 angular.module("b2bTemplate/tableMessages/tableMessage.html", []).run(["$templateCache", function($templateCache) {
21894 $templateCache.put("b2bTemplate/tableMessages/tableMessage.html",
21895 "<div class=\"b2b-table-message\">\n" +
21896 " <div class=\"b2b-message\" ng-if=\"msgType === 'noMatchingResults'\">\n" +
21897 " <div class=\"b2b-magnify-glass\"></div>\n" +
21899 " <div ng-transclude></div>\n" +
21902 " <div class=\"b2b-message\" ng-if=\"msgType == 'infoCouldNotLoad'\">\n" +
21903 " <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" +
21904 " <div>Oops!</div>\n" +
21905 " <div>The information could not load at this time.</div>\n" +
21906 " <div>Please <a href=\"javascript:void(0)\" title=\"Refresh the page\" ng-click=\"refreshAction($event)\">refresh the page</a>\n" +
21909 " <div class=\"b2b-message\" ng-if=\"msgType == 'magnifySearch'\">\n" +
21910 " <div class=\"b2b-magnify-glass\"></div>\n" +
21912 " <p class=\"b2b-message-title\">Please input values to\n" +
21913 " <br/> begin your search.</p>\n" +
21916 " <div class=\"b2b-message\" ng-if=\"msgType === 'loadingTable'\">\n" +
21917 " <div class=\"icon-primary-spinner-ddh b2b-loading-dots\"></div>\n" +
21918 " <div ng-transclude></div>\n" +
21924 angular.module("b2bTemplate/tableScrollbar/tableScrollbar.html", []).run(["$templateCache", function($templateCache) {
21925 $templateCache.put("b2bTemplate/tableScrollbar/tableScrollbar.html",
21926 "<div class=\"b2b-table-scrollbar\">\n" +
21927 " <div class=\"b2b-scrollbar-arrows\">\n" +
21928 " <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" +
21929 " <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" +
21931 " <div class=\"b2b-table-inner-container\">\n" +
21932 " <span ng-transclude></span>\n" +
21937 angular.module("b2bTemplate/tables/b2bTable.html", []).run(["$templateCache", function($templateCache) {
21938 $templateCache.put("b2bTemplate/tables/b2bTable.html",
21939 "<table ng-class=\"{'striped': responsive, 'complex-table': responsive && active}\" ng-transclude></table>");
21942 angular.module("b2bTemplate/tables/b2bTableBody.html", []).run(["$templateCache", function($templateCache) {
21943 $templateCache.put("b2bTemplate/tables/b2bTableBody.html",
21944 "<td ng-hide=\"isHidden()\" ng-transclude></td>");
21947 angular.module("b2bTemplate/tables/b2bTableHeaderSortable.html", []).run(["$templateCache", function($templateCache) {
21948 $templateCache.put("b2bTemplate/tables/b2bTableHeaderSortable.html",
21949 "<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" +
21950 " <span ng-transclude></span>\n" +
21951 " <i ng-class=\"{'icon-primary-arrows-sort-arrow active': sortPattern === 'ascending', 'icon-primary-arrows-sort-arrow active down': sortPattern === 'descending'}\"></i>\n" +
21955 angular.module("b2bTemplate/tables/b2bTableHeaderUnsortable.html", []).run(["$templateCache", function($templateCache) {
21956 $templateCache.put("b2bTemplate/tables/b2bTableHeaderUnsortable.html",
21957 "<th scope=\"col\" ng-hide=\"isHidden()\" ng-transclude></th>");
21960 angular.module("b2bTemplate/tabs/b2bTab.html", []).run(["$templateCache", function($templateCache) {
21961 $templateCache.put("b2bTemplate/tabs/b2bTab.html",
21962 "<li role=\"tab\" aria-selected=\"{{isTabActive()}}\" aria-controls=\"{{tabPanelId}}\" class=\"tab\" \n" +
21963 " ng-class=\"{'active': isTabActive()}\" ng-click=\"clickTab()\" ng-hide=\"tabItem.disabled\">\n" +
21964 " <a href=\"javascript:void(0)\" tabindex=\"{{(isTabActive() && tabItem.disabled)?-1:0}}\"\n" +
21965 " ng-disabled=\"tabItem.disabled\" aria-expanded=\"{{(isTabActive() && !tabItem.disabled)}}\" \n" +
21966 " b2b-accessibility-click=\"13,32\" ng-transclude></a>\n" +
21967 " <span class=\"hidden-spoken\" ng-if=\"isTabActive()\">Active tab</span>\n" +
21971 angular.module("b2bTemplate/tabs/b2bTabset.html", []).run(["$templateCache", function($templateCache) {
21972 $templateCache.put("b2bTemplate/tabs/b2bTabset.html",
21973 "<ul class=\"tabs promo-tabs\" role=\"tablist\" ng-transclude></ul>");
21976 angular.module("b2bTemplate/treeNav/groupedTree.html", []).run(["$templateCache", function($templateCache) {
21977 $templateCache.put("b2bTemplate/treeNav/groupedTree.html",
21978 "<ul role=\"group\">\n" +
21979 " <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" +
21980 " <ul role=\"group\">\n" +
21981 " <b2b-member ng-repeat='member in value.childArray' member='member' time-delay='{{timeDelay}}' group-it='groupIt'></b2b-member>\n" +
21987 angular.module("b2bTemplate/treeNav/treeMember.html", []).run(["$templateCache", function($templateCache) {
21988 $templateCache.put("b2bTemplate/treeNav/treeMember.html",
21989 "<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" +
21990 " <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" +
21991 " <span class=\"{{!member.child?'end':''}} b2b-tree-node-icon\">\n" +
21992 " <i class=\"b2b-tree-expandCollapse-icon\" ng-class=\"{'icon-primary-expanded':member.active}\"></i>\n" +
21994 " <div id=\"description_{{$id}}\" class=\"offscreen-text\">\n" +
21995 " {{member.descriptionText}}\n" +
21997 " <div class=\"b2b-tree-tooltip\" ng-if=\"member.showTooltip\">\n" +
21998 " <span class=\"b2b-tree-arrow-left\"></span>\n" +
21999 " <div class=\"b2b-tree-tooltip-content\">\n" +
22000 " {{member.tooltipContent}}\n" +
22007 angular.module("b2bTemplate/treeNav/ungroupedTree.html", []).run(["$templateCache", function($templateCache) {
22008 $templateCache.put("b2bTemplate/treeNav/ungroupedTree.html",
22009 "<ul role=\"{{setRole}}\"><b2b-member ng-repeat='member in collection' member='member' group-it='groupIt'></b2b-member></ul>");
22012 angular.module("b2bTemplate/treeNodeCheckbox/groupedTree.html", []).run(["$templateCache", function($templateCache) {
22013 $templateCache.put("b2bTemplate/treeNodeCheckbox/groupedTree.html",
22014 "<ul role=\"group\">\n" +
22015 " <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" +
22016 " <span class=\"ng-hide\">\n" +
22017 " <label class=\"checkbox\">\n" +
22018 " <input type=\"checkbox\" tabindex=\"-1\" class=\"treeCheckBox grpTreeCheckbox\" style=\"margin-top:2px;\"/><i class=\"skin\"></i><span> {{(key)?key:''}}</span>\n" +
22022 " {{(key)?key:''}} \n" +
22024 " <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" +
22025 " <ul role=\"group\">\n" +
22026 " <b2b-tree-member ng-repeat='member in value.childArray' member='member' group-it='groupIt'></b2b-tree-member>\n" +
22032 angular.module("b2bTemplate/treeNodeCheckbox/treeMember.html", []).run(["$templateCache", function($templateCache) {
22033 $templateCache.put("b2bTemplate/treeNodeCheckbox/treeMember.html",
22034 "<li role=\"treeitem\" aria-expanded=\"{{(member.active?true:false)}}\" aria-label=\"{{member.name}}\" ng-class=\"{'bg':member.selected}\" b2b-tree-node-link>\n" +
22035 " <a tabindex=\"-1\" title=\"{{member.name}}\" href=\"javascript:void(0)\" ng-class=\"{'active':member.active}\">\n" +
22036 " <span ng-show=\"member.displayCheckbox\">\n" +
22037 " <label class=\"checkbox\">\n" +
22038 " <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" +
22041 " <span ng-show=\"!member.displayCheckbox\">\n" +
22042 " {{member.name}} \n" +
22044 " <span class=\"nodeIcon {{!member.child?'end':''}}\">\n" +
22045 " <i class=\"expandCollapseIcon\" ng-class=\"{'icon-primary-expanded':member.active}\"></i>\n" +
22051 angular.module("b2bTemplate/treeNodeCheckbox/ungroupedTree.html", []).run(["$templateCache", function($templateCache) {
22052 $templateCache.put("b2bTemplate/treeNodeCheckbox/ungroupedTree.html",
22053 "<ul role=\"{{setRole}}\"><b2b-tree-member ng-repeat='member in collection' member='member' group-it='groupIt'></b2b-tree-member></ul>");