Source: widgets/advCtrls/kekule.widget.textEditors.js

/**
 * @fileoverview
 * Implementation of an advanced plain text editor.
 * @author Partridge Jiang
 */

/*
 * requires /lan/classes.js
 * requires /utils/kekule.utils.js
 * requires /utils/kekule.domUtils.js
 * requires /xbrowsers/kekule.x.js
 * requires /widgets/kekule.widget.base.js
 * requires /widgets/kekule.widget.sys.js
 * requires /widgets/kekule.widget.styleResources.js
 * requires /widgets/commonCtrls/kekule.widget.formControls.js
 */

(function(){
"use strict";

var DU = Kekule.DomUtils;
var EU = Kekule.HtmlElementUtils;
var CNS = Kekule.Widget.HtmlClassNames;
//var CWT = Kekule.WidgetTexts;

/** @ignore */
Kekule.Widget.HtmlClassNames = Object.extend(Kekule.Widget.HtmlClassNames, {
	TEXTEDITOR: 'K-TextEditor',
	TEXTEDITOR_TOOLBAR: 'K-TextEditor-Toolbar',
	TEXTEDITOR_TEXTAREA: 'K-TextEditor-TextArea',
	TEXTEDITOR_FONTBOX: 'K-TextEditor-FontBox',
	TEXTEDITOR_BTN_FONTSIZEINC: 'K-TextEditor-Btn-FontSizeInc',
	TEXTEDITOR_BTN_FONTSIZEDEC: 'K-TextEditor-Btn-FontSizeDec',
	TEXTEDITOR_BTN_TEXTWRAP: 'K-TextEditor-Btn-TextWrap'
});

/**
 * An widget to edit plain text.
 * @class
 * @augments Kekule.Widget.FormWidget
 *
 * @property {String} text Text in editor.
 * @property {Bool} readOnly
 * @property {String} wrap Wrap mode of textarea in editor, value between "virtual", "physical" and "off".
 * @property {Bool} showToolbar Whether show toolbar in editor.
 * @property {Array} toolbarComponents Shown widgets in toolbar.
 * @property {Array} candidateFontFamilies Array of font names that may be shown in font family combo box in toolbar.
 * @property {Float} fontSizeLevel Size of text in editor. E.g., set this property value to 1.2 will add a 1.2em rule to textarea.
 */
/**
 * Invoked when the color value is selected. Event param of it has field: {value, colorClassName}.
 * @name Kekule.Widget.TextEditor#valueSet
 * @event
 */
Kekule.Widget.TextEditor = Class.create(Kekule.Widget.FormWidget,
/** @lends Kekule.Widget.TextEditor# */
{
	/** @private */
	CLASS_NAME: 'Kekule.Widget.TextEditor',
	/** @private */
	BINDABLE_TAG_NAMES: ['div', 'span'],
	/** @private */
	DEF_TOOLBAR_COMPONENTS: ['fontFamily', 'fontSizeInc', 'fontSizeDec', 'textWrap'],
	/** @constructs */
	initialize: function($super, parentOrElementOrDocument)
	{
		this._toolCompWidgets = [];
		this.setPropStoreFieldValue('showToolbar', true);
		$super(parentOrElementOrDocument);
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('text', {'dataType': DataType.STRING, 'serializable': false,
			'getter': function()
			{
				var textArea = this.getTextArea();
				return textArea? textArea.getText(): null;
			},
			'setter': function(value)
			{
				var textArea = this.getTextArea();
				if (textArea)
				{
					textArea.setText(value);
				}
			}
		});
		this.defineProp('readOnly', {'dataType': DataType.BOOL, 'serializable': false,
			'getter': function() { return this.getTextArea().getReadOnly(); },
			'setter': function(value) { return this.getTextArea().setReadOnly(value); }
		});
		this.defineProp('wrap', {'dataType': DataType.STRING, 'serializable': false,
			'getter': function() { return this.getTextArea().getWrap(); },
			'setter': function(value)
			{
				var result = this.getTextArea().setWrap(value);
				this.updateToolButtonStates();
				return result;
			}
		});
		// private
		this.defineProp('textArea', {'dataType': 'Kekule.Widget.TextArea', 'serializable': false, 'setter': null,
			'scope': Class.PropertyScope.PRIVATE,
			'getter': function()
			{
				var result = this.getPropStoreFieldValue('textArea');
				if (!result)
				{
					result = this.createTextArea();
					this.setPropStoreFieldValue('textArea', result);
				}
				return result;
			}
		});
		// private
		this.defineProp('toolbar', {'dataType': 'Kekule.Widget.Toolbar', 'serializable': false, 'setter': null,
			'scope': Class.PropertyScope.PRIVATE,
			'getter': function()
			{
				var result = this.getPropStoreFieldValue('toolbar');
				if (!result)
				{
					if (this.getShowToolbar())
					{
						result = this.createToolbar();
						this.setPropStoreFieldValue('styleToolbar', result);
					}
				}
				return result;
			}
		});
		this.defineProp('showToolbar', {'dataType': DataType.BOOL,
			'setter': function(value)
			{
				this.setPropStoreFieldValue('showToolbar', value);
				var toolbar = this.getToolbar();
				toolbar.setDisplayed(value);
			}
		});
		this.defineProp('toolbarPos', {'dataType': DataType.INT,
			'setter': function(value)
			{
				this.setPropStoreFieldValue('toolbarPos', value);
				this.updateChildWidgetPos();
			}
		});
		this.defineProp('toolbarComponents', {'dataType': DataType.ARRAY, 'serializable': false,
			'getter': function()
			{
				var result = this.getPropStoreFieldValue('toolbarComponents');
				if (!result)
				{
					result = null;  // default one
					this.setPropStoreFieldValue('toolbarComponents', result);
				}
				return result;
			},
			'setter': function(value)
			{
				this.setPropStoreFieldValue('toolbarComponents', value);
				this.recreateToolbarComponents(value);
			}
		});
		this.defineProp('candidateFontFamilies', {'dataType': DataType.ARRAY});
		this.defineProp('fontSizeLevel', {'dataType': DataType.FLOAT,
			'setter': function(value)
			{
				this.setPropStoreFieldValue('fontSizeLevel', value);
				this.setTextStyle({'fontSize': '' + value + 'em'});
			}
		});
	},
	finalize: function($super)
	{
		this._finalizeSubElements();
		$super();
	},
	_finalizeSubElements: function()
	{
		var textArea = this.getTextArea();
		if (textArea)
		{
			textArea.finalize();
		}
	},
	/** @ignore */
	getCoreElement: function($super)
	{
		var textArea = this.getTextArea();
		if (textArea)
			return textArea.getElement();
		else
			return $super();
	},

	/** @ignore */
	doGetWidgetClassName: function($super)
	{
		return $super() + ' ' + CNS.TEXTEDITOR;
	},
	/** @ignore */
	doCreateRootElement: function(doc)
	{
		var result = doc.createElement('span');
		return result;
	},
	/** @ignore */
	doCreateSubElements: function(doc, rootElem)
	{
		// toolbar element
		var toolbar = this.createToolbar();
		if (toolbar)
			toolbar.appendToElem(rootElem);
		// text area element
		var textArea = this.createTextArea();
		textArea.appendToElem(rootElem);
		this.updateChildWidgetPos();
		return [toolbar.getElement(), textArea.getElement()];
	},
	/** @ignore */
	doWidgetShowStateChanged: function($super, isShown)
	{
		$super(isShown);
		if (isShown)
		{
			this.updateChildWidgetPos();
			this.updateToolButtonStates();
		}
	},

	/** @private */
	createToolbar: function()
	{
		if (this.getShowToolbar())
		{
			var toolbar = new Kekule.Widget.Toolbar(this);
			this.setPropStoreFieldValue('toolbar', toolbar);
			toolbar.addClassName(CNS.TEXTEDITOR_TOOLBAR);
			this.recreateToolbarComponents();
			//this.adjustAssocToolbarPositions();
			return toolbar;
		}
		else
		{
			return null;
		}
	},
	/** @private */
	recreateToolbarComponents: function(comps)
	{
		if (!comps)
			comps = this.getToolbarComponents();
		var components = comps || this.DEF_TOOLBAR_COMPONENTS;
		var toolbar = this.getToolbar();
		if (toolbar)
		{
			this._toolCompWidgets.length = 0;
			toolbar.clearWidgets();
			toolbar.setShowText(false);
			toolbar.setShowGlyph(true);
			var widget;
			for (var i = 0, l = components.length; i < l; ++i)
			{
				var comp = components[i];
				if (comp === 'fontFamily')  // font family list box
					widget = this.createFontFamilyComboBox(toolbar);
				else
					widget = this.createToolButton(toolbar, comp);
				this._toolCompWidgets[comp] = widget;
			}
			this.updateToolButtonStates();
		}
	},
	/** @private */
	createFontFamilyComboBox: function(toolbar)
	{
		var result = new Kekule.Widget.ComboBox(toolbar);
		// fill fonts
		var fontFamilies = Kekule.Widget.FontEnumerator.getAvailableFontFamilies(this.getCandidateFontFamilies());
		var boxItems = [];  //[{'text': Kekule.ChemWidgetTexts.S_VALUE_DEFAULT, 'value': ''}];
		for (var i = 0, l = fontFamilies.length; i < l; ++i)
		{
			boxItems.push({'text': fontFamilies[i], 'value': fontFamilies[i]});
		}
		result.setHint(/*CWT.HINT_CHOOSE_FONT_FAMILY*/Kekule.$L('WidgetTexts.HINT_CHOOSE_FONT_FAMILY'));
		result.addClassName(CNS.TEXTEDITOR_FONTBOX);
		result.setItems(boxItems);
		result.appendToWidget(toolbar);
		result.addEventListener('valueChange', function(e){
			this.setTextStyle({'fontFamily': result.getValue()});
		}, this);
		return result;
	},
	/** @private */
	createToolButton: function(toolbar,btnName)
	{
		var caption, hint;
		var cssClassName;
		var btnClass = (btnName === 'textWrap')? Kekule.Widget.CheckButton: Kekule.Widget.Button;
		var result = new btnClass(toolbar);
		result.appendToWidget(toolbar);
		if (btnName === 'textWrap')
		{
			caption = Kekule.$L('WidgetTexts.CAPTION_TOGGLE_TEXTWRAP'); // CWT.CAPTION_TOGGLE_TEXTWRAP;
			hint = Kekule.$L('WidgetTexts.HINT_TOGGLE_TEXTWRAP'); // CWT.HINT_TOGGLE_TEXTWRAP;
			cssClassName = CNS.TEXTEDITOR_BTN_TEXTWRAP;
			result.setChecked(this.getWrap() !== 'off');
			result.addEventListener('checkChange', function(e){
				var wrap = result.getChecked();
				this.setWrap(wrap? 'virtual': 'off');
			}, this);
		}
		else if (btnName === 'fontSizeInc')
		{
			caption = Kekule.$L('WidgetTexts.CAPTION_INC_TEXT_SIZE'); // CWT.CAPTION_INC_TEXT_SIZE;
			hint = Kekule.$L('WidgetTexts.HINT_INC_TEXT_SIZE'); // CWT.HINT_INC_TEXT_SIZE;
			cssClassName = CNS.TEXTEDITOR_BTN_FONTSIZEINC;
			result.addEventListener('execute', this.increaseTextSize, this);
		}
		else if (btnName === 'fontSizeDec')
		{
			caption = Kekule.$L('WidgetTexts.CAPTION_DEC_TEXT_SIZE'); //CWT.CAPTION_DEC_TEXT_SIZE;
			hint = Kekule.$L('WidgetTexts.HINT_DEC_TEXT_SIZE'); //CWT.HINT_DEC_TEXT_SIZE;
			cssClassName = CNS.TEXTEDITOR_BTN_FONTSIZEDEC;
			result.addEventListener('execute', this.decreaseTextSize, this);
		}
		result.setText(caption);
		result.setHint(hint);
		result.addClassName(cssClassName);
		return result;
	},
	/** @private */
	createToolCheckButton: function(toolbar,btnName)
	{
		var result = new Kekule.Widget.CheckButton(toolbar);
		result.appendToWidget(toolbar);
		return result;
	},
	/** @private */
	createTextArea: function()
	{
		var result = new Kekule.Widget.TextArea(this);
		this.setPropStoreFieldValue('textArea', result);
		result.addClassName(CNS.TEXTEDITOR_TEXTAREA);
		return result;
	},
	/** @private */
	updateChildWidgetPos: function()
	{
		/*
		var toolbarHeight;
		var toolbar = this.getToolbar();
		if (toolbar)
		{
			var toolbarBound = Kekule.HtmlElementUtils.getElemBoundingClientRect(toolbar.getElement()) || 0;
			toolbarHeight = toolbarBound.height || 0;
		}
		var textArea = this.getTextArea();
		textArea.setStyleProperty('marginTop', toolbarHeight + 'px');
		*/
		var toolbarPos = this.getToolbarPos();
		var toolbarElem = this.getToolbar().getElement();
		var textAreaElem = this.getTextArea().getElement();
		var parentElem = toolbarElem.parentNode;
		if (toolbarPos === Kekule.Widget.Position.BOTTOM)
		{
			parentElem.insertBefore(textAreaElem, toolbarElem);
		}
		else  // defaultly, toolbar on top
		{
			parentElem.insertBefore(toolbarElem, textAreaElem);
		}
	},

	/** @private */
	updateToolButtonStates: function()
	{
		//console.log('update state', this.getWrap());
		var btn = this._toolCompWidgets['textWrap'];
		if (btn)
			btn.setChecked(this.getWrap() !== 'off');
	},

	/**
	 * Set display style of text area in editor.
	 * @param {Hash} cssStyles A hash of css styles, e.g. {'fontSize': '19px', 'fontFamily': 'arial'}.
	 */
	setTextStyle: function(cssStyles)
	{
		var textArea = this.getTextArea();
		if (textArea)
		{
			var props = Kekule.ObjUtils.getOwnedFieldNames(cssStyles);
			for (var i = 0, l = props.length; i < l; ++i)
			{
				var p = props[i];
				textArea.setStyleProperty(p, cssStyles[p]);
			}
		}
		return this;
	},
	/**
	 * Increase text size in editor.
	 */
	increaseTextSize: function()
	{
		var level = this.getFontSizeLevel() || 1;
		var newLevel = Kekule.ZoomUtils.getNextZoomInRatio(level);
		this.setFontSizeLevel(newLevel);
	},
	/**
	 * Decrease text size in editor.
	 */
	decreaseTextSize: function()
	{
		var level = this.getFontSizeLevel() || 1;
		var newLevel = Kekule.ZoomUtils.getNextZoomOutRatio(level);
		this.setFontSizeLevel(newLevel);
	}
});

})();