<commit_before>/*!
 * jQuery UI Selectmenu @VERSION
 * http://jqueryui.com
 *
 * Copyright 2013 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/selectmenu
 *
 * Depends:
 *	jquery.ui.core.js
 *	jquery.ui.widget.js
 *	jquery.ui.position.js
 *	jquery.ui.menu.js
 */
(function( $, undefined ) {

$.widget( "ui.selectmenu", {
	version: "@VERSION",
	defaultElement: "<select>",
	options: {
		appendTo: null,
		icons: {
			button: "ui-icon-triangle-1-s"
		},
		position: {
			my: "left top",
			at: "left bottom",
			collision: "none"
		},

		// callbacks
		change: null,
		close: null,
		focus: null,
		open: null,
		select: null
	},

	_create: function() {
		var selectmenuId = this.element.uniqueId().attr( "id" );
		this.ids = {
			element: selectmenuId,
			button: selectmenuId + "-button",
			menu: selectmenuId + "-menu"
		};

		this._drawButton();
		this._drawMenu();

		this._on( document, this._documentClick );

		if ( this.options.disabled ) {
			this.disable();
		}
	},

	_drawButton: function() {
		var tabindex = this.element.attr( "tabindex" );

		// Fix existing label
		this.label = $( "label[for='" + this.ids.element + "']" ).attr( "for", this.ids.button );
		this._on( this.label, {
			click: function( event ) {
				this.button.focus();
				event.preventDefault();
			}
		});

		// Hide original select tag
		this.element.hide();

		// Create button
		this.button = $( "<span>", {
			"class": "ui-selectmenu-button ui-widget ui-state-default ui-corner-all",
			tabindex: tabindex || this.options.disabled ? -1 : 0,
			id: this.ids.button,
			width: this.element.outerWidth(),
			role: "combobox",
			"aria-expanded": "false",
			"aria-autocomplete": "list",
			"aria-owns": this.ids.menu,
			"aria-haspopup": "true"
		})
		.insertAfter( this.element );

		$( "<span>", {
			"class": "ui-icon " + this.options.icons.button
		}).prependTo( this.button );

		this.buttonText = $( "<span>", {
			"class": "ui-selectmenu-text"
		})
		.appendTo( this.button );
		this._setText( this.buttonText, this.element.find( "option:selected" ).text() );

		this._on( this.button, this._buttonEvents );
		this._hoverable( this.button );
		this._focusable( this.button );
	},

	_drawMenu: function() {
		var menuInstance,
			that = this;

		// Create menu portion, append to body
		this.menu = $( "<ul>", {
			"aria-hidden": "true",
			"aria-labelledby": this.ids.button,
			id: this.ids.menu
		});

		// Wrap menu
		this.menuWrap = $( "<div>", {
				"class": "ui-selectmenu-menu",
				outerWidth: this.button.outerWidth()
			})
			.append( this.menu )
			.appendTo( this._appendTo() );

		// Init menu widget
		menuInstance = this.menu.menu({
			select: function( event, ui ) {
				var item = ui.item.data( "ui-selectmenu-item" );

				that._select( item, event );

				if ( that.isOpen ) {
					event.preventDefault();
					that.close( event );
				}
			},
			focus: function( event, ui ) {
				var item = ui.item.data( "ui-selectmenu-item" );

				// prevent inital focus from firing and checks if its a newly focused item
				if ( !that.isOpen && that.focusIndex !== undefined && item.index !== that.focusIndex ) {
					that._trigger( "focus", event, { item: item } );
					if ( !that.isOpen ) {
						that._select( item, event );
					}
				}
				that.focusIndex = item.index;

				that.button.attr( "aria-activedescendant", that.menuItems.eq( item.index ).attr( "id" ) );
			},
			role: "listbox"
		})
		.menu( "instance" );

		// adjust menu styles to dropdown
		this.menu.addClass( "ui-corner-bottom" ).removeClass( "ui-corner-all" );

		// Make sure focus stays on selected item
		menuInstance.delay = 999999999;
		// Unbind uneeded Menu events
		menuInstance._off( this.menu, "mouseleave" );
	},

	refresh: function() {
		this.menu.empty();

		var item,
			options = this.element.find( "option" );

		if ( !options.length ) {
			return;
		}

		this._readOptions( options );
		this._renderMenu( this.menu, this.items );

		this.menu.menu( "refresh" );
		this.menuItems = this.menu.find( "li" ).not( ".ui-selectmenu-optgroup" ).find( "a" );

		item = this._getSelectedItem();

		// Make sure menu is selected item aware
		this.menu.menu( "focus", null, item );
		this._setAria( item.data( "ui-selectmenu-item" ) );

		// Set disabled state
		this._setOption( "disabled", this._getCreateOptions().disabled );
	},

	open: function( event ) {
		if ( this.options.disabled ) {
			return;
		}
		if ( !this.menuItems ) {
			this.refresh();
		}

		this.isOpen = true;
		this._toggleAttr();
		this.menuWrap.position( $.extend( { of: this.button }, this.options.position ) );

		this._trigger( "open", event );
	},

	close: function( event ) {
		if ( !this.isOpen ) {
			return;
		}

		this.isOpen = false;
		this._toggleAttr();

		// Check if we have an item to select
		if ( this.menuItems ) {
			this.menu.menu( "focus", null, this._getSelectedItem() );
		}

		this._trigger( "close", event );
	},

	widget: function() {
		return this.button;
	},

	menuWidget: function() {
		return this.menu;
	},

	_renderMenu: function( ul, items ) {
		var that = this,
			currentOptgroup = "";

		$.each( items, function( index, item ) {
			if ( item.optgroup !== currentOptgroup ) {
				$( "<li>", {
					"class": "ui-selectmenu-optgroup" + ( item.element.parent( "optgroup" ).attr( "disabled" ) ? " ui-state-disabled" : "" ),
					text: item.optgroup
				}).appendTo( ul );
				currentOptgroup = item.optgroup;
			}
			that._renderItemData( ul, item );
		});
	},

	_renderItemData: function( ul, item ) {
		return this._renderItem( ul, item ).data( "ui-selectmenu-item", item );
	},

	_renderItem: function( ul, item ) {
		var li = $( "<li>" ),
			a = $( "<a>", { href: "#" });

		if ( item.disabled ) {
			li.addClass( "ui-state-disabled" );
		}
		this._setText( a, item.label );

		return li.append( a ).appendTo( ul );
	},

	_setText: function( element, value ) {
		if ( value ) {
			element.text( value );
		} else {
			element.html( " " );
		}
	},

	_move: function( direction, event ) {
		if ( direction === "first" || direction === "last" ) {
			// Set focus manually for first or last item
			this.menu.menu( "focus", event, this.menuItems[ direction ]() );
		} else {
			// Move to and focus next or prev item
			this.menu.menu( direction, event );
		}
	},

	_getSelectedItem: function() {
		return this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" );
	},

	_toggle: function( event ) {
		if ( this.isOpen ) {
			this.close( event );
		} else {
			this.open( event );
		}
	},

	_documentClick: {
		click: function( event ) {
			if ( this.isOpen && !$( event.target ).closest( "li.ui-state-disabled, li.ui-selectmenu-optgroup, #" + this.ids.button ).length ) {
				this.close( event );
			}
		}
	},

	_buttonEvents: {
		focus: function() {
			// Init Menu on first focus
			this.refresh();
			// Reset focus class as its removed by ui.widget._setOption
			this.button.addClass( "ui-state-focus" );
			this._off( this.button, "focus" );
		},
		click: function( event ) {
			this._toggle( event );
			event.preventDefault();
		},
		keydown: function( event ) {
			var prevDef = true;
			switch ( event.keyCode ) {
				case $.ui.keyCode.TAB:
				case $.ui.keyCode.ESCAPE:
					if ( this.isOpen ) {
						this.close( event );
					}
					prevDef = false;
					break;
				case $.ui.keyCode.ENTER:
					if ( this.isOpen ) {
						this.menu.menu( "select", event );
					}
					break;
				case $.ui.keyCode.UP:
					if ( event.altKey ) {
						this._toggle( event );
					} else {
						this._move( "previous", event );
					}
					break;
				case $.ui.keyCode.DOWN:
					if ( event.altKey ) {
						this._toggle( event );
					} else {
						this._move( "next", event );
					}
					break;
				case $.ui.keyCode.SPACE:
					if ( this.isOpen ) {
						this.menu.menu( "select", event );
					} else {
						this._toggle( event );
					}
					break;
				case $.ui.keyCode.LEFT:
					this._move( "previous", event );
					break;
				case $.ui.keyCode.RIGHT:
					this._move( "next", event );
					break;
				case $.ui.keyCode.HOME:
				case $.ui.keyCode.PAGE_UP:
					this._move( "first", event );
					break;
				case $.ui.keyCode.END:
				case $.ui.keyCode.PAGE_DOWN:
					this._move( "last", event );
					break;
				default:
					this.menu.trigger( event );
					prevDef = false;
			}
			if ( prevDef ) {
				event.preventDefault();
			}
		}
	},

	_select: function( item, event ) {
		var oldIndex = this.element[ 0 ].selectedIndex;
		// Change native select element
		this.element[ 0 ].selectedIndex = item.index;
		this._setText( this.buttonText, item.label );
		this._setAria( item );
		this._trigger( "select", event, { item: item } );

		if ( item.index !== oldIndex ) {
			this._trigger( "change", event, { item: item } );
		}
	},

	_setAria: function( item ) {
		var link = this.menuItems.eq( item.index ),
			id = link.attr( "id" );

		this.button.attr({
			"aria-labelledby": id,
			"aria-activedescendant": id
		});
		this.menu.attr( "aria-activedescendant", id );
	},

	_setOption: function( key, value ) {
		if ( key === "icons" ) {
			this.button.find( "span.ui-icon" )
				.removeClass( this.options.icons.button )
				.addClass( value.button );
		}

		this._super( key, value );

		if ( key === "appendTo" ) {
			this.menuWrap.appendTo( this._appendTo() );
		}
		if ( key === "disabled" ) {
			this.menu.menu( "option", "disabled", value );
			this.button
				.toggleClass( "ui-state-disabled", !!value )
				.attr( "aria-disabled", value );

			if ( value ) {
				this.element.attr( "disabled", "disabled" );
				this.button.attr( "tabindex", -1 );
				this.close();
			} else {
				this.element.removeAttr( "disabled" );
				this.button.attr( "tabindex", 0 );
			}
		}
	},

	_appendTo: function() {
		var element = this.options.appendTo;

		if ( element ) {
			element = element.jquery || element.nodeType ?
				$( element ) :
				this.document.find( element ).eq( 0 );
		}

		if ( !element ) {
			element = this.element.closest( ".ui-front" );
		}

		if ( !element.length ) {
			element = this.document[ 0 ].body;
		}

		return element;
	},

	_toggleAttr: function(){
		this.button.toggleClass( "ui-corner-top", this.isOpen ).toggleClass( "ui-corner-all", !this.isOpen );
		this.menuWrap.toggleClass( "ui-selectmenu-open", this.isOpen );
		this.menu.attr( "aria-hidden", !this.isOpen);
		this.button.attr( "aria-expanded", this.isOpen);
	},

	_getCreateOptions: function() {
		return { disabled: !!this.element.attr( "disabled" ) };
	},

	_readOptions: function( options ) {
		var data = [];
		options.each( function( index, item ) {
			var option = $( item ),
				optgroup = option.parent( "optgroup" );
			data.push({
				element: option,
				index: index,
				value: option.attr( "value" ),
				label: option.text(),
				optgroup: optgroup.attr( "label" ) || "",
				disabled: optgroup.attr( "disabled" ) || option.attr( "disabled" )
			});
		});
		this.items = data;
	},

	_destroy: function() {
		this.menuWrap.remove();
		this.button.remove();
		this.element.show();
		this.element.removeUniqueId();
		this.label.attr( "for", this.ids.element );
	}
});

}( jQuery ));
<commit_msg>Selectmenu: do not call _getCreateOptions directly<commit_after>/*!
 * jQuery UI Selectmenu @VERSION
 * http://jqueryui.com
 *
 * Copyright 2013 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/selectmenu
 *
 * Depends:
 *	jquery.ui.core.js
 *	jquery.ui.widget.js
 *	jquery.ui.position.js
 *	jquery.ui.menu.js
 */
