<commit_before>/**
 * IndexLayouts contain {@link OO.ui.CardLayout card layouts} as well as
 * {@link OO.ui.TabSelectWidget tabs} that allow users to easily navigate through the cards and
 * select which one to display. By default, only one card is displayed at a time. When a user
 * navigates to a new card, the index layout automatically focuses on the first focusable element,
 * unless the default setting is changed.
 *
 * TODO: This class is similar to BookletLayout, we may want to refactor to reduce duplication
 *
 *     @example
 *     // Example of a IndexLayout that contains two CardLayouts.
 *
 *     function CardOneLayout( name, config ) {
 *         CardOneLayout.parent.call( this, name, config );
 *         this.$element.append( '
First card
' );
 *     }
 *     OO.inheritClass( CardOneLayout, OO.ui.CardLayout );
 *     CardOneLayout.prototype.setupTabItem = function () {
 *         this.tabItem.setLabel( 'Card One' );
 *     };
 *
 *     function CardTwoLayout( name, config ) {
 *         CardTwoLayout.parent.call( this, name, config );
 *         this.$element.append( '
Second card
' );
 *     }
 *     OO.inheritClass( CardTwoLayout, OO.ui.CardLayout );
 *     CardTwoLayout.prototype.setupTabItem = function () {
 *         this.tabItem.setLabel( 'Card Two' );
 *     };
 *
 *     var card1 = new CardOneLayout( 'one' ),
 *         card2 = new CardTwoLayout( 'two' );
 *
 *     var index = new OO.ui.IndexLayout();
 *
 *     index.addCards ( [ card1, card2 ] );
 *     $( 'body' ).append( index.$element );
 *
 * @class
 * @extends OO.ui.MenuLayout
 *
 * @constructor
 * @param {Object} [config] Configuration options
 * @cfg {boolean} [continuous=false] Show all cards, one after another
 * @cfg {boolean} [autoFocus=true] Focus on the first focusable element when a new card is displayed.
 */
OO.ui.IndexLayout = function OoUiIndexLayout( config ) {
	// Configuration initialization
	config = $.extend( {}, config, { menuPosition: 'top' } );

	// Parent constructor
	OO.ui.IndexLayout.parent.call( this, config );

	// Properties
	this.currentCardName = null;
	this.cards = {};
	this.ignoreFocus = false;
	this.stackLayout = new OO.ui.StackLayout( { continuous: !!config.continuous } );
	this.$content.append( this.stackLayout.$element );
	this.autoFocus = config.autoFocus === undefined || !!config.autoFocus;

	this.tabSelectWidget = new OO.ui.TabSelectWidget();
	this.tabPanel = new OO.ui.PanelLayout();
	this.$menu.append( this.tabPanel.$element );

	this.toggleMenu( true );

	// Events
	this.stackLayout.connect( this, { set: 'onStackLayoutSet' } );
	this.tabSelectWidget.connect( this, { select: 'onTabSelectWidgetSelect' } );
	if ( this.autoFocus ) {
		// Event 'focus' does not bubble, but 'focusin' does
		this.stackLayout.$element.on( 'focusin', this.onStackLayoutFocus.bind( this ) );
	}

	// Initialization
	this.$element.addClass( 'oo-ui-indexLayout' );
	this.stackLayout.$element.addClass( 'oo-ui-indexLayout-stackLayout' );
	this.tabPanel.$element
		.addClass( 'oo-ui-indexLayout-tabPanel' )
		.append( this.tabSelectWidget.$element );
};

/* Setup */

OO.inheritClass( OO.ui.IndexLayout, OO.ui.MenuLayout );

/* Events */

/**
 * A 'set' event is emitted when a card is {@link #setCard set} to be displayed by the index layout.
 * @event set
 * @param {OO.ui.CardLayout} card Current card
 */

/**
 * An 'add' event is emitted when cards are {@link #addCards added} to the index layout.
 *
 * @event add
 * @param {OO.ui.CardLayout[]} card Added cards
 * @param {number} index Index cards were added at
 */

/**
 * A 'remove' event is emitted when cards are {@link #clearCards cleared} or
 * {@link #removeCards removed} from the index.
 *
 * @event remove
 * @param {OO.ui.CardLayout[]} cards Removed cards
 */

/* Methods */

/**
 * Handle stack layout focus.
 *
 * @private
 * @param {jQuery.Event} e Focusin event
 */
OO.ui.IndexLayout.prototype.onStackLayoutFocus = function ( e ) {
	var name, $target;

	// Find the card that an element was focused within
	$target = $( e.target ).closest( '.oo-ui-cardLayout' );
	for ( name in this.cards ) {
		// Check for card match, exclude current card to find only card changes
		if ( this.cards[ name ].$element[ 0 ] === $target[ 0 ] && name !== this.currentCardName ) {
			this.setCard( name );
			break;
		}
	}
};

/**
 * Handle stack layout set events.
 *
 * @private
 * @param {OO.ui.PanelLayout|null} card The card panel that is now the current panel
 */
OO.ui.IndexLayout.prototype.onStackLayoutSet = function ( card ) {
	var layout = this;
	if ( card ) {
		card.scrollElementIntoView( { complete: function () {
			if ( layout.autoFocus ) {
				layout.focus();
			}
		} } );
	}
};

/**
 * Focus the first input in the current card.
 *
 * If no card is selected, the first selectable card will be selected.
 * If the focus is already in an element on the current card, nothing will happen.
 * @param {number} [itemIndex] A specific item to focus on
 */
OO.ui.IndexLayout.prototype.focus = function ( itemIndex ) {
	var $input, card,
		items = this.stackLayout.getItems();

	if ( itemIndex !== undefined && items[ itemIndex ] ) {
		card = items[ itemIndex ];
	} else {
		card = this.stackLayout.getCurrentItem();
	}

	if ( !card ) {
		this.selectFirstSelectableCard();
		card = this.stackLayout.getCurrentItem();
	}
	if ( !card ) {
		return;
	}
	// Only change the focus if is not already in the current card
	if ( !card.$element.find( ':focus' ).length ) {
		$input = card.$element.find( ':input:first' );
		if ( $input.length ) {
			$input[ 0 ].focus();
		}
	}
};

/**
 * Find the first focusable input in the index layout and focus
 * on it.
 */
OO.ui.IndexLayout.prototype.focusFirstFocusable = function () {
	var i, len,
		found = false,
		items = this.stackLayout.getItems(),
		checkAndFocus = function () {
			if ( OO.ui.isFocusableElement( $( this ) ) ) {
				$( this ).focus();
				found = true;
				return false;
			}
		};

	for ( i = 0, len = items.length; i < len; i++ ) {
		if ( found ) {
			break;
		}
		// Find all potentially focusable elements in the item
		// and check if they are focusable
		items[ i ].$element
			.find( 'input, select, textarea, button, object' )
			.each( checkAndFocus );
	}
};

/**
 * Handle tab widget select events.
 *
 * @private
 * @param {OO.ui.OptionWidget|null} item Selected item
 */
OO.ui.IndexLayout.prototype.onTabSelectWidgetSelect = function ( item ) {
	if ( item ) {
		this.setCard( item.getData() );
	}
};

/**
 * Get the card closest to the specified card.
 *
 * @param {OO.ui.CardLayout} card Card to use as a reference point
 * @return {OO.ui.CardLayout|null} Card closest to the specified card
 */
OO.ui.IndexLayout.prototype.getClosestCard = function ( card ) {
	var next, prev, level,
		cards = this.stackLayout.getItems(),
		index = cards.indexOf( card );

	if ( index !== -1 ) {
		next = cards[ index + 1 ];
		prev = cards[ index - 1 ];
		// Prefer adjacent cards at the same level
		level = this.tabSelectWidget.getItemFromData( card.getName() ).getLevel();
		if (
			prev &&
			level === this.tabSelectWidget.getItemFromData( prev.getName() ).getLevel()
		) {
			return prev;
		}
		if (
			next &&
			level === this.tabSelectWidget.getItemFromData( next.getName() ).getLevel()
		) {
			return next;
		}
	}
	return prev || next || null;
};

/**
 * Get the tabs widget.
 *
 * @return {OO.ui.TabSelectWidget} Tabs widget
 */
OO.ui.IndexLayout.prototype.getTabs = function () {
	return this.tabSelectWidget;
};

/**
 * Get a card by its symbolic name.
 *
 * @param {string} name Symbolic name of card
 * @return {OO.ui.CardLayout|undefined} Card, if found
 */
OO.ui.IndexLayout.prototype.getCard = function ( name ) {
	return this.cards[ name ];
};

/**
 * Get the current card.
 *
 * @return {OO.ui.CardLayout|undefined} Current card, if found
 */
OO.ui.IndexLayout.prototype.getCurrentCard = function () {
	var name = this.getCurrentCardName();
	return name ? this.getCard( name ) : undefined;
};

/**
 * Get the symbolic name of the current card.
 *
 * @return {string|null} Symbolic name of the current card
 */
OO.ui.IndexLayout.prototype.getCurrentCardName = function () {
	return this.currentCardName;
};

/**
 * Add cards to the index layout
 *
 * When cards are added with the same names as existing cards, the existing cards will be
 * automatically removed before the new cards are added.
 *
 * @param {OO.ui.CardLayout[]} cards Cards to add
 * @param {number} index Index of the insertion point
 * @fires add
 * @chainable
 */
OO.ui.IndexLayout.prototype.addCards = function ( cards, index ) {
	var i, len, name, card, item, currentIndex,
		stackLayoutCards = this.stackLayout.getItems(),
		remove = [],
		items = [];

	// Remove cards with same names
	for ( i = 0, len = cards.length; i < len; i++ ) {
		card = cards[ i ];
		name = card.getName();

		if ( Object.prototype.hasOwnProperty.call( this.cards, name ) ) {
			// Correct the insertion index
			currentIndex = stackLayoutCards.indexOf( this.cards[ name ] );
			if ( currentIndex !== -1 && currentIndex + 1 < index ) {
				index--;
			}
			remove.push( this.cards[ name ] );
		}
	}
	if ( remove.length ) {
		this.removeCards( remove );
	}

	// Add new cards
	for ( i = 0, len = cards.length; i < len; i++ ) {
		card = cards[ i ];
		name = card.getName();
		this.cards[ card.getName() ] = card;
		item = new OO.ui.TabOptionWidget( { data: name } );
		card.setTabItem( item );
		items.push( item );
	}

	if ( items.length ) {
		this.tabSelectWidget.addItems( items, index );
		this.selectFirstSelectableCard();
	}
	this.stackLayout.addItems( cards, index );
	this.emit( 'add', cards, index );

	return this;
};

/**
 * Remove the specified cards from the index layout.
 *
 * To remove all cards from the index, you may wish to use the #clearCards method instead.
 *
 * @param {OO.ui.CardLayout[]} cards An array of cards to remove
 * @fires remove
 * @chainable
 */
OO.ui.IndexLayout.prototype.removeCards = function ( cards ) {
	var i, len, name, card,
		items = [];

	for ( i = 0, len = cards.length; i < len; i++ ) {
		card = cards[ i ];
		name = card.getName();
		delete this.cards[ name ];
		items.push( this.tabSelectWidget.getItemFromData( name ) );
		card.setTabItem( null );
	}
	if ( items.length ) {
		this.tabSelectWidget.removeItems( items );
		this.selectFirstSelectableCard();
	}
	this.stackLayout.removeItems( cards );
	this.emit( 'remove', cards );

	return this;
};

/**
 * Clear all cards from the index layout.
 *
 * To remove only a subset of cards from the index, use the #removeCards method.
 *
 * @fires remove
 * @chainable
 */
OO.ui.IndexLayout.prototype.clearCards = function () {
	var i, len,
		cards = this.stackLayout.getItems();

	this.cards = {};
	this.currentCardName = null;
	this.tabSelectWidget.clearItems();
	for ( i = 0, len = cards.length; i < len; i++ ) {
		cards[ i ].setTabItem( null );
	}
	this.stackLayout.clearItems();

	this.emit( 'remove', cards );

	return this;
};

