Source: modules/ui/components/base.jsxinc

BX.use('brixy', 'modules/ui/helpers.jsxinc');
BX.use('brixy', 'modules/ui/SuiValidator.jsxinc');

/**
* Defines base {@link module:'brixy.ui.SuiBuilder'~SuiBuilder} components.
* 
* @module 'brixy.ui.components.base'
*/
BX.module.define('brixy.ui.components.base', function() {
	var helpers = BX.module('brixy.ui.helpers'),
		SuiValidator = BX.module('brixy.ui.SuiValidator').Me;

	/*
	* Creates new standard ScriptUI element.
	* 
	* @param {ScriptUIcontrol} container - Container element.
	* @param {string} type - ScriptUI control type.
	* @param {string|Array<string>} resource - Text property or array of items or resource string. (optional)
	* @return {ScriptUIcontrol}
	*/
	function addSuiElement(container, type, resource) {
		var tx = false;
		
		if (resource == undefined) // undefined or null
			resource = '{}';
		else if (resource.constructor.name === 'Array')
			resource = '{properties: {items: ' + resource.toSource() + '}}';
		else if (!helpers.isResourceString(resource)) { // String
			tx = resource.toString();
			resource = '{}';
		}

		var e = container.add(type + resource);
		if (tx)
			e.text = tx;
			
		return e;
	}
	
	
	// publish
	return {

		/* ***** Base builder's methods. ***** */

		/**
		* Registers an event listener to the current element.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} eventName - Event name.
		* @param {function} callback - Event handler.
		* @param {boolean} capturePhase - When true, the handler is called only in the capturing phase of the event propagation. Default is false. (optional)
		* @throws {Error} Exception if callback is not a function.
		*/
		addEventListener: function(eventName, callback, capturePhase) {
			if (!callback)
				throw Error('Null is not a function.');
			if (typeof callback !== 'function')
				throw Error(callback + ' is not a function.');
			
			this.element.addEventListener(eventName, callback, capturePhase);
		},
		
		/**
		* The alignment style for current element.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string|Array<string>} alignment - Alignment style.
		*/
		align: function(alignment) {
			this.element.alignment = alignment;
		},
		
		/**
		* The alignment style for children elements.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {String|Array<string>} alignment - Alignment style.
		* @throws {Error} Exception if element doesn't support alignChildren property.
		*/
		alignChildren: function(alignment) {
			if ('alignChildren' in this.element)
				this.element.alignChildren = alignment;
			else
				throw Error('Method alignChildren() is invalid in this context.');
		},
		
		/**
		* Adds onClick event that closes a window with the result code. 
		* If validate is true, it runs a builder validate() method. Window remains open if validation failed.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {boolean} [validate] - Validate the controls before closing.
		* @param {int} [result] - Code returned by window. (optional)
		*/
		closeOnClick: function(validate, result) {
			var b = this;
			
			// InDesign CC (2015, Win): addEventListener('click', ...) doesn't work with OK and Cancel button
			this.element.onClick = function() {
				var w = b._containers[0];
				if (!w || w.constructor.name !== 'Window')
					throw Error('Window does not exist.');
					
				if (validate && b.validator && !b.validator.validate())
					return false;
			
				w.close((result == undefined) ? 1 : result);
			};
		},

		/**
		* Sets builder counter. It helps to identify builder method.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {int} counter - Your number.
		*/
		counter: function(counter) {
			var c = Number(counter);
			this._counter = c > 0 ? Math.floor(c) : 0;
		},
		
		/**
		* Doubles ampersands. 
		* ScriptUI fields use ampersand to marking a shortcut character.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [property='text'] - Property name. (optional)
		*/
		doubleAmps: function(property) {
			property = (property == undefined) ? 'text' : property + '';
				
			if (property in this.element)
				this.element[property] = helpers.doubleAmps(this.element[property]);
		},

		/**
		* Sets the parent container as the current container.
		* @memberOf module:'brixy.ui.components.base'
		*/
		end: function() {
			var c = this._containers,
				i = c.length;
				
			if (i > 1) { // don't delete the first container - Window
				this.element = c.pop();
				this.container = c[i-2]; // the last
			}
		},
		
		/**
		* Immediately executes the callback method.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {function} callback - Custom method (.execute(callback, par1, par2)). Keyword 'this' references the builder.
		* @param {...*} [pars, ...] - Additional parameters will be passed to callback method. (optional)
		* @throws {Error} Exception if callback is not a function.
		*/
		execute: function(callback /*, callback parameters */) {
			if (typeof callback !== 'function')
				throw Error(callback + ' is not a function.');
				
			callback.apply(this, [].slice.call(arguments, 1));
		},
		
		/**
		* Saves the current element into SuiBuilder's repository.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} id - ID of this element.
		* @throws {Error} Exception on error.
		*/
		id: function(id) {
			if (!id)
				throw Error('Invalid id key.');
			if (!this.element)
				throw Error('Element is undefined.');
				
			this._ids[id + ''] = this.element;
		},
		
		/**
		* Adds a click event listener that activates a target element.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} id - ID of target element.
		*/
		labelFor: function(id) {
			var b = this.builder;
			
			this.element.addEventListener('click', function(event) {
				try {
					var el = b.get(id);
					el.active = false;
					el.active = true;
				}
				catch (e) { // ignore all problems
				}
			});
		},
		
		/**
		* Sets the property/properties of the current element.
		* Does not check if this property exists in the element.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string|Object} property - Property name or Object of the property name-value pairs.
		* @param {*} [value] - Ignored if property is Object. (optional)
		*/
		set: function(property, value) {
			if (typeof property === 'object' && property.constructor.name !== 'String') {
				for (var p in property) {
					this.element[p] = property[p];
				}
			}
			else
				this.element[property + ''] = value;
		},
		
		/**
		* Shows the window.
		*
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [position] - The window position. Supported only 'center'. (optional)
		* @throws {Error} Exception if Window doesn't exist.
		*/
		showWindow: function(position) {
			if (!this._containers.length)
				throw Error('Window does not exist.');
			
			var w = this._containers[0];
			if (!w || w.constructor.name !== 'Window')
				throw Error('Window does not exist.');
			
			this._containers.length = 1;
			this.container = this.element = w;
			
			if (position === 'center')
				w.center();
			this._result = w.show();
		},
		
		/**
		* Sets the subitem of the multicolumn ListBox item.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {int} index - Index of the subitem. Note: subitem[0] is the second item of the ScriptUI ListBox row.
		* @param {string} text - Text of this subitem.
		* @param {string|File|ScriptUIImage} [image] - Image of this subitem. (optional)
		* @throws {Error} Exception if element doesn't have 'item' property.
		*/
		subItem: function(index, text, image) {
			if (this.element.type === 'item' && this.element.parent.type === 'listbox') {
				if (index >= this.element.subItems.length)
					throw Error('ListBox multicolumn row has just ' + this.element.subItems.length + ' subitems.');
					
				this.element.subItems[index].text = text;
				if (image)
					this.element.subItems[index].image = image;
			}
			else
				throw Error('Method subItem() is invalid in this context.');
		},
		
		/**
		* Sets the validation method on the current element.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string|function} callback - Validator method.
		* @param {...*} [args, ...] - Additional arguments will be passed to the callback method. (optional)
		* @throws {Error} Exception if a validator cannot be set.
		*/
		validator: function(callback /*, callback parameters */) {
			if (!this.validator)
				this.validator = new SuiValidator();
				
			this.validator.addRule(this.element, callback, [].slice.call(arguments, 1));
		},
	
	
		/* ***** Base builder's components. ***** */

		/**
		* Adds new Button element.  
		* Element component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [resource] - Text property or resource specification. (optional)
		*/
		button: function(resource) {
			this.element = addSuiElement(this.container, 'button', resource);
		},
		
		/**
		* Adds new Checkbox element.  
		* Element component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [resource] - Text property or resource specification. (optional)
		*/
		checkbox: function(resource) {
			this.element = addSuiElement(this.container, 'checkbox', resource);
		},
		
		/**
		* Adds new Group with orientation 'column'.  
		* Container component.
		* @memberOf module:'brixy.ui.components.base'
		*/
		column: function() {
			this.container = this.container.add("group {orientation: 'column'}");
		},
		
		/**
		* Adds new Panel with orientation 'column'.  
		* Container component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [title] - Panel title. (optional)
		*/
		columnPanel: function(title) {
			this.container = this.container.add("panel {orientation: 'column'}");
			this.container.text = title || '';
		},
		
		/**
		* Adds new DropDownList element. Specify items as the array parameter or use item() for adding further items.  
		* Container component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {Array|string} [resource] - Array of items or resource specification. (optional)
		*/
		dropDownList: function(resource) {
			this.container = addSuiElement(this.container, 'dropdownlist', resource);
		},
		
		/**
		* Adds new EditText element.  
		* Element component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [resource] - Text property or resource specification. (optional)
		*/
		editText: function(resource) {
			this.element = addSuiElement(this.container, 'edittext', resource);
		},
		
		/**
		* Adds new FlashPlayer element.  
		* Element component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string|File} [resource] - Resource string or file path or File to load. (optional)
		* @param {string|File} [file] - File path or File to load. (optional)
		*/
		flashPlayer: function(resource, file) {
			var f = null;
			
			if (resource == undefined) // undefined or null
				resource = '{}';
			else if (!helpers.isResourceString(resource)) {
				f = resource;
				resource = '{}';
			}
			if (file)
				f = file;

			this.element = this.container.add('flashplayer' + resource);
			if (f)
				this.element.loadMovie(f);
		},
		
		/**
		* Adds new Group element.  
		* Container component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [resource] - Creation properties. (optional)
		*/
		group: function(resource) {
			this.container = addSuiElement(this.container, 'group', resource);
		},
		
		/**
		* Adds new IconButton element.  
		* Element component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string|File|Array<string>|ScriptUIImage} [resource] - IconButton resource string or image path or Array|ScriptUIImage of the 4-state button images. (optional)
		*/
		iconButton: function(resource) {
			var im = null;
			
			if (resource == undefined) // undefined or null
				resource = '{}';
			else if (resource.constructor.name === 'Array') {
				im = ScriptUI.newImage(resource[0], resource[1], resource[2], resource[3]);
				resource = '{}';
			}
			else if (!helpers.isResourceString(resource)){
				im = resource;
				resource = '{}';
			}

			this.element = this.container.add('iconbutton' + resource);
			if (im)
				this.element.image = im;
		},
		
		/**
		* Adds new Image element.  
		* Element component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string|File|ScriptUIImage} [resource] - Image resource string or image file|path. (optional)
		*/
		image: function(resource) {
			var im = null;
			
			if (resource == undefined) // undefined or null
				resource = '{}';
			else if (!helpers.isResourceString(resource)) {
				im = resource;
				resource = '{}';
			}

			this.element = this.container.add('image' + resource);
			if (im)
				this.element.image = im;
		},
		
		/**
		* Adds new item to the ListBox, DropDownList or TreeView.  
		* Element component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string|Array<string>} text - Text of this item or array of strings for the columns of multicolumn ListBox row.
		* @param {int} [index] - The index of this item in the list of items. (optional)
		* @throws {Error} Exception if element doesn't have 'items' property.
		* @throws {Error} Exception if ListBox doesn't have enough columns.
		*/
		item: function(text, index) {
			var c = this.container,
				el,
				i,
				n;
				
			if ('items' in c) {
				if (text && text.constructor.name === 'Array' && text.length && c.type === 'listbox' && 'columns' in c) {
					n = text.length;
					if (n > c.columns.titles.length)
						throw Error('ListBox has ' + c.columns.titles.length + ' columns only.');
						
					el = c.add('item', text[0], index);
					for (i = 1; i < n; i++)
						el.subItems[i-1].text = text[i];
					
					this.element = el;
				}
				else
					this.element = c.add((text === '-' && c.type === 'dropdownlist') ? 'separator' : 'item', text, index);
			}
			else
				throw Error('Component type "item" is invalid in this context.');
		},
		
		/**
		* Adds new ListBox.  
		* Specify lines as the array parameter or use item() for adding further lines.  
		* In case of multiline ListBox specify the column titles as the array parameter. Use item() for adding new line. Use subItem() for setting of the item cell.  
		* Container component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string|Array<string>} [resource] -  Resource string or array of lines or array of column titles for the multicolumn ListBox. (optional)
		*/
		listBox: function(resource) {
			var pr = null;
			
			if (resource == undefined) // undefined or null
				resource = '{}';
			else if (resource.constructor.name === 'Array' && 'columns' in ListBox) {
				pr = { 
					numberOfColumns: resource.length, 
					showHeaders: true, 
					columnTitles: resource
				};
			}

			if (pr)
				this.container = this.container.add('listbox', undefined, '', pr);
			else
				this.container = this.container.add('listbox' + resource);
		},
		
		/**
		* Adds new node item into the TreeView.  
		* Container component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} text - Text of this item.
		* @param {int} [index] - The index of this node in the list of items (optional)
		* @throws {Error} Exception if element doesn't have 'items' property.
		*/
		nodeItem: function(text, index) {
			var c = this.container;
			if ('items' in c) {
				this.container = c.add('node', text, index);
			}
			else
				throw Error('Component type "nodeItem" is invalid in this context.');
		},
		
		/**
		* Adds new Panel element.  
		* Container component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [resource] - Text property or resource specification. (optional)
		*/
		panel: function(resource) {
			this.container = addSuiElement(this.container, 'panel', resource);
		},
		
		/**
		* Adds new Progressbar element.  
		* Element component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [resource] - Resource specification. (optional)
		*/
		progressBar: function(resource) {
			this.element = addSuiElement(this.container, 'progressbar', resource);
		},
		
		/**
		* Adds new RadioButton element.  
		* Element component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [resource] - Text property or resource specification. (optional)
		*/
		radioButton: function(resource) {
			this.element = addSuiElement(this.container, 'radiobutton', resource);
		},
		
		/**
		* Adds new Group with orientation 'row'.  
		* Container component.
		* @memberOf module:'brixy.ui.components.base'
		*/
		row: function() {
			this.container = this.container.add('group {orientation: "row"}');
		},
		
		/**
		* Adds new Panel with orientation 'row'.  
		* Container component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [title] - Panel title. (optional)
		*/
		rowPanel: function(title) {
			this.container = this.container.add('panel {orientation: "row"}');
			this.container.text = title || '';
		},
		
		/**
		* Adds new Scrollbar element.  
		* Element component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [resource] - Resource specification. (optional)
		*/
		scrollbar: function(resource) {
			this.element = addSuiElement(this.container, 'scrollbar', resource);
		},
		
		/**
		* Adds new Slider element.  
		* Element component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [resource] - Resource specification. (optional)
		*/
		slider: function(resource) {
			this.element = addSuiElement(this.container, 'slider', resource);
		},
		
		/**
		* Adds new Group with orientation 'stack'.  
		* Container component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		*/
		stack: function() {
			this.container = this.container.add('group {orientation: "stack"}');
		},
		
		/**
		* Adds new Panel with orientation 'stack'.  
		* Container component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [title] - Panel title. (optional)
		*/
		stackPanel: function(title) {
			this.container = this.container.add('panel {orientation: "stack"}');
			this.container.text = title || '';
		},
		
		/**
		* Adds new StaticText element.  
		* Element component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [resource] - Text property or resource specification. (optional)
		*/
		staticText: function(resource) {
			this.element = addSuiElement(this.container, 'statictext', resource);
		},
		
		/**
		* Adds new Tab element.  
		* Container component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [resource] - Text property or resource specification. (optional)
		*/
		tab: function(resource) {
			this.container = addSuiElement(this.container, 'tab', resource);
		},
		
		/**
		* Adds new TabbedPanel element.  
		* Container component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} [resource] - Text property or resource specification. (optional)
		*/
		tabbedPanel: function(resource) {
			this.container = addSuiElement(this.container, 'tabbedpanel', resource);
		},
		
		/**
		* Adds new TreeView element.  
		* Container component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {Array|string} [resource] - Array of items or resource specification. (optional)
		*/
		treeView: function(resource) {
			this.container = addSuiElement(this.container, 'treeview', resource);
		},
		
		/**
		* Creates new window with the standard ScriptUI Window parameters.
		* Window is the base element and it is only a single Window element in each SuiBuilder instance.  
		* Container component.
		* 
		* @memberOf module:'brixy.ui.components.base'
		* @param {string} type - Window type: 'window'|'palette'|'dialog'.
		* @param {string} [title] - Window title. (optional)
		* @param {Bounds} [bounds] - Position and size of the window. (optional)
		* @param {Object} [properties] - Creation-only properties. (optional)
		* @throws {Error} Exception if Window is not the first element in this builder.
		*/
		window: function(type, title, bounds, properties) {
			if (this.container)
				throw Error('Window must be the first component, some already exists.');
			
			if (title && !this.name)
				this.name = title;
				
			this.container = new Window(type, title, bounds, properties);
		}
		
	};

});