(function( $, undefined ) {

$.widget( "ui.selectmenu", {
	version: "@VERSION",
	defaultElement: "<select>",
	options: {
		appendTo: null,
		icons: {
			button: "ui-icon-triangle-1-s"
		},
		position: {
			my: "left top",
			at: "left bottom",
			collision: "none"
		},

		// callbacks
		change: null,
		close: null,
		focus: null,
		open: null,
		select: null
	},

	_create: function() {
		var selectmenuId = this.element.uniqueId().attr( "id" );
		this.ids = {
			element: selectmenuId,
			button: selectmenuId + "-button",
			menu: selectmenuId + "-menu"
		};

		this._drawButton();
		this._drawMenu();

		this._on( document, this._documentClick );

		if ( this.options.disabled ) {
			this.disable();
		}
	},

	_drawButton: function() {
		var tabindex = this.element.attr( "tabindex" );

		// Fix existing label
		this.label = $( "label[for='" + this.ids.element + "']" ).attr( "for", this.ids.button );
		this._on( this.label, {
			click: function( event ) {
				this.button.focus();
				event.preventDefault();
			}
		});

		// Hide original select tag
		this.element.hide();

		// Create button
		this.button = $( "<span>", {
			"class": "ui-selectmenu-button ui-widget ui-state-default ui-corner-all",
			tabindex: tabindex || this.options.disabled ? -1 : 0,
			id: this.ids.button,
			width: this.element.outerWidth(),
			role: "combobox",
			"aria-expanded": "false",
			"aria-autocomplete": "list",
			"aria-owns": this.ids.menu,
			"aria-haspopup": "true"
		})
		.insertAfter( this.element );

		$( "<span>", {
			"class": "ui-icon " + this.options.icons.button
		}).prependTo( this.button );

		this.buttonText = $( "<span>", {
			"class": "ui-selectmenu-text"
		})
		.appendTo( this.button );
		this._setText( this.buttonText, this.element.find( "option:selected" ).text() );

		this._on( this.button, this._buttonEvents );
		this._hoverable( this.button );
		this._focusable( this.button );
	},

	_drawMenu: function() {
		var menuInstance,
			that = this;

		// Create menu portion, append to body
		this.menu = $( "<ul>", {
			"aria-hidden": "true",
			"aria-labelledby": this.ids.button,
			id: this.ids.menu
		});

		// Wrap menu
		this.menuWrap = $( "<div>", {
				"class": "ui-selectmenu-menu",
				outerWidth: this.button.outerWidth()
			})
			.append( this.menu )
			.appendTo( this._appendTo() );

		// Init menu widget
		menuInstance = this.menu.menu({
			select: function( event, ui ) {
				var item = ui.item.data( "ui-selectmenu-item" );

				that._select( item, event );

				if ( that.isOpen ) {
					event.preventDefault();
					that.close( event );
				}
			},
			focus: function( event, ui ) {
				var item = ui.item.data( "ui-selectmenu-item" );

				// prevent inital focus from firing and checks if its a newly focused item
				if ( !that.isOpen && that.focusIndex !== undefined && item.index !== that.focusIndex ) {
					that._trigger( "focus", event, { item: item } );
					if ( !that.isOpen ) {
						that._select( item, event );
					}
				}
				that.focusIndex = item.index;

				that.button.attr( "aria-activedescendant", that.menuItems.eq( item.index ).attr( "id" ) );
			},
			role: "listbox"
		})
		.menu( "instance" );

		// adjust menu styles to dropdown
		this.menu.addClass( "ui-corner-bottom" ).removeClass( "ui-corner-all" );

		// Make sure focus stays on selected item
		menuInstance.delay = 999999999;
		// Unbind uneeded Menu events
		menuInstance._off( this.menu, "mouseleave" );
	},

	refresh: function() {
		this.menu.empty();

		var item,
			options = this.element.find( "option" );

		if ( !options.length ) {
			return;
		}

		this._readOptions( options );
		this._renderMenu( this.menu, this.items );

		this.menu.menu( "refresh" );
		this.menuItems = this.menu.find( "li" ).not( ".ui-selectmenu-optgroup" ).find( "a" );

		item = this._getSelectedItem();

		// Make sure menu is selected item aware
		this.menu.menu( "focus", null, item );
		this._setAria( item.data( "ui-selectmenu-item" ) );

		// Set disabled state
		this._setOption( "disabled", !!this.element.attr( "disabled" ) );
	},

	open: function( event ) {
		if ( this.options.disabled ) {
			return;
		}
		if ( !this.menuItems ) {
			this.refresh();
		}

		this.isOpen = true;
		this._toggleAttr();
		this.menuWrap.position( $.extend( { of: this.button }, this.options.position ) );

		this._trigger( "open", event );
	},

	close: function( event ) {
		if ( !this.isOpen ) {
			return;
		}

		this.isOpen = false;
		this._toggleAttr();

		// Check if we have an item to select
		if ( this.menuItems ) {
			this.menu.menu( "focus", null, this._getSelectedItem() );
		}

		this._trigger( "close", event );
	},

	widget: function() {
		return this.button;
	},

	menuWidget: function() {
		return this.menu;
	},

	_renderMenu: function( ul, items ) {
		var that = this,
			currentOptgroup = "";

		$.each( items, function( index, item ) {
			if ( item.optgroup !== currentOptgroup ) {
				$( "<li>", {
					"class": "ui-selectmenu-optgroup" + ( item.element.parent( "optgroup" ).attr( "disabled" ) ? " ui-state-disabled" : "" ),
					text: item.optgroup
				}).appendTo( ul );
				currentOptgroup = item.optgroup;
			}
			that._renderItemData( ul, item );
		});
	},

	_renderItemData: function( ul, item ) {
		return this._renderItem( ul, item ).data( "ui-selectmenu-item", item );
	},

	_renderItem: function( ul, item ) {
		var li = $( "<li>" ),
			a = $( "<a>", { href: "#" });

		if ( item.disabled ) {
			li.addClass( "ui-state-disabled" );
		}
		this._setText( a, item.label );

		return li.append( a ).appendTo( ul );
	},

	_setText: function( element, value ) {
		if ( value ) {
			element.text( value );
		} else {
			element.html( " " );
		}
	},

	_move: function( direction, event ) {
		if ( direction === "first" || direction === "last" ) {
			// Set focus manually for first or last item
			this.menu.menu( "focus", event, this.menuItems[ direction ]() );
		} else {
			// Move to and focus next or prev item
			this.menu.menu( direction, event );
		}
	},

	_getSelectedItem: function() {
		return this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" );
	},

	_toggle: function( event ) {
		if ( this.isOpen ) {
			this.close( event );
		} else {
			this.open( event );
		}
	},

	_documentClick: {
		click: function( event ) {
			if ( this.isOpen && !$( event.target ).closest( "li.ui-state-disabled, li.ui-selectmenu-optgroup, #" + this.ids.button ).length ) {
				this.close( event );
			}
		}
	},

	_buttonEvents: {
		focus: function() {
			// Init Menu on first focus
			this.refresh();
			// Reset focus class as its removed by ui.widget._setOption
			this.button.addClass( "ui-state-focus" );
			this._off( this.button, "focus" );
		},
		click: function( event ) {
			this._toggle( event );
			event.preventDefault();
		},
		keydown: function( event ) {
			var prevDef = true;
			switch ( event.keyCode ) {
				case $.ui.keyCode.TAB:
				case $.ui.keyCode.ESCAPE:
					if ( this.isOpen ) {
						this.close( event );
					}
					prevDef = false;
					break;
				case $.ui.keyCode.ENTER:
					if ( this.isOpen ) {
						this.menu.menu( "select", event );
					}
					break;
				case $.ui.keyCode.UP:
					if ( event.altKey ) {
						this._toggle( event );
					} else {
						this._move( "previous", event );
					}
					break;
				case $.ui.keyCode.DOWN:
					if ( event.altKey ) {
						this._toggle( event );
					} else {
						this._move( "next", event );
					}
					break;
				case $.ui.keyCode.SPACE:
					if ( this.isOpen ) {
						this.menu.menu( "select", event );
					} else {
						this._toggle( event );
					}
					break;
				case $.ui.keyCode.LEFT:
					this._move( "previous", event );
					break;
				case $.ui.keyCode.RIGHT:
					this._move( "next", event );
					break;
				case $.ui.keyCode.HOME:
				case $.ui.keyCode.PAGE_UP:
					this._move( "first", event );
					break;
				case $.ui.keyCode.END:
				case $.ui.keyCode.PAGE_DOWN:
					this._move( "last", event );
					break;
				default:
					this.menu.trigger( event );
					prevDef = false;
			}
			if ( prevDef ) {
				event.preventDefault();
			}
		}
	},

	_select: function( item, event ) {
		var oldIndex = this.element[ 0 ].selectedIndex;
		// Change native select element
		this.element[ 0 ].selectedIndex = item.index;
		this._setText( this.buttonText, item.label );
		this._setAria( item );
		this._trigger( "select", event, { item: item } );

		if ( item.index !== oldIndex ) {
			this._trigger( "change", event, { item: item } );
		}
	},

	_setAria: function( item ) {
		var link = this.menuItems.eq( item.index ),
			id = link.attr( "id" );

		this.button.attr({
			"aria-labelledby": id,
			"aria-activedescendant": id
		});
		this.menu.attr( "aria-activedescendant", id );
	},

	_setOption: function( key, value ) {
		if ( key === "icons" ) {
			this.button.find( "span.ui-icon" )
				.removeClass( this.options.icons.button )
				.addClass( value.button );
		}

		this._super( key, value );

		if ( key === "appendTo" ) {
			this.menuWrap.appendTo( this._appendTo() );
		}
		if ( key === "disabled" ) {
			this.menu.menu( "option", "disabled", value );
			this.button
				.toggleClass( "ui-state-disabled", !!value )
				.attr( "aria-disabled", value );

			if ( value ) {
				this.element.attr( "disabled", "disabled" );
				this.button.attr( "tabindex", -1 );
				this.close();
			} else {
				this.element.removeAttr( "disabled" );
				this.button.attr( "tabindex", 0 );
			}
		}
	},

	_appendTo: function() {
		var element = this.options.appendTo;

		if ( element ) {
			element = element.jquery || element.nodeType ?
				$( element ) :
				this.document.find( element ).eq( 0 );
		}

		if ( !element ) {
			element = this.element.closest( ".ui-front" );
		}

		if ( !element.length ) {
			element = this.document[ 0 ].body;
		}

		return element;
	},

	_toggleAttr: function(){
		this.button.toggleClass( "ui-corner-top", this.isOpen ).toggleClass( "ui-corner-all", !this.isOpen );
		this.menuWrap.toggleClass( "ui-selectmenu-open", this.isOpen );
		this.menu.attr( "aria-hidden", !this.isOpen);
		this.button.attr( "aria-expanded", this.isOpen);
	},

	_getCreateOptions: function() {
		return { disabled: !!this.element.attr( "disabled" ) };
	},

	_readOptions: function( options ) {
		var data = [];
		options.each( function( index, item ) {
			var option = $( item ),
				optgroup = option.parent( "optgroup" );
			data.push({
				element: option,
				index: index,
				value: option.attr( "value" ),
				label: option.text(),
				optgroup: optgroup.attr( "label" ) || "",
				disabled: optgroup.attr( "disabled" ) || option.attr( "disabled" )
			});
		});
		this.items = data;
	},

	_destroy: function() {
		this.menuWrap.remove();
		this.button.remove();
		this.element.show();
		this.element.removeUniqueId();
		this.label.attr( "for", this.ids.element );
	}
});

}( jQuery ));
<|endoftext|>