/**
 * Set the current card by symbolic name.
 *
 * @fires set
 * @param {string} name Symbolic name of card
 */
OO.ui.IndexLayout.prototype.setCard = function ( name ) {
	var selectedItem,
		$focused,
		card = this.cards[ name ];

	if ( name !== this.currentCardName ) {
		selectedItem = this.tabSelectWidget.getSelectedItem();
		if ( selectedItem && selectedItem.getData() !== name ) {
			this.tabSelectWidget.selectItemByData( name );
		}
		if ( card ) {
			if ( this.currentCardName && this.cards[ this.currentCardName ] ) {
				this.cards[ this.currentCardName ].setActive( false );
				// Blur anything focused if the next card doesn't have anything focusable - this
				// is not needed if the next card has something focusable because once it is focused
				// this blur happens automatically
				if ( this.autoFocus && !card.$element.find( ':input' ).length ) {
					$focused = this.cards[ this.currentCardName ].$element.find( ':focus' );
					if ( $focused.length ) {
						$focused[ 0 ].blur();
					}
				}
			}
			this.currentCardName = name;
			this.stackLayout.setItem( card );
			card.setActive( true );
			this.emit( 'set', card );
		}
	}
};

/**
 * Select the first selectable card.
 *
 * @chainable
 */
OO.ui.IndexLayout.prototype.selectFirstSelectableCard = function () {
	if ( !this.tabSelectWidget.getSelectedItem() ) {
		this.tabSelectWidget.selectItem( this.tabSelectWidget.getFirstSelectableItem() );
	}

	return this;
};
<commit_msg>IndexLayout: Add 'expanded' option, passed through to StackLayout<commit_after>/**
 * IndexLayouts contain {@link OO.ui.CardLayout card layouts} as well as
 * {@link OO.ui.TabSelectWidget tabs} that allow users to easily navigate through the cards and
 * select which one to display. By default, only one card is displayed at a time. When a user
 * navigates to a new card, the index layout automatically focuses on the first focusable element,
 * unless the default setting is changed.
 *
 * TODO: This class is similar to BookletLayout, we may want to refactor to reduce duplication
 *
 *     @example
 *     // Example of a IndexLayout that contains two CardLayouts.
 *
 *     function CardOneLayout( name, config ) {
 *         CardOneLayout.parent.call( this, name, config );
 *         this.$element.append( '
First card
' );
 *     }
 *     OO.inheritClass( CardOneLayout, OO.ui.CardLayout );
 *     CardOneLayout.prototype.setupTabItem = function () {
 *         this.tabItem.setLabel( 'Card One' );
 *     };
 *
 *     function CardTwoLayout( name, config ) {
 *         CardTwoLayout.parent.call( this, name, config );
 *         this.$element.append( '
Second card
' );
 *     }
 *     OO.inheritClass( CardTwoLayout, OO.ui.CardLayout );
 *     CardTwoLayout.prototype.setupTabItem = function () {
 *         this.tabItem.setLabel( 'Card Two' );
 *     };
 *
 *     var card1 = new CardOneLayout( 'one' ),
 *         card2 = new CardTwoLayout( 'two' );
 *
 *     var index = new OO.ui.IndexLayout();
 *
 *     index.addCards ( [ card1, card2 ] );
 *     $( 'body' ).append( index.$element );
 *
 * @class
 * @extends OO.ui.MenuLayout
 *
 * @constructor
 * @param {Object} [config] Configuration options
 * @cfg {boolean} [continuous=false] Show all cards, one after another
 * @cfg {boolean} [expanded=true] Expand the content panel to fill the entire parent element.
 * @cfg {boolean} [autoFocus=true] Focus on the first focusable element when a new card is displayed.
 */
OO.ui.IndexLayout = function OoUiIndexLayout( config ) {
	// Configuration initialization
	config = $.extend( {}, config, { menuPosition: 'top' } );

	// Parent constructor
	OO.ui.IndexLayout.parent.call( this, config );

	// Properties
	this.currentCardName = null;
	this.cards = {};
	this.ignoreFocus = false;
	this.stackLayout = new OO.ui.StackLayout( {
		continuous: !!config.continuous,
		expanded: config.expanded
	} );
	this.$content.append( this.stackLayout.$element );
	this.autoFocus = config.autoFocus === undefined || !!config.autoFocus;

	this.tabSelectWidget = new OO.ui.TabSelectWidget();
	this.tabPanel = new OO.ui.PanelLayout();
	this.$menu.append( this.tabPanel.$element );

	this.toggleMenu( true );

	// Events
	this.stackLayout.connect( this, { set: 'onStackLayoutSet' } );
	this.tabSelectWidget.connect( this, { select: 'onTabSelectWidgetSelect' } );
	if ( this.autoFocus ) {
		// Event 'focus' does not bubble, but 'focusin' does
		this.stackLayout.$element.on( 'focusin', this.onStackLayoutFocus.bind( this ) );
	}

	// Initialization
	this.$element.addClass( 'oo-ui-indexLayout' );
	this.stackLayout.$element.addClass( 'oo-ui-indexLayout-stackLayout' );
	this.tabPanel.$element
		.addClass( 'oo-ui-indexLayout-tabPanel' )
		.append( this.tabSelectWidget.$element );
};

/* Setup */

OO.inheritClass( OO.ui.IndexLayout, OO.ui.MenuLayout );

/* Events */

/**
 * A 'set' event is emitted when a card is {@link #setCard set} to be displayed by the index layout.
 * @event set
 * @param {OO.ui.CardLayout} card Current card
 */

/**
 * An 'add' event is emitted when cards are {@link #addCards added} to the index layout.
 *
 * @event add
 * @param {OO.ui.CardLayout[]} card Added cards
 * @param {number} index Index cards were added at
 */

/**
 * A 'remove' event is emitted when cards are {@link #clearCards cleared} or
 * {@link #removeCards removed} from the index.
 *
 * @event remove
 * @param {OO.ui.CardLayout[]} cards Removed cards
 */

/* Methods */

/**
 * Handle stack layout focus.
 *
 * @private
 * @param {jQuery.Event} e Focusin event
 */
OO.ui.IndexLayout.prototype.onStackLayoutFocus = function ( e ) {
	var name, $target;

	// Find the card that an element was focused within
	$target = $( e.target ).closest( '.oo-ui-cardLayout' );
	for ( name in this.cards ) {
		// Check for card match, exclude current card to find only card changes
		if ( this.cards[ name ].$element[ 0 ] === $target[ 0 ] && name !== this.currentCardName ) {
			this.setCard( name );
			break;
		}
	}
};

/**
 * Handle stack layout set events.
 *
 * @private
 * @param {OO.ui.PanelLayout|null} card The card panel that is now the current panel
 */
OO.ui.IndexLayout.prototype.onStackLayoutSet = function ( card ) {
	var layout = this;
	if ( card ) {
		card.scrollElementIntoView( { complete: function () {
			if ( layout.autoFocus ) {
				layout.focus();
			}
		} } );
	}
};

/**
 * Focus the first input in the current card.
 *
 * If no card is selected, the first selectable card will be selected.
 * If the focus is already in an element on the current card, nothing will happen.
 * @param {number} [itemIndex] A specific item to focus on
 */
OO.ui.IndexLayout.prototype.focus = function ( itemIndex ) {
	var $input, card,
		items = this.stackLayout.getItems();

	if ( itemIndex !== undefined && items[ itemIndex ] ) {
		card = items[ itemIndex ];
	} else {
		card = this.stackLayout.getCurrentItem();
	}

	if ( !card ) {
		this.selectFirstSelectableCard();
		card = this.stackLayout.getCurrentItem();
	}
	if ( !card ) {
		return;
	}
	// Only change the focus if is not already in the current card
	if ( !card.$element.find( ':focus' ).length ) {
		$input = card.$element.find( ':input:first' );
		if ( $input.length ) {
			$input[ 0 ].focus();
		}
	}
};

/**
 * Find the first focusable input in the index layout and focus
 * on it.
 */
OO.ui.IndexLayout.prototype.focusFirstFocusable = function () {
	var i, len,
		found = false,
		items = this.stackLayout.getItems(),
		checkAndFocus = function () {
			if ( OO.ui.isFocusableElement( $( this ) ) ) {
				$( this ).focus();
				found = true;
				return false;
			}
		};

	for ( i = 0, len = items.length; i < len; i++ ) {
		if ( found ) {
			break;
		}
		// Find all potentially focusable elements in the item
		// and check if they are focusable
		items[ i ].$element
			.find( 'input, select, textarea, button, object' )
			.each( checkAndFocus );
	}
};

/**
 * Handle tab widget select events.
 *
 * @private
 * @param {OO.ui.OptionWidget|null} item Selected item
 */
OO.ui.IndexLayout.prototype.onTabSelectWidgetSelect = function ( item ) {
	if ( item ) {
		this.setCard( item.getData() );
	}
};

/**
 * Get the card closest to the specified card.
 *
 * @param {OO.ui.CardLayout} card Card to use as a reference point
 * @return {OO.ui.CardLayout|null} Card closest to the specified card
 */
OO.ui.IndexLayout.prototype.getClosestCard = function ( card ) {
	var next, prev, level,
		cards = this.stackLayout.getItems(),
		index = cards.indexOf( card );

	if ( index !== -1 ) {
		next = cards[ index + 1 ];
		prev = cards[ index - 1 ];
		// Prefer adjacent cards at the same level
		level = this.tabSelectWidget.getItemFromData( card.getName() ).getLevel();
		if (
			prev &&
			level === this.tabSelectWidget.getItemFromData( prev.getName() ).getLevel()
		) {
			return prev;
		}
		if (
			next &&
			level === this.tabSelectWidget.getItemFromData( next.getName() ).getLevel()
		) {
			return next;
		}
	}
	return prev || next || null;
};

/**
 * Get the tabs widget.
 *
 * @return {OO.ui.TabSelectWidget} Tabs widget
 */
OO.ui.IndexLayout.prototype.getTabs = function () {
	return this.tabSelectWidget;
};

/**
 * Get a card by its symbolic name.
 *
 * @param {string} name Symbolic name of card
 * @return {OO.ui.CardLayout|undefined} Card, if found
 */
OO.ui.IndexLayout.prototype.getCard = function ( name ) {
	return this.cards[ name ];
};

/**
 * Get the current card.
 *
 * @return {OO.ui.CardLayout|undefined} Current card, if found
 */
OO.ui.IndexLayout.prototype.getCurrentCard = function () {
	var name = this.getCurrentCardName();
	return name ? this.getCard( name ) : undefined;
};

/**
 * Get the symbolic name of the current card.
 *
 * @return {string|null} Symbolic name of the current card
 */
OO.ui.IndexLayout.prototype.getCurrentCardName = function () {
	return this.currentCardName;
};

/**
 * Add cards to the index layout
 *
 * When cards are added with the same names as existing cards, the existing cards will be
 * automatically removed before the new cards are added.
 *
 * @param {OO.ui.CardLayout[]} cards Cards to add
 * @param {number} index Index of the insertion point
 * @fires add
 * @chainable
 */
OO.ui.IndexLayout.prototype.addCards = function ( cards, index ) {
	var i, len, name, card, item, currentIndex,
		stackLayoutCards = this.stackLayout.getItems(),
		remove = [],
		items = [];

	// Remove cards with same names
	for ( i = 0, len = cards.length; i < len; i++ ) {
		card = cards[ i ];
		name = card.getName();

		if ( Object.prototype.hasOwnProperty.call( this.cards, name ) ) {
			// Correct the insertion index
			currentIndex = stackLayoutCards.indexOf( this.cards[ name ] );
			if ( currentIndex !== -1 && currentIndex + 1 < index ) {
				index--;
			}
			remove.push( this.cards[ name ] );
		}
	}
	if ( remove.length ) {
		this.removeCards( remove );
	}

	// Add new cards
	for ( i = 0, len = cards.length; i < len; i++ ) {
		card = cards[ i ];
		name = card.getName();
		this.cards[ card.getName() ] = card;
		item = new OO.ui.TabOptionWidget( { data: name } );
		card.setTabItem( item );
		items.push( item );
	}

	if ( items.length ) {
		this.tabSelectWidget.addItems( items, index );
		this.selectFirstSelectableCard();
	}
	this.stackLayout.addItems( cards, index );
	this.emit( 'add', cards, index );

	return this;
};

