<commit_before>/*
 * responsive-carousel
 * https://github.com/filamentgroup/responsive-carousel
 *
 * Copyright (c) 2012 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

(function($) {

	var pluginName = "carousel",
		initSelector = "." + pluginName,
		transitionAttr = "data-transition",
		prevAttr = "data-prev",
		prevTitleAttr = "data-prev-title",
		nextAttr = "data-next",
		nextTitleAttr = "data-next-title",
		transitioningClass = pluginName + "-transitioning",
		itemClass = pluginName + "-item",
		activeClass = pluginName + "-active",
		prevClass = pluginName + "-item-prev",
		nextClass = pluginName + "-item-next",
		inClass = pluginName + "-in",
		outClass = pluginName + "-out",
		navClass =  pluginName + "-nav",
		focusables = "a, input, button, select, [tabindex], textarea",
		prototype,
		cssTransitionsSupport = (function(){
			var prefixes = "webkit Moz O Ms".split( " " ),
				supported = false,
				property;

			prefixes.push("");

			while( prefixes.length ){
				property = prefixes.shift();
				property += (property === "" ? "t" : "T" )+ "ransition";

				if ( property in document.documentElement.style !== undefined && property in document.documentElement.style !== false ) {
					supported = true;
					break;
				}
			}
			return supported;
		}()),
		methods = {
			_create: function(){
				$( this )
					.trigger( "beforecreate." + pluginName )
					[ pluginName ]( "_init" )
					[ pluginName ]( "_addNextPrev" )
					.trigger( "create." + pluginName );
			},

			_init: function(){
				var trans = $( this ).attr( transitionAttr );

				if( !trans ){
					cssTransitionsSupport = false;
				}

				$( this )
					.addClass(
						pluginName +
						" " + ( trans ? pluginName + "-" + trans : "" ) + " "
					)
					.attr( "role", "region" )
					.attr( "aria-label", "carousel" )
					.children()
					.addClass( itemClass );

				$(this)[ pluginName ]( "_addNextPrevClasses" );
				$(this)[ pluginName ]( "update" );
				$( this ).data( pluginName + "data", "init"  );
			},

			_addNextPrevClasses: function(){
				var $items = $( this ).find( "." + itemClass ),
					$active = $items.filter( "." + activeClass ),
					$next = $active.next().filter( "." + itemClass ),
					$prev = $active.prev().filter( "." + itemClass );

				if( !$next.length ){
					$next = $items.first().not( "." + activeClass );
				}
				if( !$prev.length ){
					$prev = $items.last().not( "." + activeClass );
				}

				$items.removeClass( prevClass + " " + nextClass );
				$prev.addClass( prevClass );
				$next.addClass( nextClass );

			},

			next: function(){
				$( this )[ pluginName ]( "goTo", "+1" );
			},

			prev: function(){
				$( this )[ pluginName ]( "goTo", "-1" );
			},

			goTo: function( num ){
				if( this.isNavDisabled ){
					return;
				}

				// disable navigation when moving from slide to slide
				// enabled in `_transitionEnd`
				this.isNavDisabled = true;
				var $self = $(this),
					trans = $self.attr( transitionAttr ),
					reverseClass = " " + pluginName + "-" + trans + "-reverse";

				// clean up children
				$( this ).find( "." + itemClass ).removeClass( [ outClass, inClass, reverseClass ].join( " " ) );

				var $from = $( this ).find( "." + activeClass ),
					prevs = $from.index(),
					activeNum = ( prevs < 0 ? 0 : prevs ) + 1,
					nextNum = typeof( num ) === "number" ? num : activeNum + parseFloat(num),
					carouselItems = $( this ).find( "." + itemClass ),
					index = (nextNum - 1) % carouselItems.length,
					beforeGoto = "beforegoto." + pluginName,
					$to = carouselItems.eq( index ),
					reverse = ( typeof( num ) === "string" && !(parseFloat(num)) ) || nextNum > activeNum ? "" : reverseClass,
					data;

				$self.trigger( beforeGoto, data = {
					$from: $from,
					$to: $to,
					direction: nextNum > activeNum ? "forward" : "backward"
				});

				// NOTE this is a quick hack to approximate the api that jQuery provides
				//      without depending on the API (for use with similarly shaped apis)
				if( data.isDefaultPrevented ) {
					return;
				}

				if( !$to.length ){
					$to = $( this ).find( "." + itemClass )[ reverse.length ? "last" : "first" ]();
				}

				if( cssTransitionsSupport ){
					$self[ pluginName ]( "_transitionStart", $from, $to, reverse, index );
				} else {
					$self[ pluginName ]( "_transitionEnd", $from, $to, reverse, index );
				}

				// added to allow pagination to track
				$self.trigger( "goto." + pluginName, [ $to, index ] );
			},

			update: function(){
				var $items = $(this).children().not( "." + navClass );
				var $activeItem = $items.filter( "." + activeClass );
				if( !$activeItem.length ){
					$activeItem = $items.first();
				}

				$items
					.addClass( itemClass )
					.attr( "tabindex", "-1" )
					.attr( "aria-hidden", "true" )
					.attr( "role", "region" )
					.each(function( i ){
						$( this ).attr( "aria-label", "slide " + ( i + 1 ) );
						$( this ).find( focusables ).attr( "tabindex", "-1" );
					});

				$activeItem
					.addClass( activeClass )
					.attr( "tabindex", "0" )
					.each(function( i ){
						$( this ).find( focusables ).attr( "tabindex", "0" );
					})
					.attr( "aria-hidden", "false" );

				return $(this).trigger( "update." + pluginName );
			},

			_transitionStart: function( $from, $to, reverseClass, index ){
				var $self = $(this);
				var self = this;

				var endEvent = navigator.userAgent.indexOf( "AppleWebKit" ) > -1 ? "webkitTransitionEnd" : "transitionend otransitionend";

				$(this).addClass( reverseClass );

				if( reverseClass ){
					// if we are going in reverse we need to wait until the final transition,
					// which is the old slide $from moving out, to re-enable transitions
					$from.one( endEvent, function(){
						self.isNavDisabled = false;
					});

					// when going in reverse we want to move the $to slide into place
					// immediately.
					$to.addClass("no-transition");
					$to.addClass( inClass );

					// once the $to slide is in place then we want to do the normal transition
					// by removing no-transition and letting the removal of classes move things
					// forward
					setTimeout(function(){
						$to.removeClass("no-transition");
						$self[ pluginName ]( "_transitionEnd", $from, $to, reverseClass, index );
					}, 20);
				} else {
					$to.one( endEvent, function(){
						$self[ pluginName ]( "_transitionEnd", $from, $to, reverseClass, index );

						// if we're not going in reverse this is the end of the transitions, enable nav
						if( !reverseClass ){
							self.isNavDisabled = false;
						}
					});

					$from.addClass( outClass );
					$to.addClass( inClass );
				}
			},

			_transitionEnd: function( $from, $to, reverseClass, index ){
				$( this ).removeClass( reverseClass );

				// If the slides are moving forward prevent, the previous slide from
				// transitioning slowly to the slide stack.

				// This prevents botched transitions for 2 slide carousels because,
				// unless there's a third slide to move into position from the right,
				// the slow transition to the stack can leave it in an intermediate
				// state when the user clicks "next" again.
				if( !reverseClass ){
					$from.addClass("no-transition");
					setTimeout(function(){
						$from.removeClass("no-transition");
					});
				}

				$from.removeClass( outClass + " " + activeClass );
				$to.removeClass( inClass ).addClass( activeClass );
				$( this )[ pluginName ]( "update" );
				$( this )[ pluginName ]( "_addNextPrevClasses" );
				$( this ).trigger( "aftergoto." + pluginName, [ $to, index ] );
				if( $( document.activeElement ).closest( $from[ 0 ] ).length ){
					$to.focus();
				}
			},

			_bindEventListeners: function(){
				var $elem = $( this )
					.bind( "click", function( e ){
						var targ = $( e.target ).closest( "a[href='#next'],a[href='#prev']" );
						if( targ.length ){
							$elem[ pluginName ]( targ.is( "[href='#next']" ) ? "next" : "prev" );
							e.preventDefault();
						}
					});

				return this;
			},

			_addNextPrev: function(){
				var $nav, $this = $( this ), $items, $active;

				var prev = $( this ).attr( prevAttr ) || "Prev",
					next = $( this ).attr( nextAttr ) || "Next",
					prevTitle = $( this ).attr( prevTitleAttr) || "Previous",
					nextTitle = $( this ).attr( nextTitleAttr) || "Next";

				$nav = $("" +
					"" + prev + "" +
					"" + next + "" +
					"");

				$this.trigger( "beforecreatenav." + pluginName, { $nav: $nav });

				return $this.append( $nav )[ pluginName ]( "_bindEventListeners" );
			},

			destroy: function(){
				// TODO
			}
		};

	// Collection method.
	$.fn[ pluginName ] = function( arrg, a, b, c ) {
		return this.each(function() {

			// if it's a method
			if( arrg && typeof( arrg ) === "string" ){
				return $.fn[ pluginName ].prototype[ arrg ].call( this, a, b, c );
			}

			// don't re-init
			if( $( this ).data( pluginName + "active" ) ){
				return $( this );
			}

			// otherwise, init
			$( this ).data( pluginName + "active", true );
			$.fn[ pluginName ].prototype._create.call( this );
		});
	};

	// add methods
	prototype = $.extend( $.fn[ pluginName ].prototype, methods );
}(jQuery));
<commit_msg>remove spurious extra check<commit_after>/*
 * responsive-carousel
 * https://github.com/filamentgroup/responsive-carousel
 *
 * Copyright (c) 2012 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

(function($) {

	var pluginName = "carousel",
		initSelector = "." + pluginName,
		transitionAttr = "data-transition",
		prevAttr = "data-prev",
		prevTitleAttr = "data-prev-title",
		nextAttr = "data-next",
		nextTitleAttr = "data-next-title",
		transitioningClass = pluginName + "-transitioning",
		itemClass = pluginName + "-item",
		activeClass = pluginName + "-active",
		prevClass = pluginName + "-item-prev",
		nextClass = pluginName + "-item-next",
		inClass = pluginName + "-in",
		outClass = pluginName + "-out",
		navClass =  pluginName + "-nav",
		focusables = "a, input, button, select, [tabindex], textarea",
		prototype,
		cssTransitionsSupport = (function(){
			var prefixes = "webkit Moz O Ms".split( " " ),
				supported = false,
				property;

			prefixes.push("");

			while( prefixes.length ){
				property = prefixes.shift();
				property += (property === "" ? "t" : "T" )+ "ransition";

				if ( property in document.documentElement.style !== undefined && property in document.documentElement.style !== false ) {
					supported = true;
					break;
				}
			}
			return supported;
		}()),
		methods = {
			_create: function(){
				$( this )
					.trigger( "beforecreate." + pluginName )
					[ pluginName ]( "_init" )
					[ pluginName ]( "_addNextPrev" )
					.trigger( "create." + pluginName );
			},

			_init: function(){
				var trans = $( this ).attr( transitionAttr );

				if( !trans ){
					cssTransitionsSupport = false;
				}

				$( this )
					.addClass(
						pluginName +
						" " + ( trans ? pluginName + "-" + trans : "" ) + " "
					)
					.attr( "role", "region" )
					.attr( "aria-label", "carousel" )
					.children()
					.addClass( itemClass );

				$(this)[ pluginName ]( "_addNextPrevClasses" );
				$(this)[ pluginName ]( "update" );
				$( this ).data( pluginName + "data", "init"  );
			},

			_addNextPrevClasses: function(){
				var $items = $( this ).find( "." + itemClass ),
					$active = $items.filter( "." + activeClass ),
					$next = $active.next().filter( "." + itemClass ),
					$prev = $active.prev().filter( "." + itemClass );

				if( !$next.length ){
					$next = $items.first().not( "." + activeClass );
				}
				if( !$prev.length ){
					$prev = $items.last().not( "." + activeClass );
				}

				$items.removeClass( prevClass + " " + nextClass );
				$prev.addClass( prevClass );
				$next.addClass( nextClass );

			},

			next: function(){
				$( this )[ pluginName ]( "goTo", "+1" );
			},

			prev: function(){
				$( this )[ pluginName ]( "goTo", "-1" );
			},

			goTo: function( num ){
				if( this.isNavDisabled ){
					return;
				}

				// disable navigation when moving from slide to slide
				// enabled in `_transitionEnd`
				this.isNavDisabled = true;
				var $self = $(this),
					trans = $self.attr( transitionAttr ),
					reverseClass = " " + pluginName + "-" + trans + "-reverse";

				// clean up children
				$( this ).find( "." + itemClass ).removeClass( [ outClass, inClass, reverseClass ].join( " " ) );

				var $from = $( this ).find( "." + activeClass ),
					prevs = $from.index(),
					activeNum = ( prevs < 0 ? 0 : prevs ) + 1,
					nextNum = typeof( num ) === "number" ? num : activeNum + parseFloat(num),
					carouselItems = $( this ).find( "." + itemClass ),
					index = (nextNum - 1) % carouselItems.length,
					beforeGoto = "beforegoto." + pluginName,
					$to = carouselItems.eq( index ),
					reverse = ( typeof( num ) === "string" && !(parseFloat(num)) ) || nextNum > activeNum ? "" : reverseClass,
					data;

				$self.trigger( beforeGoto, data = {
					$from: $from,
					$to: $to,
					direction: nextNum > activeNum ? "forward" : "backward"
				});

				// NOTE this is a quick hack to approximate the api that jQuery provides
				//      without depending on the API (for use with similarly shaped apis)
				if( data.isDefaultPrevented ) {
					return;
				}

				if( !$to.length ){
					$to = $( this ).find( "." + itemClass )[ reverse.length ? "last" : "first" ]();
				}

				if( cssTransitionsSupport ){
					$self[ pluginName ]( "_transitionStart", $from, $to, reverse, index );
				} else {
					$self[ pluginName ]( "_transitionEnd", $from, $to, reverse, index );
				}

				// added to allow pagination to track
				$self.trigger( "goto." + pluginName, [ $to, index ] );
			},

			update: function(){
				var $items = $(this).children().not( "." + navClass );
				var $activeItem = $items.filter( "." + activeClass );
				if( !$activeItem.length ){
					$activeItem = $items.first();
				}

				$items
					.addClass( itemClass )
					.attr( "tabindex", "-1" )
					.attr( "aria-hidden", "true" )
					.attr( "role", "region" )
					.each(function( i ){
						$( this ).attr( "aria-label", "slide " + ( i + 1 ) );
						$( this ).find( focusables ).attr( "tabindex", "-1" );
					});

				$activeItem
					.addClass( activeClass )
					.attr( "tabindex", "0" )
					.each(function( i ){
						$( this ).find( focusables ).attr( "tabindex", "0" );
					})
					.attr( "aria-hidden", "false" );

				return $(this).trigger( "update." + pluginName );
			},

			_transitionStart: function( $from, $to, reverseClass, index ){
				var $self = $(this);
				var self = this;

				var endEvent = navigator.userAgent.indexOf( "AppleWebKit" ) > -1 ? "webkitTransitionEnd" : "transitionend otransitionend";

				$(this).addClass( reverseClass );

				if( reverseClass ){
					// if we are going in reverse we need to wait until the final transition,
					// which is the old slide $from moving out, to re-enable transitions
					$from.one( endEvent, function(){
						self.isNavDisabled = false;
					});

					// when going in reverse we want to move the $to slide into place
					// immediately.
					$to.addClass("no-transition");
					$to.addClass( inClass );

					// once the $to slide is in place then we want to do the normal transition
					// by removing no-transition and letting the removal of classes move things
					// forward
					setTimeout(function(){
						$to.removeClass("no-transition");
						$self[ pluginName ]( "_transitionEnd", $from, $to, reverseClass, index );
					}, 20);
				} else {
					$to.one( endEvent, function(){
						$self[ pluginName ]( "_transitionEnd", $from, $to, reverseClass, index );

						// if we're not going in reverse this is the end of the transitions, enable nav
						self.isNavDisabled = false;
					});

					$from.addClass( outClass );
					$to.addClass( inClass );
				}
			},

			_transitionEnd: function( $from, $to, reverseClass, index ){
				$( this ).removeClass( reverseClass );

				// If the slides are moving forward prevent, the previous slide from
				// transitioning slowly to the slide stack.

				// This prevents botched transitions for 2 slide carousels because,
				// unless there's a third slide to move into position from the right,
				// the slow transition to the stack can leave it in an intermediate
				// state when the user clicks "next" again.
				if( !reverseClass ){
					$from.addClass("no-transition");
					setTimeout(function(){
						$from.removeClass("no-transition");
					});
				}

				$from.removeClass( outClass + " " + activeClass );
				$to.removeClass( inClass ).addClass( activeClass );
				$( this )[ pluginName ]( "update" );
				$( this )[ pluginName ]( "_addNextPrevClasses" );
				$( this ).trigger( "aftergoto." + pluginName, [ $to, index ] );
				if( $( document.activeElement ).closest( $from[ 0 ] ).length ){
					$to.focus();
				}
			},

			_bindEventListeners: function(){
				var $elem = $( this )
					.bind( "click", function( e ){
						var targ = $( e.target ).closest( "a[href='#next'],a[href='#prev']" );
						if( targ.length ){
							$elem[ pluginName ]( targ.is( "[href='#next']" ) ? "next" : "prev" );
							e.preventDefault();
						}
					});

				return this;
			},

			_addNextPrev: function(){
				var $nav, $this = $( this ), $items, $active;

				var prev = $( this ).attr( prevAttr ) || "Prev",
					next = $( this ).attr( nextAttr ) || "Next",
					prevTitle = $( this ).attr( prevTitleAttr) || "Previous",
					nextTitle = $( this ).attr( nextTitleAttr) || "Next";

				$nav = $("" +
					"" + prev + "" +
					"" + next + "" +
					"");

				$this.trigger( "beforecreatenav." + pluginName, { $nav: $nav });

				return $this.append( $nav )[ pluginName ]( "_bindEventListeners" );
			},

			destroy: function(){
				// TODO
			}
		};

	// Collection method.
	$.fn[ pluginName ] = function( arrg, a, b, c ) {
		return this.each(function() {

			// if it's a method
			if( arrg && typeof( arrg ) === "string" ){
				return $.fn[ pluginName ].prototype[ arrg ].call( this, a, b, c );
			}

			// don't re-init
			if( $( this ).data( pluginName + "active" ) ){
				return $( this );
			}

			// otherwise, init
			$( this ).data( pluginName + "active", true );
			$.fn[ pluginName ].prototype._create.call( this );
		});
	};

	// add methods
	prototype = $.extend( $.fn[ pluginName ].prototype, methods );
}(jQuery));
<|endoftext|>


<commit_before>import moment from 'moment';
import eventInformationTemplate from 'views/eventDetails/eventInformation.html';
import regOptionsTemplate from 'views/eventDetails/regOptions.html';
import regTypesTemplate from 'views/eventDetails/regTypes.html';
import paymentOptionsTemplate from 'views/eventDetails/paymentOptions.html';
import promotionsTemplate from 'views/eventDetails/promotions.html';
import contactInfoTemplate from 'views/eventDetails/contactInfo.html';
import addRegistrantTypeModalTemplate from 'views/modals/addRegistrantType.html';

angular.module('confRegistrationWebApp')
  .controller('eventDetailsCtrl', function ($rootScope, $scope, $http, $sce, $timeout, $window, $uibModal, modalMessage, $filter, $location, conference, ConfCache, uuid, gettextCatalog) {
    $rootScope.globalPage = {
      type: 'admin',
      mainClass: 'container event-details',
      bodyClass: '',
      confId: conference.id,
      footer: true
    };

    $scope.tabs = [
      {id: 'eventInfo', name: 'Event Information', view: eventInformationTemplate},
      {id: 'regOptions', name: 'Registration Options', view: regOptionsTemplate},
      {id: 'regTypes', name: 'Registrant Types', view: regTypesTemplate},
      {id: 'paymentOptions', name: 'Payment Options', view: paymentOptionsTemplate},
      {id: 'promotions', name: 'Promotions', view: promotionsTemplate},
      {id: 'contactInfo', name: 'Contact Information', view: contactInfoTemplate}
    ];

    $scope.changeTab = function(tab){
      $scope.activeTab = tab;
    };
    $scope.changeTab($scope.tabs[0]);

    $scope.paymentGateways = {
      TSYS: {
        name: gettextCatalog.getString('TSYS'),
        fields: {
          paymentGatewayId: { title: gettextCatalog.getString('Merchant Account ID') }
        }
      }
    };

    $scope.originalConference = conference;
    $scope.conference = angular.copy(conference);

    // Get the payment gateway type for this conference
    $scope.getPaymentGatewayType = function () {
      // The UI will be distorted if the payment gateway type is not a key of $scope.paymentGateways, so treat it as
      // TSYS if it is not a valid payment gateway type.
      if (!_.includes(_.keys($scope.paymentGateways), $scope.conference.paymentGatewayType)) {
        return 'TSYS';
      }

      return $scope.conference.paymentGatewayType;
    };

    $scope.$on('$locationChangeStart', function(event, newLocation) {
      if(!angular.equals(conference, $scope.conference)){
        event.preventDefault();
        modalMessage.confirm({
          title: 'Warning: Unsaved Changes',
          question: 'You have some unsaved changes on this page, are you sure you want to leave? Your changes will be lost.',
          yesString: 'Discard changes',
          noString: 'Stay on this page',
          normalSize: true
        }).then(function(){
          conference = angular.copy($scope.conference);
          $location.url($location.url(newLocation).hash());
        });
      }
    });

    $scope.addPromotion = function(){
      $scope.conference.promotions.push({
        id: uuid(),
        applyToAllRegistrants: true,
        registrantTypeIds: _.map($scope.conference.registrantTypes, 'id'),
        activationDate: $scope.conference.registrationStartTime,
        deactivationDate: $scope.conference.registrationEndTime
      });
    };

    $scope.promotionRegistrantTypeToggle = function (registrantTypes, id) {
      if (registrantTypes.indexOf(id) === -1) {
        registrantTypes.push(id);
      } else {
        registrantTypes.splice(registrantTypes.indexOf(id), 1);
      }
    };

    $scope.addRegType = function(){
      var modalInstance = $uibModal.open({
        templateUrl: addRegistrantTypeModalTemplate,
        controller: function($scope, $uibModalInstance, registrantTypes){
          $scope.types = registrantTypes.data;

          $scope.selectType = function (type) {
            $uibModalInstance.close(type);
          };
        },
        resolve: {
          registrantTypes: function () {
            return $http.get('registranttypes', {cache: true});
          }
        }
      });

      modalInstance.result.then(function(type) {
        type.id = uuid();
        $scope.conference.registrantTypes.push(type);
      });

      return modalInstance;
    };

    $scope.deleteRegType = function(id){
      if ($scope.conference.registrantTypes.length > 1) {
        _.remove($scope.conference.registrantTypes, function(type) { return type.id === id; });
      } else {
        $scope.notify = {
          class: 'alert-danger',
          message: $sce.trustAsHtml('You must have at least one registrant type per event.')
        };
        $timeout(function() { $scope.notify = {}; }, 3500);
      }
    };

    $scope.addEarlyRegistrationDiscount = function(type){
      type.earlyRegistrationDiscounts.push({id: uuid(), enabled: true});
    };

    $scope.saveEvent = function () {
      //validation check
      var validationErrors = [];
      var urlPattern = new RegExp(/[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/gi);
      var httpPattern = new RegExp(/^https?:\/\//gi);

      //contact website
      if(!_.isEmpty($scope.conference.contactWebsite)) {
        if (!urlPattern.test($scope.conference.contactWebsite)) {
          validationErrors.push('Please enter a valid contact website.');
        } else {
          if (!httpPattern.test($scope.conference.contactWebsite)) {
            $scope.conference.contactWebsite = 'http://' + $scope.conference.contactWebsite;
          }
        }
      }

      //Event Name
      if (_.isEmpty($scope.conference.name)) {
        validationErrors.push('Please enter an event name.');
      }

      if($scope.conference.abbreviation && $scope.conference.abbreviation.length > 10) {
        validationErrors.push('Event abbreviation must be no longer than 10 characters.');
      }

      //Event Dates
      if ($scope.conference.eventStartTime > $scope.conference.eventEndTime) {
        validationErrors.push('Event end date/time must be after event start date/time.');
      }

      //Registration Window
      if ($scope.conference.registrationStartTime > $scope.conference.registrationEndTime) {
        validationErrors.push('Registration end date/time must be after registration start date/time.');
      }

      //allowEditRegistrationAfterComplete
      if ($scope.conference.allowEditRegistrationAfterComplete && !$scope.conference.requireLogin) {
        validationErrors.push('You must require sign in if allowing users to edit their registration after it\'s complete.');
      }

      //registration complete redirect website
      if(!_.isEmpty($scope.conference.registrationCompleteRedirect)) {
        urlPattern.lastIndex = 0;
        httpPattern.lastIndex = 0;
        if (!urlPattern.test($scope.conference.registrationCompleteRedirect)) {
          validationErrors.push('Please enter a valid completion redirect website.');
        } else {
          if (!httpPattern.test($scope.conference.registrationCompleteRedirect)) {
            $scope.conference.registrationCompleteRedirect = 'http://' + $scope.conference.registrationCompleteRedirect;
          }
        }
      }

      //Promo codes
      angular.forEach($scope.conference.promotions, function(p, index) {
        if (_.isEmpty(p.code)) {
          validationErrors.push('Please enter a code for promotion ' + (index + 1) + '.');
        }else{
          if(p.code.length < 5 || p.code.length > 20){
            validationErrors.push('Code for promotion ' + (index + 1) + ' must be greater than 5 characters and less than 20.');
          }

          if(/[^a-zA-Z0-9]/.test(p.code)){
            validationErrors.push('Code for promotion ' + (index + 1) + ' must only contain letters and/or numbers.');
          }
        }

        if (!p.amount || Number(p.amount) <= 0) {
          validationErrors.push('Please enter a discount amount greater than 0 for promotion ' + (index + 1) + '.');
        }
      });

      //Registrant Name
      angular.forEach($scope.conference.registrantTypes, function(t) {
        if (_.isEmpty(t.name)) {
          validationErrors.push('Please enter a name for all Registrant types.');
        }
      });

      //Event Cost
      angular.forEach($scope.conference.registrantTypes, function(t) {
        t.cost = Number(t.cost);
        if ( _.isNaN(t.cost) || t.cost < 0) {
          validationErrors.push('Event cost for \'' + t.name + '\' must be a positive number.');
        }
      });

      //Credit cards
      if (_.isEmpty($scope.conference.paymentGatewayId) && _.some($scope.conference.registrantTypes, 'acceptCreditCards')) {
        var paymentGateway = $scope.paymentGateways[$scope.conference.paymentGatewayType];
        var fields = paymentGateway ? _.map(paymentGateway.fields, 'title').join(gettextCatalog.getString(' and ')) : gettextCatalog.getString('fields');
        validationErrors.push(gettextCatalog.getString('Please enter the credit card {{fields}} under the "Payment Options" tab.', { fields: fields }));
      }

      //Minimum Deposit
      angular.forEach($scope.conference.registrantTypes, function(t) {
        if ($scope.conference.requireLogin && $scope.anyPaymentMethodAccepted(t) && String(t.minimumDeposit).length > 0 && !_.isNull(t.minimumDeposit)) {
          t.minimumDeposit = Number(t.minimumDeposit);
          if (t.minimumDeposit > t.cost) {
            validationErrors.push('The minimum deposit for \'' + t.name + '\' must be less than the cost.');
          }
        } else {
          t.minimumDeposit = null;
        }
      });

      //Early bird discount
      angular.forEach($scope.conference.registrantTypes, function(t) {
        angular.forEach(t.earlyRegistrationDiscounts, function(d, index){
          if (d.enabled) {
            d.amountOfDiscount = Number(d.amountOfDiscount);
            if (d.amountOfDiscount <= 0) {
              validationErrors.push('Early registration discount ' + (index + 1) + ' for \'' + t.name + '\' must be a positive number.');
            }
            if (!d.deadline) {
              validationErrors.push('Early registration discount ' + (index + 1) + ' for \'' + t.name + '\' must include a valid date and time.');
            }
          }
        });
      });

      $window.scrollTo(0, 0);
      if (validationErrors.length > 0) {
        var errorMsg = 'Error! Please fix the following issues:
';
        angular.forEach(validationErrors, function (e) {
          errorMsg = errorMsg + '
' + e + '
';
        });
        errorMsg = errorMsg + '
';
        $scope.notify = {
          class: 'alert-danger',
          message: $sce.trustAsHtml(errorMsg)
        };
      } else {
        $scope.notify = {
          class: 'alert-warning',
          message: $sce.trustAsHtml('Saving...')
        };

        var payload = angular.copy($scope.conference);

        // If the conference does not have a gateway type, set it to the default (TSYS) if an id is provided
        if (!payload.paymentGatewayType && payload.paymentGatewayId) {
          payload.paymentGatewayType = 'TSYS';
        }

        $http(
          {method: 'PUT',
            url: 'conferences/' + conference.id,
            data: payload
        }).then(function () {
            $scope.notify = {
              class: 'alert-success',
              message: $sce.trustAsHtml('Saved! Your event details have been updated.')
            };

            $scope.originalConference = conference = angular.copy(payload);
            //Clear cache
            ConfCache.empty();
          }).catch(function (response) {
            $scope.notify = {
              class: 'alert-danger',
              message: $sce.trustAsHtml('Error: ' + (response.data && response.data.error ? response.data.error.message : 'Details could not be saved.'))
            };
          });
      }
    };

    $scope.anyPaymentMethodAccepted = function(type){
      return type.acceptCreditCards || type.acceptChecks || type.acceptTransfers || type.acceptScholarships;
    };

    $scope.previewEmail = function(reg){
      var cost = $filter('currency')(reg.cost, '$');
      var eventStartTime = moment(conference.eventStartTime).format('dddd, MMMM D YYYY, h:mm a');
      var eventEndTime = moment(conference.eventEndTime).format('dddd, MMMM D YYYY, h:mm a');
      modalMessage.info({
        'title': 'Email Preview',
        'message': '
Hello ' + $rootScope.globalGreetingName() + '!

You are registered for ' + $scope.conference.name + '.
' +
        '
Start Time: ' + eventStartTime + '
End Time: ' + eventEndTime + '
' +
        '
Total Cost: ' + cost + '
Total Amount Paid: ' + cost + '
Remaining Balance: $0.00
' +
        reg.customConfirmationEmailText,
        'okString': 'Close'
      });
    };

    $scope.disableField = function(field, defaultTypeKey){
      var fields = {
        groupSubRegistrantType: ['SPOUSE', 'CHILD']
      };
      return _.includes(fields[field], defaultTypeKey);
    };

    $scope.wysiwygButtons = [
      ['bold', 'italic', 'underline'],
      ['font'],
      ['font-size'],
      ['remove-format'],
      ['ordered-list', 'unordered-list'],
      ['left-justify', 'center-justify', 'right-justify'],
      ['link', 'image']
    ];
  });
<commit_msg>Fix discard changes redirect on leaving event details page<commit_after>import moment from 'moment';
import eventInformationTemplate from 'views/eventDetails/eventInformation.html';
import regOptionsTemplate from 'views/eventDetails/regOptions.html';
import regTypesTemplate from 'views/eventDetails/regTypes.html';
import paymentOptionsTemplate from 'views/eventDetails/paymentOptions.html';
import promotionsTemplate from 'views/eventDetails/promotions.html';
import contactInfoTemplate from 'views/eventDetails/contactInfo.html';
import addRegistrantTypeModalTemplate from 'views/modals/addRegistrantType.html';

angular.module('confRegistrationWebApp')
  .controller('eventDetailsCtrl', function ($rootScope, $scope, $http, $sce, $timeout, $window, $uibModal, modalMessage, $filter, $location, conference, ConfCache, uuid, gettextCatalog) {
    $rootScope.globalPage = {
      type: 'admin',
      mainClass: 'container event-details',
      bodyClass: '',
      confId: conference.id,
      footer: true
    };

    $scope.tabs = [
      {id: 'eventInfo', name: 'Event Information', view: eventInformationTemplate},
      {id: 'regOptions', name: 'Registration Options', view: regOptionsTemplate},
      {id: 'regTypes', name: 'Registrant Types', view: regTypesTemplate},
      {id: 'paymentOptions', name: 'Payment Options', view: paymentOptionsTemplate},
      {id: 'promotions', name: 'Promotions', view: promotionsTemplate},
      {id: 'contactInfo', name: 'Contact Information', view: contactInfoTemplate}
    ];

    $scope.changeTab = function(tab){
      $scope.activeTab = tab;
    };
    $scope.changeTab($scope.tabs[0]);

    $scope.paymentGateways = {
      TSYS: {
        name: gettextCatalog.getString('TSYS'),
        fields: {
          paymentGatewayId: { title: gettextCatalog.getString('Merchant Account ID') }
        }
      }
    };

    $scope.originalConference = conference;
    $scope.conference = angular.copy(conference);

    // Get the payment gateway type for this conference
    $scope.getPaymentGatewayType = function () {
      // The UI will be distorted if the payment gateway type is not a key of $scope.paymentGateways, so treat it as
      // TSYS if it is not a valid payment gateway type.
      if (!_.includes(_.keys($scope.paymentGateways), $scope.conference.paymentGatewayType)) {
        return 'TSYS';
      }

      return $scope.conference.paymentGatewayType;
    };

    $scope.$on('$locationChangeStart', function(event) {
      if(!angular.equals(conference, $scope.conference)){
        const newLocation = $location.path();
        event.preventDefault();
        modalMessage.confirm({
          title: 'Warning: Unsaved Changes',
          question: 'You have some unsaved changes on this page, are you sure you want to leave? Your changes will be lost.',
          yesString: 'Discard changes',
          noString: 'Stay on this page',
          normalSize: true
        }).then(function(){
          conference = angular.copy($scope.conference);
          $location.path(newLocation);
        });
      }
    });

    $scope.addPromotion = function(){
      $scope.conference.promotions.push({
        id: uuid(),
        applyToAllRegistrants: true,
        registrantTypeIds: _.map($scope.conference.registrantTypes, 'id'),
        activationDate: $scope.conference.registrationStartTime,
        deactivationDate: $scope.conference.registrationEndTime
      });
    };

    $scope.promotionRegistrantTypeToggle = function (registrantTypes, id) {
      if (registrantTypes.indexOf(id) === -1) {
        registrantTypes.push(id);
      } else {
        registrantTypes.splice(registrantTypes.indexOf(id), 1);
      }
    };

    $scope.addRegType = function(){
      var modalInstance = $uibModal.open({
        templateUrl: addRegistrantTypeModalTemplate,
        controller: function($scope, $uibModalInstance, registrantTypes){
          $scope.types = registrantTypes.data;

          $scope.selectType = function (type) {
            $uibModalInstance.close(type);
          };
        },
        resolve: {
          registrantTypes: function () {
            return $http.get('registranttypes', {cache: true});
          }
        }
      });

      modalInstance.result.then(function(type) {
        type.id = uuid();
        $scope.conference.registrantTypes.push(type);
      });

      return modalInstance;
    };

    $scope.deleteRegType = function(id){
      if ($scope.conference.registrantTypes.length > 1) {
        _.remove($scope.conference.registrantTypes, function(type) { return type.id === id; });
      } else {
        $scope.notify = {
          class: 'alert-danger',
          message: $sce.trustAsHtml('You must have at least one registrant type per event.')
        };
        $timeout(function() { $scope.notify = {}; }, 3500);
      }
    };

    $scope.addEarlyRegistrationDiscount = function(type){
      type.earlyRegistrationDiscounts.push({id: uuid(), enabled: true});
    };

    $scope.saveEvent = function () {
      //validation check
      var validationErrors = [];
      var urlPattern = new RegExp(/[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/gi);
      var httpPattern = new RegExp(/^https?:\/\//gi);

      //contact website
      if(!_.isEmpty($scope.conference.contactWebsite)) {
        if (!urlPattern.test($scope.conference.contactWebsite)) {
          validationErrors.push('Please enter a valid contact website.');
        } else {
          if (!httpPattern.test($scope.conference.contactWebsite)) {
            $scope.conference.contactWebsite = 'http://' + $scope.conference.contactWebsite;
          }
        }
      }

      //Event Name
      if (_.isEmpty($scope.conference.name)) {
        validationErrors.push('Please enter an event name.');
      }

      if($scope.conference.abbreviation && $scope.conference.abbreviation.length > 10) {
        validationErrors.push('Event abbreviation must be no longer than 10 characters.');
      }

      //Event Dates
      if ($scope.conference.eventStartTime > $scope.conference.eventEndTime) {
        validationErrors.push('Event end date/time must be after event start date/time.');
      }

      //Registration Window
      if ($scope.conference.registrationStartTime > $scope.conference.registrationEndTime) {
        validationErrors.push('Registration end date/time must be after registration start date/time.');
      }

      //allowEditRegistrationAfterComplete
      if ($scope.conference.allowEditRegistrationAfterComplete && !$scope.conference.requireLogin) {
        validationErrors.push('You must require sign in if allowing users to edit their registration after it\'s complete.');
      }

      //registration complete redirect website
      if(!_.isEmpty($scope.conference.registrationCompleteRedirect)) {
        urlPattern.lastIndex = 0;
        httpPattern.lastIndex = 0;
        if (!urlPattern.test($scope.conference.registrationCompleteRedirect)) {
          validationErrors.push('Please enter a valid completion redirect website.');
        } else {
          if (!httpPattern.test($scope.conference.registrationCompleteRedirect)) {
            $scope.conference.registrationCompleteRedirect = 'http://' + $scope.conference.registrationCompleteRedirect;
          }
        }
      }

      //Promo codes
      angular.forEach($scope.conference.promotions, function(p, index) {
        if (_.isEmpty(p.code)) {
          validationErrors.push('Please enter a code for promotion ' + (index + 1) + '.');
        }else{
          if(p.code.length < 5 || p.code.length > 20){
            validationErrors.push('Code for promotion ' + (index + 1) + ' must be greater than 5 characters and less than 20.');
          }

          if(/[^a-zA-Z0-9]/.test(p.code)){
            validationErrors.push('Code for promotion ' + (index + 1) + ' must only contain letters and/or numbers.');
          }
        }

        if (!p.amount || Number(p.amount) <= 0) {
          validationErrors.push('Please enter a discount amount greater than 0 for promotion ' + (index + 1) + '.');
        }
      });

      //Registrant Name
      angular.forEach($scope.conference.registrantTypes, function(t) {
        if (_.isEmpty(t.name)) {
          validationErrors.push('Please enter a name for all Registrant types.');
        }
      });

      //Event Cost
      angular.forEach($scope.conference.registrantTypes, function(t) {
        t.cost = Number(t.cost);
        if ( _.isNaN(t.cost) || t.cost < 0) {
          validationErrors.push('Event cost for \'' + t.name + '\' must be a positive number.');
        }
      });

      //Credit cards
      if (_.isEmpty($scope.conference.paymentGatewayId) && _.some($scope.conference.registrantTypes, 'acceptCreditCards')) {
        var paymentGateway = $scope.paymentGateways[$scope.conference.paymentGatewayType];
        var fields = paymentGateway ? _.map(paymentGateway.fields, 'title').join(gettextCatalog.getString(' and ')) : gettextCatalog.getString('fields');
        validationErrors.push(gettextCatalog.getString('Please enter the credit card {{fields}} under the "Payment Options" tab.', { fields: fields }));
      }

      //Minimum Deposit
      angular.forEach($scope.conference.registrantTypes, function(t) {
        if ($scope.conference.requireLogin && $scope.anyPaymentMethodAccepted(t) && String(t.minimumDeposit).length > 0 && !_.isNull(t.minimumDeposit)) {
          t.minimumDeposit = Number(t.minimumDeposit);
          if (t.minimumDeposit > t.cost) {
            validationErrors.push('The minimum deposit for \'' + t.name + '\' must be less than the cost.');
          }
        } else {
          t.minimumDeposit = null;
        }
      });

      //Early bird discount
      angular.forEach($scope.conference.registrantTypes, function(t) {
        angular.forEach(t.earlyRegistrationDiscounts, function(d, index){
          if (d.enabled) {
            d.amountOfDiscount = Number(d.amountOfDiscount);
            if (d.amountOfDiscount <= 0) {
              validationErrors.push('Early registration discount ' + (index + 1) + ' for \'' + t.name + '\' must be a positive number.');
            }
            if (!d.deadline) {
              validationErrors.push('Early registration discount ' + (index + 1) + ' for \'' + t.name + '\' must include a valid date and time.');
            }
          }
        });
      });

      $window.scrollTo(0, 0);
      if (validationErrors.length > 0) {
        var errorMsg = 'Error! Please fix the following issues:
';
        angular.forEach(validationErrors, function (e) {
          errorMsg = errorMsg + '
' + e + '
';
        });
        errorMsg = errorMsg + '
';
        $scope.notify = {
          class: 'alert-danger',
          message: $sce.trustAsHtml(errorMsg)
        };
      } else {
        $scope.notify = {
          class: 'alert-warning',
          message: $sce.trustAsHtml('Saving...')
        };

        var payload = angular.copy($scope.conference);

        // If the conference does not have a gateway type, set it to the default (TSYS) if an id is provided
        if (!payload.paymentGatewayType && payload.paymentGatewayId) {
          payload.paymentGatewayType = 'TSYS';
        }

        $http(
          {method: 'PUT',
            url: 'conferences/' + conference.id,
            data: payload
        }).then(function () {
            $scope.notify = {
              class: 'alert-success',
              message: $sce.trustAsHtml('Saved! Your event details have been updated.')
            };

            $scope.originalConference = conference = angular.copy(payload);
            //Clear cache
            ConfCache.empty();
          }).catch(function (response) {
            $scope.notify = {
              class: 'alert-danger',
              message: $sce.trustAsHtml('Error: ' + (response.data && response.data.error ? response.data.error.message : 'Details could not be saved.'))
            };
          });
      }
    };

    $scope.anyPaymentMethodAccepted = function(type){
      return type.acceptCreditCards || type.acceptChecks || type.acceptTransfers || type.acceptScholarships;
    };

    $scope.previewEmail = function(reg){
      var cost = $filter('currency')(reg.cost, '$');
      var eventStartTime = moment(conference.eventStartTime).format('dddd, MMMM D YYYY, h:mm a');
      var eventEndTime = moment(conference.eventEndTime).format('dddd, MMMM D YYYY, h:mm a');
      modalMessage.info({
        'title': 'Email Preview',
        'message': '
Hello ' + $rootScope.globalGreetingName() + '!

You are registered for ' + $scope.conference.name + '.
' +
        '
Start Time: ' + eventStartTime + '
End Time: ' + eventEndTime + '
' +
        '
Total Cost: ' + cost + '
Total Amount Paid: ' + cost + '
Remaining Balance: $0.00
' +
        reg.customConfirmationEmailText,
        'okString': 'Close'
      });
    };

    $scope.disableField = function(field, defaultTypeKey){
      var fields = {
        groupSubRegistrantType: ['SPOUSE', 'CHILD']
      };
      return _.includes(fields[field], defaultTypeKey);
    };

    $scope.wysiwygButtons = [
      ['bold', 'italic', 'underline'],
      ['font'],
      ['font-size'],
      ['remove-format'],
      ['ordered-list', 'unordered-list'],
      ['left-justify', 'center-justify', 'right-justify'],
      ['link', 'image']
    ];
  });
<|endoftext|>


<commit_before>(function () {
  'use strict';

  angular
    .module('app')
    .factory('FormFactory', FormFactory);

  FormFactory.$inject = ['$document', '$ionicPopup', '$log', '$rootScope', 'DataModelsFactory', 'SpotFactory',
    'StratSectionFactory'];

  function FormFactory($document, $ionicPopup, $log, $rootScope, DataModelsFactory, SpotFactory, StratSectionFactory) {

    var form = {};   // form = {'choices': {}, 'survey': {}};
    var formName = [];

    return {
      'clearForm': clearForm,
      'clearFormElements': clearFormElements,
      'getForm': getForm,
      'getFormName': getFormName,
      'getMax': getMax,
      'getMin': getMin,
      'getPattern': getPattern,
      'handleTraceFeatureToggled': handleTraceFeatureToggled,
      'isRelevant': isRelevant,
      'setForm': setForm,
      //'handleStratModeToggled': handleStratModeToggled,
      'validate': validate,
      'validateForm': validateForm,
      'validateSedData': validateSedData
    };

    /**
     * Private Functions
     */

    /**
     * Public Functions
     */

    function clearForm() {
      form = {};
    }

    function clearFormElements() {
      var formCtrl = angular.element(document.getElementById('straboFormCtrlId')).scope();
      _.each(form.survey, function (field) {
        if (field.name) {
          var ele = document.getElementsByName(field.name)[0];
          if (ele) {
            var formEle = formCtrl.straboForm[ele.name];
            if (formEle && formEle.$valid === false) {
              ele.value = "";
              // formEle.$valid = true;  // These cause an error "Cannot create property 'min' on boolean 'false'"
              // formEle.$error = false; // when typing in a number
            }
          }
        }
      });
    }

    function getForm() {
      return form;
    }

    function getFormName() {
      return formName
    }

    // Get the max value allowed for a number field
    function getMax(constraint) {
      try {
        // Look for <= (= is optional) in constraint, followed by a space and then any number of digits (- preceding the digits is optional)
        var regexMax = /<=?\s(-?\d*)/i;
        // Return just the number
        return regexMax.exec(constraint)[1];
      } catch (e) {
        return undefined;
      }
    }

    // Get the min value allowed for a number field
    function getMin(constraint) {
      try {
        // Look for >= (= is optional) in constraint, followed by a space and any number of digits (- preceding the digits is optional)
        var regexMin = />=?\s(-?\d*)/i;
        // Return just the number
        return regexMin.exec(constraint)[1];
      } catch (e) {
        return undefined;
      }
    }

    // Get the pattern to be applied for specific fields
    function getPattern(fieldName) {
      switch (fieldName) {
        case 'eruption_start_datetime_utc':
        case 'eruption_end_datetime_utc':
          return "\\d{4}-[0-1]\\d{1}-[0-3]\\d{1}T[0-2]\\d{1}:[0-5]\\d{1}:[0-5]\\d{1}Z"
      }
      return undefined;
    }

    // Handle Trace Feature being toggled on off on the Spot Home page
    function handleTraceFeatureToggled(field, data) {
      if (data && !data.trace_feature) {
        var confirmPopup = $ionicPopup.confirm({
          'title': 'Close Group?',
          'template': 'By closing this group you will be clearing all Trace Feature data for this Spot. Continue?'
        });
        confirmPopup.then(function (res) {
          if (res) delete data[field];
          else data[field] = true;
        });
      }
    }

    // Handle Strat Mode being toggled on or off on the Project Preferences page
    /*    function handleStratModeToggled(field, data) {
          if (data[field] && !_.isEmpty(SpotFactory.getSpotsWithOtherSedCharacter())) {
            $log.log('strat mode toggled on');
            var confirmPopup1 = $ionicPopup.confirm({
              'title': 'Entering Strat Mode Warning!',
              'template': 'Are you sure you want to enter Strat Mode? Sed Characteristics of "other" in ALL Spots will be deleted.'
            });
            confirmPopup1.then(function (res) {
              if (res) SpotFactory.deleteOtherSedCharacteristicsForAllSpots();
              else delete data[field];
            });
          }
          else if (!data[field] && (!_.isEmpty(SpotFactory.getSpotsWithIntervalData()) ||
            !_.isEmpty(SpotFactory.getSpotsWithUnmeasuredSedCharacter()))) {
            $log.log('strat mode toggled off');
            var confirmPopup2 = $ionicPopup.confirm({
              'title': 'Leaving Strat Mode Warning!',
              'template': 'Are you sure you want to leave Strat Mode? Data for Interval Thicknesses and ' +
                'Interval Types of "unexposed/covered" or "not measured" in ALL Spots will be deleted.'
            });
            confirmPopup2.then(function (res) {
              if (res) SpotFactory.deleteIntervalDataForAllSpots();
              else data[field] = true;
            });
          }
          else if (!data[field]) SpotFactory.deleteIntervalDataForAllSpots();
        }*/

    // Determine if the field should be shown or not by looking at the relevant key-value pair
    // The 2nd param, properties, is used in the eval method
    function isRelevant(relevant, properties) {
      if (!relevant) return true;

      relevant = relevant.replace(/selected\(\$/g, '_.contains(');
      relevant = relevant.replace(/\$/g, '');
      relevant = relevant.replace(/{/g, 'properties.');
      relevant = relevant.replace(/}/g, '');
      relevant = relevant.replace(/''/g, 'undefined');
      relevant = relevant.replace(/ = /g, ' == ');
      relevant = relevant.replace(/ or /g, ' || ');
      relevant = relevant.replace(/ and /g, ' && ');

      try {
        return eval(relevant);
      } catch (e) {
        return false;
      }
    }

    function setForm(inFormName, type) {
      $log.log('Setting form to', inFormName, type);
      formName = type ? inFormName + '.' + type : inFormName;
      try {
        if (type) {
          form.survey = DataModelsFactory.getDataModel(inFormName)[type].survey;
          form.choices = DataModelsFactory.getDataModel(inFormName)[type].choices;
        }
        else {
          form.survey = DataModelsFactory.getDataModel(inFormName).survey;
          form.choices = DataModelsFactory.getDataModel(inFormName).choices;
        }
      } catch (e) {
        form.survey = {};
        form.choices = {};
      }
      $rootScope.$broadcast('formUpdated', form);
    }

    function validate(data) {
      if (_.isEmpty(data)) return true;
      $log.log('Validating form with data:', data);
      var errorMessages = [];
      var formCtrl = angular.element(document.getElementById('straboFormCtrlId')).scope();
      // If a field is visible and required but empty give the user an error message and return to the form
      _.each(form.survey, function (field) {
        if (field.name) {
          var ele = $document[0].getElementById(field.name);
          var formEle = formCtrl.straboForm[ele.id];
          if (getComputedStyle(ele).display !== 'none' && formEle && formEle.$valid === false) {
            if (field.required === 'true') errorMessages.push('' + field.label + ': Required!');
            else {
              var constraint = field.constraint_message ? field.constraint_message : 'Error in field.';
              errorMessages.push('' + field.label + ': ' + constraint);
            }
          }
          else if (getComputedStyle(ele).display === 'none' && !_.isEmpty(data[field.name])) {
            $log.warn('Deleting form data:', field.name, ':', data[field.name]);
            delete data[field.name];
          }
        }
      });

      if (_.isEmpty(errorMessages)) return true;
      else {
        $ionicPopup.alert({
          'title': 'Validation Error!',
          'template': 'Fix the following errors before continuing:
' + errorMessages.join('
')
        });
        return false;
      }
    }

    // Validate Spot Tab
    function validateForm(stateName, spot, data) {
      var errorMessages = [];
      if (_.isEmpty(form.survey)) {
        if (stateName === 'app.spotTab.spot') {
          if (!spot.properties.name) errorMessages.push('Spot Name is required.');
          if (spot.geometry && spot.geometry.type === 'Point' && (!spot.geometry.coordinates[0] ||
            !spot.geometry.coordinates[1])) {
            errorMessages.push('Both Latitude and longitude are required.');
          }
        }
      }
      else if (validate(data)) {
        if (stateName === 'app.spotTab.spot') {
          if (!spot.properties.name) errorMessages.push('Spot Name is required.');
          if (spot.geometry && spot.geometry.type === 'Point' && (!spot.geometry.coordinates[0] ||
            !spot.geometry.coordinates[1])) {
            errorMessages.push('Both Latitude and longitude are required.');
          }
        }
      }
      else return false;
      if (_.isEmpty(errorMessages)) return true;
      else {
        $ionicPopup.alert({
          'title': 'Data Validation Error',
          'template': errorMessages.join('
')
        });
        return false;
      }
    }

    // Check special conditions for sed data where n is the number of the current lithology
    function validateSedData(spot, n, stateName) {
      var errorMessages = [];
      var isMappedInterval = StratSectionFactory.isMappedInterval(spot);
      var sed = spot.properties.sed;

      // Validation checks for Interval page
      if (stateName === 'app.spotTab.sed-interval' && isMappedInterval) {
        if (sed.interval) {
          var units = StratSectionFactory.getDefaultUnits(spot);
          if (units !== sed.interval.thickness_units) {
            errorMessages.push('- Thickness Units must be ' + units + ' since ' + units +
              ' have been assigned for the properties of this strat section.')
          }
        }
        else {
          errorMessages.push('- Interval data must be specified for a/an ' +
            DataModelsFactory.getLabelFromNewDictionary(sed.character, sed.character) + ' interval.');
        }
      }

      // Validation checks for Lithologies page
      if (stateName === 'app.spotTab.sed-lithologies' && isMappedInterval && (sed.character === 'bed' ||
        sed.character === 'bed_mixed_lit' || sed.character === 'interbedded' || sed.character === 'package_succe')) {
        if (sed.lithologies && sed.lithologies[n]) {
          if (!sed.lithologies[n].primary_lithology) {
            errorMessages.push('- Primary Lithology must be specified for Lithology ' + (n + 1) + ' if there ' +
              'is any type of bedding.');
          }
          if (sed.lithologies[n].primary_lithology === 'siliciclastic'
            && (!sed.lithologies[n].mud_silt_grain_size && !sed.lithologies[n].sand_grain_size
              && !sed.lithologies[n].congl_grain_size && !sed.lithologies[n].breccia_grain_size)) {
            errorMessages.push(
              '- Grain Size must be specified for Lithology ' + (n + 1) + ' if the Primary Lithology is ' +
              'siliciclastic.');
          }
          if ((sed.lithologies[n].primary_lithology === 'limestone'
            || sed.lithologies[n].primary_lithology === 'dolostone') && !sed.lithologies[n].dunham_classification) {
            errorMessages.push(
              '- Dunham Classification must be specified for Lithology ' + (n + 1) + ' if the Primary ' +
              'Lithology is limestone or dolostone.');
          }
        }
        else {
          errorMessages.push('- Lithologies must be specified for a/an ' +
            DataModelsFactory.getLabelFromNewDictionary(sed.character, sed.character) + ' interval.');
        }
      }

      // Validation checks for Bedding page
      if (stateName === 'app.spotTab.sed-bedding') {
        var units = StratSectionFactory.getDefaultUnits(spot);
        if (sed.bedding && ((sed.bedding.package_thickness_units && units !== sed.bedding.package_thickness_units) ||
          (sed.bedding.beds && sed.bedding.beds[n] && sed.bedding.beds[n].interbed_thickness_units &&
            units !== sed.bedding.beds[n].interbed_thickness_units))) {
          errorMessages.push('- Thickness Units must be ' + units + ' since ' + units +
            ' have been assigned for the properties of this strat section.')
        }
        if (isMappedInterval && (sed.character === 'interbedded' || sed.character === 'bed_mixed_lit')) {
          if (sed.bedding && sed.bedding.beds) {
            if (!sed.bedding.interbed_proportion) {
              errorMessages.push('- ' +
                DataModelsFactory.getLabelFromNewDictionary('interbed_proportion', 'interbed_proportion') +
                ' must be specified.');
            }
            if (!sed.bedding.interbed_proportion_change) {
              errorMessages.push('- ' +
                DataModelsFactory.getLabelFromNewDictionary('interbed_proportion_change',
                  'interbed_proportion_change') +
                ' must be specified.');
            }
            else {
              if ((sed.bedding.interbed_proportion_change === 'increase' ||
                sed.bedding.interbed_proportion_change === 'decrease') && (!sed.bedding.beds[n] ||
                (sed.bedding.beds[n] && !(sed.bedding.beds[n].max_thickness && sed.bedding.beds[n].min_thickness)))) {
                errorMessages.push('- Both ' +
                  DataModelsFactory.getLabelFromNewDictionary('max_thickness', 'max_thickness') +
                  ' and ' +
                  DataModelsFactory.getLabelFromNewDictionary('min_thickness', 'min_thickness') +
                  ' must be specified for Lithology ' + (n + 1) + '.');
              }
              else if (sed.bedding.interbed_proportion_change === 'no_change' && (!sed.bedding.beds[n] ||
                (sed.bedding.beds[n] && !sed.bedding.beds[n].avg_thickness))) {
                errorMessages.push(
                  '- ' + DataModelsFactory.getLabelFromNewDictionary('avg_thickness', 'avg_thickness') +
                  ' must be specified for Lithology ' + (n + 1) + '.');
              }
            }
          }
          else {
            errorMessages.push('- Bedding measurements must be specified for a/an ' +
              DataModelsFactory.getLabelFromNewDictionary(sed.character, sed.character) + '  interval.');
          }
        }
      }

      if (_.isEmpty(errorMessages)) return true;
      else {
        $ionicPopup.alert({
          'title': 'Data Validation Error',
          'template': errorMessages.join('
')
        });
        return false;
      }
    }
  }
}());
<commit_msg>Form: Updated form to handle "not" in the skip logic<commit_after>(function () {
  'use strict';

  angular
    .module('app')
    .factory('FormFactory', FormFactory);

  FormFactory.$inject = ['$document', '$ionicPopup', '$log', '$rootScope', 'DataModelsFactory', 'SpotFactory',
    'StratSectionFactory'];

  function FormFactory($document, $ionicPopup, $log, $rootScope, DataModelsFactory, SpotFactory, StratSectionFactory) {

    var form = {};   // form = {'choices': {}, 'survey': {}};
    var formName = [];

    return {
      'clearForm': clearForm,
      'clearFormElements': clearFormElements,
      'getForm': getForm,
      'getFormName': getFormName,
      'getMax': getMax,
      'getMin': getMin,
      'getPattern': getPattern,
      'handleTraceFeatureToggled': handleTraceFeatureToggled,
      'isRelevant': isRelevant,
      'setForm': setForm,
      //'handleStratModeToggled': handleStratModeToggled,
      'validate': validate,
      'validateForm': validateForm,
      'validateSedData': validateSedData
    };

    /**
     * Private Functions
     */

    /**
     * Public Functions
     */

    function clearForm() {
      form = {};
    }

    function clearFormElements() {
      var formCtrl = angular.element(document.getElementById('straboFormCtrlId')).scope();
      _.each(form.survey, function (field) {
        if (field.name) {
          var ele = document.getElementsByName(field.name)[0];
          if (ele) {
            var formEle = formCtrl.straboForm[ele.name];
            if (formEle && formEle.$valid === false) {
              ele.value = "";
              // formEle.$valid = true;  // These cause an error "Cannot create property 'min' on boolean 'false'"
              // formEle.$error = false; // when typing in a number
            }
          }
        }
      });
    }

    function getForm() {
      return form;
    }

    function getFormName() {
      return formName
    }

    // Get the max value allowed for a number field
    function getMax(constraint) {
      try {
        // Look for <= (= is optional) in constraint, followed by a space and then any number of digits (- preceding the digits is optional)
        var regexMax = /<=?\s(-?\d*)/i;
        // Return just the number
        return regexMax.exec(constraint)[1];
      } catch (e) {
        return undefined;
      }
    }

    // Get the min value allowed for a number field
    function getMin(constraint) {
      try {
        // Look for >= (= is optional) in constraint, followed by a space and any number of digits (- preceding the digits is optional)
        var regexMin = />=?\s(-?\d*)/i;
        // Return just the number
        return regexMin.exec(constraint)[1];
      } catch (e) {
        return undefined;
      }
    }

    // Get the pattern to be applied for specific fields
    function getPattern(fieldName) {
      switch (fieldName) {
        case 'eruption_start_datetime_utc':
        case 'eruption_end_datetime_utc':
          return "\\d{4}-[0-1]\\d{1}-[0-3]\\d{1}T[0-2]\\d{1}:[0-5]\\d{1}:[0-5]\\d{1}Z"
      }
      return undefined;
    }

    // Handle Trace Feature being toggled on off on the Spot Home page
    function handleTraceFeatureToggled(field, data) {
      if (data && !data.trace_feature) {
        var confirmPopup = $ionicPopup.confirm({
          'title': 'Close Group?',
          'template': 'By closing this group you will be clearing all Trace Feature data for this Spot. Continue?'
        });
        confirmPopup.then(function (res) {
          if (res) delete data[field];
          else data[field] = true;
        });
      }
    }

    // Handle Strat Mode being toggled on or off on the Project Preferences page
    /*    function handleStratModeToggled(field, data) {
          if (data[field] && !_.isEmpty(SpotFactory.getSpotsWithOtherSedCharacter())) {
            $log.log('strat mode toggled on');
            var confirmPopup1 = $ionicPopup.confirm({
              'title': 'Entering Strat Mode Warning!',
              'template': 'Are you sure you want to enter Strat Mode? Sed Characteristics of "other" in ALL Spots will be deleted.'
            });
            confirmPopup1.then(function (res) {
              if (res) SpotFactory.deleteOtherSedCharacteristicsForAllSpots();
              else delete data[field];
            });
          }
          else if (!data[field] && (!_.isEmpty(SpotFactory.getSpotsWithIntervalData()) ||
            !_.isEmpty(SpotFactory.getSpotsWithUnmeasuredSedCharacter()))) {
            $log.log('strat mode toggled off');
            var confirmPopup2 = $ionicPopup.confirm({
              'title': 'Leaving Strat Mode Warning!',
              'template': 'Are you sure you want to leave Strat Mode? Data for Interval Thicknesses and ' +
                'Interval Types of "unexposed/covered" or "not measured" in ALL Spots will be deleted.'
            });
            confirmPopup2.then(function (res) {
              if (res) SpotFactory.deleteIntervalDataForAllSpots();
              else data[field] = true;
            });
          }
          else if (!data[field]) SpotFactory.deleteIntervalDataForAllSpots();
        }*/

    // Determine if the field should be shown or not by looking at the relevant key-value pair
    // The 2nd param, properties, is used in the eval method
    function isRelevant(relevant, properties) {
      if (!relevant) return true;

      relevant = relevant.replace(/not/g, '!');
      relevant = relevant.replace(/selected\(\$/g, '_.contains(');
      relevant = relevant.replace(/\$/g, '');
      relevant = relevant.replace(/{/g, 'properties.');
      relevant = relevant.replace(/}/g, '');
      relevant = relevant.replace(/''/g, 'undefined');
      relevant = relevant.replace(/ = /g, ' == ');
      relevant = relevant.replace(/ or /g, ' || ');
      relevant = relevant.replace(/ and /g, ' && ');

      try {
        return eval(relevant);
      } catch (e) {
        return false;
      }
    }

    function setForm(inFormName, type) {
      $log.log('Setting form to', inFormName, type);
      formName = type ? inFormName + '.' + type : inFormName;
      try {
        if (type) {
          form.survey = DataModelsFactory.getDataModel(inFormName)[type].survey;
          form.choices = DataModelsFactory.getDataModel(inFormName)[type].choices;
        }
        else {
          form.survey = DataModelsFactory.getDataModel(inFormName).survey;
          form.choices = DataModelsFactory.getDataModel(inFormName).choices;
        }
      } catch (e) {
        form.survey = {};
        form.choices = {};
      }
      $rootScope.$broadcast('formUpdated', form);
    }

    function validate(data) {
      if (_.isEmpty(data)) return true;
      $log.log('Validating form with data:', data);
      var errorMessages = [];
      var formCtrl = angular.element(document.getElementById('straboFormCtrlId')).scope();
      // If a field is visible and required but empty give the user an error message and return to the form
      _.each(form.survey, function (field) {
        if (field.name) {
          var ele = $document[0].getElementById(field.name);
          var formEle = formCtrl.straboForm[ele.id];
          if (getComputedStyle(ele).display !== 'none' && formEle && formEle.$valid === false) {
            if (field.required === 'true') errorMessages.push('' + field.label + ': Required!');
            else {
              var constraint = field.constraint_message ? field.constraint_message : 'Error in field.';
              errorMessages.push('' + field.label + ': ' + constraint);
            }
          }
          else if (getComputedStyle(ele).display === 'none' && !_.isEmpty(data[field.name])) {
            $log.warn('Deleting form data:', field.name, ':', data[field.name]);
            delete data[field.name];
          }
        }
      });

      if (_.isEmpty(errorMessages)) return true;
      else {
        $ionicPopup.alert({
          'title': 'Validation Error!',
          'template': 'Fix the following errors before continuing:
' + errorMessages.join('
')
        });
        return false;
      }
    }

    // Validate Spot Tab
    function validateForm(stateName, spot, data) {
      var errorMessages = [];
      if (_.isEmpty(form.survey)) {
        if (stateName === 'app.spotTab.spot') {
          if (!spot.properties.name) errorMessages.push('Spot Name is required.');
          if (spot.geometry && spot.geometry.type === 'Point' && (!spot.geometry.coordinates[0] ||
            !spot.geometry.coordinates[1])) {
            errorMessages.push('Both Latitude and longitude are required.');
          }
        }
      }
      else if (validate(data)) {
        if (stateName === 'app.spotTab.spot') {
          if (!spot.properties.name) errorMessages.push('Spot Name is required.');
          if (spot.geometry && spot.geometry.type === 'Point' && (!spot.geometry.coordinates[0] ||
            !spot.geometry.coordinates[1])) {
            errorMessages.push('Both Latitude and longitude are required.');
          }
        }
      }
      else return false;
      if (_.isEmpty(errorMessages)) return true;
      else {
        $ionicPopup.alert({
          'title': 'Data Validation Error',
          'template': errorMessages.join('
')
        });
        return false;
      }
    }

    // Check special conditions for sed data where n is the number of the current lithology
    function validateSedData(spot, n, stateName) {
      var errorMessages = [];
      var isMappedInterval = StratSectionFactory.isMappedInterval(spot);
      var sed = spot.properties.sed;

      // Validation checks for Interval page
      if (stateName === 'app.spotTab.sed-interval' && isMappedInterval) {
        if (sed.interval) {
          var units = StratSectionFactory.getDefaultUnits(spot);
          if (units !== sed.interval.thickness_units) {
            errorMessages.push('- Thickness Units must be ' + units + ' since ' + units +
              ' have been assigned for the properties of this strat section.')
          }
        }
        else {
          errorMessages.push('- Interval data must be specified for a/an ' +
            DataModelsFactory.getLabelFromNewDictionary(sed.character, sed.character) + ' interval.');
        }
      }

      // Validation checks for Lithologies page
      if (stateName === 'app.spotTab.sed-lithologies' && isMappedInterval && (sed.character === 'bed' ||
        sed.character === 'bed_mixed_lit' || sed.character === 'interbedded' || sed.character === 'package_succe')) {
        if (sed.lithologies && sed.lithologies[n]) {
          if (!sed.lithologies[n].primary_lithology) {
            errorMessages.push('- Primary Lithology must be specified for Lithology ' + (n + 1) + ' if there ' +
              'is any type of bedding.');
          }
          if (sed.lithologies[n].primary_lithology === 'siliciclastic'
            && (!sed.lithologies[n].mud_silt_grain_size && !sed.lithologies[n].sand_grain_size
              && !sed.lithologies[n].congl_grain_size && !sed.lithologies[n].breccia_grain_size)) {
            errorMessages.push(
              '- Grain Size must be specified for Lithology ' + (n + 1) + ' if the Primary Lithology is ' +
              'siliciclastic.');
          }
          if ((sed.lithologies[n].primary_lithology === 'limestone'
            || sed.lithologies[n].primary_lithology === 'dolostone') && !sed.lithologies[n].dunham_classification) {
            errorMessages.push(
              '- Dunham Classification must be specified for Lithology ' + (n + 1) + ' if the Primary ' +
              'Lithology is limestone or dolostone.');
          }
        }
        else {
          errorMessages.push('- Lithologies must be specified for a/an ' +
            DataModelsFactory.getLabelFromNewDictionary(sed.character, sed.character) + ' interval.');
        }
      }

      // Validation checks for Bedding page
      if (stateName === 'app.spotTab.sed-bedding') {
        var units = StratSectionFactory.getDefaultUnits(spot);
        if (sed.bedding && ((sed.bedding.package_thickness_units && units !== sed.bedding.package_thickness_units) ||
          (sed.bedding.beds && sed.bedding.beds[n] && sed.bedding.beds[n].interbed_thickness_units &&
            units !== sed.bedding.beds[n].interbed_thickness_units))) {
          errorMessages.push('- Thickness Units must be ' + units + ' since ' + units +
            ' have been assigned for the properties of this strat section.')
        }
        if (isMappedInterval && (sed.character === 'interbedded' || sed.character === 'bed_mixed_lit')) {
          if (sed.bedding && sed.bedding.beds) {
            if (!sed.bedding.interbed_proportion) {
              errorMessages.push('- ' +
                DataModelsFactory.getLabelFromNewDictionary('interbed_proportion', 'interbed_proportion') +
                ' must be specified.');
            }
            if (!sed.bedding.interbed_proportion_change) {
              errorMessages.push('- ' +
                DataModelsFactory.getLabelFromNewDictionary('interbed_proportion_change',
                  'interbed_proportion_change') +
                ' must be specified.');
            }
            else {
              if ((sed.bedding.interbed_proportion_change === 'increase' ||
                sed.bedding.interbed_proportion_change === 'decrease') && (!sed.bedding.beds[n] ||
                (sed.bedding.beds[n] && !(sed.bedding.beds[n].max_thickness && sed.bedding.beds[n].min_thickness)))) {
                errorMessages.push('- Both ' +
                  DataModelsFactory.getLabelFromNewDictionary('max_thickness', 'max_thickness') +
                  ' and ' +
                  DataModelsFactory.getLabelFromNewDictionary('min_thickness', 'min_thickness') +
                  ' must be specified for Lithology ' + (n + 1) + '.');
              }
              else if (sed.bedding.interbed_proportion_change === 'no_change' && (!sed.bedding.beds[n] ||
                (sed.bedding.beds[n] && !sed.bedding.beds[n].avg_thickness))) {
                errorMessages.push(
                  '- ' + DataModelsFactory.getLabelFromNewDictionary('avg_thickness', 'avg_thickness') +
                  ' must be specified for Lithology ' + (n + 1) + '.');
              }
            }
          }
          else {
            errorMessages.push('- Bedding measurements must be specified for a/an ' +
              DataModelsFactory.getLabelFromNewDictionary(sed.character, sed.character) + '  interval.');
          }
        }
      }

      if (_.isEmpty(errorMessages)) return true;
      else {
        $ionicPopup.alert({
          'title': 'Data Validation Error',
          'template': errorMessages.join('
')
        });
        return false;
      }
    }
  }
}());
<|endoftext|>


<commit_before>/*
 * responsive-carousel
 * https://github.com/filamentgroup/responsive-carousel
 *
 * Copyright (c) 2012 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

(function($) {

	var pluginName = "carousel",
		initSelector = "." + pluginName,
		transitionAttr = "data-transition",
		prevAttr = "data-prev",
		prevTitleAttr = "data-prev-title",
		nextAttr = "data-next",
		nextTitleAttr = "data-next-title",
		transitioningClass = pluginName + "-transitioning",
		itemClass = pluginName + "-item",
		activeClass = pluginName + "-active",
		prevClass = pluginName + "-item-prev",
		nextClass = pluginName + "-item-next",
		inClass = pluginName + "-in",
		outClass = pluginName + "-out",
		navClass =  pluginName + "-nav",
		prototype,
		cssTransitionsSupport = (function(){
			var prefixes = "webkit Moz O Ms".split( " " ),
				supported = false,
				property;

			while( prefixes.length ){
				property = prefixes.shift() + "Transition";

				if ( property in document.documentElement.style !== undefined && property in document.documentElement.style !== false ) {
					supported = true;
					break;
				}
			}
			return supported;
		}()),
		methods = {
			_create: function(){
				$( this )
					.trigger( "beforecreate." + pluginName )
					[ pluginName ]( "_init" )
					[ pluginName ]( "_addNextPrev" )
					.trigger( "create." + pluginName );
			},

			_init: function(){
				var trans = $( this ).attr( transitionAttr );

				if( !trans ){
					cssTransitionsSupport = false;
				}

				$( this )
					.addClass(
						pluginName +
						" " + ( trans ? pluginName + "-" + trans : "" ) + " "
					)
					.attr( "role", "region" )
					.attr( "aria-label", "carousel" )
					.children()
					.addClass( itemClass );

				$(this)[ pluginName ]( "update" );
				$(this)[ pluginName ]( "_addNextPrevClasses" );
				$( this ).data( pluginName + "data", "init"  );
			},

			_addNextPrevClasses: function(){
				var $items = $( this ).find( "." + itemClass ),
					$active = $items.filter( "." + activeClass ),
					$next = $active.next().filter( "." + itemClass ),
					$prev = $active.prev().filter( "." + itemClass );

				if( !$next.length ){
					$next = $items.first().not( "." + activeClass );
				}
				if( !$prev.length ){
					$prev = $items.last().not( "." + activeClass );
				}

				$items.removeClass( prevClass + " " + nextClass );
				$prev.addClass( prevClass );
				$next.addClass( nextClass );

			},

			next: function(){
				$( this )[ pluginName ]( "goTo", "+1" );
			},

			prev: function(){
				$( this )[ pluginName ]( "goTo", "-1" );
			},

			goTo: function( num ){

				var $self = $(this),
					trans = $self.attr( transitionAttr ),
					reverseClass = " " + pluginName + "-" + trans + "-reverse";

				// clean up children
				$( this ).find( "." + itemClass ).removeClass( [ outClass, inClass, reverseClass ].join( " " ) );

				var $from = $( this ).find( "." + activeClass ),
					prevs = $from.index(),
					activeNum = ( prevs < 0 ? 0 : prevs ) + 1,
					nextNum = typeof( num ) === "number" ? num : activeNum + parseFloat(num),
					carouselItems = $( this ).find( "." + itemClass ),
					index = (nextNum - 1) % carouselItems.length,
					beforeGoto = "beforegoto." + pluginName,
					$to = carouselItems.eq( index ),
					reverse = ( typeof( num ) === "string" && !(parseFloat(num)) ) || nextNum > activeNum ? "" : reverseClass,
					data;

				$self.trigger( beforeGoto, data = {
					$from: $from,
					$to: $to,
					direction: nextNum > activeNum ? "forward" : "backward"
				});

				// NOTE this is a quick hack to approximate the api that jQuery provides
				//      without depending on the API (for use with similarly shaped apis)
				if( data.isDefaultPrevented ) {
					return;
				}

				if( !$to.length ){
					$to = $( this ).find( "." + itemClass )[ reverse.length ? "last" : "first" ]();
				}

				if( cssTransitionsSupport ){
					$self[ pluginName ]( "_transitionStart", $from, $to, reverse, index );
				} else {
					$self[ pluginName ]( "_transitionEnd", $from, $to, reverse, index );
				}

				// added to allow pagination to track
				$self.trigger( "goto." + pluginName, [ $to, index ] );
			},

			update: function(){
				$(this).children().not( "." + navClass )
					.addClass( itemClass )
					.attr( "tabindex", "-1" )
					.attr( "aria-hidden", "true" )
					.attr( "role", "region" )
					.each(function( i ){
						$( this ).attr( "aria-label", "slide " + ( i + 1 ) );
					})
					.first()
					.addClass( activeClass )
					.attr( "tabindex", "0" )
					.attr( "aria-hidden", "false" );

				return $(this).trigger( "update." + pluginName );
			},

			_transitionStart: function( $from, $to, reverseClass, index ){
				var $self = $(this);

				$to.one( navigator.userAgent.indexOf( "AppleWebKit" ) > -1 ? "webkitTransitionEnd" : "transitionend otransitionend", function(){
					$self[ pluginName ]( "_transitionEnd", $from, $to, reverseClass, index );
				});

				$(this).addClass( reverseClass );
				$from.addClass( outClass );
				$to.addClass( inClass );
			},

			_transitionEnd: function( $from, $to, reverseClass, index ){
				$( this ).removeClass( reverseClass );
				$from
					.removeClass( outClass + " " + activeClass )
					.attr( "tabindex", "-1" )
					.attr( "aria-hidden", "true" );
				$to
					.removeClass( inClass )
					.addClass( activeClass )
					.attr( "tabindex", "0" )
					.attr( "aria-hidden", "false" );
				$( this )[ pluginName ]( "_addNextPrevClasses" );
				$( this ).trigger( "aftergoto." + pluginName, [ $to, index ] );
				if( $( document.activeElement ).closest( $from[ 0 ] ).length ){
					$to.focus();
				}

			},

			_bindEventListeners: function(){
				var $elem = $( this )
					.bind( "click", function( e ){
						var targ = $( e.target ).closest( "a[href='#next'],a[href='#prev']" );
						if( targ.length ){
							$elem[ pluginName ]( targ.is( "[href='#next']" ) ? "next" : "prev" );
							e.preventDefault();
						}
					});

				return this;
			},

			_addNextPrev: function(){
				var $nav, $this = $( this ), $items, $active;

				var prev = $( this ).attr( prevAttr ) || "Prev",
					next = $( this ).attr( nextAttr ) || "Next",
					prevTitle = $( this ).attr( prevTitleAttr) || "Previous",
					nextTitle = $( this ).attr( nextTitleAttr) || "Next";

				$nav = $("" +
					"" + prev + "" +
					"" + next + "" +
					"");

				$this.trigger( "beforecreatenav." + pluginName, { $nav: $nav });

				return $this.append( $nav )[ pluginName ]( "_bindEventListeners" );
			},

			destroy: function(){
				// TODO
			}
		};

	// Collection method.
	$.fn[ pluginName ] = function( arrg, a, b, c ) {
		return this.each(function() {

			// if it's a method
			if( arrg && typeof( arrg ) === "string" ){
				return $.fn[ pluginName ].prototype[ arrg ].call( this, a, b, c );
			}

			// don't re-init
			if( $( this ).data( pluginName + "active" ) ){
				return $( this );
			}

			// otherwise, init
			$( this ).data( pluginName + "active", true );
			$.fn[ pluginName ].prototype._create.call( this );
		});
	};

	// add methods
	prototype = $.extend( $.fn[ pluginName ].prototype, methods );
}(jQuery));
<commit_msg>manip tabindex on child focusables as well<commit_after>/*
 * responsive-carousel
 * https://github.com/filamentgroup/responsive-carousel
 *
 * Copyright (c) 2012 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

(function($) {

	var pluginName = "carousel",
		initSelector = "." + pluginName,
		transitionAttr = "data-transition",
		prevAttr = "data-prev",
		prevTitleAttr = "data-prev-title",
		nextAttr = "data-next",
		nextTitleAttr = "data-next-title",
		transitioningClass = pluginName + "-transitioning",
		itemClass = pluginName + "-item",
		activeClass = pluginName + "-active",
		prevClass = pluginName + "-item-prev",
		nextClass = pluginName + "-item-next",
		inClass = pluginName + "-in",
		outClass = pluginName + "-out",
		navClass =  pluginName + "-nav",
		focusables = "a, input, button, select, [tabindex], textarea",
		prototype,
		cssTransitionsSupport = (function(){
			var prefixes = "webkit Moz O Ms".split( " " ),
				supported = false,
				property;

			while( prefixes.length ){
				property = prefixes.shift() + "Transition";

				if ( property in document.documentElement.style !== undefined && property in document.documentElement.style !== false ) {
					supported = true;
					break;
				}
			}
			return supported;
		}()),
		methods = {
			_create: function(){
				$( this )
					.trigger( "beforecreate." + pluginName )
					[ pluginName ]( "_init" )
					[ pluginName ]( "_addNextPrev" )
					.trigger( "create." + pluginName );
			},

			_init: function(){
				var trans = $( this ).attr( transitionAttr );

				if( !trans ){
					cssTransitionsSupport = false;
				}

				$( this )
					.addClass(
						pluginName +
						" " + ( trans ? pluginName + "-" + trans : "" ) + " "
					)
					.attr( "role", "region" )
					.attr( "aria-label", "carousel" )
					.children()
					.addClass( itemClass );

				$(this)[ pluginName ]( "_addNextPrevClasses" );
				$(this)[ pluginName ]( "update" );
				$( this ).data( pluginName + "data", "init"  );
			},

			_addNextPrevClasses: function(){
				var $items = $( this ).find( "." + itemClass ),
					$active = $items.filter( "." + activeClass ),
					$next = $active.next().filter( "." + itemClass ),
					$prev = $active.prev().filter( "." + itemClass );

				if( !$next.length ){
					$next = $items.first().not( "." + activeClass );
				}
				if( !$prev.length ){
					$prev = $items.last().not( "." + activeClass );
				}

				$items.removeClass( prevClass + " " + nextClass );
				$prev.addClass( prevClass );
				$next.addClass( nextClass );

			},

			next: function(){
				$( this )[ pluginName ]( "goTo", "+1" );
			},

			prev: function(){
				$( this )[ pluginName ]( "goTo", "-1" );
			},

			goTo: function( num ){

				var $self = $(this),
					trans = $self.attr( transitionAttr ),
					reverseClass = " " + pluginName + "-" + trans + "-reverse";

				// clean up children
				$( this ).find( "." + itemClass ).removeClass( [ outClass, inClass, reverseClass ].join( " " ) );

				var $from = $( this ).find( "." + activeClass ),
					prevs = $from.index(),
					activeNum = ( prevs < 0 ? 0 : prevs ) + 1,
					nextNum = typeof( num ) === "number" ? num : activeNum + parseFloat(num),
					carouselItems = $( this ).find( "." + itemClass ),
					index = (nextNum - 1) % carouselItems.length,
					beforeGoto = "beforegoto." + pluginName,
					$to = carouselItems.eq( index ),
					reverse = ( typeof( num ) === "string" && !(parseFloat(num)) ) || nextNum > activeNum ? "" : reverseClass,
					data;

				$self.trigger( beforeGoto, data = {
					$from: $from,
					$to: $to,
					direction: nextNum > activeNum ? "forward" : "backward"
				});

				// NOTE this is a quick hack to approximate the api that jQuery provides
				//      without depending on the API (for use with similarly shaped apis)
				if( data.isDefaultPrevented ) {
					return;
				}

				if( !$to.length ){
					$to = $( this ).find( "." + itemClass )[ reverse.length ? "last" : "first" ]();
				}

				if( cssTransitionsSupport ){
					$self[ pluginName ]( "_transitionStart", $from, $to, reverse, index );
				} else {
					$self[ pluginName ]( "_transitionEnd", $from, $to, reverse, index );
				}

				// added to allow pagination to track
				$self.trigger( "goto." + pluginName, [ $to, index ] );
			},

			update: function(){
				var $items = $(this).children().not( "." + navClass );
				var $activeItem = $items.filter( "." + activeClass );
				if( !$activeItem.length ){
					$activeItem = $items.first();
				}

				$items
					.addClass( itemClass )
					.attr( "tabindex", "-1" )
					.attr( "aria-hidden", "true" )
					.attr( "role", "region" )
					.each(function( i ){
						$( this ).attr( "aria-label", "slide " + ( i + 1 ) );
						$( this ).find( focusables ).attr( "tabindex", "-1" );
					});

				$activeItem
					.addClass( activeClass )
					.attr( "tabindex", "0" )
					.each(function( i ){
						$( this ).find( focusables ).attr( "tabindex", "0" );
					})
					.attr( "aria-hidden", "false" );

				return $(this).trigger( "update." + pluginName );
			},

			_transitionStart: function( $from, $to, reverseClass, index ){
				var $self = $(this);

				$to.one( navigator.userAgent.indexOf( "AppleWebKit" ) > -1 ? "webkitTransitionEnd" : "transitionend otransitionend", function(){
					$self[ pluginName ]( "_transitionEnd", $from, $to, reverseClass, index );
				});

				$(this).addClass( reverseClass );
				$from.addClass( outClass );
				$to.addClass( inClass );
			},

			_transitionEnd: function( $from, $to, reverseClass, index ){
				$( this ).removeClass( reverseClass );
				$from.removeClass( outClass + " " + activeClass );
				$to.removeClass( inClass ).addClass( activeClass );
				$( this )[ pluginName ]( "update" );
				$( this )[ pluginName ]( "_addNextPrevClasses" );
				$( this ).trigger( "aftergoto." + pluginName, [ $to, index ] );
				if( $( document.activeElement ).closest( $from[ 0 ] ).length ){
					$to.focus();
				}

			},

			_bindEventListeners: function(){
				var $elem = $( this )
					.bind( "click", function( e ){
						var targ = $( e.target ).closest( "a[href='#next'],a[href='#prev']" );
						if( targ.length ){
							$elem[ pluginName ]( targ.is( "[href='#next']" ) ? "next" : "prev" );
							e.preventDefault();
						}
					});

				return this;
			},

			_addNextPrev: function(){
				var $nav, $this = $( this ), $items, $active;

				var prev = $( this ).attr( prevAttr ) || "Prev",
					next = $( this ).attr( nextAttr ) || "Next",
					prevTitle = $( this ).attr( prevTitleAttr) || "Previous",
					nextTitle = $( this ).attr( nextTitleAttr) || "Next";

				$nav = $("" +
					"" + prev + "" +
					"" + next + "" +
					"");

				$this.trigger( "beforecreatenav." + pluginName, { $nav: $nav });

				return $this.append( $nav )[ pluginName ]( "_bindEventListeners" );
			},

			destroy: function(){
				// TODO
			}
		};

	// Collection method.
	$.fn[ pluginName ] = function( arrg, a, b, c ) {
		return this.each(function() {

			// if it's a method
			if( arrg && typeof( arrg ) === "string" ){
				return $.fn[ pluginName ].prototype[ arrg ].call( this, a, b, c );
			}

			// don't re-init
			if( $( this ).data( pluginName + "active" ) ){
				return $( this );
			}

			// otherwise, init
			$( this ).data( pluginName + "active", true );
			$.fn[ pluginName ].prototype._create.call( this );
		});
	};

	// add methods
	prototype = $.extend( $.fn[ pluginName ].prototype, methods );
}(jQuery));
<|endoftext|>


<commit_before>'use strict';

angular.module('confRegistrationWebApp')
  .controller('paymentModal', function ($scope, $modalInstance, $http, registration, conference) {
    $scope.registration = registration;
    $scope.conference = conference;
    $scope.currentYear = new Date().getFullYear();
    $scope.processing = false;

    $scope.close = function () {
      $modalInstance.close($scope.registration);
    };

    $scope.registrantName = function(registrantId) {
      var nameBlock = _.find(_.flatten(conference.registrationPages, 'blocks'), { 'profileType': 'NAME' }).id;
      var registrant = _.find(registration.registrants, { 'id': registrantId });
      nameBlock = _.find(registrant.answers, { 'blockId': nameBlock }).value;

      return nameBlock.firstName + ' ' + nameBlock.lastName;
    };

    $scope.newTransaction = {
      registrationId: registration.id,
      amount: registration.remainingBalance
    };

    $scope.updateCostRegistration = [];
    angular.forEach(registration.registrants, function (r) {
      $scope.updateCostRegistration[r.id] = r.calculatedTotalDue;
    });

    $scope.processTransaction = function () {
      if (_.isEmpty($scope.newTransaction.paymentType)) {
        alert('Please select a transaction type.');
        return;
      }
      if (Number($scope.newTransaction.amount) <= 0) {
        alert('Transaction amount must be a positive number.');
        return;
      }

      $scope.processing = true;

      var path = 'payments';
      if($scope.newTransaction.paymentType === 'ADDITIONAL_EXPENSE') {
        path = 'expenses';
        delete $scope.newTransaction.paymentType;
      } else {
          $scope.newTransaction.readyToProcess = true;
      }

      $http.post(path, $scope.newTransaction).success(function () {
        $http.get('registrations/' + $scope.registration.id).success(function (data) {
          $scope.registration = data;
          $scope.processing = false;

          $scope.newTransaction = {
            registrationId: registration.id,
            amount: data.remainingBalance
          };
        });

      }).error(function () {
        alert('Transaction failed...');
        $scope.processing = false;
      });
    };

    $scope.canBeRefunded = function (payment) {
      return payment.paymentType !== 'CREDIT_CARD_REFUND' &&
        payment.paymentType !== 'REFUND' &&
        $scope.calculateRefundableAmount(payment) > 0;
    };

    $scope.calculateRefundableAmount = function (payment) {
      if(angular.isUndefined(payment)) {
        return 0;
      }
      var sum = payment.amount;
      _.each($scope.registration.pastPayments, function (prevRefund) {
        if ((prevRefund.paymentType === 'CREDIT_CARD_REFUND' || prevRefund.paymentType === 'REFUND') &&
          prevRefund.refundedPaymentId === payment.id) {
          sum -= prevRefund.amount;
        }
      });
      return sum;
    };

    $scope.isCreditCardPayment = function () {
      return $scope.paymentToRefund && $scope.paymentToRefund.paymentType === 'CREDIT_CARD';
    };

    $scope.startRefund = function (payment) {
      $scope.paymentToRefund = payment;

      if ($scope.isCreditCardPayment()) {
        $scope.refund = {
          amount: $scope.calculateRefundableAmount(payment),
          refundedPaymentId: payment.id,
          registrationId: payment.registrationId,
          paymentType: 'CREDIT_CARD_REFUND',
          creditCard: {
            lastFourDigits: payment.creditCard.lastFourDigits
          },
          readyToProcess: true
        };
      } else {
        $scope.refund = {
          amount: $scope.calculateRefundableAmount(payment),
          refundedPaymentId: payment.id,
          registrationId: payment.registrationId,
          paymentType: 'REFUND',
          readyToProcess: true
        };
      }
    };

    $scope.processRefund = function () {
      $scope.processing = true;
      $http.post('payments/', $scope.refund).success(function () {
        $http.get('registrations/' + $scope.registration.id).success(function (data) {
          //RegistrationCache.update('registrations/' + data.id, data, function () {});
          $scope.registration = data;
          $scope.processing = false;
          $scope.refund = null;
          if(angular.isDefined($scope.newTransaction)) {
            $scope.newTransaction.amount = data.remainingBalance;
          }
        });
      }).error(function () {
        alert('Refund failed...');
        $scope.processing = false;
      });
    };

    $scope.cancelRefund = function () {
      delete $scope.refund;
    };

    $scope.removeExpense = function (expense) {
      $http.delete('expenses/' + expense.id).success(function() {
        $http.get('registrations/' + $scope.registration.id).success(function (data) {
          $scope.registration = data;
        })
      }).error(function () {
        alert('Error removing expense.');
      })
    }
  });<commit_msg>jshint<commit_after>'use strict';

angular.module('confRegistrationWebApp')
  .controller('paymentModal', function ($scope, $modalInstance, $http, registration, conference) {
    $scope.registration = registration;
    $scope.conference = conference;
    $scope.currentYear = new Date().getFullYear();
    $scope.processing = false;

    $scope.close = function () {
      $modalInstance.close($scope.registration);
    };

    $scope.registrantName = function(registrantId) {
      var nameBlock = _.find(_.flatten(conference.registrationPages, 'blocks'), { 'profileType': 'NAME' }).id;
      var registrant = _.find(registration.registrants, { 'id': registrantId });
      nameBlock = _.find(registrant.answers, { 'blockId': nameBlock }).value;

      return nameBlock.firstName + ' ' + nameBlock.lastName;
    };

    $scope.newTransaction = {
      registrationId: registration.id,
      amount: registration.remainingBalance
    };

    $scope.updateCostRegistration = [];
    angular.forEach(registration.registrants, function (r) {
      $scope.updateCostRegistration[r.id] = r.calculatedTotalDue;
    });

    $scope.processTransaction = function () {
      if (_.isEmpty($scope.newTransaction.paymentType)) {
        alert('Please select a transaction type.');
        return;
      }
      if (Number($scope.newTransaction.amount) <= 0) {
        alert('Transaction amount must be a positive number.');
        return;
      }

      $scope.processing = true;

      var path = 'payments';
      if($scope.newTransaction.paymentType === 'ADDITIONAL_EXPENSE') {
        path = 'expenses';
        delete $scope.newTransaction.paymentType;
      } else {
          $scope.newTransaction.readyToProcess = true;
      }

      $http.post(path, $scope.newTransaction).success(function () {
        $http.get('registrations/' + $scope.registration.id).success(function (data) {
          $scope.registration = data;
          $scope.processing = false;

          $scope.newTransaction = {
            registrationId: registration.id,
            amount: data.remainingBalance
          };
        });

      }).error(function () {
        alert('Transaction failed...');
        $scope.processing = false;
      });
    };

    $scope.canBeRefunded = function (payment) {
      return payment.paymentType !== 'CREDIT_CARD_REFUND' &&
        payment.paymentType !== 'REFUND' &&
        $scope.calculateRefundableAmount(payment) > 0;
    };

    $scope.calculateRefundableAmount = function (payment) {
      if(angular.isUndefined(payment)) {
        return 0;
      }
      var sum = payment.amount;
      _.each($scope.registration.pastPayments, function (prevRefund) {
        if ((prevRefund.paymentType === 'CREDIT_CARD_REFUND' || prevRefund.paymentType === 'REFUND') &&
          prevRefund.refundedPaymentId === payment.id) {
          sum -= prevRefund.amount;
        }
      });
      return sum;
    };

    $scope.isCreditCardPayment = function () {
      return $scope.paymentToRefund && $scope.paymentToRefund.paymentType === 'CREDIT_CARD';
    };

    $scope.startRefund = function (payment) {
      $scope.paymentToRefund = payment;

      if ($scope.isCreditCardPayment()) {
        $scope.refund = {
          amount: $scope.calculateRefundableAmount(payment),
          refundedPaymentId: payment.id,
          registrationId: payment.registrationId,
          paymentType: 'CREDIT_CARD_REFUND',
          creditCard: {
            lastFourDigits: payment.creditCard.lastFourDigits
          },
          readyToProcess: true
        };
      } else {
        $scope.refund = {
          amount: $scope.calculateRefundableAmount(payment),
          refundedPaymentId: payment.id,
          registrationId: payment.registrationId,
          paymentType: 'REFUND',
          readyToProcess: true
        };
      }
    };

    $scope.processRefund = function () {
      $scope.processing = true;
      $http.post('payments/', $scope.refund).success(function () {
        $http.get('registrations/' + $scope.registration.id).success(function (data) {
          //RegistrationCache.update('registrations/' + data.id, data, function () {});
          $scope.registration = data;
          $scope.processing = false;
          $scope.refund = null;
          if(angular.isDefined($scope.newTransaction)) {
            $scope.newTransaction.amount = data.remainingBalance;
          }
        });
      }).error(function () {
        alert('Refund failed...');
        $scope.processing = false;
      });
    };

    $scope.cancelRefund = function () {
      delete $scope.refund;
    };

    $scope.removeExpense = function (expense) {
      $http.delete('expenses/' + expense.id).success(function() {
        $http.get('registrations/' + $scope.registration.id).success(function (data) {
          $scope.registration = data;
        });
      }).error(function () {
        alert('Error removing expense.');
      });
    };
  });<|endoftext|>