<commit_before>(function($) {
	$('div.display-logic, div.display-logic-master').entwine({

		escapeSelector: function(selector) {
			return selector.replace(/(\[|])/g, '\\$1');
		},

		findHolder: function(name) {
			return this.closest('form').find(
				this.escapeSelector('#'+this.nameToHolder(name))
			);
		},

		getFormField: function() {
			var name = this.getFieldName();
			if (name) {
				name = this.escapeSelector(name);
			}
			return this.find('[name='+name+']');
		},

		getFieldName: function() {
			var fieldID = this.attr('id');

			if(fieldID) {
				return this.attr('id')
						.replace(new RegExp('^'+this.getFormID()+'_'),'')
						.replace(/_Holder$/,'');
			}
		},

		nameToHolder: function (name) {
			// Hack!
			// Remove this when OptionsetField_holder.ss uses $HolderID
			// as its div ID instead of $ID
			if(this.closest('form').find('ul.optionset li input[name='+name+']:first').length) {
				return name;
			}

			return this.getFormID()+'_'+name+'_Holder';
		},

		getFormID: function () {
			return this.closest('form').attr('id');
		},

		getFieldValue: function() {
			return this.getFormField().val();
		},

		evaluateEqualTo: function(val) {
			return this.getFieldValue() === val;
		},

		evaluateNotEqualTo: function(val) {
			return this.getFieldValue() !== val;
		},

		evaluateLessThan: function(val) {
			var num = parseFloat(val);

			return this.getFieldValue() < num;
		},

		evaluateGreaterThan: function(val) {
			var num = parseFloat(val);

			return parseFloat(this.getFieldValue()) > num;
		},

		evaluateLessThan: function(val) {
			num = parseFloat(val);
			return parseFloat(this.getFieldValue()) < num;
		},

		evaluateContains: function(val) {
			return this.getFieldValue().match(val) !== null;
		},

		evaluateStartsWith: function(val) {
			return this.getFieldValue().match(new RegExp('^'+val)) !== null;
		},

		evaluateEndsWith: function(val) {
			return this.getFieldValue().match(new RegExp(val+'$')) !== null;
		},

		evaluateEmpty: function() {
			return $.trim(this.getFieldValue()).length === 0;
		},

		evaluateNotEmpty: function() {
			return !this.evaluateEmpty();
		},

		evaluateBetween: function(minmax) {
			v = parseFloat(this.getFieldValue());
			parts = minmax.split("-");
			if(parts.length === 2) {
				return v > parseFloat(parts[0]) && v < parseFloat(parts[1]);
			}
			return false;
		},

		evaluateChecked: function() {
			return this.getFormField().is(":checked");
		},

		onmatch: function () {

			var allReadonly = true;
			var masters = [];
			var field = this.getFormField();

			if(field.data('display-logic-eval') && field.data('display-logic-masters')) {
				this.data('display-logic-eval', field.data('display-logic-eval'))
					.data('display-logic-masters', field.data('display-logic-masters'));
			}

			masters = this.getMasters();

			for(m in masters) {
				var holderName = this.nameToHolder(masters[m]);

				var master = this.closest('form').find(this.escapeSelector('#'+holderName));
				if(!master.is('.readonly')) allReadonly = false;

				master.addClass("display-logic-master");
				if(master.find('input[type=radio]').length) {
					master.addClass('optionset');
				}
				if(master.find("input[type=checkbox]").length > 1) {
					master.addClass('checkboxset');
				}
			}

			// If all the masters are readonly fields, the field has no way of displaying.
			if(masters.length && allReadonly) {
				this.show();
			}
		},

		getLogic: function() {
			return $.trim(this.data('display-logic-eval'));
		},

		parseLogic: function() {
			var js = this.getLogic();
			var result = new Function("return " + js).bind(this)();

			return result;
		},

		getMasters: function() {
			var masters = this.getFormField().data('display-logic-masters');

			return (masters) ? masters.split(",") : [];
		}

	});

	$('div.optionset').entwine({

		getFormField: function() {
			f = this._super().filter(":checked");
			return f;
		}

	});

	$('div.checkboxset').entwine({

		evaluateHasCheckedOption: function(val) {
			var found = false;
			this.find(':checkbox').filter(':checked').each(function() {
				found = (found || ($(this).val() === val || $(this).getLabel().text() === val));
			})

			return found;
		},

		evaluateHasCheckedAtLeast: function(num) {
			return this.find(':checked').length >= num;
		},

		evaluateHasCheckedLessThan: function(num) {
			return this.find(':checked').length <= num;
		}

	});

	$('input[type=checkbox]').entwine({
		getLabel: function() {
			return this.closest('form').find('label[for='+this.getHolder().escapeSelector(this.attr('id'))+']');
		}
	})

	$('div.display-logic.display-logic-display').entwine({
		testLogic: function() {
			this.toggle(this.parseLogic());
		}
	});

	$('div.display-logic.display-logic-hide').entwine({
		testLogic: function() {
			this.toggle(!this.parseLogic());
		}
	});

	$('div.display-logic-master :input').entwine({
		onmatch: function() {
			this.closest(".display-logic-master").notify();
		},

		onchange: function() {
			this.closest(".display-logic-master").notify();
		}
	});

	$('div.display-logic-master :checkbox, div.display-logic-master :radio').entwine({
		onmatch: function() {
			this.closest(".display-logic-master").notify();
		},

		onclick: function() {
			this.closest(".display-logic-master").notify();
		}
	});

	$('div.display-logic.optionset, div.display-logic-master.optionset').entwine({
		getFieldValue: function () {
			return this.find(':checked').val();
		}
	});

	$('div.display-logic-master').entwine({
		Listeners: null,

		notify: function() {
			$.each(this.getListeners(), function() {
				$(this).testLogic();
			});
		},

		getListeners: function() {
			if(l = this._super()) {
				return l;
			}
			var self = this;
			var listeners = [];
			this.closest("form").find('.display-logic').each(function() {
				masters = $(this).getMasters();
				for(m in  masters) {
					if(self.nameToHolder(masters[m]) == self.attr('id')) {
						listeners.push($(this)[0]);
						break;
					}
				}
			});
			this.setListeners(listeners);
			return this.getListeners();
		}
	});

	$('div.display-logic.displaylogicwrapper.display-logic-display, div.display-logic.displaylogicwrapper.display-logic-hide').entwine({
		getFormField: function () {
			return this;
		},

		getFieldName: function () {
			return ''
		}
	});

	$('div.display-logic *').entwine({
		getHolder: function() {
			return this.closest('.display-logic');
		}
	});

})(jQuery);
<commit_msg>Fix - Uncaught TypeError: this.getHolder is not a function<commit_after>(function($) {
	$('div.display-logic, div.display-logic-master').entwine({

		escapeSelector: function(selector) {
			return selector.replace(/(\[|])/g, '\\$1');
		},

		findHolder: function(name) {
			return this.closest('form').find(
				this.escapeSelector('#'+this.nameToHolder(name))
			);
		},

		getFormField: function() {
			var name = this.getFieldName();
			if (name) {
				name = this.escapeSelector(name);
			}
			return this.find('[name='+name+']');
		},

		getFieldName: function() {
			var fieldID = this.attr('id');

			if(fieldID) {
				return this.attr('id')
						.replace(new RegExp('^'+this.getFormID()+'_'),'')
						.replace(/_Holder$/,'');
			}
		},

		nameToHolder: function (name) {
			// Hack!
			// Remove this when OptionsetField_holder.ss uses $HolderID
			// as its div ID instead of $ID
			if(this.closest('form').find('ul.optionset li input[name='+name+']:first').length) {
				return name;
			}

			return this.getFormID()+'_'+name+'_Holder';
		},

		getFormID: function () {
			return this.closest('form').attr('id');
		},

		getFieldValue: function() {
			return this.getFormField().val();
		},

		evaluateEqualTo: function(val) {
			return this.getFieldValue() === val;
		},

		evaluateNotEqualTo: function(val) {
			return this.getFieldValue() !== val;
		},

		evaluateLessThan: function(val) {
			var num = parseFloat(val);

			return this.getFieldValue() < num;
		},

		evaluateGreaterThan: function(val) {
			var num = parseFloat(val);

			return parseFloat(this.getFieldValue()) > num;
		},

		evaluateLessThan: function(val) {
			num = parseFloat(val);
			return parseFloat(this.getFieldValue()) < num;
		},

		evaluateContains: function(val) {
			return this.getFieldValue().match(val) !== null;
		},

		evaluateStartsWith: function(val) {
			return this.getFieldValue().match(new RegExp('^'+val)) !== null;
		},

		evaluateEndsWith: function(val) {
			return this.getFieldValue().match(new RegExp(val+'$')) !== null;
		},

		evaluateEmpty: function() {
			return $.trim(this.getFieldValue()).length === 0;
		},

		evaluateNotEmpty: function() {
			return !this.evaluateEmpty();
		},

		evaluateBetween: function(minmax) {
			v = parseFloat(this.getFieldValue());
			parts = minmax.split("-");
			if(parts.length === 2) {
				return v > parseFloat(parts[0]) && v < parseFloat(parts[1]);
			}
			return false;
		},

		evaluateChecked: function() {
			return this.getFormField().is(":checked");
		},

		onmatch: function () {

			var allReadonly = true;
			var masters = [];
			var field = this.getFormField();

			if(field.data('display-logic-eval') && field.data('display-logic-masters')) {
				this.data('display-logic-eval', field.data('display-logic-eval'))
					.data('display-logic-masters', field.data('display-logic-masters'));
			}

			masters = this.getMasters();

			for(m in masters) {
				var holderName = this.nameToHolder(masters[m]);

				var master = this.closest('form').find(this.escapeSelector('#'+holderName));
				if(!master.is('.readonly')) allReadonly = false;

				master.addClass("display-logic-master");
				if(master.find('input[type=radio]').length) {
					master.addClass('optionset');
				}
				if(master.find("input[type=checkbox]").length > 1) {
					master.addClass('checkboxset');
				}
			}

			// If all the masters are readonly fields, the field has no way of displaying.
			if(masters.length && allReadonly) {
				this.show();
			}
		},

		getLogic: function() {
			return $.trim(this.data('display-logic-eval'));
		},

		parseLogic: function() {
			var js = this.getLogic();
			var result = new Function("return " + js).bind(this)();

			return result;
		},

		getMasters: function() {
			var masters = this.getFormField().data('display-logic-masters');

			return (masters) ? masters.split(",") : [];
		}

	});

	$('div.optionset').entwine({

		getFormField: function() {
			f = this._super().filter(":checked");
			return f;
		}

	});

	$('div.checkboxset').entwine({

		evaluateHasCheckedOption: function(val) {
			var found = false;
			this.find(':checkbox').filter(':checked').each(function() {
				found = (found || ($(this).val() === val || $(this).getLabel().text() === val));
			})

			return found;
		},

		evaluateHasCheckedAtLeast: function(num) {
			return this.find(':checked').length >= num;
		},

		evaluateHasCheckedLessThan: function(num) {
			return this.find(':checked').length <= num;
		}

	});

	$('input[type=checkbox]').entwine({
		getLabel: function() {
			return this.closest('form').find('label[for='+this.getHolder().escapeSelector(this.attr('id'))+']');
		}
	})

	$('div.display-logic.display-logic-display').entwine({
		testLogic: function() {
			this.toggle(this.parseLogic());
		}
	});

	$('div.display-logic.display-logic-hide').entwine({
		testLogic: function() {
			this.toggle(!this.parseLogic());
		}
	});

	$('div.display-logic-master :input').entwine({
		onmatch: function() {
			this.closest(".display-logic-master").notify();
		},

		onchange: function() {
			this.closest(".display-logic-master").notify();
		}
	});

	$('div.display-logic-master :checkbox, div.display-logic-master :radio').entwine({
		onmatch: function() {
			this.closest(".display-logic-master").notify();
		},

		onclick: function() {
			this.closest(".display-logic-master").notify();
		}
	});

	$('div.display-logic.optionset, div.display-logic-master.optionset').entwine({
		getFieldValue: function () {
			return this.find(':checked').val();
		}
	});

	$('div.display-logic-master').entwine({
		Listeners: null,

		notify: function() {
			$.each(this.getListeners(), function() {
				$(this).testLogic();
			});
		},

		getListeners: function() {
			if(l = this._super()) {
				return l;
			}
			var self = this;
			var listeners = [];
			this.closest("form").find('.display-logic').each(function() {
				masters = $(this).getMasters();
				for(m in  masters) {
					if(self.nameToHolder(masters[m]) == self.attr('id')) {
						listeners.push($(this)[0]);
						break;
					}
				}
			});
			this.setListeners(listeners);
			return this.getListeners();
		}
	});

	$('div.display-logic.displaylogicwrapper.display-logic-display, div.display-logic.displaylogicwrapper.display-logic-hide').entwine({
		getFormField: function () {
			return this;
		},

		getFieldName: function () {
			return ''
		}
	});

	$('div.field *').entwine({
		getHolder: function() {
			return this.parents('.field');
		}
	});

})(jQuery);
<|endoftext|>