/**
 * Remove the specified cards from the index layout.
 *
 * To remove all cards from the index, you may wish to use the #clearCards method instead.
 *
 * @param {OO.ui.CardLayout[]} cards An array of cards to remove
 * @fires remove
 * @chainable
 */
OO.ui.IndexLayout.prototype.removeCards = function ( cards ) {
	var i, len, name, card,
		items = [];

	for ( i = 0, len = cards.length; i < len; i++ ) {
		card = cards[ i ];
		name = card.getName();
		delete this.cards[ name ];
		items.push( this.tabSelectWidget.getItemFromData( name ) );
		card.setTabItem( null );
	}
	if ( items.length ) {
		this.tabSelectWidget.removeItems( items );
		this.selectFirstSelectableCard();
	}
	this.stackLayout.removeItems( cards );
	this.emit( 'remove', cards );

	return this;
};

/**
 * Clear all cards from the index layout.
 *
 * To remove only a subset of cards from the index, use the #removeCards method.
 *
 * @fires remove
 * @chainable
 */
OO.ui.IndexLayout.prototype.clearCards = function () {
	var i, len,
		cards = this.stackLayout.getItems();

	this.cards = {};
	this.currentCardName = null;
	this.tabSelectWidget.clearItems();
	for ( i = 0, len = cards.length; i < len; i++ ) {
		cards[ i ].setTabItem( null );
	}
	this.stackLayout.clearItems();

	this.emit( 'remove', cards );

	return this;
};

/**
 * Set the current card by symbolic name.
 *
 * @fires set
 * @param {string} name Symbolic name of card
 */
OO.ui.IndexLayout.prototype.setCard = function ( name ) {
	var selectedItem,
		$focused,
		card = this.cards[ name ];

	if ( name !== this.currentCardName ) {
		selectedItem = this.tabSelectWidget.getSelectedItem();
		if ( selectedItem && selectedItem.getData() !== name ) {
			this.tabSelectWidget.selectItemByData( name );
		}
		if ( card ) {
			if ( this.currentCardName && this.cards[ this.currentCardName ] ) {
				this.cards[ this.currentCardName ].setActive( false );
				// Blur anything focused if the next card doesn't have anything focusable - this
				// is not needed if the next card has something focusable because once it is focused
				// this blur happens automatically
				if ( this.autoFocus && !card.$element.find( ':input' ).length ) {
					$focused = this.cards[ this.currentCardName ].$element.find( ':focus' );
					if ( $focused.length ) {
						$focused[ 0 ].blur();
					}
				}
			}
			this.currentCardName = name;
			this.stackLayout.setItem( card );
			card.setActive( true );
			this.emit( 'set', card );
		}
	}
};

/**
 * Select the first selectable card.
 *
 * @chainable
 */
OO.ui.IndexLayout.prototype.selectFirstSelectableCard = function () {
	if ( !this.tabSelectWidget.getSelectedItem() ) {
		this.tabSelectWidget.selectItem( this.tabSelectWidget.getFirstSelectableItem() );
	}

	return this;
};
<|endoftext|>


<commit_before>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2015, Olivier Biot, Jason Oster, Aaron McLeod
 * http://www.melonjs.org
 *
 */
