<commit_before>// TODO: rework bookmarking to separate out the event broadcasting and the reaction to the events.  With bookmark capability
//		 in the details section as well, this got a little ugly and can be cleaned up and simplified
//		 1: send the event that an update is needed
//		 2: make the ajax call to update and send an event on success or failure
//		 3: respond to the event by updating the classes
(function () {
	'use strict';

	mare.views.Gallery = Backbone.View.extend({
		// this view controls everything inside the element with class 'gallery'
		el: '.gallery',
		// bind standard events to functions within the view
		events: {
			'click .child-media-box'						: 'displayChildDetails',
			'click .sibling-group-media-box'				: 'displaySiblingGroupDetails',
			'click .child-bookmark'							: 'toggleChildBookmark',
			'click .sibling-group-bookmark'					: 'toggleSiblingGroupBookmark',
			'change .waiting-child-profiles__gallery-filter': 'sortGallery'
		},

		/* initialize the gallery view */
		initialize: function initialize() {
			// create a hook to access the gallery template
			var galleryChildrenHtml				= $( '#gallery-children-template' ).html();
			var gallerySiblingGroupsHtml		= $( '#gallery-sibling-groups-template' ).html();
			// compile the templates to be used during rendering/repainting the gallery
			this.childrenTemplate				= Handlebars.compile( galleryChildrenHtml );
			this.siblingGroupsTemplate			= Handlebars.compile( gallerySiblingGroupsHtml );
			// initialize a subview for the details modals
			mare.views.childDetails				= mare.views.childDetails || new mare.views.ChildDetails();
			mare.views.siblingGroupDetails		= mare.views.siblingGroupDetails || new mare.views.SiblingGroupDetails();

			// initialize the gallery once we've fetched the child data needed to display the gallery (this doesn't include child details data)
			mare.promises.childrenDataLoaded.done( function() {
				this.childrenCollection			= mare.collections.galleryChildren;
				this.siblingGroupsCollection	= mare.collections.gallerySiblingGroups;
			}.bind( this ) );

			// bind to change events
			this.on( 'sorted', function() {
				this.render();
			}.bind( this ) );

			// when we get a response from the server that the bookmark for a child has successfully updated, update the view
			mare.collections.galleryChildren.on( 'childBookmarkUpdated', function( registrationNumber, action ) {
				this.updateChildBookmarkView( registrationNumber, action );
			}.bind( this ) );

			// when we get a response from the server that the bookmark for a group of siblings successfully updated, update the view
			mare.collections.galleryChildren.on( 'siblingGroupBookmarkUpdated', function( registrationNumbers, action ) {
				this.updateSiblingGroupBookmarkView( registrationNumbers, action );
			}.bind( this ) );

			// when the details view sends an event to trigger updating the child bookmark, send the request to the server
			mare.collections.galleryChildren.on( 'childBookmarkUpdateNeeded', function( registrationNumber, action ) {
				if( action === 'add' ) {
					this.addChildBookmark( registrationNumber );
				} else if( action === 'remove' ) {
					this.removeChildBookmark( registrationNumber );
				}
			}.bind( this ) );

			// when the details view sends an event to trigger updating the sibling group bookmark, send the request to the server
			mare.collections.galleryChildren.on( 'siblingGroupBookmarkUpdateNeeded', function( registrationNumbers, action ) {
				if( action === 'add' ) {
					this.addSiblingGroupBookmark( registrationNumbers );
				} else if( action === 'remove' ) {
					this.removeSiblingGroupBookmark( registrationNumbers );
				}
			}.bind( this ) );
		},

		/* render the view onto the page */
		render: function render() {
			// store a reference to this for insde callbacks where context is lost
			var view = this;
			// unbind any existing media box plugins
			this.unbindMediaBoxes();
			// the gallery can't render until we have the user permissions and the child data is loaded
			// use the promise bound to both data to delay rendering until we have them
			$.when( mare.promises.permissionsLoaded, mare.promises.childrenDataLoaded ).then( function() {
				// pass the collection data through the gallery template to generate the HTML to be added to the gallery
				var siblingGroupsHtml	= view.siblingGroupsTemplate( view.siblingGroupsCollection.toJSON() );
				var childrenHtml		= view.childrenTemplate( view.childrenCollection.toJSON() );

				view.$( '#children-grid' ).html( siblingGroupsHtml + childrenHtml );
				// once the html is rendered to the page, initialize the gallery display plugin
				view.initializeMediaBoxes();
			});
		},
		/* clearing this search is needed as using modify search while this has content causes
		   the page to render incorrectly when navigating back to the gallery */
		clearRegistrationSearch: function clearRegistrationSearch() {
			this.$( '#registration-number-search' ).val( '' ).trigger( 'keyup' );
		},

		/* ititializes the media box plugin that drives the images in the gallery */
		initializeMediaBoxes: function initializeMediaBoxes() {
			// initialize the photo listing children gallery grid
			$( '#children-grid' ).mediaBoxes({
				boxesToLoadStart: 32,
				boxesToLoad 	: 24,
				search			: '#registration-number-search',
				searchTarget	: '.media-box-registration-number'
			});
		},

		unbindMediaBoxes: function unbindMediaBoxes() {
			$( '#children-grid' ).mediaBoxes( 'destroy' );
		},

		/* pass the request for child details to the subview in charge of the details modal */
		displayChildDetails: function displayChildDetails( event ) {

			mare.views.childDetails.handleGalleryClick( event );
		},

		/* pass the request for sibling group detials to the subview in charge of the details modal */
		displaySiblingGroupDetails: function displaySiblingGroupDetails( event ) {

			mare.views.siblingGroupDetails.handleGalleryClick( event );
		},

		/* determine how to handle a click on the bookmark element */
		toggleChildBookmark: function toggleChildBookmark( event ) {
			// TODO: see if this is needed
			event.stopPropagation();
			// DOM cache the current target for performance
			var $currentTarget = $( event.currentTarget );
			// get the child's registration number to match them in the database
			var registrationNumber = $currentTarget.closest( '.media-box' ).data( 'registration-number' );

			// if we are currently saving the users attempt to toggle the bookmark and the server hasn't processed the change yet, ignore the click event
			if( $currentTarget.hasClass( 'bookmark--disabled' ) ) {

				return;

			// if the child is currently bookmarked, remove them
			} else if( $currentTarget.hasClass( 'bookmark--active' ) ) {

				$currentTarget.addClass( 'bookmark--disabled' );
				this.removeChildBookmark( registrationNumber );

			// if the child is not currently bookmarked, add them
			} else {

				$currentTarget.addClass( 'bookmark--disabled' );
				this.addChildBookmark( registrationNumber );

			}
		},

		/* determine how to handle a click on the bookmark element */
		toggleSiblingGroupBookmark: function toggleSiblingGroupBookmark( event ) {

			event.stopPropagation();
			// DOM cache the current target for performance
			var $currentTarget = $( event.currentTarget );
			// get the child's registration number to match them in the database
			var registrationNumbers = $currentTarget.closest( '.media-box' ).data( 'registration-numbers' );

			// if we are currently saving the users attempt to toggle the bookmark and the server hasn't processed the change yet, ignore the click event
			if( $currentTarget.hasClass( 'bookmark--disabled' ) ) {

				return;

			// if the child is currently bookmarked, remove them
			} else if( $currentTarget.hasClass( 'bookmark--active' ) ) {

				$currentTarget.addClass( 'bookmark--disabled' );
				this.removeSiblingGroupBookmark( registrationNumbers );

			// if the child is not currently bookmarked, add them
			} else {

				$currentTarget.addClass( 'bookmark--disabled' );
				this.addSiblingGroupBookmark( registrationNumbers );

			}
		},
		// TODO: combine the add / remove calls here and for siblings
		/* make a call to the server to bookmark the child */
		addChildBookmark: function addChildBookmark( registrationNumber ) {

			$.ajax({
				url: '/services/add-child-bookmark',
				type: 'POST',
				data: {
					registrationNumber: registrationNumber
				}
			}).done( function( response ) {
				// update the isBookmarked field for the target child model
				mare.collections.galleryChildren.each( function( child ) {
					if( child.get( 'registrationNumber' ) === registrationNumber ) {
						child.set( 'isBookmarked', true );
					}
				});
				// emit an event on the collection showing that the child's bookmark has been updated
				// this is bound to the collection so other views bound to the same data ( the details modal ) can respond
				mare.collections.galleryChildren.trigger( 'childBookmarkUpdated', registrationNumber, 'add' );

			}).fail( function( err ) {
				// TODO: Show an error message to the user
				console.log( err );
			});
		},

		/* make a call to the server to remove the bookmark for the child, then modify the view */
		removeChildBookmark: function removeChildBookmark( registrationNumber ) {

			$.ajax({
				url: '/services/remove-child-bookmark',
				type: 'POST',
				data: {
					registrationNumber: registrationNumber
				}
			}).done( function( response ) {
				// update the isBookmarked field for the target child model
				mare.collections.galleryChildren.each( function( child ) {
					if( child.get( 'registrationNumber' ) === registrationNumber ) {
						child.set( 'isBookmarked', false );
					}
				});
				// TODO: the following event and all like it should trigger off the isBookmarked attribute changing
				// emit an event on the collection showing that the child's bookmark has been updated
				// this is bound to the collection so other views bound to the same data ( the details modal ) can respond
				mare.collections.galleryChildren.trigger( 'childBookmarkUpdated', registrationNumber, 'remove' );

			}).fail( function( err ) {
				// TODO: Show an error message to the user
				console.log( err );
			});

		},

		/* make a call to the server to add bookmarks for all children in the sibling group, then modify the view */
		addSiblingGroupBookmark: function addSiblingGroupBookmark( registrationNumbers ) {

			$.ajax({
				url: '/services/add-sibling-group-bookmark',
				type: 'POST',
				data: {
					registrationNumbers: registrationNumbers
				}
			}).done( function( response ) {
				// create a string array from the string of registration numbers in the sibling group
				var registrationNumbersStringArray = registrationNumbers.split( ',' );
				// create a number array for comparisons from the string array
				var registrationNumbersArray = registrationNumbersStringArray.map( function( numberAsString ) {
					return Number.parseInt( numberAsString, 10 );
				});

				// update the isBookmarked field for the target siblingGroup model
				mare.collections.gallerySiblingGroups.each( function( siblingGroup ) {
					if( _.intersection( registrationNumbersArray, siblingGroup.get( 'registrationNumbers' ) ).length > 0 ) {
						siblingGroup.set( 'isBookmarked', true );
					}
				});
				// emit an event on the collection showing that the sibling group's bookmark has been updated
				// this is bound to the collection so other views bound to the same data ( the details modal ) can respond
				mare.collections.galleryChildren.trigger( 'siblingGroupBookmarkUpdated', registrationNumbers, 'add' );

			}).fail( function( err ) {
				// TODO: Show an error message to the user
				console.log( err );
			});
		},

		/* make a call to the server to remove bookmarks for all children in the sibling group, then modify the view */
		removeSiblingGroupBookmark: function removeSiblingGroupBookmark( registrationNumbers ) {

			$.ajax({
				url: '/services/remove-sibling-group-bookmark',
				type: 'POST',
				data: {
					registrationNumbers: registrationNumbers
				}
			}).done( function( response ) {
				// create a string array from the string of registration numbers in the sibling group
				var registrationNumbersStringArray = registrationNumbers.split( ',' );
				// create a number array for comparisons from the string array
				var registrationNumbersArray = registrationNumbersStringArray.map( function( numberAsString ) {
					return Number.parseInt( numberAsString, 10 );
				});

				// update the isBookmarked field for the target siblingGroup model
				mare.collections.gallerySiblingGroups.each( function( siblingGroup ) {
					if( _.intersection( registrationNumbersArray, siblingGroup.get( 'registrationNumbers' ) ).length > 0 ) {
						siblingGroup.set( 'isBookmarked', false );
					}
				});
				// emit an event on the collection showing that the sibling group's bookmark has been updated
				// this is bound to the collection so other views bound to the same data ( the details modal ) can respond
				mare.collections.galleryChildren.trigger( 'siblingGroupBookmarkUpdated', registrationNumbers, 'remove' );

			}).fail( function( err ) {
				// TODO: Show an error message to the user
				console.log( err );
			});
		},
		// TODO: this can be combined with the handler for sibling groups below
		updateChildBookmarkView: function updateChildBookmarkView( registrationNumber, action ) {

			var targetChild = $( '.media-boxes-container' ).find( "[data-registration-number='" + registrationNumber + "']" );
			var targetButton = targetChild.find( '.bookmark' );

			switch( action ) {
				case 'add':
					// change the icon from a plus to a minus
					targetButton.children( '.bookmark__icon' ).removeClass( 'fa-plus-square-o' ).addClass( 'fa-minus-square-o' );
					targetButton.addClass( 'bookmark--active' );
					break;
				case 'remove':
					// change the icon from a minus to a plus
					targetButton.children( '.bookmark__icon' ).removeClass( 'fa-minus-square-o' ).addClass( 'fa-plus-square-o' );
					targetButton.removeClass( 'bookmark--active' );
					break;
			}

			targetButton.removeClass( 'bookmark--disabled' );
		},

		updateSiblingGroupBookmarkView: function updateSiblingGroupBookmarkView( registrationNumbers, action ) {

			var targetSiblingGroup = $( '.media-boxes-container' ).find( "[data-registration-numbers='" + registrationNumbers + "']" );
			var targetButton = targetSiblingGroup.find( '.bookmark' );

			switch( action ) {
				case 'add':
				// change the icon from a plus to a minus
					targetButton.children( '.bookmark__icon' ).removeClass( 'fa-plus-square-o' ).addClass( 'fa-minus-square-o' );
					targetButton.addClass( 'bookmark--active' );
					break;
				case 'remove':
					// change the icon from a minus to a plus
					targetButton.children( '.bookmark__icon' ).removeClass( 'fa-minus-square-o' ).addClass( 'fa-plus-square-o' );
					targetButton.removeClass( 'bookmark--active' );
					break;
			}

			targetButton.removeClass( 'bookmark--disabled' );
		},

		/* sort the children in the gallery */
		sortGallery: function sortGallery( event ) {
			// get the selected option to sort by from the dropdown menu
			var sortBy = $( event.currentTarget ).val();
			// update the order of the children being shown in the gallery
			mare.collections.galleryChildren.reorder( sortBy );
			// update the order of the sibling groups being shown in the gallery
			mare.collections.gallerySiblingGroups.reorder( sortBy );

			this.trigger( 'sorted' );
		}
	});
}());
<commit_msg>fixes a bug in the gallery where bookmarking a profile and clicking show bookmarked profiles without reloading the page wouldn’t display the newly bookmarked profile<commit_after>// TODO: rework bookmarking to separate out the event broadcasting and the reaction to the events.  With bookmark capability
//		 in the details section as well, this got a little ugly and can be cleaned up and simplified
//		 1: send the event that an update is needed
//		 2: make the ajax call to update and send an event on success or failure
//		 3: respond to the event by updating the classes
(function () {
	'use strict';

	mare.views.Gallery = Backbone.View.extend({
		// this view controls everything inside the element with class 'gallery'
		el: '.gallery',
		// bind standard events to functions within the view
		events: {
			'click .child-media-box'						: 'displayChildDetails',
			'click .sibling-group-media-box'				: 'displaySiblingGroupDetails',
			'click .child-bookmark'							: 'toggleChildBookmark',
			'click .sibling-group-bookmark'					: 'toggleSiblingGroupBookmark',
			'change .waiting-child-profiles__gallery-filter': 'sortGallery'
		},

		/* initialize the gallery view */
		initialize: function initialize() {
			// create a hook to access the gallery template
			var galleryChildrenHtml				= $( '#gallery-children-template' ).html();
			var gallerySiblingGroupsHtml		= $( '#gallery-sibling-groups-template' ).html();
			// compile the templates to be used during rendering/repainting the gallery
			this.childrenTemplate				= Handlebars.compile( galleryChildrenHtml );
			this.siblingGroupsTemplate			= Handlebars.compile( gallerySiblingGroupsHtml );
			// initialize a subview for the details modals
			mare.views.childDetails				= mare.views.childDetails || new mare.views.ChildDetails();
			mare.views.siblingGroupDetails		= mare.views.siblingGroupDetails || new mare.views.SiblingGroupDetails();

			// initialize the gallery once we've fetched the child data needed to display the gallery (this doesn't include child details data)
			mare.promises.childrenDataLoaded.done( function() {
				this.childrenCollection			= mare.collections.galleryChildren;
				this.siblingGroupsCollection	= mare.collections.gallerySiblingGroups;
			}.bind( this ) );

			// bind to change events
			this.on( 'sorted', function() {
				this.render();
			}.bind( this ) );

			// when we get a response from the server that the bookmark for a child has successfully updated, update the view
			mare.collections.galleryChildren.on( 'childBookmarkUpdated', function( registrationNumber, action ) {
				this.updateChildBookmarkView( registrationNumber, action );
			}.bind( this ) );

			// when we get a response from the server that the bookmark for a group of siblings successfully updated, update the view
			mare.collections.galleryChildren.on( 'siblingGroupBookmarkUpdated', function( registrationNumbers, action ) {
				this.updateSiblingGroupBookmarkView( registrationNumbers, action );
			}.bind( this ) );

			// when the details view sends an event to trigger updating the child bookmark, send the request to the server
			mare.collections.galleryChildren.on( 'childBookmarkUpdateNeeded', function( registrationNumber, action ) {
				if( action === 'add' ) {
					this.addChildBookmark( registrationNumber );
				} else if( action === 'remove' ) {
					this.removeChildBookmark( registrationNumber );
				}
			}.bind( this ) );

			// when the details view sends an event to trigger updating the sibling group bookmark, send the request to the server
			mare.collections.galleryChildren.on( 'siblingGroupBookmarkUpdateNeeded', function( registrationNumbers, action ) {
				if( action === 'add' ) {
					this.addSiblingGroupBookmark( registrationNumbers );
				} else if( action === 'remove' ) {
					this.removeSiblingGroupBookmark( registrationNumbers );
				}
			}.bind( this ) );
		},

		/* render the view onto the page */
		render: function render() {
			// store a reference to this for insde callbacks where context is lost
			var view = this;
			// unbind any existing media box plugins
			this.unbindMediaBoxes();
			// the gallery can't render until we have the user permissions and the child data is loaded
			// use the promise bound to both data to delay rendering until we have them
			$.when( mare.promises.permissionsLoaded, mare.promises.childrenDataLoaded ).then( function() {
				// pass the collection data through the gallery template to generate the HTML to be added to the gallery
				var siblingGroupsHtml	= view.siblingGroupsTemplate( view.siblingGroupsCollection.toJSON() );
				var childrenHtml		= view.childrenTemplate( view.childrenCollection.toJSON() );

				view.$( '#children-grid' ).html( siblingGroupsHtml + childrenHtml );
				// once the html is rendered to the page, initialize the gallery display plugin
				view.initializeMediaBoxes();
			});
		},
		/* clearing this search is needed as using modify search while this has content causes
		   the page to render incorrectly when navigating back to the gallery */
		clearRegistrationSearch: function clearRegistrationSearch() {
			this.$( '#registration-number-search' ).val( '' ).trigger( 'keyup' );
		},

		/* ititializes the media box plugin that drives the images in the gallery */
		initializeMediaBoxes: function initializeMediaBoxes() {
			// initialize the photo listing children gallery grid
			$( '#children-grid' ).mediaBoxes({
				boxesToLoadStart: 32,
				boxesToLoad 	: 24,
				search			: '#registration-number-search',
				searchTarget	: '.media-box-registration-number'
			});
		},

		unbindMediaBoxes: function unbindMediaBoxes() {
			$( '#children-grid' ).mediaBoxes( 'destroy' );
		},

		/* pass the request for child details to the subview in charge of the details modal */
		displayChildDetails: function displayChildDetails( event ) {

			mare.views.childDetails.handleGalleryClick( event );
		},

		/* pass the request for sibling group detials to the subview in charge of the details modal */
		displaySiblingGroupDetails: function displaySiblingGroupDetails( event ) {

			mare.views.siblingGroupDetails.handleGalleryClick( event );
		},

		/* determine how to handle a click on the bookmark element */
		toggleChildBookmark: function toggleChildBookmark( event ) {
			// TODO: see if this is needed
			event.stopPropagation();
			// DOM cache the current target for performance
			var $currentTarget = $( event.currentTarget );
			// get the child's registration number to match them in the database
			var registrationNumber = $currentTarget.closest( '.media-box' ).data( 'registration-number' );

			// if we are currently saving the users attempt to toggle the bookmark and the server hasn't processed the change yet, ignore the click event
			if( $currentTarget.hasClass( 'bookmark--disabled' ) ) {

				return;

			// if the child is currently bookmarked, remove them
			} else if( $currentTarget.hasClass( 'bookmark--active' ) ) {

				$currentTarget.addClass( 'bookmark--disabled' );
				this.removeChildBookmark( registrationNumber );

			// if the child is not currently bookmarked, add them
			} else {

				$currentTarget.addClass( 'bookmark--disabled' );
				this.addChildBookmark( registrationNumber );

			}
		},

		/* determine how to handle a click on the bookmark element */
		toggleSiblingGroupBookmark: function toggleSiblingGroupBookmark( event ) {

			event.stopPropagation();
			// DOM cache the current target for performance
			var $currentTarget = $( event.currentTarget );
			// get the child's registration number to match them in the database
			var registrationNumbers = $currentTarget.closest( '.media-box' ).data( 'registration-numbers' );

			// if we are currently saving the users attempt to toggle the bookmark and the server hasn't processed the change yet, ignore the click event
			if( $currentTarget.hasClass( 'bookmark--disabled' ) ) {

				return;

			// if the child is currently bookmarked, remove them
			} else if( $currentTarget.hasClass( 'bookmark--active' ) ) {

				$currentTarget.addClass( 'bookmark--disabled' );
				this.removeSiblingGroupBookmark( registrationNumbers );

			// if the child is not currently bookmarked, add them
			} else {

				$currentTarget.addClass( 'bookmark--disabled' );
				this.addSiblingGroupBookmark( registrationNumbers );

			}
		},
		// TODO: combine the add / remove calls here and for siblings
		/* make a call to the server to bookmark the child */
		addChildBookmark: function addChildBookmark( registrationNumber ) {

			$.ajax({
				url: '/services/add-child-bookmark',
				type: 'POST',
				data: {
					registrationNumber: registrationNumber
				}
			}).done( function( response ) {
				// update the isBookmarked field for the target child model in the display collection
				mare.collections.galleryChildren.each( function( child ) {
					if( child.get( 'registrationNumber' ) === registrationNumber ) {
						child.set( 'isBookmarked', true );
					}
				});
				// update the isBookmarked field for the target child model in the master children collection
				mare.collections.allChildren.each( function( child ) {
					if( child.get( 'registrationNumber' ) === registrationNumber ) {
						child.set( 'isBookmarked', true );
					}
				});
				// emit an event on the collection showing that the child's bookmark has been updated
				// this is bound to the collection so other views bound to the same data ( the details modal ) can respond
				mare.collections.galleryChildren.trigger( 'childBookmarkUpdated', registrationNumber, 'add' );

			}).fail( function( err ) {
				// TODO: Show an error message to the user
				console.log( err );
			});
		},

		/* make a call to the server to remove the bookmark for the child, then modify the view */
		removeChildBookmark: function removeChildBookmark( registrationNumber ) {

			$.ajax({
				url: '/services/remove-child-bookmark',
				type: 'POST',
				data: {
					registrationNumber: registrationNumber
				}
			}).done( function( response ) {
				// update the isBookmarked field for the target child model in the display collection
				mare.collections.galleryChildren.each( function( child ) {
					if( child.get( 'registrationNumber' ) === registrationNumber ) {
						child.set( 'isBookmarked', false );
					}
				});
				// update the isBookmarked field for the target child model in the master children collection
				mare.collections.allChildren.each( function( child ) {
					if( child.get( 'registrationNumber' ) === registrationNumber ) {
						child.set( 'isBookmarked', false );
					}
				});
				// TODO: the following event and all like it should trigger off the isBookmarked attribute changing
				// emit an event on the collection showing that the child's bookmark has been updated
				// this is bound to the collection so other views bound to the same data ( the details modal ) can respond
				mare.collections.galleryChildren.trigger( 'childBookmarkUpdated', registrationNumber, 'remove' );

			}).fail( function( err ) {
				// TODO: Show an error message to the user
				console.log( err );
			});

		},

		/* make a call to the server to add bookmarks for all children in the sibling group, then modify the view */
		addSiblingGroupBookmark: function addSiblingGroupBookmark( registrationNumbers ) {

			$.ajax({
				url: '/services/add-sibling-group-bookmark',
				type: 'POST',
				data: {
					registrationNumbers: registrationNumbers
				}
			}).done( function( response ) {
				// create a string array from the string of registration numbers in the sibling group
				var registrationNumbersStringArray = registrationNumbers.split( ',' );
				// create a number array for comparisons from the string array
				var registrationNumbersArray = registrationNumbersStringArray.map( function( numberAsString ) {
					return Number.parseInt( numberAsString, 10 );
				});

				// update the isBookmarked field for the target siblingGroup model in the display collection
				mare.collections.gallerySiblingGroups.each( function( siblingGroup ) {
					if( _.intersection( registrationNumbersArray, siblingGroup.get( 'registrationNumbers' ) ).length > 0 ) {
						siblingGroup.set( 'isBookmarked', true );
					}
				});
				// update the isBookmarked field for the target siblingGroup model in the master sibling group collection
				mare.collections.allSiblingGroups.each( function( siblingGroup ) {
					if( _.intersection( registrationNumbersArray, siblingGroup.get( 'registrationNumbers' ) ).length > 0 ) {
						siblingGroup.set( 'isBookmarked', true );
					}
				});
				// emit an event on the collection showing that the sibling group's bookmark has been updated
				// this is bound to the collection so other views bound to the same data ( the details modal ) can respond
				mare.collections.galleryChildren.trigger( 'siblingGroupBookmarkUpdated', registrationNumbers, 'add' );

			}).fail( function( err ) {
				// TODO: Show an error message to the user
				console.log( err );
			});
		},

		/* make a call to the server to remove bookmarks for all children in the sibling group, then modify the view */
		removeSiblingGroupBookmark: function removeSiblingGroupBookmark( registrationNumbers ) {

			$.ajax({
				url: '/services/remove-sibling-group-bookmark',
				type: 'POST',
				data: {
					registrationNumbers: registrationNumbers
				}
			}).done( function( response ) {
				// create a string array from the string of registration numbers in the sibling group
				var registrationNumbersStringArray = registrationNumbers.split( ',' );
				// create a number array for comparisons from the string array
				var registrationNumbersArray = registrationNumbersStringArray.map( function( numberAsString ) {
					return Number.parseInt( numberAsString, 10 );
				});

				// update the isBookmarked field for the target siblingGroup model in the display collection
				mare.collections.gallerySiblingGroups.each( function( siblingGroup ) {
					if( _.intersection( registrationNumbersArray, siblingGroup.get( 'registrationNumbers' ) ).length > 0 ) {
						siblingGroup.set( 'isBookmarked', false );
					}
				});
				// update the isBookmarked field for the target siblingGroup model in the master sibling group collection
				mare.collections.allSiblingGroups.each( function( siblingGroup ) {
					if( _.intersection( registrationNumbersArray, siblingGroup.get( 'registrationNumbers' ) ).length > 0 ) {
						siblingGroup.set( 'isBookmarked', false );
					}
				});
				// emit an event on the collection showing that the sibling group's bookmark has been updated
				// this is bound to the collection so other views bound to the same data ( the details modal ) can respond
				mare.collections.galleryChildren.trigger( 'siblingGroupBookmarkUpdated', registrationNumbers, 'remove' );

			}).fail( function( err ) {
				// TODO: Show an error message to the user
				console.log( err );
			});
		},
		// TODO: this can be combined with the handler for sibling groups below
		updateChildBookmarkView: function updateChildBookmarkView( registrationNumber, action ) {

			var targetChild = $( '.media-boxes-container' ).find( "[data-registration-number='" + registrationNumber + "']" );
			var targetButton = targetChild.find( '.bookmark' );

			switch( action ) {
				case 'add':
					// change the icon from a plus to a minus
					targetButton.children( '.bookmark__icon' ).removeClass( 'fa-plus-square-o' ).addClass( 'fa-minus-square-o' );
					targetButton.addClass( 'bookmark--active' );
					break;
				case 'remove':
					// change the icon from a minus to a plus
					targetButton.children( '.bookmark__icon' ).removeClass( 'fa-minus-square-o' ).addClass( 'fa-plus-square-o' );
					targetButton.removeClass( 'bookmark--active' );
					break;
			}

			targetButton.removeClass( 'bookmark--disabled' );
		},

		updateSiblingGroupBookmarkView: function updateSiblingGroupBookmarkView( registrationNumbers, action ) {

			var targetSiblingGroup = $( '.media-boxes-container' ).find( "[data-registration-numbers='" + registrationNumbers + "']" );
			var targetButton = targetSiblingGroup.find( '.bookmark' );

			switch( action ) {
				case 'add':
				// change the icon from a plus to a minus
					targetButton.children( '.bookmark__icon' ).removeClass( 'fa-plus-square-o' ).addClass( 'fa-minus-square-o' );
					targetButton.addClass( 'bookmark--active' );
					break;
				case 'remove':
					// change the icon from a minus to a plus
					targetButton.children( '.bookmark__icon' ).removeClass( 'fa-minus-square-o' ).addClass( 'fa-plus-square-o' );
					targetButton.removeClass( 'bookmark--active' );
					break;
			}

			targetButton.removeClass( 'bookmark--disabled' );
		},

		/* sort the children in the gallery */
		sortGallery: function sortGallery( event ) {
			// get the selected option to sort by from the dropdown menu
			var sortBy = $( event.currentTarget ).val();
			// update the order of the children being shown in the gallery
			mare.collections.galleryChildren.reorder( sortBy );
			// update the order of the sibling groups being shown in the gallery
			mare.collections.gallerySiblingGroups.reorder( sortBy );

			this.trigger( 'sorted' );
		}
	});
}());
<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict';
import yargs from 'yargs';
import path from 'path';