<commit_before>var ver = '4.8.11';<commit_msg>Set new version<commit_after>var ver = '4.8.12';<|endoftext|>


<commit_before>Meteor.publish(null, function() {
    return Meteor.roles.find({});
});

Meteor.publish("myself", function() {
    if (this.userId) {
        return Meteor.users.find({
            _id: this.userId
        }, {
            fields: {
                'isBanned': 1,
                'createdAt': 1
            }
        });
    } else {
        return this.ready();
    }
});

Meteor.publish("users", function() {
    if (Roles.userIsInRole(this.userId, ['moderator'])) {
        return Meteor.users.find({});
    } else {
        return Meteor.users.find({}, {
            fields: {
                "emails" : false,
                "services" : false,
                "defaultEmail" : false //Exclude defaultEmail from the sent data
            }
        });
    }
});

Meteor.publish("post", function(id) {
    if (id == 'rootNode') {
        return Post.find({
            $where : 'this.links.length < 1'
        });
    }
    return Post.find({
        _id: id
    });
});

Meteor.publish("postRange", function(startDate, endDate) {
    return Post.find({
        "createdAt" : { $gte : startDate, $lte : endDate }
    }, {limit: 1000});
});
<commit_msg>Added publication for newest posts.<commit_after>Meteor.publish(null, function() {
    return Meteor.roles.find({});
});