(function () {
    /**
     * a level manager object

     * once ressources loaded, the level director contains all references of defined levels

     * There is no constructor function for me.levelDirector, this is a static object
     * @namespace me.levelDirector
     * @memberOf me
     */
    me.levelDirector = (function () {
        // hold public stuff in our singletong
        var api = {};

        /*
         * PRIVATE STUFF
         */

        // our levels
        var levels = {};
        // level index table
        var levelIdx = [];
        // current level index
        var currentLevelIdx = 0;

        function safeLoadLevel(levelId, options, restart) {
            // clean the destination container
            options.container.destroy();

            // clean the current (previous) level
            if (levels[api.getCurrentLevelId()]) {
                levels[api.getCurrentLevelId()].destroy();
            }

            // update current level index
            currentLevelIdx = levelIdx.indexOf(levelId);

            // add the specified level to the game world
            loadTMXLevel(levelId, options.container, options.flatten);

            // publish the corresponding message
            me.event.publish(me.event.LEVEL_LOADED, [ levelId ]);

            // fire the callback
            options.onLoaded(levelId);

            if (restart) {
                // resume the game loop if it was previously running
                me.state.restart();
            }
        }

        /**
         * Load a TMX level
         * @name loadTMXLevel
         * @memberOf me.game
         * @private
         * @param {String} level level id
         * @param {me.Container} target container
         * @param {boolean} flatten if true, flatten all objects into the given container
         * @ignore
         * @function
         */
        function loadTMXLevel(levelId, container, flatten) {
            var level = levels[levelId];

            // disable auto-sort for the given container
            var autoSort = container.autoSort;
            container.autoSort = false;

            // reset the GUID generator
            // and pass the level id as parameter
            me.utils.resetGUID(levelId, level.nextobjectid);

            // add all level elements to the target container
            level.addTo(container, flatten);

            // sort everything (recursively)
            container.sort(true);
            container.autoSort = autoSort;

            container.resize(level.width, level.height);

            if (container === me.game.world) {

                // update the viewport bounds
                me.game.viewport.setBounds(
                    0, 0,
                    Math.max(level.width, me.game.viewport.width),
                    Math.max(level.height, me.game.viewport.height)
                );

                // center the map if smaller than the current viewport
                container.pos.set(
                    Math.max(0, ~~((me.game.viewport.width - level.width) / 2)),
                    Math.max(0, ~~((me.game.viewport.height - level.height) / 2)),
                    0
                );

                // translate the display if required
                container.transform.identity();
                container.transform.translateV(container.pos);
            }
        }

        /*
         * PUBLIC STUFF
         */

        /**
         * reset the level director
         * @ignore
         */
        api.reset = function () {};

        /**
         * add a level
         * @ignore
         */
        api.addLevel = function () {
            throw new me.Error("no level loader defined");
        };

        /**
         * add a TMX level
         * @ignore
         */
        api.addTMXLevel = function (levelId, callback) {
            // just load the level with the XML stuff
            if (levels[levelId] == null) {
                //console.log("loading "+ levelId);
                levels[levelId] = new me.TMXTileMap(levelId, me.loader.getTMX(levelId));
                // level index
                levelIdx.push(levelId);
            }
            else  {
                //console.log("level %s already loaded", levelId);
                return false;
            }

            // call the callback if defined
            if (callback) {
                callback();
            }
            // true if level loaded
            return true;
        };

        /**
         * load a level into the game manager

         * (will also create all level defined entities, etc..)
         * @name loadLevel
         * @memberOf me.levelDirector
         * @public
         * @function
         * @param {String} level level id
         * @param {Object} [options] additional optional parameters
         * @param {me.Container} [options.container=me.game.world] container in which to load the specified level
         * @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded
         * @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container
         * @example
         * // the game defined ressources
         * // to be preloaded by the loader
         * // TMX maps
         * var resources = [
         *     {name: "a4_level1",   type: "tmx",   src: "data/level/a4_level1.tmx"},
         *     {name: "a4_level2",   type: "tmx",   src: "data/level/a4_level2.tmx"},
         *     {name: "a4_level3",   type: "tmx",   src: "data/level/a4_level3.tmx"},
         *     // ...
         * ];
         *
         * // ...
         *
         * // load a level
         * me.levelDirector.loadLevel("a4_level1");
         */
        api.loadLevel = function (levelId, options) {
            options = Object.assign({
                "container" : me.game.world,
                "onLoaded"  : me.game.onLevelLoaded,
                "flatten"   : me.game.mergeGroup
            }, options || {});

            // throw an exception if not existing
            if (typeof(levels[levelId]) === "undefined") {
                throw new me.Error("level " + levelId + " not found");
            }

            if (levels[levelId] instanceof me.TMXTileMap) {

                // check the status of the state mngr
                var wasRunning = me.state.isRunning();

                if (wasRunning) {
                    // stop the game loop to avoid
                    // some silly side effects
                    me.state.stop();

                    safeLoadLevel.defer(this, levelId, options, true);
                }
                else {
                    safeLoadLevel(levelId, options);
                }
            }
            else {
                throw new me.Error("no level loader defined");
            }
            return true;
        };

        /**
         * return the current level id

         * @name getCurrentLevelId
         * @memberOf me.levelDirector
         * @public
         * @function
         * @return {String}
         */
        api.getCurrentLevelId = function () {
            return levelIdx[currentLevelIdx];
        };

        /**
         * return the current level definition.
         * for a reference to the live instantiated level,
         * rather use the container in which it was loaded (e.g. me.game.world)
         * @name getCurrentLevel
         * @memberOf me.levelDirector
         * @public
         * @function
         * @return {me.TMXTileMap}
         */
        api.getCurrentLevel = function () {
            return levels[api.getCurrentLevelId()];
        };

        /**
         * reload the current level

         * @name reloadLevel
         * @memberOf me.levelDirector
         * @public
         * @function
         * @param {Object} [options] additional optional parameters
         * @param {me.Container} [options.container=me.game.world] container in which to load the specified level
         * @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded
         * @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container
         */
        api.reloadLevel = function (options) {
            // reset the level to initial state
            //levels[currentLevel].reset();
            return api.loadLevel(api.getCurrentLevelId(), options);
        };

        /**
         * load the next level

         * @name nextLevel
         * @memberOf me.levelDirector
         * @public
         * @function
         * @param {Object} [options] additional optional parameters
         * @param {me.Container} [options.container=me.game.world] container in which to load the specified level
         * @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded
         * @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container
         */
        api.nextLevel = function (options) {
            //go to the next level
            if (currentLevelIdx + 1 < levelIdx.length) {
                return api.loadLevel(levelIdx[currentLevelIdx + 1], options);
            }
            else {
                return false;
            }
        };

        /**
         * load the previous level

         * @name previousLevel
         * @memberOf me.levelDirector
         * @public
         * @function
         * @param {Object} [options] additional optional parameters
         * @param {me.Container} [options.container=me.game.world] container in which to load the specified level
         * @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded
         * @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container
         */
        api.previousLevel = function (options) {
            // go to previous level
            if (currentLevelIdx - 1 >= 0) {
                return api.loadLevel(levelIdx[currentLevelIdx - 1], options);
            }
            else {
                return false;
            }
        };

        /**
         * return the amount of level preloaded

         * @name levelCount
         * @memberOf me.levelDirector
         * @public
         * @function
         */
        api.levelCount = function () {
            return levelIdx.length;
        };

        // return our object
        return api;
    })();
})();
<commit_msg>[#704] Fix an exception caused by race condition between viewport instantiation and entity instantiation<commit_after>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2015, Olivier Biot, Jason Oster, Aaron McLeod
 * http://www.melonjs.org
 *
 */
(function () {
    /**
     * a level manager object

     * once ressources loaded, the level director contains all references of defined levels

     * There is no constructor function for me.levelDirector, this is a static object
     * @namespace me.levelDirector
     * @memberOf me
     */
    me.levelDirector = (function () {
        // hold public stuff in our singletong
        var api = {};

        /*
         * PRIVATE STUFF
         */

        // our levels
        var levels = {};
        // level index table
        var levelIdx = [];
        // current level index
        var currentLevelIdx = 0;

        function safeLoadLevel(levelId, options, restart) {
            // clean the destination container
            options.container.destroy();

            // clean the current (previous) level
            if (levels[api.getCurrentLevelId()]) {
                levels[api.getCurrentLevelId()].destroy();
            }

            // update current level index
            currentLevelIdx = levelIdx.indexOf(levelId);

            // add the specified level to the game world
            loadTMXLevel(levelId, options.container, options.flatten);

            // publish the corresponding message
            me.event.publish(me.event.LEVEL_LOADED, [ levelId ]);

            // fire the callback
            options.onLoaded(levelId);

            if (restart) {
                // resume the game loop if it was previously running
                me.state.restart();
            }
        }

        /**
         * Load a TMX level
         * @name loadTMXLevel
         * @memberOf me.game
         * @private
         * @param {String} level level id
         * @param {me.Container} target container
         * @param {boolean} flatten if true, flatten all objects into the given container
         * @ignore
         * @function
         */
        function loadTMXLevel(levelId, container, flatten) {
            var level = levels[levelId];

            // disable auto-sort for the given container
            var autoSort = container.autoSort;
            container.autoSort = false;

            if (container === me.game.world) {

                // update the viewport bounds
                me.game.viewport.setBounds(
                    0, 0,
                    Math.max(level.width, me.game.viewport.width),
                    Math.max(level.height, me.game.viewport.height)
                );
            }

            // reset the GUID generator
            // and pass the level id as parameter
            me.utils.resetGUID(levelId, level.nextobjectid);

            // add all level elements to the target container
            level.addTo(container, flatten);

            // sort everything (recursively)
            container.sort(true);
            container.autoSort = autoSort;

            container.resize(level.width, level.height);

            if (container === me.game.world) {

                // center the map if smaller than the current viewport
                container.pos.set(
                    Math.max(0, ~~((me.game.viewport.width - level.width) / 2)),
                    Math.max(0, ~~((me.game.viewport.height - level.height) / 2)),
                    0
                );

                // translate the display if required
                container.transform.identity();
                container.transform.translateV(container.pos);
            }
        }

        /*
         * PUBLIC STUFF
         */

        /**
         * reset the level director
         * @ignore
         */
        api.reset = function () {};

        /**
         * add a level
         * @ignore
         */
        api.addLevel = function () {
            throw new me.Error("no level loader defined");
        };

        /**
         * add a TMX level
         * @ignore
         */
        api.addTMXLevel = function (levelId, callback) {
            // just load the level with the XML stuff
            if (levels[levelId] == null) {
                //console.log("loading "+ levelId);
                levels[levelId] = new me.TMXTileMap(levelId, me.loader.getTMX(levelId));
                // level index
                levelIdx.push(levelId);
            }
            else  {
                //console.log("level %s already loaded", levelId);
                return false;
            }

            // call the callback if defined
            if (callback) {
                callback();
            }
            // true if level loaded
            return true;
        };

        /**
         * load a level into the game manager

         * (will also create all level defined entities, etc..)
         * @name loadLevel
         * @memberOf me.levelDirector
         * @public
         * @function
         * @param {String} level level id
         * @param {Object} [options] additional optional parameters
         * @param {me.Container} [options.container=me.game.world] container in which to load the specified level
         * @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded
         * @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container
         * @example
         * // the game defined ressources
         * // to be preloaded by the loader
         * // TMX maps
         * var resources = [
         *     {name: "a4_level1",   type: "tmx",   src: "data/level/a4_level1.tmx"},
         *     {name: "a4_level2",   type: "tmx",   src: "data/level/a4_level2.tmx"},
         *     {name: "a4_level3",   type: "tmx",   src: "data/level/a4_level3.tmx"},
         *     // ...
         * ];
         *
         * // ...
         *
         * // load a level
         * me.levelDirector.loadLevel("a4_level1");
         */
        api.loadLevel = function (levelId, options) {
            options = Object.assign({
                "container" : me.game.world,
                "onLoaded"  : me.game.onLevelLoaded,
                "flatten"   : me.game.mergeGroup
            }, options || {});

            // throw an exception if not existing
            if (typeof(levels[levelId]) === "undefined") {
                throw new me.Error("level " + levelId + " not found");
            }

            if (levels[levelId] instanceof me.TMXTileMap) {

                // check the status of the state mngr
                var wasRunning = me.state.isRunning();

                if (wasRunning) {
                    // stop the game loop to avoid
                    // some silly side effects
                    me.state.stop();

                    safeLoadLevel.defer(this, levelId, options, true);
                }
                else {
                    safeLoadLevel(levelId, options);
                }
            }
            else {
                throw new me.Error("no level loader defined");
            }
            return true;
        };

        /**
         * return the current level id

         * @name getCurrentLevelId
         * @memberOf me.levelDirector
         * @public
         * @function
         * @return {String}
         */
        api.getCurrentLevelId = function () {
            return levelIdx[currentLevelIdx];
        };

        /**
         * return the current level definition.
         * for a reference to the live instantiated level,
         * rather use the container in which it was loaded (e.g. me.game.world)
         * @name getCurrentLevel
         * @memberOf me.levelDirector
         * @public
         * @function
         * @return {me.TMXTileMap}
         */
        api.getCurrentLevel = function () {
            return levels[api.getCurrentLevelId()];
        };

        /**
         * reload the current level

         * @name reloadLevel
         * @memberOf me.levelDirector
         * @public
         * @function
         * @param {Object} [options] additional optional parameters
         * @param {me.Container} [options.container=me.game.world] container in which to load the specified level
         * @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded
         * @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container
         */
        api.reloadLevel = function (options) {
            // reset the level to initial state
            //levels[currentLevel].reset();
            return api.loadLevel(api.getCurrentLevelId(), options);
        };

        /**
         * load the next level

         * @name nextLevel
         * @memberOf me.levelDirector
         * @public
         * @function
         * @param {Object} [options] additional optional parameters
         * @param {me.Container} [options.container=me.game.world] container in which to load the specified level
         * @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded
         * @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container
         */
        api.nextLevel = function (options) {
            //go to the next level
            if (currentLevelIdx + 1 < levelIdx.length) {
                return api.loadLevel(levelIdx[currentLevelIdx + 1], options);
            }
            else {
                return false;
            }
        };

        /**
         * load the previous level

         * @name previousLevel
         * @memberOf me.levelDirector
         * @public
         * @function
         * @param {Object} [options] additional optional parameters
         * @param {me.Container} [options.container=me.game.world] container in which to load the specified level
         * @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded
         * @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container
         */
        api.previousLevel = function (options) {
            // go to previous level
            if (currentLevelIdx - 1 >= 0) {
                return api.loadLevel(levelIdx[currentLevelIdx - 1], options);
            }
            else {
                return false;
            }
        };

        /**
         * return the amount of level preloaded

         * @name levelCount
         * @memberOf me.levelDirector
         * @public
         * @function
         */
        api.levelCount = function () {
            return levelIdx.length;
        };

        // return our object
        return api;
    })();
})();
<|endoftext|>


<commit_before>import {Router} from 'express'
import models from '../models';

export default () => {
	let app = Router()

	app.get('/new', (req, res) => {
		res.render('sessions/new')
	})

	app.post('/', (req, res) => {
		var email = req.body.user.email
		models.User.find({
			where: {
				email: email
			}
		}).then((user) => {
			if(user.email === email && user.password === req.body.user.password){
				req.session.userId = user.dataValues.id
				req.app.locals.userId = user.dataValues.id
				res.redirect('/users/' + user.dataValues.id);
			} else {
				res.render('sessions/new', {error: "Incorrect username/password combination."})
			}
		})
	})

	app.delete('/', (req, res) => {
		req.session.userId = null
		res.redirect('/')
	})
	return app
}<commit_msg>Add proper routes for sessions<commit_after>import {Router} from 'express'
import models from '../models';

export default () => {
	let app = Router()

	app.get('/new', (req, res) => {
		res.render('sessions/new')
	})

	app.post('/', (req, res) => {
		var email = req.body.email
		models.User.find({
			where: {
				email: email
			}
		}).then((user) => {

			let token;
			let authenticated;
			let err;
			console.log(user)
			if (userExists(user) && req.body.password_hash === user.password_hash){
				authenticated = true
				token = Math.random().toString(36).substring(7)
			} else {
				authenticated = false
				token = null
				if (userExists(user)) {
					err = {type: "nil-user"}
				} else {
					err = {type: "incorrect-password"}
				}
			}

			res.send({
				authenticated,
				token,
				err
			});
		})
	})

	app.delete('/', (req, res) => {
		req.session.userId = null
		res.redirect('/')
	})

	function userExists(user) {
		return user !== null
	}

	return app
}
<|endoftext|>


<commit_before>'use strict';

const utils = require('../utils');
const {FILE, FOLDER} = require('../CONSTANTS');

const normalizeEntryName = utils.normalizeEntryName;
const forIn              = utils.forIn;
const isFunction         = utils.isFunction;
const tryRequire         = utils.tryRequire;

const tryReqFn  = tryRequire.fn;
const tryReqObj = tryRequire.obj;

const DONT_REMOVE_EXT = false;

const supportedVerbs = [
	'get',
	'post',
	'put',
	'delete',
];

const noVerb = [
	'no-verb',
	'no_verb'
];

function remove$ (verb) {
	if (verb[0] === '$') {
		verb = verb.substr(1);
	}

	return verb;
}

function isVerb (verb) {
	verb = remove$(verb);

	return supportedVerbs.includes(verb);
}

function isNoVerb (verb) {
	verb = remove$(verb);

	return noVerb.includes(verb);
}

const entryHandlers = {
	$in (entryMap) {
		this.in      = tryReqFn(entryMap.path);
		this.in.path = entryMap.path;
	},
	index (entryMap) {
		this.index      = tryReqFn(entryMap.path);
		this.index.path = entryMap.path;
	},
	$before_verb (entryMap) {
		this.index      = tryReqFn(entryMap.path);
		this.index.path = entryMap.path;
	},
	'$before-verb' (entryMap) {
		this.index      = tryReqFn(entryMap.path);
		this.index.path = entryMap.path;
	},
	$get (entryMap) {
		this.verbs.get      = tryReqFn(entryMap.path);
		this.verbs.get.path = entryMap.path;
	},
	$post (entryMap) {
		this.verbs.post      = tryReqFn(entryMap.path);
		this.verbs.post.path = entryMap.path;
	},
	$put (entryMap) {
		this.verbs.put      = tryReqFn(entryMap.path);
		this.verbs.put.path = entryMap.path;
	},
	$delete (entryMap) {
		this.verbs.delete      = tryReqFn(entryMap.path);
		this.verbs.delete.path = entryMap.path;
	},
	'$no-verb' (entryMap) {
		this.noVerb      = tryReqFn(entryMap.path);
		this.noVerb.path = entryMap.path;
	},
	$no_verb (entryMap) {
		this.noVerb      = tryReqFn(entryMap.path);
		this.noVerb.path = entryMap.path;
	},
	$after_verb (entryMap) {
		this.afterVerb      = tryReqFn(entryMap.path);
		this.afterVerb.path = entryMap.path;
	},
	'$after-verb' (entryMap) {
		this.afterVerb      = tryReqFn(entryMap.path);
		this.afterVerb.path = entryMap.path;
	},
	$pre_method (entryMap) {
		this.preMethod      = tryReqFn(entryMap.path);
		this.preMethod.path = entryMap.path;
	},
	'$pre-method' (entryMap) {
		this.preMethod      = tryReqFn(entryMap.path);
		this.preMethod.path = entryMap.path;
	},
	$post_method (entryMap) {
		this.postMethod      = tryReqFn(entryMap.path);
		this.postMethod.path = entryMap.path;
	},
	'$post-method' (entryMap) {
		this.postMethod      = tryReqFn(entryMap.path);
		this.postMethod.path = entryMap.path;
	},
	$pre_sub (entryMap) {
		this.preSub      = tryReqFn(entryMap.path);
		this.preSub.path = entryMap.path;
	},
	'$pre-sub' (entryMap) {
		this.preSub      = tryReqFn(entryMap.path);
		this.preSub.path = entryMap.path;
	},
	$post_sub (entryMap) {
		this.postSub      = tryReqFn(entryMap.path);
		this.postSub.path = entryMap.path;
	},
	'$post-sub' (entryMap) {
		this.postSub      = tryReqFn(entryMap.path);
		this.postSub.path = entryMap.path;
	},
	$out (entryMap) {
		this.out      = tryReqFn(entryMap.path);
		this.out.path = entryMap.path;
	},
	$verbs (entryMap) {
		if (entryMap.type === FOLDER) {
			forIn(entryMap.entries, (verbName, verbMap) => {
				verbName = normalizeEntryName(verbName, verbMap.type);

				const [isVerbOk, isNoVerbOk] = checkVerbName(this, verbName);

				if (isVerbOk || isNoVerbOk) {
					entryHandlers['$' + verbName].call(this, verbMap); // eslint-disable-line prefer-template
				}
			});
		}
		else if (entryMap.type === FILE) {
			const verbsObj = tryReqObj(entryMap.path);

			if (verbsObj) {
				forIn(verbsObj, (verbName, fn) => {
					if (!isFunction(fn)) return;

					verbName = normalizeEntryName(verbName, DONT_REMOVE_EXT);

					const [isVerbOk, isNoVerbOk] = checkVerbName(this, verbName);

					if (isVerbOk) {
						this.verbs[verbName] = fn;
						this.verbs[verbName].path = entryMap.path;
					}
					else if (isNoVerbOk) {
						this.noVerb = fn;
						this.noVerb.path = entryMap.path;
					}
				});
			}
		}
	}
};

function checkVerbName (ctrl, verbName) {
	const isVerbOk   = isVerb(verbName) && !ctrl.verbs[verbName];
	const isNoVerbOk = isNoVerb(verbName) && !ctrl.noVerb;

	return [isVerbOk, isNoVerbOk];
}

// -------------------------------------
module.exports = function () {
	return Object.create(entryHandlers);
};
<commit_msg>fix entry names inside $verbs to with or w/o $<commit_after>'use strict';

const utils = require('../utils');
const {FILE, FOLDER} = require('../CONSTANTS');

const normalizeEntryName = utils.normalizeEntryName;
const forIn              = utils.forIn;
const isFunction         = utils.isFunction;
const tryRequire         = utils.tryRequire;

const tryReqFn  = tryRequire.fn;
const tryReqObj = tryRequire.obj;

const DONT_REMOVE_EXT = false;

const entryHandlers = {
	$in (entryMap) {
		this.in      = tryReqFn(entryMap.path);
		this.in.path = entryMap.path;
	},
	index (entryMap) {
		this.index      = tryReqFn(entryMap.path);
		this.index.path = entryMap.path;
	},
	$before_verb (entryMap) {
		this.index      = tryReqFn(entryMap.path);
		this.index.path = entryMap.path;
	},
	'$before-verb' (entryMap) {
		this.index      = tryReqFn(entryMap.path);
		this.index.path = entryMap.path;
	},
	$get (entryMap) {
		this.verbs.get      = tryReqFn(entryMap.path);
		this.verbs.get.path = entryMap.path;
	},
	$post (entryMap) {
		this.verbs.post      = tryReqFn(entryMap.path);
		this.verbs.post.path = entryMap.path;
	},
	$put (entryMap) {
		this.verbs.put      = tryReqFn(entryMap.path);
		this.verbs.put.path = entryMap.path;
	},
	$delete (entryMap) {
		this.verbs.delete      = tryReqFn(entryMap.path);
		this.verbs.delete.path = entryMap.path;
	},
	'$no-verb' (entryMap) {
		this.noVerb      = tryReqFn(entryMap.path);
		this.noVerb.path = entryMap.path;
	},
	$no_verb (entryMap) {
		this.noVerb      = tryReqFn(entryMap.path);
		this.noVerb.path = entryMap.path;
	},
	$after_verb (entryMap) {
		this.afterVerb      = tryReqFn(entryMap.path);
		this.afterVerb.path = entryMap.path;
	},
	'$after-verb' (entryMap) {
		this.afterVerb      = tryReqFn(entryMap.path);
		this.afterVerb.path = entryMap.path;
	},
	$pre_method (entryMap) {
		this.preMethod      = tryReqFn(entryMap.path);
		this.preMethod.path = entryMap.path;
	},
	'$pre-method' (entryMap) {
		this.preMethod      = tryReqFn(entryMap.path);
		this.preMethod.path = entryMap.path;
	},
	$post_method (entryMap) {
		this.postMethod      = tryReqFn(entryMap.path);
		this.postMethod.path = entryMap.path;
	},
	'$post-method' (entryMap) {
		this.postMethod      = tryReqFn(entryMap.path);
		this.postMethod.path = entryMap.path;
	},
	$pre_sub (entryMap) {
		this.preSub      = tryReqFn(entryMap.path);
		this.preSub.path = entryMap.path;
	},
	'$pre-sub' (entryMap) {
		this.preSub      = tryReqFn(entryMap.path);
		this.preSub.path = entryMap.path;
	},
	$post_sub (entryMap) {
		this.postSub      = tryReqFn(entryMap.path);
		this.postSub.path = entryMap.path;
	},
	'$post-sub' (entryMap) {
		this.postSub      = tryReqFn(entryMap.path);
		this.postSub.path = entryMap.path;
	},
	$out (entryMap) {
		this.out      = tryReqFn(entryMap.path);
		this.out.path = entryMap.path;
	},
	$verbs (entryMap) {
		if (entryMap.type === FOLDER) {
			forIn(entryMap.entries, (verbName, verbMap) => {
				verbName = normalizeEntryName(verbName, verbMap.type);

				const [isVerbOk, isNoVerbOk] = validateVerbName(this, verbName);

				if (isVerbOk || isNoVerbOk) {
					verbName = remove$(verbName);
					entryHandlers[`$${verbName}`].call(this, verbMap);
				}
			});
		}
		else if (entryMap.type === FILE) {
			const verbsObj = tryReqObj(entryMap.path);

			if (verbsObj) {
				forIn(verbsObj, (verbName, fn) => {
					if (!isFunction(fn)) return;

					verbName = normalizeEntryName(verbName, DONT_REMOVE_EXT);

					const [isVerbOk, isNoVerbOk] = validateVerbName(this, verbName);

					if (isVerbOk) {
						this.verbs[verbName] = fn;
						this.verbs[verbName].path = entryMap.path;
					}
					else if (isNoVerbOk) {
						this.noVerb = fn;
						this.noVerb.path = entryMap.path;
					}
				});
			}
		}
	}
};

function validateVerbName (ctrl, verbName) {
	verbName = remove$(verbName);

	const isVerbOk   = isVerb(verbName) && !ctrl.verbs[verbName];
	const isNoVerbOk = isNoVerb(verbName) && !ctrl.noVerb;

	return [isVerbOk, isNoVerbOk];
}

function remove$ (verb) {
	if (verb[0] === '$') {
		verb = verb.substr(1);
	}

	return verb;
}

const supportedVerbs = [
	'get',
	'post',
	'put',
	'delete',
];

const noVerb = [
	'no-verb',
	'no_verb'
];

function isVerb (verb) {
	return supportedVerbs.includes(verb);
}

function isNoVerb (verb) {
	return noVerb.includes(verb);
}

// -------------------------------------
module.exports = function () {
	return Object.create(entryHandlers);
};
<|endoftext|>


<commit_before>function capitaliseFirstLetter(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
}

if (!String.prototype.trim) {
  String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g, '');};
}