import App from '../app';
import validate from './utils/validate';
import {log, error as wrong, warnLog as error} from '../helper/util';
import pkg from '../../package.json';

const argv = yargs
    .usage('Usage: foxman [options]')
    .alias('C', 'config')
    .alias('C', 'c')
    .describe('C', '配置文件路径')
    .default('C', 'foxman.config.js')

    .alias('P', 'proxy')
    .describe('P', '使用配置中的代理，填写代理名即可')
    .default('P', false)

    .alias('p', 'port')
    .describe('p', '指定临时端口')
    .default('p', false)

    .alias('U', 'update')
    .alias('U', 'u')
    .describe('U', '是否远程nei中拉取接口信息')
    .default('U', false)

    // .command('tpl', 'code generate', function (yargs) {
    //     return yargs.option('T', {
    //         alias: 'type',
    //         default: 'FoxmanConfig'
    //     }).option('W', {
    //         alias: 'where',
    //         default: 'foxman.config.js'
    //     })
    // })

    // .command('lint', 'eslint for foxman', function (yargs) {
    //     return yargs.option('T', {
    //         alias: 'type',
    //         default: 'FoxmanConfig'
    //     }).option('W', {
    //         alias: 'where',
    //         default: 'foxman.config.js'
    //     })
    // })

    .alias('V', 'version')
    .alias('V', 'v')
    .describe('V', 'Show version')
    .version(() => `v${pkg.version}`)

    .help('H')
    .alias('H', 'help')
    .alias('H', 'h')

    .example('foxman -C ./config.json -P local -U')
    .argv;