Meteor.publish("myself", function() {
    if (this.userId) {
        return Meteor.users.find({
            _id: this.userId
        }, {
            fields: {
                'isBanned': 1,
                'createdAt': 1
            }
        });
    } else {
        return this.ready();
    }
});

Meteor.publish("users", function() {
    if (Roles.userIsInRole(this.userId, ['moderator'])) {
        return Meteor.users.find({});
    } else {
        return Meteor.users.find({}, {
            fields: {
                "emails" : false,
                "services" : false,
                "defaultEmail" : false //Exclude defaultEmail from the sent data
            }
        });
    }
});

Meteor.publish("post", function(id) {
    if (id == 'rootNode') {
        return Post.find({
            $where : 'this.links.length < 1'
        });
    }
    return Post.find({
        _id: id
    });
});

Meteor.publish("postRange", function(startDate, endDate) {
    return Post.find({
        "createdAt" : { $gte : startDate, $lte : endDate }
    }, {limit: 1000});
});

Meteor.publish("newestPosts", function(startDate, endDate) {
    return Post.find({
        //"createdAt" : { $gte : startDate, $lte : endDate }
    }, {sort: {createdAt: -1}, limit: 1000});
});
<|endoftext|>


<commit_before>import React from 'react';
import {
  StyleSheet,
  View,
  Text,
  ListView,
  Button,
  TouchableOpacity,
} from 'react-native';
import SessionDetail from './SessionDetail';
import Icon from 'react-native-vector-icons/FontAwesome';
import style from '../../common/style';
import moment from 'moment';

const styles = StyleSheet.create({
  listContainer: {
    flex: 1,
    backgroundColor: style.colors.background,
  },
  constainer: {
    margin: 10,
  },
  sessionTitle: {
    color: style.colors.primary,
    fontSize: 16,
    fontWeight: '600',
  },
  textStyle: {
    color: style.colors.primary,
  },
  listItemWrapper: {
    padding: 10,
    flexDirection: 'row',
  },
  filterButtonWrapper: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    // alignItems: 'center'
  },
  filterButton: {
    flexGrow: 2,
    backgroundColor: style.colors.backgroundColor,
    borderColor: style.colors.backgroundSecondary,
    borderWidth: 3,
  },
  filterButtonText: {
    alignSelf: 'center',
    paddingTop: 10,
    fontSize: 15,
    color: style.colors.color3,
  },
  formatPresentation: {
    color: style.colors.color1,
  },
  formatLightningTalk: {
    color: style.colors.color4,
  },
  formatWorkshop: {
    color: style.colors.color3,
  }
});

class SessionList extends React.Component {
  static propTypes = {
    sessionsData: PropTypes.array
  };

  getHeader() {
    return (
      <View style= { styles.filterButtonWrapper }>
        <TouchableOpacity
          style={ styles.filterButton }
          onPress={this.filterSessionDay.bind(1)}
          title="Day 1"
          accessibilityLabel="List sessions, day one">
            <Text style={styles.filterButtonText}>DAY 1</Text></TouchableOpacity>
        <TouchableOpacity
          style={ styles.filterButton }
          onPress={this.filterSessionDay.bind(1)}
          title="Day 1"
          accessibilityLabel="List sessions, day one"
        ><Text style={styles.filterButtonText}>DAY 2</Text></TouchableOpacity>
        <Icon.Button
          borderRadius={0}
          backgroundColor={style.colors.backgroundSecondary}
          name="filter"
          size={30}
          onPress={this.filterSessions()}>
        </Icon.Button>
      </View>
    );
  }

  getTimeSpan(fromTime, endTime) {
    let from = (moment(new Date(fromTime)).format('dddd, DD MMM HH:mm'));
    let end = (moment (new Date(endTime)).format('HH:mm'));
    return (from + ' - ' + end);
  }

  getSessionFormat(format) {
    if (format === 'presentation') {
      return <Text style={ styles.formatPresentation }>{format}</Text>
    } else if(format === 'lightning-talk') {
      return <Text style={ styles.formatLightningTalk }>{format}</Text>
    } else {
      return <Text style={ styles.formatWorkshop }>{format}</Text>
    }
  }

