Brick Global  1.0.0

Abricos! User Interface Library > Sys > editor.js (source view)
Filters
/*
@version $Id: editor.js 270 2009-12-28 13:24:34Z roosit $
@copyright Copyright (C) 2008 Abricos. All rights reserved.
@license http://www.gnu.org/copyleft/gpl.html GNU/GPL, see LICENSE.php
*/

/**
 * @module Sys
 * @namespace Brick.widget
 */

Brick.namespace('widget');

var Component = new Brick.Component({
	buildTemplate: false
});
Component.requires = { 
	yahoo: ['element', 'button'] 
};
Component.entryPoint = function(){
	
	var Dom = YAHOO.util.Dom,
		E = YAHOO.util.Event,
		L = YAHOO.lang;

	var TM = this.template,
		BU = Brick.util;

(function(){
	
	/**
	 * Менеджер визуальных редакторов.
	 * <p>
	 * В момент инициализации менеджера, выполняется метод
	 * EditorManager.findEditors, который осуществляет поиск в имеющихся модулях, 
	 * компонент с именем <b>v_editor</b>. Менеджер предполагает,
	 * что именно в компонентах с таким именем описана реализация
	 * визуального редактора. Например, модуль TinyMCE.
	 * </p>
	 * @class EditorManager
	 * @static
	 */
	var EditorManager = {
	
		/**
		 * Имя компонента содержащий реализацию визуального редактора.
		 * 
		 * @property VEDITOR_NAME
		 * @static
		 * @type String
		 * @default v_editor
		 */
		VEDITOR_NAME: 'v_editor',
		
		/**
		 * Массив имен модулей имеющих компонент с именем указанным в VEDITOR_NAME
		 * 
		 * @property editors
		 * @static
		 * @type Object
		 */
		editors: {},
		
		/**
		 * Хеш таблица зарегистрированных движков визуального редактора.
		 * 
		 * @property engines
		 * @static
		 * @type Object
		 */
		engines: {},

		/**
		 * В существующих модулях осуществить поиск компонентов 
		 * имеющих имя VEDITOR_NAME.
		 * Результат поиска занести в editors.
		 * 
		 * @method findEditors
		 * @static
		 */
		findEditors: function(){
			for (var modName in Brick.Modules){
				if (Brick.componentExists(modName, EditorManager.VEDITOR_NAME)){
					EditorManager.editors[modName] = EditorManager.VEDITOR_NAME;
				}
			}
		},
		
		/**
		 * Проверить, существует ли движок визуального редактора.
		 * 
		 * @method editorExist
		 * @param {String} name Имя редактора.
		 * @return {Boolean}
		 */
		editorExist: function(name){
			if (EditorManager.editors[name]){
				return true;
			}
			return false;
		},
		
		/**
		 * Зарегистрировать движок редактора.
		 * 
		 * @method registerEngine
		 * @static
		 * @param {String} name Наименование редактора.
		 * @param {Brick.widget.VisualEditor} engine Движок визуального редактора.
		 */
		registerEngine: function(name, engine){
			this.engines[name] = engine;
		},
		
		/**
		 * Загрузить визуальный редактор. 
		 * По окончанию загрузки выполнить callback.
		 * 
		 * @method loadEngine
		 * @static
		 * @param {String} name Имя движка визуального редактора.
		 * @param {Function} callback Функция, которая будет выполнена по
		 * окончанию загрузки визуального редактора. Параметр функции 
		 * oArgs.name - Имя движка визуального редактора, 
		 * oArgs.engine - Движок визуального редактора.
		 * Если null, компонент был загружен, но движок визуального редактора
		 * не зарегистрирован. 
		 */
		loadEngine: function(name, callback){
			var fire = function(){
				callback({
					'name': name,
					engine: EditorManager.engines[name]
				});
			};
			if (!this.engines[name]){
				Brick.Component.API.fireFunction(name, EditorManager.VEDITOR_NAME, function(){
					fire();
				});
			}else{
				fire();
			}
		}
	};
	
	EditorManager.findEditors();
	
	Brick.widget.EditorManager = EditorManager;
})();

	var EM = Brick.widget.EditorManager;
	
	var _T = {};
	var _TId = {};

	/**
     * @class Editor
     * @description <p>Прототип редактора.</p>
     * @constructor
     * @extends YAHOO.util.Element
     * @param {String/HTMLElement} el HTML элемент TEXTAREA.
     * @param {Object} attrs Object liternal containing configuration parameters.
	 */
	var Editor = function (el, attrs){
		
        var oConfig = {
            element: el,
            attributes: attrs || {}
        };

        Editor.superclass.constructor.call(this, oConfig.element, oConfig.attributes);    
	};
	
	/**
	 * Имя текущего визуального редактора.
	 * 
	 * @property CURRENT_VISUAL_EDITOR
	 * @static 
	 * @default tinymce
	 */
	Editor.CURRENT_VISUAL_EDITOR = 'tinymce';
	
	/**
	 * Режим редактора "Код HTML"
	 * 
	 * @property MODE_CODE
	 * @static
	 * @type String
	 * @value code
	 */
	Editor.MODE_CODE = 'code';
	
	/**
	 * Режим редактора "Визуальный"
	 * 
	 * @property MODE_VISUAL
	 * @static
	 * @type String
	 * @value code
	 */
	Editor.MODE_VISUAL = 'visual';
	
	/**
	 * Режим панели инструментов - Расширеный.
	 * 
	 * @property TOOLBAR_FULL
	 * @static
	 * @type String
	 */
	Editor.TOOLBAR_FULL = 'full';

	/**
	 * Режим панели инструментов - Средний.
	 * 
	 * @property TOOLBAR_STANDART
	 * @static
	 * @type String
	 */
	Editor.TOOLBAR_STANDART = 'average';

	/**
	 * Режим панели инструментов - Минимальный.
	 * 
	 * @property TOOLBAR_MINIMAL
	 * @static
	 * @type String
	 */
	Editor.TOOLBAR_MINIMAL = 'minimal';
    
	/**
	 * Хеш таблица экземпляров Editor.
	 * 
	 * @private
	 * @static
	 * @property _instances
	 * @type Object
	 */
	Editor._instances = {};

	/**
	 * Получить объект Editor по HTML идентификатору .
	 * @method getEditorById
	 * @static
	 * @param {String} Идентификатор HTML элемента.
	 */
	Editor.getEditorById = function(id) {
        if (Editor._instances[id]) {
            return Editor._instances[id];
        }
        return null;
    };
	
    YAHOO.extend(Editor, YAHOO.util.Element, {
    	
    	/**
    	 * Экземпляр визуального редактора.
    	 * 
    	 * @property _ve
    	 * @private
    	 */
    	_ve: null,

    	/**
    	 * HTML элемент обертка текстового редактора
    	 * 
    	 * @property _wrap
    	 * @private
    	 */
        _wrap: null,
        
        /**
         * Дополнительные кнопки редактора (справа от редактора).
         * 
         * @property _buttons
         * @type Object
         * @private
         */
        _buttons: {},
        
    	/**
    	 * Обвернуть текстовый редактор.
    	 * 
    	 * @method _createTextArea
    	 * @private
    	 */
        _createTextArea: function() {
    	
    		var el = this.get('element');

    		var T = _T[el.id];
    		var TId = _TId[el.id];
    		
	        this._wrap = document.createElement('div');
	        this._wrap.id = el.id + '_wrap';
	        this._wrap.innerHTML = T['editor'];
	        
	        var par = el.parentNode;
	        par.replaceChild(this._wrap, el);
	        Dom.get(TId['editor']['editowrap']).appendChild(el);
	    },
	    
	    _initButtons: function(){
	    	
	    	var __self = this;
	    	
	    	this._buttons = {};
	    	
	    	this._buttons['code'] = new EditorButton(this, {
	    		name: 'code',
	    		image: '/modules/sys/images/ed_code.gif',
	    		onClick: function(){
	    			__self.set('mode', Editor.MODE_CODE);
	    		}
	    	});
	    	
	    	this._buttons['visual'] = new EditorButton(this, {
	    		name: 'visual',
	    		image: '/modules/sys/images/ed_visual.gif',
	    		onClick: function(){
	    			__self.set('mode', Editor.MODE_VISUAL);
	    		}
	    	});
	    	this._buttons['tb_full'] = new EditorButton(this, {
	    		name: 'tb_full',
	    		image: '/modules/sys/images/ed_tb_full.gif',
	    		onClick: function(){
	    			__self.set('toolbar', Editor.TOOLBAR_FULL);
	    		}
	    	});
	    	this._buttons['tb_standart'] = new EditorButton(this, {
	    		name: 'tb_standart',
	    		image: '/modules/sys/images/ed_tb_standart.gif',
	    		onClick: function(){
	    			__self.set('toolbar', Editor.TOOLBAR_STANDART);
	    		}
	    	});
	    	this._buttons['tb_minimal'] = new EditorButton(this, {
	    		name: 'tb_minimal',
	    		image: '/modules/sys/images/ed_tb_minimal.gif',
	    		onClick: function(){
	    			__self.set('toolbar', Editor.TOOLBAR_MINIMAL);
	    		}
	    	});
	    	this._buttons['filemanager'] = new EditorButton(this, {
	    		name: 'filemanager',
	    		image: '/modules/sys/images/ed_filemanager.gif',
	    		onClick: function(){
		    		Brick.Component.API.fire('filemanager', 'api', 'showFileBrowserPanel', function(result){
		    			__self.insertValue(result['html']);
		        	});
	    		}
	    	});
	    	this._updateButtons();
	    },
	    
	    _updateButtons: function(){
	    	
	    	var btns = this._buttons;
	    	
	    	var showOrHide = function(show, buttons){
	    		var arr = buttons.split(',');
	    		for (var i=0;i<arr.length;i++){
	    			if (show){
	    				btns[arr[i]].show();
	    			}else{
	    				btns[arr[i]].hide();
	    			}
	    		}
	    	};
	    	
	    	var isConfigButtons = this.get('configButtons');
	    	
	    	if(isConfigButtons){
		    	var mode = this.get('mode');
		    	if (mode == Editor.MODE_CODE){
		    		showOrHide(true, 'visual');
		    		showOrHide(false, 'code,tb_full,tb_standart,tb_minimal');
		    	}else{
		    		showOrHide(false, 'visual');
		    		showOrHide(true, 'code,tb_full,tb_standart,tb_minimal');
		    	}
		    	
	    		showOrHide(this.get('fileManager'), 'filemanager');
		    	
	    	} else {
	    		for (var n in btns){
	    			btns[n].hide();
	    		}
	    	}
	    },
	    
	    /**
	     * Загрузить и создать визуальный редактор.
	     * 
	     * @method _createVisualEditor
	     * @private
	     */
	    _createVisualEditor: function(){
	    	if (!L.isNull(this._ve)){ return; }
	    	var veName = this.get('veName');
	    	if (!veName){ return; }
	    	var __self = this;
	    	EM.loadEngine(veName, function(oArgs){
	    		if (L.isNull(oArgs.engine)){
	    			return;
	    		}
	    		__self._ve = new oArgs.engine(__self);
	    	});
	    },
	    
	    _removeVisualEditor: function(){
	    	if (L.isNull(this._ve)){ return; }
	    	this._ve.destroy();
	    	this._ve = null;
	    },
	    
	    _onModeChange: function(){
	    	var mode = this.get('mode');
        	if (mode == Editor.MODE_CODE){
        		this._removeVisualEditor();
        	}else if (mode == Editor.MODE_VISUAL){
        		this._createVisualEditor();
        	}                	
	    	this._updateButtons();
	    },
	    
	    _onToolbarChange: function(){
	    	if (L.isNull(this._ve)){ return; }
	    	var toolbarMode = this.get('toolbar');
    		this._ve.setToolbar(toolbarMode);
	    	this._updateButtons();
	    },
	    
	    _onConfigButtons: function(){
	    	this._updateButtons();
	    },
	    
	    _onFileManager: function(){
	    	this._updateButtons();
	    },
    	
	    /**
	     * Инициализация Editor.
	     * 
	     * @method init
	     * @private
	     */
        init: function(p_oElement, p_oAttributes) {
    		Editor.superclass.init.call(this, p_oElement, p_oAttributes);

	        var id = p_oElement;
	        
	        if (!L.isString(id)) {
	            if (id.tagName && (id.tagName.toLowerCase() == 'textarea')) {
	                id = Dom.generateId(id);                    
	            } else { return false; }
	        } else {
	            var el = Dom.get(id);
	            if (el.tagName && el.tagName.toLowerCase() == 'textarea') {
	                //All good
	            } else { return false; }
	        }
	
	        Editor._instances[id] = this;

	        var tm = TM.get(id);
			_T[id] = tm.data; 
			_TId[id] = tm.idManager;
	        
	        this._createTextArea();
	        this._initButtons();
	        this._onModeChange();
	        
			this.on("modeChange", this._onModeChange);
			this.on("toolbarChange", this._onToolbarChange);
	    },
        initAttributes: function(attr) {
            Editor.superclass.initAttributes.call(this, attr);
            
            /**
             * Имя модуля платформы Abricos, который содержит в себе
             * реализацию визуального редактора.
             * 
             * @attribute veName
             * @type String
             */
            this.setAttributeConfig('veName', {
                value: attr.veName || Editor.CURRENT_VISUAL_EDITOR,
                writeOnce: true,
                validator: function(value) {
            		return EM.editorExist(value);
                }
            });

            /**
             * Режим работы редактора. <br>
             * Атрибут принимает следующие значения:
             * <a href="Brick.widget.Editor.html#property_MODE_CODE">MODE_CODE</a> |
             * <a href="Brick.widget.Editor.html#property_MODE_VISUAL">MODE_VISUAL</a>.<br>
             * <i>Примечание: для режима визуального редактора, должен быть указан артибут 
             * <a href="Brick.widget.Editor.html#config_veName">veName</a></i>
             * 
             * @attribute mode
             * @type String
             */
            this.setAttributeConfig('mode', {
                value: attr.mode || Editor.MODE_CODE,
                validator: function(value) {
            		return value == Editor.MODE_CODE || value == Editor.MODE_VISUAL;
                }
            });
            
            /**
             * Режим панели инструментов.<br>
             * Атрибут принимает следующие значения:
             * <a href="Brick.widget.Editor.html#property_TOOLBAR_FULL">TOOLBAR_FULL</a> |
             * <a href="Brick.widget.Editor.html#property_TOOLBAR_STANDART">TOOLBAR_STANDART</a> |
             * <a href="Brick.widget.Editor.html#property_TOOLBAR_MINIMAL">TOOLBAR_MINIMAL</a>.
             * 
             * @attribute toolbar
             * @type String
             */
            this.setAttributeConfig('toolbar', {
                value: attr.toolbar || Editor.TOOLBAR_STANDART,
                validator: function(value) {
            		return value == Editor.TOOLBAR_STANDART 
            			|| value == Editor.TOOLBAR_FULL
            			|| value == Editor.TOOLBAR_MINIMAL;
                }
            });

            /**
             * Показать кнопки конфигурации редактора (группа кнопок справа).
             * 
             * @attribute configButtons
             * @type Boolean
             * @default true
             */
            this.setAttributeConfig('configButtons',{
            	value: attr.configButtons || true
            });

            /**
             * Показать кнопку вызова менеджера файлов.
             * Если модуль "Менеджер файлов" не установлен, кнопка будет скрыта
             * в любом случае.
             * 
             * @attribute fileManager
             * @type Boolean
             * @default true
             */
            this.setAttributeConfig('fileManager',{
            	value: attr.fileManager || true,
            	validator: function(value){
            		if (!value){
            			return false;
            		}
            		if (Brick.Modules['filemanager']){
            			return Brick.componentExists('filemanager', 'api');
            		}
            		return false;
            	}
            });
            
	    },
	    /**
	     * Отписаться от всех событий, удалить обертку элемента TEXTAREA,
	     * вызвать destroy визуального редактора, если он был создан.
	     * 
	     * @method destroy
	     */
	    destroy: function(){
	    	if (!L.isNull(this._ve)){ 
	    		this._ve.destroy();
	    		this._ve = null;
	    	}
	    	
	        var el = this.get('element');
			delete _T[el.id]; 
			delete _TId[el.id];

	        var par = this._wrap.parentNode;
	        // this._wrap.removeChild(el);
	        par.replaceChild(el, this._wrap);
	        this._wrap = null;
	        
	    },
	    
    	/**
    	 * Установить текст в текстовый редактор.
    	 * 
    	 * @method setContent
    	 * @param {String} text
    	 */
    	setContent: function(text){  
	    	if (L.isNull(this._ve)){ 
	    		this.get('element').value = text;
	    	}else{
		   		this._ve.setContent(text);
	    	}
	    },
    	
    	/**
    	 * Получить текст из текстового редактора.
    	 * 
    	 * @method getContent
    	 * @return {String}
    	 */
    	getContent: function(){
	    	if (L.isNull(this._ve)){
	    		this.get('element').value;
	    	}else{
	    		return this._ve.getContent();
	    	}
	    },
	    
	    /**
	     * Вставить текст по положению курсора в текстовый редактор.
	     * 
	     * @method insertValue
	     * @param {String} text
	     */
	    insertValue: function(text){
	    	if (L.isNull(this._ve)){ 
	    		
	    		var editor = this.get('element');
				if (document.selection) { // ie
					editor.focus();  
					sel = document.selection.createRange();  
					sel.text = text;  
				} else if (editor.selectionStart || editor.selectionStart == '0') { // firefox, opera  
					var startPos = editor.selectionStart;  
					var endPos = editor.selectionEnd;  
					editor.value = editor.value.substring(0, startPos) + text + editor.value.substring(endPos, editor.value.length);  
				} else { // over  
					editor.value += text;  
				}  
	    	}else{
		   		this._ve.insertValue(text);
	    	}
	    }

    });
    
    Brick.widget.Editor = Editor;
    
    var _buttonsAddedCss = {};
    
    var EditorButton = function(owner, config){
    	config = L.merge({
    		name: Dom.generateId(),
    		image: '',
    		title: '',
    		titleId: '',
    		onClick: null
    	}, config || {});
    	this.init(owner, config);
    };
    
    EditorButton.prototype = {
    	
    	_element: null,
    	
    	init: function(owner, config){
    		
    		this.owner = owner;
    		this.name = config.name;
			var el = owner.get('element');
			
			var T = _T[el.id];
			var TId = _TId[el.id];
			var tm = TM.get(el.id);
			
			if (!_buttonsAddedCss[this.name]){
		    	Brick.util.CSS.update(tm.replace('css', {
		    		name: this.name,
		    		image: config.image
		    	}));
		    	_buttonsAddedCss[this.name] = true;
			}
			
	    	this._element = new YAHOO.widget.Button({ 
	    		label:"", 
	    		id: this.name, 
	    		container: TId['editor']['buttons']
	    	});
	    	this._element.on("click", function(){
		    	if (L.isFunction(config.onClick)){
		    		config.onClick();
		    	}
		    	return true;
	    	});
	    	
    	},
    	
    	hide: function(){
    		var el = this._element.get('element');
    		el.style.display = 'none';
    	},
    	
    	show: function(){
    		var el = this._element.get('element');
    		el.style.display = '';
    	}
    };
    
    Brick.widget.EditorButton = EditorButton;
    
    /**
     * Абстрактный класс визуального редактора.
     * 
     * @class VisualEditor
     * @constructor
     * @param {String} name Имя визуального редактора
     * @param {Brick.widget.Editor} owner Экземпляр класса редактора
     */
    var VisualEditor = function(name, owner){
    	this.name = name;
    	this.owner = owner;
    	
    	this.init();
    };
    
    VisualEditor.prototype = {
    	
    	/**
    	 * Имя визуального редактора.
    	 * 
    	 * @property name
    	 * @type String
    	 */
    	name: '',
    	
    	/**
    	 * Экземпляр класса редактора.
    	 * 
    	 * @property owner
    	 * @type Brick.widget.Editor
    	 */
    	owner: null,
    	
    	/**
    	 * Абстрактный метод. Инициализировать класс
    	 * 
    	 * @method init
    	 */
    	init: function(){},
    	
    	/**
    	 * Абстрактный метод. Запросить визуальный редактор 
    	 * сменить режим.
    	 * 
    	 * @method setMode
    	 * @param {String} mode Режим редактора: 
    	 * <a href="Brick.widget.Editor.html#property_MODE_CODE">Editor.MODE_CODE</a> |
    	 * <a href="Brick.widget.Editor.html#property_MODE_VISUAL">Editor.MODE_VISUAL</a>.
    	 */
    	setMode: function(mode){},
    	
    	/**
    	 * Абстрактный метод. Запросить визуальный редактор
    	 * сменить режим панели инструментов.
    	 * 
    	 * @method setToolbar
    	 * @param {String} toolbarMode Режим панели инструментов, имеет значение: 
         * <a href="Brick.widget.Editor.html#property_TOOLBAR_FULL">Editor.TOOLBAR_FULL</a> |
         * <a href="Brick.widget.Editor.html#property_TOOLBAR_STANDART">Editor.TOOLBAR_STANDART</a> |
         * <a href="Brick.widget.Editor.html#property_TOOLBAR_MINIMAL">Editor.TOOLBAR_MINIMAL</a>.
    	 */
    	setToolbar: function(toolbarMode){},
    	
    	/**
    	 * Абстрактный метод. Разрушить экзепляр класса.
    	 * 
    	 * @method destroy
    	 */
    	destroy: function(){},
    	
    	/**
    	 * Получить HTML элемент TEXTAREA.
    	 * 
    	 * @method getElement
    	 * @return {HTMLElement} Элемент TEXTAREA
    	 */
    	getElement: function(){
    		return this.owner.get('element');
    	},
    	
    	/**
    	 * Абстрактный метод. Установить текст в текстовый редактор.
    	 * 
    	 * @method setContent
    	 * @param {String} text
    	 */
    	setContent: function(text){  },
    	
    	/**
    	 * Абстрактный метод. Получить текст из текстового редактора.
    	 * 
    	 * @method getContent
    	 * @return {String}
    	 */
    	getContent: function(){return '';},
    	
	    /**
	     * Абстрактный метод. Вставить текст по положению курсора в текстовый редактор.
	     * 
	     * @method insertValue
	     * @param {String} text
	     */
    	insertValue: function(text){}
    };
    
    Brick.widget.VisualEditor = VisualEditor;
};