let config;
const configPath = path.join(process.cwd(), argv.config);

try {
    config = require(configPath);
} catch (err) {
    const e = 'error'.red;
    if (err.code === 'MODULE_NOT_FOUND') {
        if (~err.toString().indexOf(configPath)) {
            error(`Please add foxman.config.js in current directory.`);
            error(`Also you can appoint your special name,`);
            error(`use command 'foxman --config yourfoxman.config.js'.`);
            error(`See more in command 'foxman --help'`);

        } else {
            error(`Make sure you have the latest version of node.js and foxman.`);
            error(`If you do, this is most likely a problem with the plugins used in ${path.join(process.cwd(), 'foxman.config.js')},`);
            error(`not with foxman itself`);
            log('\n');
            log(err.stack);
            log('\n');
            error(`You can try 'npm install' or check the foxman.config.js`);
        }
    } else {
        error(`Maybe it's a problem with foxman.config.js, check it or contact us(http://github.com/kaola-fed/foxman/issues)`);
        log(err)
    }

    process.exit(1);
}

if (argv.port) {
    config.server.port = parseInt(argv.port);
}
if (!config.server.templatePaths)  {
    config.server.templatePaths = {};
}

let res = validate(config);
if (res !== true) {
    wrong(res);
}

App(Object.assign({}, config, {argv}, {configPath}));
<commit_msg>初始化错误提示方法更正<commit_after>#!/usr/bin/env node
'use strict';
import yargs from 'yargs';
import path from 'path';