  componentWillReceiveProps(nextProps) {
    if(this.props.sessionsData !== nextProps.sessionsData) {
      this.setState(()=> {
        return {ds: this.state.ds.cloneWithRows(nextProps.sessionsData)}
      });
    }
  }

  render() {
    if(!this.state.ds) return null;

    return (
      <View style={ styles.listContainer }>
        {this.getHeader()}
        <ListView
          enableEmptySections={true} style={ styles.list }
          dataSource={ this.state.ds }
          renderRow={this.renderRow.bind(this)} />
      </View>
    );
  }

  renderRow(rowData, rowID) {
    return (
        <View style={ styles.listItemWrapper} key={ rowData.sessionId }>
          <View>
            <Icon name="star-o" style={{paddingRight: 10}}size={30} color={style.colors.color4}/>
        </View>
          <TouchableOpacity style={{paddingRight: 40}} onPress={() => this._onRowPressed(rowData)} key={rowID}>
            <Text style={ styles.sessionTitle }>{rowData.title}</Text>
            {this.getSessionFormat(rowData.format)}
            <Text style={ styles.textStyle }>{this.getTimeSpan(rowData.startTime, rowData.endTime)}</Text>
            <Text style={ styles.textStyle }>{rowData.room}</Text>
      </TouchableOpacity>
        </View>
    );
  }

    _onRowPressed(rowData) {
      this.props.navigation.navigate('SessionDetail',
      { sessionData: rowData })
    }
  }

const mapStateToProps = (state) => {
  return {
    sessionsData: state.sessions
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(SessionList);

<commit_msg>added sort function to session list<commit_after>import React from 'react';
import {
  StyleSheet,
  View,
  Text,
  ListView,
  Button,
  TouchableOpacity,
} from 'react-native';
import SessionDetail from './SessionDetail';
import Icon from 'react-native-vector-icons/FontAwesome';

import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import style from '../../common/style';
import moment from 'moment';

const styles = StyleSheet.create({
  listContainer: {
    flex: 1,
    backgroundColor: style.colors.background,
  },
  constainer: {
    margin: 10,
  },
  sessionTitle: {
    color: style.colors.primary,
    fontSize: 16,
    fontWeight: '600',
  },
  textStyle: {
    color: style.colors.primary,
  },
  listItemWrapper: {
    padding: 10,
    flexDirection: 'row',
  },
  filterButtonWrapper: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    // alignItems: 'center'
  },
  filterButton: {
    flexGrow: 2,
    backgroundColor: style.colors.backgroundColor,
    borderColor: style.colors.backgroundSecondary,
    borderWidth: 3,
  },
  filterButtonText: {
    alignSelf: 'center',
    paddingTop: 10,
    fontSize: 15,
    color: style.colors.color3,
  },
  formatPresentation: {
    color: style.colors.color1,
  },
  formatLightningTalk: {
    color: style.colors.color4,
  },
  formatWorkshop: {
    color: style.colors.color3,
  }
});

class SessionList extends React.Component {

  constructor(props) {
    super(props);
    const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
      ds: ds.cloneWithRows(this.props.sessionsData)
    }
  }
  static propTypes = {
    sessionsData: PropTypes.array
  };

  formatArray(array) {

    return array.sort((a,b) => {
      return b.startTime > a.startTime ? -1
            :b.startTime > a.startTime ? 1
            :0
    })
  }
  filterSessionDay() {

  }

  filterSessions() {

  }
  getHeader() {
    return (
      <View style= { styles.filterButtonWrapper }>
        <TouchableOpacity
          style={ styles.filterButton }
          onPress={this.filterSessionDay.bind(1)}
          title="Day 1"
          accessibilityLabel="List sessions, day one">
            <Text style={styles.filterButtonText}>DAY 1</Text></TouchableOpacity>
        <TouchableOpacity
          style={ styles.filterButton }
          onPress={this.filterSessionDay.bind(1)}
          title="Day 1"
          accessibilityLabel="List sessions, day one"
        ><Text style={styles.filterButtonText}>DAY 2</Text></TouchableOpacity>
        <Icon.Button
          borderRadius={0}
          backgroundColor={style.colors.backgroundSecondary}
          name="filter"
          size={30}
          onPress={this.filterSessions()}>
        </Icon.Button>
      </View>
    );
  }

  getTimeSpan(fromTime, endTime) {
    let from = (moment(new Date(fromTime)).format('dddd, DD MMM HH:mm'));
    let end = (moment (new Date(endTime)).format('HH:mm'));
    return (from + ' - ' + end);
  }

  getSessionFormat(format) {
    if (format === 'presentation') {
      return <Text style={ styles.formatPresentation }>{format}</Text>
    } else if(format === 'lightning-talk') {
      return <Text style={ styles.formatLightningTalk }>{format}</Text>
    } else {
      return <Text style={ styles.formatWorkshop }>{format}</Text>
    }
  }

  componentWillReceiveProps(nextProps) {
    if(this.props.sessionsData !== nextProps.sessionsData) {
      this.setState(()=> {
        return {ds: this.state.ds.cloneWithRows(this.formatArray(nextProps.sessionsData))}
      });
    }
  }

  render() {
    if(!this.state.ds) return null;

    return (
      <View style={ styles.listContainer }>
        {this.getHeader()}
        <ListView
          enableEmptySections={true} style={ styles.list }
          dataSource={ this.state.ds }
          renderRow={this.renderRow.bind(this)} />
      </View>
    );
  }

  renderRow(rowData, rowID) {
    return (
        <View style={ styles.listItemWrapper} key={ rowData.sessionId }>
          <View>
            <Icon name="star-o" style={{paddingRight: 10}}size={30} color={style.colors.color4}/>
          </View>
          <TouchableOpacity style={{paddingRight: 40}} onPress={() => this._onRowPressed(rowData)} key={rowID}>
            <Text style={ styles.sessionTitle }>{rowData.title}</Text>
            {this.getSessionFormat(rowData.format)}
            <Text style={ styles.textStyle }>{this.getTimeSpan(rowData.startTime, rowData.endTime)}</Text>
            <Text style={ styles.textStyle }>{rowData.room}</Text>
          </TouchableOpacity>
        </View>
    );
  }

    _onRowPressed(rowData) {
      this.props.navigation.navigate('SessionDetail',
      { sessionData: rowData })
    }
  }