FLOW.ProjectListView = FLOW.View.extend({
  templateName: 'navSurveys/project-list'

});

FLOW.ProjectView = FLOW.View.extend({
  templateName: 'navSurveys/project',
});

FLOW.Project = FLOW.View.extend({

  showProjectDetails: false,
  showAdvancedSettings: false,
  selectedLanguage: null,
  monitoringGroupEnabled: false,
  selectedRegistrationForm: null,

  project: function() {
    return FLOW.projectControl.get('currentProject');
  }.property(),

  toggleShowProjectDetails: function() {
    this.set('showProjectDetails', !this.get('showProjectDetails'));
  },

  toggleShowAdvancedSettings: function() {
    this.set('showAdvancedSettings', !this.get('showAdvancedSettings'));
  },

  isNewProject: function() {
    var currentProject = FLOW.projectControl.get('currentProject');
    return currentProject && currentProject.get('code') == "New project"
  }.property('FLOW.projectControl.currentProject'),

  visibleProjectBasics: function() {
    return this.get('isNewProject') || this.get('showProjectDetails')
  }.property('showProjectDetails'),

  updateSelectedLanguage: function() {
    var currentProject = FLOW.projectControl.get('currentProject');
    if (currentProject)
      currentProject.set('defaultLanguageCode', this.selectedLanguage.get('value'))
  }.observes('this.selectedLanguage'),

  showMonitoringGroupCheckbox: function() {
    return FLOW.projectControl.get('formCount') < 2;
  }.property("FLOW.projectControl.formCount"),

  updateSelectedRegistrationForm: function() {
    FLOW.projectControl.currentProject.set('newLocaleSurveyId', this.selectedRegistrationForm.get('keyId'));
  }.observes('selectedRegistrationForm')

})

FLOW.ProjectMainView = FLOW.View.extend({

  doSave: function() {
    FLOW.store.commit();
  },

  hasUnsavedChanges: function() {
    var selectedProject = FLOW.projectControl.get('currentProject');
    var isProjectDirty = selectedProject ? selectedProject.get('isDirty') : false;

    var selectedForm = FLOW.selectedControl.get('selectedSurvey');
    var isFormDirty = selectedForm ? selectedForm.get('isDirty') : false;

    return isProjectDirty || isFormDirty;

  }.property('FLOW.projectControl.currentProject.isDirty',
              'FLOW.selectedControl.selectedSurvey.isDirty'),

  projectView: function() {
    return FLOW.projectControl.isProject(FLOW.projectControl.get('currentProject'));
  }.property('FLOW.projectControl.currentProject'),

  projectListView: function() {
    return FLOW.projectControl.isProjectFolder(FLOW.projectControl.get('currentProject'));
  }.property('FLOW.projectControl.currentProject'),

});

FLOW.ProjectList = FLOW.View.extend({
  tagName: 'ul',
  classNameBindings: ['classProperty'],
  classProperty: function() {
    return FLOW.projectControl.moveTarget || FLOW.projectControl.copyTarget
      ? 'actionProcess' : '';
  }.property('FLOW.projectControl.moveTarget', 'FLOW.projectControl.copyTarget')
});

FLOW.ProjectItemView = FLOW.View.extend({
  tagName: 'li',
  content: null,
  classNameBindings: ['classProperty'],
  folderEdit: false,

  classProperty: function() {
    var isFolder = FLOW.projectControl.isProjectFolder(this.content);
    var isFolderEmpty = FLOW.projectControl.isProjectFolderEmpty(this.content);
    var isMoving = this.content === FLOW.projectControl.get('moveTarget');
    var isCopying = this.content === FLOW.projectControl.get('copyTarget');

    var classes = "aSurvey";
    if (isFolder) classes += " aFolder";
    if (isFolderEmpty) classes += " folderEmpty"
    if (isMoving || isCopying) classes += " highLighted";

    return classes;
  }.property('FLOW.projectControl.moveTarget', 'FLOW.projectControl.copyTarget'),

  toggleEditFolderName: function(evt) {
    this.set('folderEdit', !this.get('folderEdit'));
  },

  isFolder: function() {
    return FLOW.projectControl.isProjectFolder(this.content);
  }.property(),

  formatDate: function(datetime) {
    var date = new Date(parseInt(datetime));
    return date.getDay() + "." + (date.getMonth() + 1) + " " + date.getFullYear();
  },

  created: function() {
    return this.formatDate(this.content.get('createdDateTime'));
  }.property('this.content.createdDateTime'),

  modified: function() {
    return this.formatDate(this.content.get('lastUpdateDateTime'));
  }.property('this.content.lastUpdateDateTime'),

  isPrivate: function() {
    return this.content.get('privacyLevel') === "PRIVATE";
  }.property(),

  language: function() {
    var langs = {en: "English", es: "Español", fr: "Français"};
    return langs[this.content.get('defaultLanguageCode')];
  }.property(),

});

FLOW.FolderEditView = Ember.TextField.extend({
  content: null,

  focusOut: function() {
    this.content.set('name', this.content.get('code'));
    FLOW.store.commit();
  }
});