import App from '../app';
import validate from './utils/validate';
import {log, error as wrong, warnLog as error} from '../helper/util';
import pkg from '../../package.json';

const argv = yargs
    .usage('Usage: foxman [options]')
    .alias('C', 'config')
    .alias('C', 'c')
    .describe('C', '配置文件路径')
    .default('C', 'foxman.config.js')

    .alias('P', 'proxy')
    .describe('P', '使用配置中的代理，填写代理名即可')
    .default('P', false)

    .alias('p', 'port')
    .describe('p', '指定临时端口')
    .default('p', false)

    .alias('U', 'update')
    .alias('U', 'u')
    .describe('U', '是否远程nei中拉取接口信息')
    .default('U', false)

    // .command('tpl', 'code generate', function (yargs) {
    //     return yargs.option('T', {
    //         alias: 'type',
    //         default: 'FoxmanConfig'
    //     }).option('W', {
    //         alias: 'where',
    //         default: 'foxman.config.js'
    //     })
    // })

    // .command('lint', 'eslint for foxman', function (yargs) {
    //     return yargs.option('T', {
    //         alias: 'type',
    //         default: 'FoxmanConfig'
    //     }).option('W', {
    //         alias: 'where',
    //         default: 'foxman.config.js'
    //     })
    // })

    .alias('V', 'version')
    .alias('V', 'v')
    .describe('V', 'Show version')
    .version(() => `v${pkg.version}`)

    .help('H')
    .alias('H', 'help')
    .alias('H', 'h')

    .example('foxman -C ./config.json -P local -U')
    .argv;

let config;
const configPath = path.join(process.cwd(), argv.config);

try {
    config = require(configPath);
} catch (err) {
    const e = 'error'.red;
    if (err.code === 'MODULE_NOT_FOUND') {
        if (~err.toString().indexOf(configPath)) {
            error(`Please add foxman.config.js in current directory.`);
            error(`Also you can appoint your special name,`);
            error(`use command 'foxman --config yourfoxman.config.js'.`);
            error(`See more in command 'foxman --help'`);

        } else {
            error(`Make sure you have the latest version of node.js and foxman.`);
            error(`If you do, this is most likely a problem with the plugins used in ${path.join(process.cwd(), 'foxman.config.js')},`);
            error(`not with foxman itself`);
            console.log('\n');
            console.log(err.stack);
            console.log('\n');
            error(`You can try 'npm install' or check the foxman.config.js`);
        }
    } else {
        error(`Maybe it's a problem with foxman.config.js, check it or contact us(http://github.com/kaola-fed/foxman/issues)`);
        console.log(err);
    }

    process.exit(1);
}

if (argv.port) {
    config.server.port = parseInt(argv.port);
}
if (!config.server.templatePaths)  {
    config.server.templatePaths = {};
}

let res = validate(config);
if (res !== true) {
    wrong(res);
}

App(Object.assign({}, config, {argv}, {configPath}));
<|endoftext|>


<commit_before>const chalk = require('chalk');
const ora = require('ora');
const prettyMs = require('pretty-ms');
const print = console.log;
const printJson = require('jsome');
const logSymbols = require('log-symbols');
const emphasize = require('emphasize');
const map = require('lodash/map');

// json formatting
printJson.colors = {
  'num': 'cyan',
  'str': 'green',
  'bool': 'yellow',
  'regex': 'green',
  'undef': 'grey',
  'null': 'grey',
  'attr': ['white', 'bold'],
  'quot': 'green',
  'punc': 'grey',
  'brack': 'grey',
};

class DefaultFormatter {
  constructor(mode = 0) {
    this.spinner = null;
    this.mode = mode;
  }

  getRequestMessage(request) {
    return `${chalk.bgBlack.bold(request.method)} ${chalk.underline(request.url)}`;
  }

  startRequest(request) {
    let message = this.getRequestMessage(request);

    this.spinner = ora({message, stream: process.stdout});
    this.spinner.start();
  }

  printResponse(response, ms) {
    let symbol, color;
    if (response.status >= 200 && response.status < 300) {
      symbol = logSymbols.success;
      color = chalk.green;
    } else if (response.status >= 400 && response.status < 500) {
      symbol = logSymbols.warning;
      color = chalk.yellow;
    } else if (response.status >= 500) {
      symbol = logSymbols.error;
      color = chalk.red;
    } else {
      symbol = chalk.cyan(process.platform === 'win32' ? 'i' : 'ℹ');
      color = chalk.cyan;
    }

    let requestMessage = this.getRequestMessage(response.config);
    let text = `${color.bold(response.status)} ${color(response.statusText)} `;

    if (this.mode >= 1) {
      text += requestMessage + ' ';
    }

    text += chalk.gray(`[${prettyMs(ms)}]`);
    text += '\n';

    this.spinner.stopAndPersist({symbol, text});

    // Print response headers in verbose mode.
    if (this.mode >= 1 && response.headers) {
      this.printHeaders(response);
    }
  }

  printHeaders(response) {
    map(response.headers, (value, key) => {
      print(`${chalk.bold(key)}${chalk.gray(':')} ${chalk.yellow(value)}`);
    });
    print();
  }

  printError(error, ms) {
    let requestMessage = this.getRequestMessage(error.config);
    let text = `${chalk.red.bold(error.code)} `;

    let details;
    switch (error.code) {
      case 'ENOTFOUND': details = 'The URL didn\'t resolve.'; break;
      default: details = 'An error occurred.';
    }

    text += chalk.red(details);

    if (this.mode >= 1) {
      text += ' ' + requestMessage;
    }

    text += chalk.gray(` [${prettyMs(ms)}]`);
    text += '\n';

    this.spinner.fail(text);

    // Print full error details in verbose mode.
    if (this.mode >= 1) {
      console.error(error.stack);
    }
  }

  printData(response) {
    if (! response.data) {
      return print(chalk.gray('<empty response>'));
    }

    // Handle pretty-printing common types of content (by header).
    if (response.headers && response.headers['content-type']) {
      let type = response.headers['content-type'];

      if (type.includes('application/json')) {
        return printJson(response.data);
      } else if (type.includes('text/html') || type.includes('application/xml')) {
        return print(emphasize.highlight('xml', response.data).value);
      } else if (type.includes('text/plain')) {
        return print(response.data);
      }
    }

    // ...or try to guess!
    return print(emphasize.highlightAuto(response.data).value);
  }
}

module.exports = DefaultFormatter;
<commit_msg>Fix spinner loading message.<commit_after>const chalk = require('chalk');
const ora = require('ora');
const prettyMs = require('pretty-ms');
const print = console.log;
const printJson = require('jsome');
const logSymbols = require('log-symbols');
const emphasize = require('emphasize');
const map = require('lodash/map');

// json formatting
printJson.colors = {
  'num': 'cyan',
  'str': 'green',
  'bool': 'yellow',
  'regex': 'green',
  'undef': 'grey',
  'null': 'grey',
  'attr': ['white', 'bold'],
  'quot': 'green',
  'punc': 'grey',
  'brack': 'grey',
};

class DefaultFormatter {
  constructor(mode = 0) {
    this.spinner = null;
    this.mode = mode;
  }

  getRequestMessage(request) {
    return `${chalk.bgBlack.bold(request.method)} ${chalk.underline(request.url)}`;
  }

  startRequest(request) {
    let text = this.getRequestMessage(request);

    this.spinner = ora({text, stream: process.stdout});
    this.spinner.start();
  }

  printResponse(response, ms) {
    let symbol, color;
    if (response.status >= 200 && response.status < 300) {
      symbol = logSymbols.success;
      color = chalk.green;
    } else if (response.status >= 400 && response.status < 500) {
      symbol = logSymbols.warning;
      color = chalk.yellow;
    } else if (response.status >= 500) {
      symbol = logSymbols.error;
      color = chalk.red;
    } else {
      symbol = chalk.cyan(process.platform === 'win32' ? 'i' : 'ℹ');
      color = chalk.cyan;
    }

    let requestMessage = this.getRequestMessage(response.config);
    let text = `${color.bold(response.status)} ${color(response.statusText)} `;

    if (this.mode >= 1) {
      text += requestMessage + ' ';
    }

    text += chalk.gray(`[${prettyMs(ms)}]`);
    text += '\n';

    this.spinner.stopAndPersist({symbol, text});

    // Print response headers in verbose mode.
    if (this.mode >= 1 && response.headers) {
      this.printHeaders(response);
    }
  }

  printHeaders(response) {
    map(response.headers, (value, key) => {
      print(`${chalk.bold(key)}${chalk.gray(':')} ${chalk.yellow(value)}`);
    });
    print();
  }

  printError(error, ms) {
    let requestMessage = this.getRequestMessage(error.config);
    let text = `${chalk.red.bold(error.code)} `;

    let details;
    switch (error.code) {
      case 'ENOTFOUND': details = 'The URL didn\'t resolve.'; break;
      default: details = 'An error occurred.';
    }

    text += chalk.red(details);

    if (this.mode >= 1) {
      text += ' ' + requestMessage;
    }

    text += chalk.gray(` [${prettyMs(ms)}]`);
    text += '\n';

    this.spinner.fail(text);

    // Print full error details in verbose mode.
    if (this.mode >= 1) {
      console.error(error.stack);
    }
  }

  printData(response) {
    if (! response.data) {
      return print(chalk.gray('<empty response>'));
    }

    // Handle pretty-printing common types of content (by header).
    if (response.headers && response.headers['content-type']) {
      let type = response.headers['content-type'];

      if (type.includes('application/json')) {
        return printJson(response.data);
      } else if (type.includes('text/html') || type.includes('application/xml')) {
        return print(emphasize.highlight('xml', response.data).value);
      } else if (type.includes('text/plain')) {
        return print(response.data);
      }
    }

    // ...or try to guess!
    return print(emphasize.highlightAuto(response.data).value);
  }
}

module.exports = DefaultFormatter;
<|endoftext|>