const mapStateToProps = (state) => {
  return {
    sessionsData: state.sessions
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(SessionList);

<|endoftext|>


<commit_before>/*
**  ComponentJS -- Component System for JavaScript <http://componentjs.com>
**  Copyright (c) 2009-2013 Ralf S. Engelschall <http://engelschall.com>
**
**  This Source Code Form is subject to the terms of the Mozilla Public
**  License, v. 2.0. If a copy of the MPL was not distributed with this
**  file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

/*  generic pattern for model management  */
$cs.pattern.model = $cs.trait({
    protos: {
        /*  define model  */
        model: function (model) {
            var name;

            /*  sanity check model  */
            if (_cs.isdefined(model)) {
                for (name in model) {
                    if (typeof model[name].value === "undefined")
                        model[name].value = "";
                    if (typeof model[name].valid === "undefined")
                        model[name].valid = "string";
                    if (typeof model[name].autoreset === "undefined")
                        model[name].autoreset = false;
                    if (typeof model[name].store === "undefined")
                        model[name].store = false;
                    for (var key in model[name]) {
                        if (key !== "value" && key !== "valid" && key !== "autoreset" && key !== "store")
                            throw _cs.exception("model", "invalid specification key \"" +
                                key + "\" in specification of model field \"" + name + "\"");
                    }
                }
            }

            /*  try to load stored model values  */
            var store = this.store("model");
            if (store !== null) {
                for (name in model) {
                    if (model[name].store) {
                        if (_cs.isdefined(store[name]))
                            model[name].value = store[name];
                    }
                }
            }

            /*  retrieve old model  */
            var model_old = this.property({ name: "ComponentJS:model", bubbling: false });

            /*  store model  */
            if (_cs.isdefined(model)) {
                if (_cs.isdefined(model_old)) {
                    /*  merge model into existing one  */
                    var model_new = {};
                    _cs.extend(model_new, model_old);
                    _cs.extend(model_new, model);
                    this.property("ComponentJS:model", model_new);
                    model = model_new;
                }
                else {
                    /*  set initial model  */
                    this.property("ComponentJS:model", model);
                }

                /*  optionally save stored model values  */
                store = {};
                var save = false;
                for (name in model) {
                    if (model[name].store) {
                        store[name] = model[name].value;
                        save = true;
                    }
                }
                if (save)
                    this.store("model", store);
            }

            /*  return old model  */
            return model_old;
        },

        /*  retrieve a property based values object  */
        values: function () {
            /*  sanity check run-time environment  */
            if (_cs.istypeof(Object.defineProperty) !== "function")
                throw _cs.exception("values", "sorry, mandatory Object.defineProperty " +
                    "not supported by run-time environment");

            /*  retrieve values object  */
            var comp = this;
            var values_comp = comp.property({ name: "ComponentJS:model:values", returnowner: true });
            if (!_cs.isdefined(values_comp)) {
                /*  create initial values object  */
                model_comp = comp.property({ name: "ComponentJS:model", returnowner: true });
                if (!_cs.isdefined(model_comp))
                    throw _cs.exception("values", "no model found");
                values = {};
                model_comp.property("ComponentJS:model:values", values);

                /*  enhance values object with properties  */
                var model = model_comp.property("ComponentJS:model");
                for (var name in model) {
                    var symbol = name.replace(/[^a-zA-Z0-9_]+/g, "_");
                    (function (comp, name, symbol) {
                        Object.defineProperty(values, symbol, {
                            enumerable:   false,
                            configurable: false,
                            writeable:    true,
                            get: function ()      { return comp.value(name);        },
                            set: function (value) { return comp.value(name, value); }
                        });
                    })(comp, name, symbol);
                }
            }
            return values;
        },

        /*  get/set model value  */
        value: function () {
            /*  determine parameters  */
            var params = $cs.params("value", arguments, {
                name:        { pos: 0, def: null,     req: true },
                value:       { pos: 1, def: undefined           },
                force:       { pos: 2, def: false               },
                returnowner: {         def: false               }
            });

            /*  determine component owning model with requested value  */
            var owner = null;
            var model = null;
            var comp = this;
            while (comp !== null) {
                owner = comp.property({ name: "ComponentJS:model", returnowner: true });
                if (!_cs.isdefined(owner))
                    throw _cs.exception("value", "no model found containing value \"" + params.name + "\"");
                model = owner.property("ComponentJS:model");
                if (_cs.isdefined(model[params.name]))
                    break;
                comp = owner.parent();
            }
            if (comp === null)
                throw _cs.exception("value", "no model found containing value \"" + params.name + "\"");

            /*  get new model value  */
            var value_new = params.value;

            /*  get old model value  */
            var ev;
            var value_old = model[params.name].value;
            var result;
            if (typeof value_new === "undefined") {
                if (owner.property({ name: "ComponentJS:model:subscribers:get", bubbling: false }) === true) {
                    /*  send event to observers for value get and allow observers
                        to reject value get operation and/or change old value to get  */
                    ev = owner.publish({
                        name:      "ComponentJS:model:" + params.name + ":get",
                        args:      [ value_old ],
                        capturing: false,
                        spreading: false,
                        bubbling:  false,
                        async:     false
                    });
                    if (ev.processing()) {
                        /*  re-fetch value from model
                            (in case the callback set a new value directly)  */
                        value_old = model[params.name].value;

                        /*  allow value to be overridden by event result  */
                        result = ev.result();
                        if (typeof result !== "undefined")
                            value_old = result;
                    }
                }
            }

            /*  optionally set new model value  */
            if (   typeof value_new !== "undefined"
                && (params.force || value_old !== value_new)) {

                /*  check validity of new value  */
                if (!_cs.validate(value_new, model[params.name].valid))
                    throw _cs.exception("value", "invalid value \"" + value_new +
                        "\" for model field \"" + params.name + "\"");

                /*  send event to observers for value set operation and allow observers
                    to reject value set operation and/or change new value to set  */
                var cont = true;
                if (owner.property({ name: "ComponentJS:model:subscribers:set", bubbling: false }) === true) {
                    ev = owner.publish({
                        name:      "ComponentJS:model:" + params.name + ":set",
                        args:      [ value_new, value_old ],
                        capturing: false,
                        spreading: false,
                        bubbling:  false,
                        async:     false
                    });
                    if (!ev.processing())
                        cont = false;
                    else {
                        /*  allow value to be overridden  */
                        result = ev.result();
                        if (typeof result !== "undefined")
                            value_new = result;
                    }
                }
                if (cont && !model[params.name].autoreset) {
                    /*  set value in model  */
                    model[params.name].value = value_new;

                    /*  synchronize model with underlying store  */
                    if (model[params.name].store) {
                        var store = owner.store("model");
                        store[params.name] = model[params.name].value;
                        owner.store("model", store);
                    }

                    /*  send event to observers after value finally changed  */
                    if (owner.property({ name: "ComponentJS:model:subscribers:changed", bubbling: false }) === true) {
                        owner.publish({
                            name:      "ComponentJS:model:" + params.name + ":changed",
                            args:      [ value_new, value_old ],
                            noresult:  true,
                            capturing: false,
                            spreading: false,
                            bubbling:  false,
                            async:     false
                        });
                    }
                }
            }

            /*  return old model value  */
            return (params.returnowner ? owner : value_old);
        },

        /*  touch a model value and trigger event  */
        touch: function () {
            /*  determine parameters  */
            var params = $cs.params("touch", arguments, {
                name: { pos: 0, req: true }
            });

            /*  simply force value to same value in order to trigger event  */
            this.value({
                name: params.name,
                value: this.value(params.name),
                force: true
            });
        },

        /*  start observing model value change  */
        observe: function () {
            /*  determine parameters  */
            var params = $cs.params("observe", arguments, {
                name:      { pos: 0, req: true  },
                func:      { pos: 1, req: true  },
                touch:     {         def: false },
                operation: {         def: "set" },
                spool:     {         def: null  }
            });

            /*  determine the actual component owning the model
                as we want to subscribe the change event there only  */
            var comp = this.property({ name: "ComponentJS:model", returnowner: true });
            if (typeof comp === "undefined")
                throw _cs.exception("observe", "no model found");

            /*  subscribe to model value change event  */
            var id = comp.subscribe({
                name: "ComponentJS:model:" + params.name + ":" + params.operation,
                func: params.func
            });

            /*  mark component for having subscribers of operation
                (for performance optimization reasons)  */
            comp.property("ComponentJS:model:subscribers:" + params.operation, true);

            /*  optionally spool reverse operation  */
            if (params.spool !== null)
                this.spool(params.spool, this, "unobserve", id);

            /*  if requested, touch the model value once (for an initial observer run)  */
            if (params.touch)
                this.touch(params.name);

            return id;
        },

        /*  stop observing model value change  */
        unobserve: function () {
            /*  determine parameters  */
            var params = $cs.params("unobserve", arguments, {
                id: { pos: 0, req: true }
            });

            /*  determine the actual component owning the model
                as we want to unsubscribe the change event there only  */
            var comp = this.property({ name: "ComponentJS:model", returnowner: true });
            if (typeof comp === "undefined")
                throw _cs.exception("unobserve", "no model found");

            /*  subscribe to model value change event  */
            comp.unsubscribe(params.id);
        }
    }
});

<commit_msg>allow model observe()/unobserve() methods search for model owning component towards the root component similar to how the value() method already did it<commit_after>/*
**  ComponentJS -- Component System for JavaScript <http://componentjs.com>
**  Copyright (c) 2009-2013 Ralf S. Engelschall <http://engelschall.com>
**
**  This Source Code Form is subject to the terms of the Mozilla Public
**  License, v. 2.0. If a copy of the MPL was not distributed with this
**  file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

/*  generic pattern for model management  */
$cs.pattern.model = $cs.trait({
    protos: {
        /*  define model  */
        model: function (model) {
            var name;

            /*  sanity check model  */
            if (_cs.isdefined(model)) {
                for (name in model) {
                    if (typeof model[name].value === "undefined")
                        model[name].value = "";
                    if (typeof model[name].valid === "undefined")
                        model[name].valid = "string";
                    if (typeof model[name].autoreset === "undefined")
                        model[name].autoreset = false;
                    if (typeof model[name].store === "undefined")
                        model[name].store = false;
                    for (var key in model[name]) {
                        if (key !== "value" && key !== "valid" && key !== "autoreset" && key !== "store")
                            throw _cs.exception("model", "invalid specification key \"" +
                                key + "\" in specification of model field \"" + name + "\"");
                    }
                }
            }

            /*  try to load stored model values  */
            var store = this.store("model");
            if (store !== null) {
                for (name in model) {
                    if (model[name].store) {
                        if (_cs.isdefined(store[name]))
                            model[name].value = store[name];
                    }
                }
            }

            /*  retrieve old model  */
            var model_old = this.property({ name: "ComponentJS:model", bubbling: false });

            /*  store model  */
            if (_cs.isdefined(model)) {
                if (_cs.isdefined(model_old)) {
                    /*  merge model into existing one  */
                    var model_new = {};
                    _cs.extend(model_new, model_old);
                    _cs.extend(model_new, model);
                    this.property("ComponentJS:model", model_new);
                    model = model_new;
                }
                else {
                    /*  set initial model  */
                    this.property("ComponentJS:model", model);
                }

                /*  optionally save stored model values  */
                store = {};
                var save = false;
                for (name in model) {
                    if (model[name].store) {
                        store[name] = model[name].value;
                        save = true;
                    }
                }
                if (save)
                    this.store("model", store);
            }

            /*  return old model  */
            return model_old;
        },

        /*  retrieve a property based values object  */
        values: function () {
            /*  sanity check run-time environment  */
            if (_cs.istypeof(Object.defineProperty) !== "function")
                throw _cs.exception("values", "sorry, mandatory Object.defineProperty " +
                    "not supported by run-time environment");

            /*  retrieve values object  */
            var comp = this;
            var values_comp = comp.property({ name: "ComponentJS:model:values", returnowner: true });
            if (!_cs.isdefined(values_comp)) {
                /*  create initial values object  */
                model_comp = comp.property({ name: "ComponentJS:model", returnowner: true });
                if (!_cs.isdefined(model_comp))
                    throw _cs.exception("values", "no model found");
                values = {};
                model_comp.property("ComponentJS:model:values", values);

                /*  enhance values object with properties  */
                var model = model_comp.property("ComponentJS:model");
                for (var name in model) {
                    var symbol = name.replace(/[^a-zA-Z0-9_]+/g, "_");
                    (function (comp, name, symbol) {
                        Object.defineProperty(values, symbol, {
                            enumerable:   false,
                            configurable: false,
                            writeable:    true,
                            get: function ()      { return comp.value(name);        },
                            set: function (value) { return comp.value(name, value); }
                        });
                    })(comp, name, symbol);
                }
            }
            return values;
        },

        /*  get/set model value  */
        value: function () {
            /*  determine parameters  */
            var params = $cs.params("value", arguments, {
                name:        { pos: 0, def: null,     req: true },
                value:       { pos: 1, def: undefined           },
                force:       { pos: 2, def: false               },
                returnowner: {         def: false               }
            });

            /*  determine component owning model with requested value  */
            var owner = null;
            var model = null;
            var comp = this;
            while (comp !== null) {
                owner = comp.property({ name: "ComponentJS:model", returnowner: true });
                if (!_cs.isdefined(owner))
                    throw _cs.exception("value", "no model found containing value \"" + params.name + "\"");
                model = owner.property("ComponentJS:model");
                if (_cs.isdefined(model[params.name]))
                    break;
                comp = owner.parent();
            }
            if (comp === null)
                throw _cs.exception("value", "no model found containing value \"" + params.name + "\"");

            /*  get new model value  */
            var value_new = params.value;

            /*  get old model value  */
            var ev;
            var value_old = model[params.name].value;
            var result;
            if (typeof value_new === "undefined") {
                if (owner.property({ name: "ComponentJS:model:subscribers:get", bubbling: false }) === true) {
                    /*  send event to observers for value get and allow observers
                        to reject value get operation and/or change old value to get  */
                    ev = owner.publish({
                        name:      "ComponentJS:model:" + params.name + ":get",
                        args:      [ value_old ],
                        capturing: false,
                        spreading: false,
                        bubbling:  false,
                        async:     false
                    });
                    if (ev.processing()) {
                        /*  re-fetch value from model
                            (in case the callback set a new value directly)  */
                        value_old = model[params.name].value;

                        /*  allow value to be overridden by event result  */
                        result = ev.result();
                        if (typeof result !== "undefined")
                            value_old = result;
                    }
                }
            }

            /*  optionally set new model value  */
            if (   typeof value_new !== "undefined"
                && (params.force || value_old !== value_new)) {

                /*  check validity of new value  */
                if (!_cs.validate(value_new, model[params.name].valid))
                    throw _cs.exception("value", "invalid value \"" + value_new +
                        "\" for model field \"" + params.name + "\"");

                /*  send event to observers for value set operation and allow observers
                    to reject value set operation and/or change new value to set  */
                var cont = true;
                if (owner.property({ name: "ComponentJS:model:subscribers:set", bubbling: false }) === true) {
                    ev = owner.publish({
                        name:      "ComponentJS:model:" + params.name + ":set",
                        args:      [ value_new, value_old ],
                        capturing: false,
                        spreading: false,
                        bubbling:  false,
                        async:     false
                    });
                    if (!ev.processing())
                        cont = false;
                    else {
                        /*  allow value to be overridden  */
                        result = ev.result();
                        if (typeof result !== "undefined")
                            value_new = result;
                    }
                }
                if (cont && !model[params.name].autoreset) {
                    /*  set value in model  */
                    model[params.name].value = value_new;

                    /*  synchronize model with underlying store  */
                    if (model[params.name].store) {
                        var store = owner.store("model");
                        store[params.name] = model[params.name].value;
                        owner.store("model", store);
                    }

                    /*  send event to observers after value finally changed  */
                    if (owner.property({ name: "ComponentJS:model:subscribers:changed", bubbling: false }) === true) {
                        owner.publish({
                            name:      "ComponentJS:model:" + params.name + ":changed",
                            args:      [ value_new, value_old ],
                            noresult:  true,
                            capturing: false,
                            spreading: false,
                            bubbling:  false,
                            async:     false
                        });
                    }
                }
            }

            /*  return old model value  */
            return (params.returnowner ? owner : value_old);
        },

        /*  touch a model value and trigger event  */
        touch: function () {
            /*  determine parameters  */
            var params = $cs.params("touch", arguments, {
                name: { pos: 0, req: true }
            });

            /*  simply force value to same value in order to trigger event  */
            this.value({
                name: params.name,
                value: this.value(params.name),
                force: true
            });
        },

        /*  start observing model value change  */
        observe: function () {
            /*  determine parameters  */
            var params = $cs.params("observe", arguments, {
                name:      { pos: 0, req: true  },
                func:      { pos: 1, req: true  },
                touch:     {         def: false },
                operation: {         def: "set" },
                spool:     {         def: null  }
            });

            /*  determine the actual component owning the model
                as we want to subscribe the change event there only  */
            var owner = null;
            var model = null;
            var comp = this;
            while (comp !== null) {
                owner = comp.property({ name: "ComponentJS:model", returnowner: true });
                if (!_cs.isdefined(owner))
                    throw _cs.exception("observe", "no model found containing value \"" + params.name + "\"");
                model = owner.property("ComponentJS:model");
                if (_cs.isdefined(model[params.name]))
                    break;
                comp = owner.parent();
            }
            if (comp === null)
                throw _cs.exception("observe", "no model found containing value \"" + params.name + "\"");

            /*  subscribe to model value change event  */
            var id = owner.subscribe({
                name:      "ComponentJS:model:" + params.name + ":" + params.operation,
                capturing: false,
                spreading: false,
                bubbling:  false,
                func:      params.func
            });

            /*  mark component for having subscribers of operation
                (for performance optimization reasons)  */
            owner.property("ComponentJS:model:subscribers:" + params.operation, true);

            /*  optionally spool reverse operation  */
            if (params.spool !== null)
                this.spool(params.spool, this, "unobserve", id);

            /*  if requested, touch the model value once (for an initial observer run)  */
            if (params.touch)
                this.touch(params.name);

            return id;
        },

        /*  stop observing model value change  */
        unobserve: function () {
            /*  determine parameters  */
            var params = $cs.params("unobserve", arguments, {
                id: { pos: 0, req: true }
            });

            /*  determine the actual component owning the model
                as we want to unsubscribe the change event there only  */
            var owner = null;
            var comp = this;
            while (comp !== null) {
                owner = comp.property({ name: "ComponentJS:model", returnowner: true });
                if (!_cs.isdefined(owner))
                    throw _cs.exception("unobserve", "no model subscription found");
                if (owner.subscription(params.id))
                    break;
                comp = owner.parent();
            }
            if (comp === null)
                throw _cs.exception("unobserve", "no model subscription found");

            /*  subscribe to model value change event  */
            owner.unsubscribe(params.id);
        }
    }
});

<|endoftext|>