FLOW.FormTabView = Ember.View.extend({
  tagName: 'li',
  content: null,
  classNameBindings: ['classProperty'],

  classProperty: function() {

    var form = this.get('content');
    var classString = 'aFormTab';

    if (form === null) return classString;

    // Return "aFormTab" "active" and/or "registrationForm"
    var isActive = form === FLOW.selectedControl.get('selectedSurvey');
    var isRegistrationForm = FLOW.projectControl.currentProject.get('monitoringGroup') &&
      form.get('keyId') === FLOW.projectControl.currentProject.get('newLocaleSurveyId');

    if (isActive) classString += ' active';
    if (isRegistrationForm) classString += ' registrationForm';

    return classString;
  }.property('FLOW.selectedControl.selectedSurvey', 'FLOW.projectControl.currentProject.newLocaleSurveyId'),
})

// displays survey groups in left sidebar
FLOW.SurveyGroupMenuItemView = FLOW.View.extend({
  content: null,
  tagName: 'li',
  classNameBindings: ['classProperty'],
  monitoringGroup: false,

  classProperty: function() {

    var className = this.get('amFolder') ? "aFolder" : "aSurvey";
    return className;
  }.property('this.amFolder'),

  // true if the survey group is selected. Used to set proper display class
  amSelected: function () {
    var selected = FLOW.selectedControl.get('selectedSurveyGroup');
    if (selected) {
      var amSelected = (this.content.get('keyId') === FLOW.selectedControl.selectedSurveyGroup.get('keyId'));
      return amSelected;
    } else {
      return null;
    }
  }.property('FLOW.selectedControl.selectedSurveyGroup', 'content').cacheable(),

  amFolder: function () {
    return this.content.get('projectType') === 'PROJECT_FOLDER';
  }.property('this.projectType').cacheable(),

  amMonitoringGroup: function () {
    return this.content.get('monitoringGroup');
  }.property('this.monitoringGroup').cacheable(),

  // fired when a survey group is clicked
  makeSelected: function () {
    if (this.content.get('projectType') === "PROJECT_FOLDER") {
      FLOW.surveyGroupControl.setCurrentFolder(this.content.get('keyId'));
    } else {
      FLOW.selectedControl.set('selectedSurveyGroup', this.content);
    }
  }
});

// displays single survey in content area of survey group page
// doEditSurvey is defined in the Router. It transfers to the nav-surveys-edit handlebar view
FLOW.SurveyGroupSurveyView = FLOW.View.extend({

  // fired when 'preview survey' is clicked in the survey item display
  previewSurvey: function () {
    FLOW.selectedControl.set('selectedSurvey', this.content);
  },

  showPreview: function () {
    if (FLOW.questionControl.content.get('isLoaded')) {
      FLOW.previewControl.set('showPreviewPopup', true);
    }
  }.observes('FLOW.questionControl.content.isLoaded'),

  // fired when 'delete survey' is clicked in the survey item display
  deleteSurvey: function () {
    var sId = this.content.get('id');
    var survey = FLOW.store.find(FLOW.Survey, sId);

    // do preflight check if deleting this survey is allowed
    // if successful, the deletion action will be called from DS.FLOWrestadaptor.sideload
    FLOW.store.findQuery(FLOW.Survey, {
      preflight: 'delete',
      surveyId: sId
    });
  },

  // fired when 'inspect data' is clicked in the survey item display
  inspectData: function () {
    console.log("TODO inspect Data");
  }

});

// handles all survey-group interaction elements on survey group page
FLOW.SurveyGroupMainView = FLOW.View.extend({
  showEditField: false,
  showNewGroupField: false,
  surveyGroupName: null,
  showSGDeleteDialog: false,
  showSGDeleteNotPossibleDialog: false,
  showCopySurveyDialogBool: false,
  newSurveyName: null,
  monitoringGroup:false,

  // true if at least one survey group is active
  oneSelected: function () {
    var selected = FLOW.selectedControl.get('selectedSurveyGroup');
    if (selected) {
      return true;
    } else {
      return false;
    }
  }.property('FLOW.selectedControl.selectedSurveyGroup'),

  initVars: function () {
    if (FLOW.selectedControl && FLOW.selectedControl.selectedSurveyGroup) {
      this.set('monitoringGroup', FLOW.selectedControl.selectedSurveyGroup.get('monitoringGroup'));
    }
  }.observes('FLOW.selectedControl.selectedSurveyGroup', 'FLOW.selectedControl.selectedSurvey'),

  // fired when 'edit name' is clicked, shows edit field to change survey group name
  editSurveyGroupName: function () {
    this.set('surveyGroupName', FLOW.selectedControl.selectedSurveyGroup.get('code'));
    this.set('showEditField', true);
  },

  // fired when 'save' is clicked while showing edit group name field. Saves the new group name
  saveSurveyGroupNameEdit: function () {
    if (!Ember.empty(this.get('surveyGroupName').trim())){
      var sgId = FLOW.selectedControl.selectedSurveyGroup.get('id');
      var surveyGroup = FLOW.store.find(FLOW.SurveyGroup, sgId);
      surveyGroup.set('code', capitaliseFirstLetter(this.get('surveyGroupName')));
      surveyGroup.set('name', capitaliseFirstLetter(this.get('surveyGroupName')));
      FLOW.store.commit();
      FLOW.selectedControl.set('selectedSurveyGroup', FLOW.store.find(FLOW.SurveyGroup, sgId));
    } else {
      this.cancelSurveyGroupNameEdit();
    }
    this.set('showEditField', false);
  },

  // fired when 'cancel' is clicked while showing edit group name field. Cancels the edit.
  cancelSurveyGroupNameEdit: function () {
    this.set('surveyGroupName', FLOW.selectedControl.selectedSurveyGroup.get('code'));
    this.set('showEditField', false);
  },

  // fired when 'add a group' is clicked. Displays a new group text field in the left sidebar
  addGroup: function () {
    FLOW.selectedControl.set('selectedSurveyGroup', null);
    this.set('surveyGroupName', null);
    this.set('showNewGroupField', true);
  },

  deleteSurveyGroup: function () {
    var sgId = FLOW.selectedControl.selectedSurveyGroup.get('id');
    var surveyGroup = FLOW.store.find(FLOW.SurveyGroup, sgId);

    // do preflight check if deleting this survey is allowed
    // if successful, the deletion action will be called from DS.FLOWrestadaptor.sideload
    FLOW.store.findQuery(FLOW.SurveyGroup, {
      preflight: 'delete',
      surveyGroupId: sgId
    });
  },

  // fired when 'save' is clicked while showing new group text field in left sidebar. Saves new survey group to the data store
  saveNewSurveyGroupName: function () {
    if (!Ember.empty(this.get('surveyGroupName').trim())){
      FLOW.store.createRecord(FLOW.SurveyGroup, {
	"code": capitaliseFirstLetter(this.get('surveyGroupName')),
	"name": capitaliseFirstLetter(this.get('surveyGroupName'))
      });
      FLOW.store.commit();
    }
    this.set('showNewGroupField', false);
  },

  // fired when 'cancel' is clicked while showing new group text field in left sidebar. Cancels the new survey group creation
  cancelNewSurveyGroupName: function () {
    this.set('surveyGroupName', null);
    this.set('showNewGroupField', false);
  },

  makeMonitorGroup: function () {
    this.set('monitoringGroup', true);
    FLOW.selectedControl.selectedSurveyGroup.set('monitoringGroup', true);
    FLOW.store.commit();
  },

  saveNewLocaleSurveyIdChoice: function () {
    var newLocaleSurvey = FLOW.surveyControl.get('newLocaleSurvey');
    if (!Ember.none(newLocaleSurvey) && !Ember.none(newLocaleSurvey.get('keyId'))){
      FLOW.selectedControl.selectedSurveyGroup.set('newLocaleSurveyId', newLocaleSurvey.get('keyId'));
      FLOW.store.commit();
    }
  }.observes('FLOW.surveyControl.newLocaleSurvey'),

  showCopySurveyDialog: function (event) {
    FLOW.selectedControl.set('selectedForCopySurvey', event.context);
    this.set('showCopySurveyDialogBool', true);
    this.set('newSurveyName', event.context.get('name') + " (copy)");
    this.set('selectedSurveyGroup', FLOW.selectedControl.get('selectedSurveyGroup'));
  },

  copySurvey: function () {
    var source = FLOW.selectedControl.selectedForCopySurvey;

    FLOW.store.createRecord(FLOW.Survey, {
      sourceId: source.get('id'),
      surveyGroupId: this.selectedSurveyGroup.get('keyId'),
      code: this.get('newSurveyName'),
      name: this.get('newSurveyName'),
      createdDateTime: source.get('createdDateTime'),
      lastUpdateDateTime: source.get('lastUpdateDateTime'),
      pointType: source.get('pointType'),
      defaultLanguageCode: source.get('defaultLanguageCode'),
      instanceCount: 0
    });

    FLOW.store.commit();
    FLOW.selectedControl.set('selectedForCopySurvey', null);
    this.set('showCopySurveyDialogBool', false);

    FLOW.dialogControl.set('activeAction', "ignore");
    FLOW.dialogControl.set('header', Ember.String.loc('_copying_survey'));
    FLOW.dialogControl.set('message', Ember.String.loc('_copying_published_text_'));
    FLOW.dialogControl.set('showCANCEL', false);
    FLOW.dialogControl.set('showDialog', true);
  },

  cancelMoveSurvey: function () {
    FLOW.selectedControl.set('selectedForCopySurvey', null);
    this.set('showCopySurveyDialogBool', false);
  }
});

FLOW.JavascriptSurveyGroupListView = FLOW.View.extend({
  didInsertElement: function () {
    var menuHeight, scroll;
    this._super();
    $('.scrollUp').addClass("FadeIt");
    $('.scrollUp').click(function (e) {
      e.preventDefault();
      menuHeight = $('.menuGroupWrap').height();
      scroll = $('.menuGroupWrap').scrollTop();
      $('.scrollDown').removeClass("FadeIt");
      $('.menuGroupWrap').animate({
        'scrollTop': scroll - 72
      }, 155);

      //the value used for scroll is the old one
      if (scroll < 73) {
        $('.scrollUp').addClass("FadeIt");
      }
    });
    $('.scrollDown').click(function (e) {
      e.preventDefault();
      menuHeight = $('.menuGroupWrap').height();
      scroll = $('.menuGroupWrap').scrollTop();
      $('.scrollUp').removeClass("FadeIt");
      $('.menuGroupWrap').animate({
        'scrollTop': scroll + 72
      }, 155);
    });
  },

  checkHeight: function () {
    var scroll;

    if (FLOW.surveyGroupControl.content.content.length < 10) {
      $('.scrollDown').addClass("FadeIt");
      $('.scrollUp').addClass("FadeIt");
    } else {
      scroll = $('.menuGroupWrap').scrollTop();
      $('.scrollDown').removeClass("FadeIt");
      if (scroll < 73) {
        $('.scrollUp').addClass("FadeIt");
      } else {
        $('.scrollUp').removeClass("FadeIt");
      }
    }
  }.observes('FLOW.surveyGroupControl.content.content.length')
});
<commit_msg>[#805] Use current instead of active for highlighting current form tab<commit_after>function capitaliseFirstLetter(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
}

if (!String.prototype.trim) {
  String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g, '');};
}

FLOW.ProjectListView = FLOW.View.extend({
  templateName: 'navSurveys/project-list'

});

FLOW.ProjectView = FLOW.View.extend({
  templateName: 'navSurveys/project',
});

FLOW.Project = FLOW.View.extend({

  showProjectDetails: false,
  showAdvancedSettings: false,
  selectedLanguage: null,
  monitoringGroupEnabled: false,
  selectedRegistrationForm: null,

  project: function() {
    return FLOW.projectControl.get('currentProject');
  }.property(),

  toggleShowProjectDetails: function() {
    this.set('showProjectDetails', !this.get('showProjectDetails'));
  },

  toggleShowAdvancedSettings: function() {
    this.set('showAdvancedSettings', !this.get('showAdvancedSettings'));
  },

  isNewProject: function() {
    var currentProject = FLOW.projectControl.get('currentProject');
    return currentProject && currentProject.get('code') == "New project"
  }.property('FLOW.projectControl.currentProject'),

  visibleProjectBasics: function() {
    return this.get('isNewProject') || this.get('showProjectDetails')
  }.property('showProjectDetails'),

  updateSelectedLanguage: function() {
    var currentProject = FLOW.projectControl.get('currentProject');
    if (currentProject)
      currentProject.set('defaultLanguageCode', this.selectedLanguage.get('value'))
  }.observes('this.selectedLanguage'),

  showMonitoringGroupCheckbox: function() {
    return FLOW.projectControl.get('formCount') < 2;
  }.property("FLOW.projectControl.formCount"),

  updateSelectedRegistrationForm: function() {
    FLOW.projectControl.currentProject.set('newLocaleSurveyId', this.selectedRegistrationForm.get('keyId'));
  }.observes('selectedRegistrationForm')

})