<commit_before>define(['toastr'], function (toastr) {
	notifyManager = {

		/**
		 * Using notification feature, display a non closable danger message.
		 * @param text text message.
		 * @param html Optional HTML message bellow the title
		 */
		notifyDanger: function (html, title) {
			notifyManager.notify(html, title, 'error');
		},

		/**
 		 * Remove  all notificaions
 		 */
		clear: function () {
			toastr.clear();
		},

		/**
		 * Return the icon markup to associate with the given message type.
		 * @param  {string} type Message type.
		 * @return {string}      html markup of icon.
		 */
		typeToIcon: function (type) {
			type = notifyManager.getTypeFromBusiness(type);
			if (type === 'danger' || type === 'error') {
				return ' ';
			}
			if (type === 'warning') {
				return ' ';
			}
			if (type === 'info') {
				return ' ';
			}
			if ((typeof type) === 'undefined' || type === 'success') {
				return ' ';
			}
			return '';
		},

		/**
		 * Display a message as a notification.
		 * @param text message content.
		 * @param [optional] type 'success' (default), 'info', 'warning', 'danger', 'inverse'.
		 * @see http://nijikokun.github.com/bootstrap-notify/
		 */
		notify: function (text, title, type, position, forever) {
			var options = {
				'closeButton': !forever,
				'newestOnTop': true,
				'progressBar': true,
				'positionClass': position || 'toast-bottom-right',
				'preventDuplicates': false,
				'showDuration': '300',
				'hideDuration': '300',
				'iconClass': 'toastr-custom alert alert-' + ((type === 'error' ? 'danger' : type) || 'info'),
				'timeOut': forever || (type === 'error' ? '20000' : '2000'),
				'extendedTimeOut': forever || '2000',
				'showEasing': 'swing',
				'hideEasing': 'swing',
				'showMethod': 'fadeIn',
				'hideMethod': 'fadeOut'
			};
			toastr[type || 'info'](notifyManager.typeToIcon(type) + text, title, options);
		},

		/**
		 * Return the bootstrap notification type from the business type.
		 * @param businessType the business message type.
		 * @return the bootstrap alert type.
		 */
		getTypeFromBusiness: function (businessType) {
			switch (businessType) {
				case 'business':
					return 'warning';
				case 'technical':
				case 'internal':
					return 'error';
				default:
					return businessType;
			}
		}
	};
	return notifyManager;
});
<commit_msg>Restore broken icon of messages<commit_after>define(['toastr'], function (toastr) {
	notifyManager = {

		/**
		 * Using notification feature, display a non closable danger message.
		 * @param text text message.
		 * @param html Optional HTML message bellow the title
		 */
		notifyDanger: function (html, title) {
			notifyManager.notify(html, title, 'error');
		},

		/**
 		 * Remove  all notificaions
 		 */
		clear: function () {
			toastr.clear();
		},

		/**
		 * Return the icon markup to associate with the given message type.
		 * @param  {string} type Message type.
		 * @return {string}      html markup of icon.
		 */
		typeToIcon: function (type) {
			type = notifyManager.getTypeFromBusiness(type);
			if (type === 'danger' || type === 'error') {
				return ' ';
			}
			if (type === 'warning') {
				return ' ';
			}
			if (type === 'info') {
				return ' ';
			}
			if ((typeof type) === 'undefined' || type === 'success') {
				return ' ';
			}
			return '';
		},

		/**
		 * Display a message as a notification.
		 * @param text message content.
		 * @param [optional] type 'success' (default), 'info', 'warning', 'danger', 'inverse'.
		 * @see http://nijikokun.github.com/bootstrap-notify/
		 */
		notify: function (text, title, type, position, forever) {
			var options = {
				'closeButton': !forever,
				'newestOnTop': true,
				'progressBar': true,
				'positionClass': position || 'toast-bottom-right',
				'preventDuplicates': false,
				'showDuration': '300',
				'hideDuration': '300',
				'iconClass': 'toastr-custom alert alert-' + ((type === 'error' ? 'danger' : type) || 'info'),
				'timeOut': forever || (type === 'error' ? '20000' : '2000'),
				'extendedTimeOut': forever || '2000',
				'showEasing': 'swing',
				'hideEasing': 'swing',
				'showMethod': 'fadeIn',
				'hideMethod': 'fadeOut'
			};
			toastr[type || 'info'](notifyManager.typeToIcon(type) + text, title, options);
		},

		/**
		 * Return the bootstrap notification type from the business type.
		 * @param businessType the business message type.
		 * @return the bootstrap alert type.
		 */
		getTypeFromBusiness: function (businessType) {
			switch (businessType) {
				case 'business':
					return 'warning';
				case 'technical':
				case 'internal':
					return 'error';
				default:
					return businessType;
			}
		}
	};
	return notifyManager;
});
<|endoftext|>


<commit_before>(function ( $ ) {
    $.fn.add_calculation = function( options ) {
        var settings = $.extend({
            // These are the defaults.
            calculation: function (context) { context.result = "???"; },
            subjects: '', // E.g. "CDE01,CDE02" comma separated list of inputs to the calculation
            prefix: '',//  formcode^^sectioncode^^
            target: "value",
            observer: ''  // the cde code of the output e,g, CDE03
        }, options );

        //var subject_codes_string  = _.map(settings.subjects.split(","), function(code){return "#id_" + settings.prefix + code;}).join()

        // locate the codes anywhere on the page ( we assume only one occurance of given cde for now
        function locate_code(code) {
            var id = $('[id*=' + code + ']').attr("id");
            return "#" + id;
        }
        var subject_codes_string = _.map(settings.subjects.split(","), function(code) { return locate_code(code);}).join();
        $(subject_codes_string).on("input, change",function () {
            var context = {};
            var subject_codes = settings.subjects.split(",");

            for(var i=0;i<subject_codes.length;i++) {
                // Note how we use the prefix to map from the page to the context variable names
                // and reverse map to update the output
                var subject_code_id_on_page = locate_code(subject_codes[i]);
                context[subject_codes[i]] = $(subject_code_id_on_page).val();
            }

            try {
                settings.calculation(context);
            }
            catch(err) {
                context.result = "ERROR";
            }

            $("#id_" + settings.prefix + settings.observer).val(context.result);
        });
    };

}( jQuery ));<commit_msg>RDR-426 Forced calculation to be performed on load<commit_after>(function ( $ ) {
    $.fn.add_calculation = function( options ) {
        var settings = $.extend({
            // These are the defaults.
            calculation: function (context) { context.result = "???"; },
            subjects: '', // E.g. "CDE01,CDE02" comma separated list of inputs to the calculation
            prefix: '',//  formcode^^sectioncode^^
            target: "value",
            observer: ''  // the cde code of the output e,g, CDE03
        }, options );

        //var subject_codes_string  = _.map(settings.subjects.split(","), function(code){return "#id_" + settings.prefix + code;}).join()

        // locate the codes anywhere on the page ( we assume only one occurance of given cde for now
        function locate_code(code) {
            var id = $('[id*=' + code + ']').attr("id");
            return "#" + id;
        }
        var subject_codes_string = _.map(settings.subjects.split(","), function(code) { return locate_code(code);}).join();
        var update_function = function () {
            var context = {};
            var subject_codes = settings.subjects.split(",");

            for(var i=0;i<subject_codes.length;i++) {
                // Note how we use the prefix to map from the page to the context variable names
                // and reverse map to update the output
                var subject_code_id_on_page = locate_code(subject_codes[i]);
                context[subject_codes[i]] = $(subject_code_id_on_page).val();
            }

            try {
                settings.calculation(context);
            }
            catch(err) {
                context.result = "ERROR";
            }

            $("#id_" + settings.prefix + settings.observer).val(context.result);
        }

        $(subject_codes_string).on("input, change",update_function);

        try {
            // call on initial page load
            update_function(); // call it to ensure if calculation changes on server , we are always in sync ( RDR-426 )
        }
        catch(err) {
            //alert(err);
        }
    };

}( jQuery ));<|endoftext|>


<commit_before>/*
 *  ping.js - v0.0.1
 *  Ping Utilities in Javascript
 *  http://github.com/alfg/ping.js
 *
 *  Made by Alfred Gutierrez
 *  Under MIT License
 */
/**
 * Creates a Ping instance.
 * @returns {Ping}
 * @constructor
 */
var Ping = function() {
    this._version = "0.0.1";
};

/**
 * Pings source and triggers a callback when completed.
 * @param source Source of the website or server.
 * @param callback Callback function to trigger when completed.
 */
Ping.prototype.ping = function(source, callback) {
    this.img = new Image();

    var start = new Date();
    this.img.onload = this.img.onerror = pingCheck;

    /**
     * Times ping and triggers callback.
     */
    function pingCheck() {
        var pong = new Date() - start;

        if (typeof callback === "function") {
            callback(pong);
        }
    }

    this.img.src = "//" + source + "/?" + (+new Date()); // Trigger image load with cache buster
};

<commit_msg>Update public/libs/bower_components/ping.js/dist/ping.js<commit_after><|endoftext|>


<commit_before>// @flow

import UIEvents from '../../../../service/UI/UIEvents';

import { NOTIFICATION_TIMEOUT, showNotification } from '../../notifications';
import { CALLING, INVITED } from '../../presence-status';

import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
import {
    CONFERENCE_WILL_JOIN,
    forEachConference,
    getCurrentConference
} from '../conference';
import { JitsiConferenceEvents } from '../lib-jitsi-meet';
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
import { playSound, registerSound, unregisterSound } from '../sounds';

import {
    localParticipantIdChanged,
    localParticipantJoined,
    localParticipantLeft,
    participantLeft,
    participantUpdated,
    showParticipantJoinedNotification
} from './actions';
import {
    DOMINANT_SPEAKER_CHANGED,
    KICK_PARTICIPANT,
    MUTE_REMOTE_PARTICIPANT,
    PARTICIPANT_DISPLAY_NAME_CHANGED,
    PARTICIPANT_JOINED,
    PARTICIPANT_LEFT,
    PARTICIPANT_UPDATED
} from './actionTypes';
import {
    LOCAL_PARTICIPANT_DEFAULT_ID,
    PARTICIPANT_JOINED_SOUND_ID,
    PARTICIPANT_LEFT_SOUND_ID
} from './constants';
import {
    getAvatarURLByParticipantId,
    getLocalParticipant,
    getParticipantCount,
    getParticipantDisplayName
} from './functions';
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';

declare var APP: Object;

/**
 * Middleware that captures CONFERENCE_JOINED and CONFERENCE_LEFT actions and
 * updates respectively ID of local participant.
 *
 * @param {Store} store - The redux store.
 * @returns {Function}
 */
MiddlewareRegistry.register(store => next => action => {
    switch (action.type) {
    case APP_WILL_MOUNT:
        _registerSounds(store);

        return _localParticipantJoined(store, next, action);

    case APP_WILL_UNMOUNT:
        _unregisterSounds(store);

        return _localParticipantLeft(store, next, action);

    case CONFERENCE_WILL_JOIN:
        store.dispatch(localParticipantIdChanged(action.conference.myUserId()));
        break;

    case DOMINANT_SPEAKER_CHANGED: {
        // Ensure the raised hand state is cleared for the dominant speaker.

        const { conference, id } = action.participant;
        const participant = getLocalParticipant(store.getState());

        participant
            && store.dispatch(participantUpdated({
                conference,
                id,
                local: participant.id === id,
                raisedHand: false
            }));

        break;
    }

    case KICK_PARTICIPANT: {
        const { conference } = store.getState()['features/base/conference'];

        conference.kickParticipant(action.id);
        break;
    }

    case MUTE_REMOTE_PARTICIPANT: {
        const { conference } = store.getState()['features/base/conference'];

        conference.muteParticipant(action.id);
        break;
    }

    // TODO Remove this middleware when the local display name update flow is
    // fully brought into redux.
    case PARTICIPANT_DISPLAY_NAME_CHANGED: {
        if (typeof APP !== 'undefined') {
            const participant = getLocalParticipant(store.getState());

            if (participant && participant.id === action.id) {
                APP.UI.emitEvent(UIEvents.NICKNAME_CHANGED, action.name);
            }
        }

        break;
    }

    case PARTICIPANT_JOINED: {
        _maybePlaySounds(store, action);

        const { participant: p } = action;

        if (!p.local) {
            store.dispatch(showParticipantJoinedNotification(getParticipantDisplayName(store.getState, p.id)));
        }

        return _participantJoinedOrUpdated(store, next, action);
    }

    case PARTICIPANT_LEFT:
        _maybePlaySounds(store, action);
        break;

    case PARTICIPANT_UPDATED:
        return _participantJoinedOrUpdated(store, next, action);
    }

    return next(action);
});

/**
 * Syncs the redux state features/base/participants up with the redux state
 * features/base/conference by ensuring that the former does not contain remote
 * participants no longer relevant to the latter. Introduced to address an issue
 * with multiplying thumbnails in the filmstrip.
 */
StateListenerRegistry.register(
    /* selector */ state => getCurrentConference(state),
    /* listener */ (conference, { dispatch, getState }) => {
        for (const p of getState()['features/base/participants']) {
            !p.local
                && (!conference || p.conference !== conference)
                && dispatch(participantLeft(p.id, p.conference));
        }
    });

/**
 * Reset the ID of the local participant to
 * {@link LOCAL_PARTICIPANT_DEFAULT_ID}. Such a reset is deemed possible only if
 * the local participant and, respectively, her ID is not involved in a
 * conference which is still of interest to the user and, consequently, the app.
 * For example, a conference which is in the process of leaving is no longer of
 * interest the user, is unrecoverable from the perspective of the user and,
 * consequently, the app.
 */
StateListenerRegistry.register(
    /* selector */ state => state['features/base/conference'],
    /* listener */ ({ leaving }, { dispatch, getState }) => {
        const state = getState();
        const localParticipant = getLocalParticipant(state);
        let id;

        if (!localParticipant
                || (id = localParticipant.id)
                    === LOCAL_PARTICIPANT_DEFAULT_ID) {
            // The ID of the local participant has been reset already.
            return;
        }

        // The ID of the local may be reset only if it is not in use.
        const dispatchLocalParticipantIdChanged
            = forEachConference(
                state,
                conference =>
                    conference === leaving || conference.myUserId() !== id);

        dispatchLocalParticipantIdChanged
            && dispatch(
                localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID));
    });

/**
 * Registers listeners for participant change events.
 */
StateListenerRegistry.register(
    state => state['features/base/conference'].conference,
    (conference, store) => {
        if (conference) {
            // We joined a conference
            conference.on(
                JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
                (participant, propertyName, oldValue, newValue) => {
                    switch (propertyName) {
                    case 'features_screen-sharing':
                        store.dispatch(participantUpdated({
                            conference,
                            id: participant.getId(),
                            features: { 'screen-sharing': true }
                        }));
                        break;
                    case 'raisedHand': {
                        _raiseHandUpdated(
                            store, conference, participant.getId(), newValue);
                        break;
                    }
                    default:

                        // Ignore for now.
                    }

                });
        } else {
            // We left the conference, raise hand of the local participant must be updated.
            _raiseHandUpdated(
                store, conference, undefined, false);
        }
    }
);

/**
 * Initializes the local participant and signals that it joined.
 *
 * @private
 * @param {Store} store - The redux store.
 * @param {Dispatch} next - The redux dispatch function to dispatch the
 * specified action to the specified store.
 * @param {Action} action - The redux action which is being dispatched
 * in the specified store.
 * @private
 * @returns {Object} The value returned by {@code next(action)}.
 */
function _localParticipantJoined({ getState, dispatch }, next, action) {
    const result = next(action);

    const settings = getState()['features/base/settings'];

    dispatch(localParticipantJoined({
        avatarID: settings.avatarID,
        avatarURL: settings.avatarURL,
        email: settings.email,
        name: settings.displayName
    }));

    return result;
}

/**
 * Signals that the local participant has left.
 *
 * @param {Store} store - The redux store.
 * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
 * specified {@code action} into the specified {@code store}.
 * @param {Action} action - The redux action which is being dispatched in the
 * specified {@code store}.
 * @private
 * @returns {Object} The value returned by {@code next(action)}.
 */
function _localParticipantLeft({ dispatch }, next, action) {
    const result = next(action);

    dispatch(localParticipantLeft());

    return result;
}

/**
 * Plays sounds when participants join/leave conference.
 *
 * @param {Store} store - The redux store.
 * @param {Action} action - The redux action. Should be either
 * {@link PARTICIPANT_JOINED} or {@link PARTICIPANT_LEFT}.
 * @private
 * @returns {void}
 */
function _maybePlaySounds({ getState, dispatch }, action) {
    const state = getState();
    const { startAudioMuted } = state['features/base/config'];

    // We're not playing sounds for local participant
    // nor when the user is joining past the "startAudioMuted" limit.
    // The intention there was to not play user joined notification in big
    // conferences where 100th person is joining.
    if (!action.participant.local
            && (!startAudioMuted
                || getParticipantCount(state) < startAudioMuted)) {
        if (action.type === PARTICIPANT_JOINED) {
            const { presence } = action.participant;

            // The sounds for the poltergeist are handled by features/invite.
            if (presence !== INVITED && presence !== CALLING) {
                dispatch(playSound(PARTICIPANT_JOINED_SOUND_ID));
            }
        } else if (action.type === PARTICIPANT_LEFT) {
            dispatch(playSound(PARTICIPANT_LEFT_SOUND_ID));
        }
    }
}

/**
 * Notifies the feature base/participants that the action
 * {@code PARTICIPANT_JOINED} or {@code PARTICIPANT_UPDATED} is being dispatched
 * within a specific redux store.
 *
 * @param {Store} store - The redux store in which the specified {@code action}
 * is being dispatched.
 * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
 * specified {@code action} in the specified {@code store}.
 * @param {Action} action - The redux action {@code PARTICIPANT_JOINED} or
 * {@code PARTICIPANT_UPDATED} which is being dispatched in the specified
 * {@code store}.
 * @private
 * @returns {Object} The value returned by {@code next(action)}.
 */
function _participantJoinedOrUpdated({ getState }, next, action) {
    const { participant: { id, local, raisedHand } } = action;

    // Send an external update of the local participant's raised hand state
    // if a new raised hand state is defined in the action.
    if (typeof raisedHand !== 'undefined') {
        if (local) {
            const { conference } = getState()['features/base/conference'];

            conference
                && conference.setLocalParticipantProperty(
                    'raisedHand',
                    raisedHand);
        }
    }

    // Notify external listeners of potential avatarURL changes.
    if (typeof APP === 'object') {
        const oldAvatarURL = getAvatarURLByParticipantId(getState(), id);

        // Allow the redux update to go through and compare the old avatar
        // to the new avatar and emit out change events if necessary.
        const result = next(action);
        const newAvatarURL = getAvatarURLByParticipantId(getState(), id);

        if (oldAvatarURL !== newAvatarURL) {
            const currentKnownId = local ? APP.conference.getMyUserId() : id;

            APP.UI.refreshAvatarDisplay(currentKnownId, newAvatarURL);
            APP.API.notifyAvatarChanged(currentKnownId, newAvatarURL);
        }

        return result;
    }

    return next(action);
}

/**
 * Handles a raise hand status update.
 *
 * @param {Function} dispatch - The Redux dispatch function.
 * @param {Object} conference - The conference for which we got an update.
 * @param {string?} participantId - The ID of the participant from which we got an update. If undefined,
 * we update the local participant.
 * @param {boolean} newValue - The new value of the raise hand status.
 * @returns {void}
 */
function _raiseHandUpdated({ dispatch, getState }, conference, participantId, newValue) {
    const raisedHand = newValue === 'true';
    const pid = participantId || getLocalParticipant(getState()).id;

    dispatch(participantUpdated({
        conference,
        id: pid,
        raisedHand
    }));

    if (raisedHand) {
        dispatch(showNotification({
            titleArguments: {
                name: getParticipantDisplayName(getState, pid)
            },
            titleKey: 'notify.raisedHand'
        }, NOTIFICATION_TIMEOUT));
    }
}

/**
 * Registers sounds related with the participants feature.
 *
 * @param {Store} store - The redux store.
 * @private
 * @returns {void}
 */
function _registerSounds({ dispatch }) {
    dispatch(
        registerSound(PARTICIPANT_JOINED_SOUND_ID, PARTICIPANT_JOINED_FILE));
    dispatch(registerSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_FILE));
}

/**
 * Unregisters sounds related with the participants feature.
 *
 * @param {Store} store - The redux store.
 * @private
 * @returns {void}
 */
function _unregisterSounds({ dispatch }) {
    dispatch(unregisterSound(PARTICIPANT_JOINED_SOUND_ID));
    dispatch(unregisterSound(PARTICIPANT_LEFT_SOUND_ID));
}
<commit_msg>fix(notifications): show notifications after reducers update to get name<commit_after>// @flow

import UIEvents from '../../../../service/UI/UIEvents';

import { NOTIFICATION_TIMEOUT, showNotification } from '../../notifications';
import { CALLING, INVITED } from '../../presence-status';

import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
import {
    CONFERENCE_WILL_JOIN,
    forEachConference,
    getCurrentConference
} from '../conference';
import { JitsiConferenceEvents } from '../lib-jitsi-meet';
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
import { playSound, registerSound, unregisterSound } from '../sounds';

import {
    localParticipantIdChanged,
    localParticipantJoined,
    localParticipantLeft,
    participantLeft,
    participantUpdated,
    showParticipantJoinedNotification
} from './actions';
import {
    DOMINANT_SPEAKER_CHANGED,
    KICK_PARTICIPANT,
    MUTE_REMOTE_PARTICIPANT,
    PARTICIPANT_DISPLAY_NAME_CHANGED,
    PARTICIPANT_JOINED,
    PARTICIPANT_LEFT,
    PARTICIPANT_UPDATED
} from './actionTypes';
import {
    LOCAL_PARTICIPANT_DEFAULT_ID,
    PARTICIPANT_JOINED_SOUND_ID,
    PARTICIPANT_LEFT_SOUND_ID
} from './constants';
import {
    getAvatarURLByParticipantId,
    getLocalParticipant,
    getParticipantCount,
    getParticipantDisplayName
} from './functions';
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';

declare var APP: Object;

/**
 * Middleware that captures CONFERENCE_JOINED and CONFERENCE_LEFT actions and
 * updates respectively ID of local participant.
 *
 * @param {Store} store - The redux store.
 * @returns {Function}
 */