FLOW.ProjectMainView = FLOW.View.extend({

  doSave: function() {
    FLOW.store.commit();
  },

  hasUnsavedChanges: function() {
    var selectedProject = FLOW.projectControl.get('currentProject');
    var isProjectDirty = selectedProject ? selectedProject.get('isDirty') : false;

    var selectedForm = FLOW.selectedControl.get('selectedSurvey');
    var isFormDirty = selectedForm ? selectedForm.get('isDirty') : false;

    return isProjectDirty || isFormDirty;

  }.property('FLOW.projectControl.currentProject.isDirty',
              'FLOW.selectedControl.selectedSurvey.isDirty'),

  projectView: function() {
    return FLOW.projectControl.isProject(FLOW.projectControl.get('currentProject'));
  }.property('FLOW.projectControl.currentProject'),

  projectListView: function() {
    return FLOW.projectControl.isProjectFolder(FLOW.projectControl.get('currentProject'));
  }.property('FLOW.projectControl.currentProject'),

});

FLOW.ProjectList = FLOW.View.extend({
  tagName: 'ul',
  classNameBindings: ['classProperty'],
  classProperty: function() {
    return FLOW.projectControl.moveTarget || FLOW.projectControl.copyTarget
      ? 'actionProcess' : '';
  }.property('FLOW.projectControl.moveTarget', 'FLOW.projectControl.copyTarget')
});

FLOW.ProjectItemView = FLOW.View.extend({
  tagName: 'li',
  content: null,
  classNameBindings: ['classProperty'],
  folderEdit: false,

  classProperty: function() {
    var isFolder = FLOW.projectControl.isProjectFolder(this.content);
    var isFolderEmpty = FLOW.projectControl.isProjectFolderEmpty(this.content);
    var isMoving = this.content === FLOW.projectControl.get('moveTarget');
    var isCopying = this.content === FLOW.projectControl.get('copyTarget');

    var classes = "aSurvey";
    if (isFolder) classes += " aFolder";
    if (isFolderEmpty) classes += " folderEmpty"
    if (isMoving || isCopying) classes += " highLighted";

    return classes;
  }.property('FLOW.projectControl.moveTarget', 'FLOW.projectControl.copyTarget'),

  toggleEditFolderName: function(evt) {
    this.set('folderEdit', !this.get('folderEdit'));
  },

  isFolder: function() {
    return FLOW.projectControl.isProjectFolder(this.content);
  }.property(),

  formatDate: function(datetime) {
    var date = new Date(parseInt(datetime));
    return date.getDay() + "." + (date.getMonth() + 1) + " " + date.getFullYear();
  },

  created: function() {
    return this.formatDate(this.content.get('createdDateTime'));
  }.property('this.content.createdDateTime'),

  modified: function() {
    return this.formatDate(this.content.get('lastUpdateDateTime'));
  }.property('this.content.lastUpdateDateTime'),

  isPrivate: function() {
    return this.content.get('privacyLevel') === "PRIVATE";
  }.property(),

  language: function() {
    var langs = {en: "English", es: "Español", fr: "Français"};
    return langs[this.content.get('defaultLanguageCode')];
  }.property(),

});

FLOW.FolderEditView = Ember.TextField.extend({
  content: null,

  focusOut: function() {
    this.content.set('name', this.content.get('code'));
    FLOW.store.commit();
  }
});

FLOW.FormTabView = Ember.View.extend({
  tagName: 'li',
  content: null,
  classNameBindings: ['classProperty'],

  classProperty: function() {

    var form = this.get('content');
    var currentProject = FLOW.projectControl.get('currentProject');
    var classString = 'aFormTab';

    if (form === null || currentProject === null) return classString;

    // Return "aFormTab" "current" and/or "registrationForm"
    var isActive = form === FLOW.selectedControl.get('selectedSurvey');
    var isRegistrationForm = currentProject.get('monitoringGroup')
      && form.get('keyId') === currentProject.get('newLocaleSurveyId');

    if (isActive) classString += ' current';
    if (isRegistrationForm) classString += ' registrationForm';

    return classString;
  }.property('FLOW.selectedControl.selectedSurvey', 'FLOW.projectControl.currentProject.newLocaleSurveyId'),
})

// displays survey groups in left sidebar
FLOW.SurveyGroupMenuItemView = FLOW.View.extend({
  content: null,
  tagName: 'li',
  classNameBindings: ['classProperty'],
  monitoringGroup: false,

  classProperty: function() {

    var className = this.get('amFolder') ? "aFolder" : "aSurvey";
    return className;
  }.property('this.amFolder'),

  // true if the survey group is selected. Used to set proper display class
  amSelected: function () {
    var selected = FLOW.selectedControl.get('selectedSurveyGroup');
    if (selected) {
      var amSelected = (this.content.get('keyId') === FLOW.selectedControl.selectedSurveyGroup.get('keyId'));
      return amSelected;
    } else {
      return null;
    }
  }.property('FLOW.selectedControl.selectedSurveyGroup', 'content').cacheable(),

  amFolder: function () {
    return this.content.get('projectType') === 'PROJECT_FOLDER';
  }.property('this.projectType').cacheable(),

  amMonitoringGroup: function () {
    return this.content.get('monitoringGroup');
  }.property('this.monitoringGroup').cacheable(),

  // fired when a survey group is clicked
  makeSelected: function () {
    if (this.content.get('projectType') === "PROJECT_FOLDER") {
      FLOW.surveyGroupControl.setCurrentFolder(this.content.get('keyId'));
    } else {
      FLOW.selectedControl.set('selectedSurveyGroup', this.content);
    }
  }
});

// displays single survey in content area of survey group page
// doEditSurvey is defined in the Router. It transfers to the nav-surveys-edit handlebar view
FLOW.SurveyGroupSurveyView = FLOW.View.extend({

  // fired when 'preview survey' is clicked in the survey item display
  previewSurvey: function () {
    FLOW.selectedControl.set('selectedSurvey', this.content);
  },

  showPreview: function () {
    if (FLOW.questionControl.content.get('isLoaded')) {
      FLOW.previewControl.set('showPreviewPopup', true);
    }
  }.observes('FLOW.questionControl.content.isLoaded'),

  // fired when 'delete survey' is clicked in the survey item display
  deleteSurvey: function () {
    var sId = this.content.get('id');
    var survey = FLOW.store.find(FLOW.Survey, sId);

    // do preflight check if deleting this survey is allowed
    // if successful, the deletion action will be called from DS.FLOWrestadaptor.sideload
    FLOW.store.findQuery(FLOW.Survey, {
      preflight: 'delete',
      surveyId: sId
    });
  },

  // fired when 'inspect data' is clicked in the survey item display
  inspectData: function () {
    console.log("TODO inspect Data");
  }

});

// handles all survey-group interaction elements on survey group page
FLOW.SurveyGroupMainView = FLOW.View.extend({
  showEditField: false,
  showNewGroupField: false,
  surveyGroupName: null,
  showSGDeleteDialog: false,
  showSGDeleteNotPossibleDialog: false,
  showCopySurveyDialogBool: false,
  newSurveyName: null,
  monitoringGroup:false,

  // true if at least one survey group is active
  oneSelected: function () {
    var selected = FLOW.selectedControl.get('selectedSurveyGroup');
    if (selected) {
      return true;
    } else {
      return false;
    }
  }.property('FLOW.selectedControl.selectedSurveyGroup'),

  initVars: function () {
    if (FLOW.selectedControl && FLOW.selectedControl.selectedSurveyGroup) {
      this.set('monitoringGroup', FLOW.selectedControl.selectedSurveyGroup.get('monitoringGroup'));
    }
  }.observes('FLOW.selectedControl.selectedSurveyGroup', 'FLOW.selectedControl.selectedSurvey'),

  // fired when 'edit name' is clicked, shows edit field to change survey group name
  editSurveyGroupName: function () {
    this.set('surveyGroupName', FLOW.selectedControl.selectedSurveyGroup.get('code'));
    this.set('showEditField', true);
  },

  // fired when 'save' is clicked while showing edit group name field. Saves the new group name
  saveSurveyGroupNameEdit: function () {
    if (!Ember.empty(this.get('surveyGroupName').trim())){
      var sgId = FLOW.selectedControl.selectedSurveyGroup.get('id');
      var surveyGroup = FLOW.store.find(FLOW.SurveyGroup, sgId);
      surveyGroup.set('code', capitaliseFirstLetter(this.get('surveyGroupName')));
      surveyGroup.set('name', capitaliseFirstLetter(this.get('surveyGroupName')));
      FLOW.store.commit();
      FLOW.selectedControl.set('selectedSurveyGroup', FLOW.store.find(FLOW.SurveyGroup, sgId));
    } else {
      this.cancelSurveyGroupNameEdit();
    }
    this.set('showEditField', false);
  },

  // fired when 'cancel' is clicked while showing edit group name field. Cancels the edit.
  cancelSurveyGroupNameEdit: function () {
    this.set('surveyGroupName', FLOW.selectedControl.selectedSurveyGroup.get('code'));
    this.set('showEditField', false);
  },

  // fired when 'add a group' is clicked. Displays a new group text field in the left sidebar
  addGroup: function () {
    FLOW.selectedControl.set('selectedSurveyGroup', null);
    this.set('surveyGroupName', null);
    this.set('showNewGroupField', true);
  },

  deleteSurveyGroup: function () {
    var sgId = FLOW.selectedControl.selectedSurveyGroup.get('id');
    var surveyGroup = FLOW.store.find(FLOW.SurveyGroup, sgId);

    // do preflight check if deleting this survey is allowed
    // if successful, the deletion action will be called from DS.FLOWrestadaptor.sideload
    FLOW.store.findQuery(FLOW.SurveyGroup, {
      preflight: 'delete',
      surveyGroupId: sgId
    });
  },

  // fired when 'save' is clicked while showing new group text field in left sidebar. Saves new survey group to the data store
  saveNewSurveyGroupName: function () {
    if (!Ember.empty(this.get('surveyGroupName').trim())){
      FLOW.store.createRecord(FLOW.SurveyGroup, {
	"code": capitaliseFirstLetter(this.get('surveyGroupName')),
	"name": capitaliseFirstLetter(this.get('surveyGroupName'))
      });
      FLOW.store.commit();
    }
    this.set('showNewGroupField', false);
  },

  // fired when 'cancel' is clicked while showing new group text field in left sidebar. Cancels the new survey group creation
  cancelNewSurveyGroupName: function () {
    this.set('surveyGroupName', null);
    this.set('showNewGroupField', false);
  },

  makeMonitorGroup: function () {
    this.set('monitoringGroup', true);
    FLOW.selectedControl.selectedSurveyGroup.set('monitoringGroup', true);
    FLOW.store.commit();
  },

  saveNewLocaleSurveyIdChoice: function () {
    var newLocaleSurvey = FLOW.surveyControl.get('newLocaleSurvey');
    if (!Ember.none(newLocaleSurvey) && !Ember.none(newLocaleSurvey.get('keyId'))){
      FLOW.selectedControl.selectedSurveyGroup.set('newLocaleSurveyId', newLocaleSurvey.get('keyId'));
      FLOW.store.commit();
    }
  }.observes('FLOW.surveyControl.newLocaleSurvey'),

  showCopySurveyDialog: function (event) {
    FLOW.selectedControl.set('selectedForCopySurvey', event.context);
    this.set('showCopySurveyDialogBool', true);
    this.set('newSurveyName', event.context.get('name') + " (copy)");
    this.set('selectedSurveyGroup', FLOW.selectedControl.get('selectedSurveyGroup'));
  },

  copySurvey: function () {
    var source = FLOW.selectedControl.selectedForCopySurvey;

    FLOW.store.createRecord(FLOW.Survey, {
      sourceId: source.get('id'),
      surveyGroupId: this.selectedSurveyGroup.get('keyId'),
      code: this.get('newSurveyName'),
      name: this.get('newSurveyName'),
      createdDateTime: source.get('createdDateTime'),
      lastUpdateDateTime: source.get('lastUpdateDateTime'),
      pointType: source.get('pointType'),
      defaultLanguageCode: source.get('defaultLanguageCode'),
      instanceCount: 0
    });

    FLOW.store.commit();
    FLOW.selectedControl.set('selectedForCopySurvey', null);
    this.set('showCopySurveyDialogBool', false);

    FLOW.dialogControl.set('activeAction', "ignore");
    FLOW.dialogControl.set('header', Ember.String.loc('_copying_survey'));
    FLOW.dialogControl.set('message', Ember.String.loc('_copying_published_text_'));
    FLOW.dialogControl.set('showCANCEL', false);
    FLOW.dialogControl.set('showDialog', true);
  },

  cancelMoveSurvey: function () {
    FLOW.selectedControl.set('selectedForCopySurvey', null);
    this.set('showCopySurveyDialogBool', false);
  }
});