MiddlewareRegistry.register(store => next => action => {
    switch (action.type) {
    case APP_WILL_MOUNT:
        _registerSounds(store);

        return _localParticipantJoined(store, next, action);

    case APP_WILL_UNMOUNT:
        _unregisterSounds(store);

        return _localParticipantLeft(store, next, action);

    case CONFERENCE_WILL_JOIN:
        store.dispatch(localParticipantIdChanged(action.conference.myUserId()));
        break;

    case DOMINANT_SPEAKER_CHANGED: {
        // Ensure the raised hand state is cleared for the dominant speaker.

        const { conference, id } = action.participant;
        const participant = getLocalParticipant(store.getState());

        participant
            && store.dispatch(participantUpdated({
                conference,
                id,
                local: participant.id === id,
                raisedHand: false
            }));

        break;
    }

    case KICK_PARTICIPANT: {
        const { conference } = store.getState()['features/base/conference'];

        conference.kickParticipant(action.id);
        break;
    }

    case MUTE_REMOTE_PARTICIPANT: {
        const { conference } = store.getState()['features/base/conference'];

        conference.muteParticipant(action.id);
        break;
    }

    // TODO Remove this middleware when the local display name update flow is
    // fully brought into redux.
    case PARTICIPANT_DISPLAY_NAME_CHANGED: {
        if (typeof APP !== 'undefined') {
            const participant = getLocalParticipant(store.getState());

            if (participant && participant.id === action.id) {
                APP.UI.emitEvent(UIEvents.NICKNAME_CHANGED, action.name);
            }
        }

        break;
    }

    case PARTICIPANT_JOINED: {
        _maybePlaySounds(store, action);

        const result = _participantJoinedOrUpdated(store, next, action);

        const { participant: p } = action;

        if (!p.local) {
            store.dispatch(showParticipantJoinedNotification(getParticipantDisplayName(store.getState, p.id)));
        }

        return result;
    }

    case PARTICIPANT_LEFT:
        _maybePlaySounds(store, action);
        break;

    case PARTICIPANT_UPDATED:
        return _participantJoinedOrUpdated(store, next, action);
    }

    return next(action);
});

/**
 * Syncs the redux state features/base/participants up with the redux state
 * features/base/conference by ensuring that the former does not contain remote
 * participants no longer relevant to the latter. Introduced to address an issue
 * with multiplying thumbnails in the filmstrip.
 */
StateListenerRegistry.register(
    /* selector */ state => getCurrentConference(state),
    /* listener */ (conference, { dispatch, getState }) => {
        for (const p of getState()['features/base/participants']) {
            !p.local
                && (!conference || p.conference !== conference)
                && dispatch(participantLeft(p.id, p.conference));
        }
    });

/**
 * Reset the ID of the local participant to
 * {@link LOCAL_PARTICIPANT_DEFAULT_ID}. Such a reset is deemed possible only if
 * the local participant and, respectively, her ID is not involved in a
 * conference which is still of interest to the user and, consequently, the app.
 * For example, a conference which is in the process of leaving is no longer of
 * interest the user, is unrecoverable from the perspective of the user and,
 * consequently, the app.
 */
StateListenerRegistry.register(
    /* selector */ state => state['features/base/conference'],
    /* listener */ ({ leaving }, { dispatch, getState }) => {
        const state = getState();
        const localParticipant = getLocalParticipant(state);
        let id;

        if (!localParticipant
                || (id = localParticipant.id)
                    === LOCAL_PARTICIPANT_DEFAULT_ID) {
            // The ID of the local participant has been reset already.
            return;
        }

        // The ID of the local may be reset only if it is not in use.
        const dispatchLocalParticipantIdChanged
            = forEachConference(
                state,
                conference =>
                    conference === leaving || conference.myUserId() !== id);

        dispatchLocalParticipantIdChanged
            && dispatch(
                localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID));
    });

/**
 * Registers listeners for participant change events.
 */
StateListenerRegistry.register(
    state => state['features/base/conference'].conference,
    (conference, store) => {
        if (conference) {
            // We joined a conference
            conference.on(
                JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
                (participant, propertyName, oldValue, newValue) => {
                    switch (propertyName) {
                    case 'features_screen-sharing':
                        store.dispatch(participantUpdated({
                            conference,
                            id: participant.getId(),
                            features: { 'screen-sharing': true }
                        }));
                        break;
                    case 'raisedHand': {
                        _raiseHandUpdated(
                            store, conference, participant.getId(), newValue);
                        break;
                    }
                    default:

                        // Ignore for now.
                    }

                });
        } else {
            // We left the conference, raise hand of the local participant must be updated.
            _raiseHandUpdated(
                store, conference, undefined, false);
        }
    }
);

/**
 * Initializes the local participant and signals that it joined.
 *
 * @private
 * @param {Store} store - The redux store.
 * @param {Dispatch} next - The redux dispatch function to dispatch the
 * specified action to the specified store.
 * @param {Action} action - The redux action which is being dispatched
 * in the specified store.
 * @private
 * @returns {Object} The value returned by {@code next(action)}.
 */
function _localParticipantJoined({ getState, dispatch }, next, action) {
    const result = next(action);

    const settings = getState()['features/base/settings'];

    dispatch(localParticipantJoined({
        avatarID: settings.avatarID,
        avatarURL: settings.avatarURL,
        email: settings.email,
        name: settings.displayName
    }));

    return result;
}

/**
 * Signals that the local participant has left.
 *
 * @param {Store} store - The redux store.
 * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
 * specified {@code action} into the specified {@code store}.
 * @param {Action} action - The redux action which is being dispatched in the
 * specified {@code store}.
 * @private
 * @returns {Object} The value returned by {@code next(action)}.
 */
function _localParticipantLeft({ dispatch }, next, action) {
    const result = next(action);

    dispatch(localParticipantLeft());

    return result;
}

/**
 * Plays sounds when participants join/leave conference.
 *
 * @param {Store} store - The redux store.
 * @param {Action} action - The redux action. Should be either
 * {@link PARTICIPANT_JOINED} or {@link PARTICIPANT_LEFT}.
 * @private
 * @returns {void}
 */
function _maybePlaySounds({ getState, dispatch }, action) {
    const state = getState();
    const { startAudioMuted } = state['features/base/config'];

    // We're not playing sounds for local participant
    // nor when the user is joining past the "startAudioMuted" limit.
    // The intention there was to not play user joined notification in big
    // conferences where 100th person is joining.
    if (!action.participant.local
            && (!startAudioMuted
                || getParticipantCount(state) < startAudioMuted)) {
        if (action.type === PARTICIPANT_JOINED) {
            const { presence } = action.participant;

            // The sounds for the poltergeist are handled by features/invite.
            if (presence !== INVITED && presence !== CALLING) {
                dispatch(playSound(PARTICIPANT_JOINED_SOUND_ID));
            }
        } else if (action.type === PARTICIPANT_LEFT) {
            dispatch(playSound(PARTICIPANT_LEFT_SOUND_ID));
        }
    }
}

/**
 * Notifies the feature base/participants that the action
 * {@code PARTICIPANT_JOINED} or {@code PARTICIPANT_UPDATED} is being dispatched
 * within a specific redux store.
 *
 * @param {Store} store - The redux store in which the specified {@code action}
 * is being dispatched.
 * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
 * specified {@code action} in the specified {@code store}.
 * @param {Action} action - The redux action {@code PARTICIPANT_JOINED} or
 * {@code PARTICIPANT_UPDATED} which is being dispatched in the specified
 * {@code store}.
 * @private
 * @returns {Object} The value returned by {@code next(action)}.
 */
function _participantJoinedOrUpdated({ getState }, next, action) {
    const { participant: { id, local, raisedHand } } = action;

    // Send an external update of the local participant's raised hand state
    // if a new raised hand state is defined in the action.
    if (typeof raisedHand !== 'undefined') {
        if (local) {
            const { conference } = getState()['features/base/conference'];

            conference
                && conference.setLocalParticipantProperty(
                    'raisedHand',
                    raisedHand);
        }
    }

    // Notify external listeners of potential avatarURL changes.
    if (typeof APP === 'object') {
        const oldAvatarURL = getAvatarURLByParticipantId(getState(), id);

        // Allow the redux update to go through and compare the old avatar
        // to the new avatar and emit out change events if necessary.
        const result = next(action);
        const newAvatarURL = getAvatarURLByParticipantId(getState(), id);

        if (oldAvatarURL !== newAvatarURL) {
            const currentKnownId = local ? APP.conference.getMyUserId() : id;

            APP.UI.refreshAvatarDisplay(currentKnownId, newAvatarURL);
            APP.API.notifyAvatarChanged(currentKnownId, newAvatarURL);
        }

        return result;
    }

    return next(action);
}

/**
 * Handles a raise hand status update.
 *
 * @param {Function} dispatch - The Redux dispatch function.
 * @param {Object} conference - The conference for which we got an update.
 * @param {string?} participantId - The ID of the participant from which we got an update. If undefined,
 * we update the local participant.
 * @param {boolean} newValue - The new value of the raise hand status.
 * @returns {void}
 */
function _raiseHandUpdated({ dispatch, getState }, conference, participantId, newValue) {
    const raisedHand = newValue === 'true';
    const pid = participantId || getLocalParticipant(getState()).id;

    dispatch(participantUpdated({
        conference,
        id: pid,
        raisedHand
    }));

    if (raisedHand) {
        dispatch(showNotification({
            titleArguments: {
                name: getParticipantDisplayName(getState, pid)
            },
            titleKey: 'notify.raisedHand'
        }, NOTIFICATION_TIMEOUT));
    }
}

/**
 * Registers sounds related with the participants feature.
 *
 * @param {Store} store - The redux store.
 * @private
 * @returns {void}
 */
function _registerSounds({ dispatch }) {
    dispatch(
        registerSound(PARTICIPANT_JOINED_SOUND_ID, PARTICIPANT_JOINED_FILE));
    dispatch(registerSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_FILE));
}

/**
 * Unregisters sounds related with the participants feature.
 *
 * @param {Store} store - The redux store.
 * @private
 * @returns {void}
 */