FLOW.JavascriptSurveyGroupListView = FLOW.View.extend({
  didInsertElement: function () {
    var menuHeight, scroll;
    this._super();
    $('.scrollUp').addClass("FadeIt");
    $('.scrollUp').click(function (e) {
      e.preventDefault();
      menuHeight = $('.menuGroupWrap').height();
      scroll = $('.menuGroupWrap').scrollTop();
      $('.scrollDown').removeClass("FadeIt");
      $('.menuGroupWrap').animate({
        'scrollTop': scroll - 72
      }, 155);

      //the value used for scroll is the old one
      if (scroll < 73) {
        $('.scrollUp').addClass("FadeIt");
      }
    });
    $('.scrollDown').click(function (e) {
      e.preventDefault();
      menuHeight = $('.menuGroupWrap').height();
      scroll = $('.menuGroupWrap').scrollTop();
      $('.scrollUp').removeClass("FadeIt");
      $('.menuGroupWrap').animate({
        'scrollTop': scroll + 72
      }, 155);
    });
  },

  checkHeight: function () {
    var scroll;

    if (FLOW.surveyGroupControl.content.content.length < 10) {
      $('.scrollDown').addClass("FadeIt");
      $('.scrollUp').addClass("FadeIt");
    } else {
      scroll = $('.menuGroupWrap').scrollTop();
      $('.scrollDown').removeClass("FadeIt");
      if (scroll < 73) {
        $('.scrollUp').addClass("FadeIt");
      } else {
        $('.scrollUp').removeClass("FadeIt");
      }
    }
  }.observes('FLOW.surveyGroupControl.content.content.length')
});
<|endoftext|>


<commit_before>/**
* Copyright 2012-2021, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

// more info: http://stackoverflow.com/questions/18531624/isplainobject-thing
module.exports = function isPlainObject(obj) {
    // We need to be a little less strict in the `imagetest` container because
    // of how async image requests are handled.
    //
    // N.B. isPlainObject(new Constructor()) will return true in `imagetest`
    if(window && window.process && window.process.versions) {
        return Object.prototype.toString.call(obj) === '[object Object]';
    }

    return (
        Object.prototype.toString.call(obj) === '[object Object]' &&
        Object.getPrototypeOf(obj) === Object.prototype
    );
};
<commit_msg>revisit second condition in is_plain_object<commit_after>/**
* Copyright 2012-2021, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

// more info: http://stackoverflow.com/questions/18531624/isplainobject-thing
module.exports = function isPlainObject(obj) {
    // We need to be a little less strict in the `imagetest` container because
    // of how async image requests are handled.
    //
    // N.B. isPlainObject(new Constructor()) will return true in `imagetest`
    if(window && window.process && window.process.versions) {
        return Object.prototype.toString.call(obj) === '[object Object]';
    }

    return (
        Object.prototype.toString.call(obj) === '[object Object]' &&
        Object.getPrototypeOf(obj).hasOwnProperty('hasOwnProperty')
    );
};
<|endoftext|>


<commit_before>/* FuncyTag version 0.0.1 - see more at https://github.com/BrentNoorda/FuncyTag */
(function(m){function n(c){return c.replace(p,function(e,c){return"-"+c.toLowerCase()})}var p=RegExp("([A-Z])","g");m.funcyTag=function(c,e){var k,a;try{if("_ft_name"in c)for(a in k=c._ft_name,e=e||{},c._default_attributes)a in e||(e[a]=c._default_attributes[a])}catch(m){k=c}a=function(c){var a,b,g;g="object"===typeof c&&c.constructor===Object&&!("_ft_render_"in c);a={_ft_name:k,_insides:[],_attributes:g?c:{}};if(void 0!==e){a._attributes={};for(b in e)a._attributes[b]=e[b];if(g)for(b in c)a._attributes[b]=
c[b]}for(b=g?1:0;b<arguments.length;b++)g=arguments[b],a._insides.push(g);a._ft_render_=function(a,c){var b,e,g,l,d,h,j,f;f="";b="<"+this._ft_name;for(d in this._attributes)h=d.indexOf("_"),0!==h&&(e=this._attributes[d],-1===h?h="":(j=d.lastIndexOf("_"),h=d.substring(j+1),d=d.substring(0,j),"pct"===h&&(h="%")),"css"===d.substring(0,3)?f+=n(d).substring(4)+":"+e+h+";":b+=" "+d+'="'+e+h+'"');this._attributes._nobrout||(b=a+b);0===a.length&&!this._attributes._nobrin&&(a="\r\n");0!==f.length&&(b+=' style="'+
f+'"');f="";h=this._attributes._nobrin?"":a+"  ";j=!1;for(e=0;e<this._insides.length;e++){d=this._insides[e];g=null!==d&&"object"===typeof d?"undefined"===typeof d.push?!1:!0:!1;g||(d=[d]);for(g=0;g<d.length;g++)if(l=d[g],void 0!==l)try{f+=l._ft_render_(j?"":h,c+1),j=l._attributes._nobrout}catch(k){j?j=!1:f+=h,f+=String(l)}}return b=0===f.length&&!this._attributes._no_slfcls_?b+"/>":b+(">"+f+(this._attributes._nobrin?"":a)+"</"+this._ft_name+">")};a.toString=function(){return this._ft_render_("",
0)};return a};a._ft_name=k;a._default_attributes=e;return a}})(window);<commit_msg>minimize latest version<commit_after>(function(m){function p(a){return a.replace(q,function(a,g){return"-"+g.toLowerCase()})}function n(a){return null!==a&&"object"===typeof a?"undefined"===typeof a.push?!1:!0:!1}var q=RegExp("([A-Z])","g");m.funcyTag=function(a,f){var g,c;try{if("_ft_name"in a)for(c in g=a._ft_name,f=f||{},a.default_attributes)c in f||(f[c]=a.default_attributes[c])}catch(m){g=a}c=function(a){var c,b,l;l="object"===typeof a&&a.constructor===Object&&!("_ft_render_"in a);c={_ft_name:g,_insides:[],_attributes:l?a:{}};if(void 0!==
f){c._attributes={};for(b in f)c._attributes[b]=f[b];if(l)for(b in a)c._attributes[b]=a[b]}for(b=l?1:0;b<arguments.length;b++)l=arguments[b],c._insides.push(l);c._ft_render_=function(a,c){var b,h,e,k,d,j,f,g;k="";b="<"+this._ft_name;for(e in this._attributes)d=e.indexOf("_"),0!==d&&(h=this._attributes[e],-1===d?d="":(j=e.lastIndexOf("_"),d=e.substring(j+1),e=e.substring(0,j),"pct"===d&&(d="%")),n(h)&&(h=h.join(d+" ")),h+=d,"css"===e.substring(0,3)?k+=p(e).substring(4)+":"+h+";":b+=" "+e+'="'+h+'"');
this._attributes._nobrout||(b=a+b);0===a.length&&!this._attributes._nobrin&&(a="\r\n");0!==k.length&&(b+=' style="'+k+'"');j="";f=this._attributes._nobrin?"":a+"  ";g=!1;for(h=0;h<this._insides.length;h++){d=this._insides[h];n(d)||(d=[d]);for(e=0;e<d.length;e++)if(k=d[e],void 0!==k)try{j+=k._ft_render_(g?"":f,c+1),g=k._attributes._nobrout}catch(l){g?g=!1:j+=f,j+=String(k)}}return b=0===j.length&&!this._attributes._noslfcls?b+"/>":b+(">"+j+(this._attributes._nobrin?"":a)+"</"+this._ft_name+">")};c.toString=
function(){return this._ft_render_("",0)};return c};c._ft_name=g;c.default_attributes=f;return c};m.funcyTag.esc=function(a){return a.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}})(window);<|endoftext|>


<commit_before>'use strict'

var miss = require('mississippi')
var createPdfFromTemplate = require('tfk-template-to-pdf')
var uuid = require('uuid')
var config = require('../config')

var generateDocument = miss.through(function (chunck, encoding, callback) {
  var item = JSON.parse(chunck)
  var documents = item.distribution.documents || []
  var templates = item.distribution.documentTemplates || []
  var options = {
    templateData: '',
    templateFilepath: '',
    documentFilepath: '',
    templaterServiceUrl: 'https://templater.service.t-fk.no',
    pdfServiceUrl: 'https://pdfconvert.service.t-fk.no'
  }
  var jobsToDo = templates.length
  var jobsDone = 0

  function areWeDoneYet () {
    jobsDone++
    if (jobsToDo === jobsDone) {
      return callback(null, JSON.stringify(item))
    }
  }

  console.log(item._id + ': generate-documents')

  if (templates) {
    templates.forEach(function (document) {
      var documentPath = config.DISTRIBUTION_DIRECTORY_PATH + '/' + uuid.v4() + '.pdf'
      options.templateData = document.data
      options.templateFilepath = document.template
      options.documentFilepath = documentPath

      createPdfFromTemplate(options, function (error, data) {
        if (error) {
          item.errors.push(error.toString())
        } else {
          documents.push({
            title: item.archive.title,
            type: 'warning',
            document: documentPath
          })
          areWeDoneYet()
        }
      })
    })
  } else {
    return callback(null, JSON.stringify(item))
  }
})

module.exports = generateDocument
<commit_msg>Added documentTitle<commit_after>'use strict'

var miss = require('mississippi')
var createPdfFromTemplate = require('tfk-template-to-pdf')
var uuid = require('uuid')
var config = require('../config')

var generateDocument = miss.through(function (chunck, encoding, callback) {
  var item = JSON.parse(chunck)
  var documents = item.distribution.documents || []
  var templates = item.distribution.documentTemplates || []
  var options = {
    templateData: '',
    templateFilepath: '',
    documentFilepath: '',
    templaterServiceUrl: 'https://templater.service.t-fk.no',
    pdfServiceUrl: 'https://pdfconvert.service.t-fk.no'
  }
  var jobsToDo = templates.length
  var jobsDone = 0

  function areWeDoneYet () {
    jobsDone++
    if (jobsToDo === jobsDone) {
      return callback(null, JSON.stringify(item))
    }
  }

  console.log(item._id + ': generate-documents')

  if (templates) {
    templates.forEach(function (document) {
      var documentPath = config.DISTRIBUTION_DIRECTORY_PATH + '/' + uuid.v4() + '.pdf'
      options.templateData = document.data
      options.templateFilepath = document.template
      options.documentFilepath = documentPath

      createPdfFromTemplate(options, function (error, data) {
        if (error) {
          item.errors.push(error.toString())
        } else {
          documents.push({
            title: item.archive.documentTitle,
            type: 'warning',
            document: documentPath
          })
          areWeDoneYet()
        }
      })
    })
  } else {
    return callback(null, JSON.stringify(item))
  }
})

module.exports = generateDocument
<|endoftext|>