function _unregisterSounds({ dispatch }) {
    dispatch(unregisterSound(PARTICIPANT_JOINED_SOUND_ID));
    dispatch(unregisterSound(PARTICIPANT_LEFT_SOUND_ID));
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';

import axios from 'axios';

const api = "http://localhost:3001";

// Generate a unique token for storing your bookshelf data on the backend server.
let token = localStorage.token
if (!token)
  token = localStorage.token = Math.random().toString(36).substr(-8)

const headers = {
  'Accept': 'application/json',
  'Authorization': token
}

class NewPost extends Component {

    state = {
        username:"",
        title:"",
        body:"",
        category:"",
        open: false
    }

    handleClose = () => {
        this.setState({open: false})
    }

    uuid = () => {
        function s4() {
            return Math.floor((1 + Math.random()) * 0x10000)
            .toString(16)
            .substring(1);
        }
        return s4() + s4() + '' + s4() + '' + s4() + '' + s4();
    }

    createPost = () => {
        if(this.state.title === "" ||
            this.state.category === "" ||
            this.state.username === "") {
            this.setState({open: true});
        }
        else {
            let payload = {
                id: this.uuid(),
                timestamp: Date.now(),
                title: this.state.title,
                body: this.state.body,
                author: this.state.username,
                category: this.state.category
            }

            axios.post(`${api}/posts`, payload, { headers })
            .then(response => {
                this.setState({
                    username:"",
                    title:"",
                    body:"",
                    category:""
                });
                window.location = '/'
            });
        }
    }

    render(){
        const { username, title, body, category } = this.state;
        const actions = [
            <FlatButton
                label="OK"
                primary={true}
                onClick={this.handleClose}
            />
        ];
        return (

Username

                    <input
                        type="text"
                        value={username}
                        onChange = {(e) => (this.setState({username:e.target.value}))}/>

Title

                    <input
                        type="text"
                        value={title}
                        onChange = {(e) => (this.setState({title:e.target.value}))}/>

Body

                    <input
                        type="text"
                        value={body}
                        onChange = {(e) => (this.setState({body:e.target.value}))}/>

Category

                    <input
                        type="text"
                        value={category}
                        onChange = {(e) => (this.setState({category:e.target.value}))}/>

                    * required

                <RaisedButton
                    label="Submit"
                    secondary={true}
                    onClick={() => this.createPost()}
                    style={{marginLeft: "1em"}}/>

                    <Dialog
                        actions={actions}
                        modal={false}
                        open={this.state.open}
                        onRequestClose={this.handleClose}>
                        Fill all required fields
                    </Dialog>

        )
    }
}

export default NewPost;<commit_msg>added a category check<commit_after>import React, { Component } from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import AutoComplete from 'material-ui/AutoComplete';
import axios from 'axios';

const api = "http://localhost:3001";

// Generate a unique token for storing your bookshelf data on the backend server.
let token = localStorage.token
if (!token)
  token = localStorage.token = Math.random().toString(36).substr(-8)

const headers = {
  'Accept': 'application/json',
  'Authorization': token
}

class NewPost extends Component {

    componentDidMount(){
        const api = "http://localhost:3001";

        // Generate a unique token for storing your bookshelf data on the backend server.
        let token = localStorage.token
        if (!token)
        token = localStorage.token = Math.random().toString(36).substr(-8)

        const headers = {
        'Accept': 'application/json',
        'Authorization': token
        }

        axios.get(`${api}/categories`, {headers})
        .then(response => {
            let cat = []
            response.data.categories.map(category => cat.push(category.name))
            this.setState({categories: cat});
        })
    }

    state = {
        categories: [],
        username:"",
        title:"",
        body:"",
        category:"",
        open: false
    }

    handleClose = () => {
        this.setState({open: false})
    }

    uuid = () => {
        function s4() {
            return Math.floor((1 + Math.random()) * 0x10000)
            .toString(16)
            .substring(1);
        }
        return s4() + s4() + '' + s4() + '' + s4() + '' + s4();
    }

    createPost = () => {
        if(this.state.title === "" ||
            this.state.category === "" ||
            this.state.username === "" ||
            !this.state.categories.includes(this.state.category)) {
            this.setState({open: true});
        }
        else {
            let payload = {
                id: this.uuid(),
                timestamp: Date.now(),
                title: this.state.title,
                body: this.state.body,
                author: this.state.username,
                category: this.state.category
            }

            axios.post(`${api}/posts`, payload, { headers })
            .then(response => {
                this.setState({
                    username:"",
                    title:"",
                    body:"",
                    category:""
                });
                window.location = '/'
            });
        }
    }

    style = {
        fontSize: "initial",
        background: "white",
        borderRadius: "2px",
        width: "unset",
        display: "block",
        border: "1px solid grey"
    }

    render(){
        const { username, title, body } = this.state;
        const actions = [
            <FlatButton
                label="OK"
                primary={true}
                onClick={this.handleClose}
            />
        ];
        return (

Username

                    <input
                        type="text"
                        value={username}
                        onChange = {(e) => (this.setState({username:e.target.value}))}/>

Title

                    <input
                        type="text"
                        value={title}
                        onChange = {(e) => (this.setState({title:e.target.value}))}/>

Body

                    <input
                        type="text"
                        value={body}
                        onChange = {(e) => (this.setState({body:e.target.value}))}/>

                    <AutoComplete
                        style={this.style}
                        hintText="Category"
                        dataSource={this.state.categories}
                        onUpdateInput={(val) => this.setState({category: val})}/>

                    * required

                <RaisedButton
                    label="Submit"
                    secondary={true}
                    onClick={() => this.createPost()}
                    style={{marginLeft: "1em"}}/>

                <Dialog
                    actions={actions}
                    modal={false}
                    open={this.state.open}
                    onRequestClose={this.handleClose}>
                    Fill all required fields and/or check if category exists
                </Dialog>

        )
    }
}

export default NewPost;<|endoftext|>


<commit_before>'use strict';

angular.module('ddsApp').controller('ResultatCtrl', function($scope, $rootScope, $window, $http, $state, $stateParams, $timeout, SituationService, ResultatService, droitsDescription, $analytics) {
    $scope.awaitingResults = false;
    $scope.error = false;
    $scope.warning = false;

    function loadSituation() {
        if ($stateParams.situationId) { // If we want the result page for an already existing situation.
            return $scope.restoreRemoteSituation($stateParams.situationId);
        } else {
            return $scope.persistLocalSituation();
        }
    }

    function triggerEvaluation() {
        loadSituation()
        .then(function(situation) {
            $scope.awaitingResults = true;
            return situation;
        }).then(ResultatService.simulate)
        .then(function(droits) {
            $scope.droits = droits.droitsEligibles;
            $scope.droitsInjectes = droits.droitsInjectes;
            $scope.noDroits = _.isEmpty($scope.droits.prestationsNationales) && _.isEmpty($scope.droits.partenairesLocaux);
        })
        .catch(function(error) {
            $scope.error = JSON.stringify((error && error.data), null, 2);
            $scope.encodedError = encodeURIComponent($scope.error);
            $scope.encodedUserAgent = encodeURIComponent(window.navigator.userAgent);
            $analytics.eventTrack('error', { label: $scope.error });
        })
        .finally(function() {
            $scope.awaitingResults = false;

            $scope.yearMoins2 = moment($scope.situation.dateDeValeur).subtract('years', 2).format('YYYY');
            $scope.debutPeriode = moment($scope.situation.dateDeValeur).startOf('month').subtract('years', 1).format('MMMM YYYY');
            $scope.finPeriode = moment($scope.situation.dateDeValeur).startOf('month').subtract('months', 1).format('MMMM YYYY');
            $scope.ressourcesYearMoins2Captured = SituationService.ressourcesYearMoins2Captured($scope.situation);
            $scope.isPatrimoineCaptured = function() {
                return angular.isDefined(SituationService.hasPatrimoine($scope.situation));
            };
        });
    }

    if ($stateParams.situationId || SituationService.passSanityCheck($scope.situation)) {
        triggerEvaluation();
    } else {
        $scope.warning = true;
    }

    $scope.createTest = function() {
        // Merge national and local prestations into a flat object compatible with ludwig.
        var flatPrestations = _.merge.apply(
            null,
            _.values($scope.droits.partenairesLocaux).concat($scope.droits.prestationsNationales)
        );

        var expectedResults = _.map(flatPrestations, function(droit, id) {
            return {
                code: id,
                expectedValue: droit.montant
            };
        });

        $http.post('api/public/acceptance-tests', {
            expectedResults: expectedResults,
            scenario: { situationId: $scope.situation._id }
        }).success(function(data) {
            $window.location.href = '/tests/' + data._id + '/edit';
        }).error(function(data) {
            $window.alert(data.error.apiError);
        });
    };
});
<commit_msg>Improve error message in degraded situation #687<commit_after>'use strict';

angular.module('ddsApp').controller('ResultatCtrl', function($scope, $rootScope, $window, $http, $state, $stateParams, $timeout, SituationService, ResultatService, droitsDescription, $analytics) {
    $scope.awaitingResults = false;
    $scope.error = false;
    $scope.warning = false;

    function loadSituation() {
        if ($stateParams.situationId) { // If we want the result page for an already existing situation.
            return $scope.restoreRemoteSituation($stateParams.situationId);
        } else {
            return $scope.persistLocalSituation();
        }
    }

    function triggerEvaluation() {
        loadSituation()
        .then(function(situation) {
            $scope.awaitingResults = true;
            return situation;
        }).then(ResultatService.simulate)
        .then(function(droits) {
            $scope.droits = droits.droitsEligibles;
            $scope.droitsInjectes = droits.droitsInjectes;
            $scope.noDroits = _.isEmpty($scope.droits.prestationsNationales) && _.isEmpty($scope.droits.partenairesLocaux);
        })
        .catch(function(error) {
            $scope.error = JSON.stringify((error && error.data), null, 2);
            $scope.encodedError = encodeURIComponent($scope.error);
            $scope.encodedUserAgent = encodeURIComponent(window.navigator.userAgent);
            $analytics.eventTrack('error', { label: $scope.error || $scope.situation._id });
        })
        .finally(function() {
            $scope.awaitingResults = false;

            $scope.yearMoins2 = moment($scope.situation.dateDeValeur).subtract('years', 2).format('YYYY');
            $scope.debutPeriode = moment($scope.situation.dateDeValeur).startOf('month').subtract('years', 1).format('MMMM YYYY');
            $scope.finPeriode = moment($scope.situation.dateDeValeur).startOf('month').subtract('months', 1).format('MMMM YYYY');
            $scope.ressourcesYearMoins2Captured = SituationService.ressourcesYearMoins2Captured($scope.situation);
            $scope.isPatrimoineCaptured = function() {
                return angular.isDefined(SituationService.hasPatrimoine($scope.situation));
            };
        });
    }

    if ($stateParams.situationId || SituationService.passSanityCheck($scope.situation)) {
        triggerEvaluation();
    } else {
        $scope.warning = true;
    }

    $scope.createTest = function() {
        // Merge national and local prestations into a flat object compatible with ludwig.
        var flatPrestations = _.merge.apply(
            null,
            _.values($scope.droits.partenairesLocaux).concat($scope.droits.prestationsNationales)
        );

        var expectedResults = _.map(flatPrestations, function(droit, id) {
            return {
                code: id,
                expectedValue: droit.montant
            };
        });

        $http.post('api/public/acceptance-tests', {
            expectedResults: expectedResults,
            scenario: { situationId: $scope.situation._id }
        }).success(function(data) {
            $window.location.href = '/tests/' + data._id + '/edit';
        }).error(function(data) {
            $window.alert(data.error.apiError);
        });
    };
});
<|endoftext|>


<commit_before>(function (enyo, scope) {

	/**
	* Features a variety of commonly used widgets, including toolbars, text inputs, checkboxes, groups
	* and multiple types of buttons.
	*
	* @namespace onyx
	*/

	if (enyo && enyo.version) {
		enyo.version.onyx = "2.5.3-pre.1";
	}

})(enyo, this);
<commit_msg>Update version string to 2.5.3-zzz.3<commit_after>(function (enyo, scope) {

	/**
	* Features a variety of commonly used widgets, including toolbars, text inputs, checkboxes, groups
	* and multiple types of buttons.
	*
	* @namespace onyx
	*/

	if (enyo && enyo.version) {
		enyo.version.onyx = "2.5.3-zzz.3";
	}

})(enyo, this);
<|endoftext|>