minimeta

just a little bit beyond…

Another Plugin for the Extjs HtmlEditor

This plugin provides a format-block for the extjs HtmlEditor. It is based on the work of Shea Frederick and some code from TinyMCE. This plugin has sofar only been tested in Firefox, so if anybody uses it and finds it to be buggy, please drop me a line and a patch if possible. Save the following code to a file, add this to your html before extjs-all.js and install it the same way as the plugins by Shea Frederick, so go and take a look there. Since I took some code from TinyMCE this code is licensed under the LGPL.

Ext.ux.form.HtmlEditor.Formatblock = Ext.extend(Ext.util.Observable , {

	// private
	init: function(cmp){
		this.cmp = cmp;
		this.cmd = 'FormatBlock';
		this.store = new Ext.data.SimpleStore({
			fields: ['tag', 'name'],
			data : [
				['p', 'Paragraph'],
				['div', 'Div'],
				['h1', 'Header 1'],
				['h2', 'Header 2'],
				['h3', 'Header 3'],
				['h4', 'Header 4'],
				['address', 'Address'],
				['blockquote', 'Blockquote'],
				['pre', 'Preformated']
			]
		});
		this.cmp.on('render', this.onRender, this);
		this.cmp.on('initialize', this.onInit, this, {delay:100, single: true});
	},
	// private
	onInit: function(){
		Ext.EventManager.on(this.cmp.getDoc(), {
			'mousedown': this.onEditorEvent,
			'dblclick': this.onEditorEvent,
			'click': this.onEditorEvent,
			'keyup': this.onEditorEvent,
			buffer:100,
			scope: this
		});
	},

	// private
	onRender: function() {
		var tb = this.cmp.getToolbar();
		this.formatSelector = this.createFormatSelector();
		tb.add(this.formatSelector);
	},

	//private
	createFormatSelector: function(){
		var combo = new Ext.form.ComboBox({
			store: this.store,
			displayField:'name',
			valueField:'tag',
			typeAhead: true,
			mode: 'local',
			width: 80,
			triggerAction: 'all',
			emptyText:'Select a format ...',
			valueNotFoundText: 'Select a format ...',
			forceSelection: true,
			editable: false,
			listWidth: 120,
			selectOnFocus:true,
			listeners:{
				scope: this,
				'select': function(combo, record, index){
					tag = record.data.tag;
					this.insertFormatblock(tag);
					this.cmp.getToolbar().focus();
					this.cmp.deferFocus();
					this.formatSelector.reset();
				}
			}
		});
		return combo;
	},

	insertFormatblock : function(val) {
		if (val.indexOf('<') == -1)
			val = '<' + val + '>';
		if (Ext.isGecko)
			val = val.replace(/<(div|blockquote|code|dt|dd|dl|samp)>/gi, '$1');
		this.cmp.relayCmd('FormatBlock', val);
	},

	getSelection : function() {
	  win = this.cmp.getWin();
		return win.getSelection ? win.getSelection() : win.document.selection;
	},

	getRange : function() {
		var win = this.cmp.getWin(), s, r;

		try {
			if (s = this.getSelection())
				r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : win.document.createRange());
		} catch (ex) {	}

		// No range found then create an empty one
		// This can occur when the editor is placed in a hidden container element on Gecko
		// Or on IE when there was an exception
		if (!r)
			r = Exi.isIE ? win.document.body.createTextRange() : win.document.createRange();

		return r;
	},	

	getNode: function(){
		var elem, e, r = this.getRange(), s = this.getSelection();
		if (!Ext.isIE) {
			// Range maybe lost after the editor is made visible again
			if (!r)
				return this.cmp.getDoc().dom.getRoot();

			e = r.commonAncestorContainer;
			// Handle selection a image or other control like element such as anchors
			if (!r.collapsed) {
				// If the anchor node is a element instead of a text node then return this element
				if (Ext.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1)
					return s.anchorNode.childNodes[s.anchorOffset]; 

				if (r.startContainer == r.endContainer) {
					if (r.startOffset - r.endOffset < 2) {
						if (r.startContainer.hasChildNodes())
							e = r.startContainer.childNodes[r.startOffset];
					}
				}
			}
			elem = e.parentNode;
		} else {
			if (r.item)
				elem = r.item(0);
			else
				elem = r.parentElement();
		}
		return Ext.get(elem);
	},

	getBlocklevelElement: function(n){
		if(n){
			if (/^(P|DIV|H[1-6]|ADDRESS|BODY|BLOCKQUOTE|PRE)$/.test(n.dom.nodeName)){
				return n;
			} else {
				return this.getBlocklevelElement(n.findParentNode());
			}
		}
		return null;
	},	

	// private
	onEditorEvent: function(){
		var fs = this.formatSelector, r, p;
		p = this.getBlocklevelElement(this.getNode());
		if (p && p.dom && p.dom.nodeName != 'BODY'){
			fs.setValue(p.dom.nodeName.toLowerCase());
		} else {
			fs.reset();
		}
	}

});

Previous

Staatstheoretische Todo-Liste

Next

In North America

9 Comments

  1. Erzsebet

    Hi,

    I’ve tried your extension and its really cool but i get an error everytime i try to select text. The error is “ss is undefined” trying to do “ss = ss.replace(trimRe, “”);” and it’s located on line 139 -return this.getBlocklevelElement(n.findParentNode()); – and on line 148 -p = this.getBlocklevelElement(this.getNode()); – If u know whats the problem please, contact me. Thank u

  2. Erzsebet

    I think the problem is with line 139 n.findParentNode() i think you should use n.findParentElement(), but i’m not completely sure

  3. admin

    I’ve seen that error you mention as well but so far haven’t bothered looking for the cause because it ususally continued to work just fine.

    your solution sounds reasonable. have you tried it?

    Best

  4. Erzsebet

    I’ve tried but it doesnt work 🙁 And on the crappy ie6 sometimes this error doesnt allow to select text anymore.

    On the other hand i’ve seen that if you include any span,b or any tag which arent at the combobox of the formatblock, or you paste any html code with those tags the combobox gets lost and doest know what to show…

  5. caribu

    too bad. and I don’t have time to really look into it right now. I was already suspecting that this code would not work in ie. and I don’t use windows, so it is a bit of a hassle to test it on ie.

    the combobox is only supposed to show something, if an ancestor of the current selection is one of ‘p’, ‘div’, ‘h1 – 4′,’address’, ‘blockquote’, ‘pre’. So it won’t show anything if that is not the case. So maybe what you are describing in your last post is actually expected behaviour.

  6. Nikolas Hagelstein

    Well the quickest way to fix that \ss is undefined\ thing, is to change :

    this.getBlocklevelElement(n.findParentNode());
    to
    this.getBlocklevelElement(n.findParentNode(‘*’));

    Cheers,
    Nikolas

  7. Jangla

    Is there a way of tweaking this code so I could actually apply, say, a div with a predefined class to the HTML?

  8. admin

    that is not so easy because this plugin is using execCommand to insert the html tags. and execCommand doesn’t have a feature to include css classes.
    I wrote another somewhat awkward plugin to insert css-classes. It allows the user to include and remove css clases on arbitrary tags. It is awkward because the UI is not very pretty. If you are interested in that I’ll post it in the next few days.

  9. Tobias Hochguertel

    Thanks to “Nikolas Hagelstein”, for the fix of the Error which belongs to “ss = ss.replace(trimRe, “”);”. With this.getBlocklevelElement(n.findParentNode(‘*’)); it works pretty!

    Thank you for the Plugin which also works with current Ext JS 4.2.1 Version.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Powered by WordPress & Theme by Anders Norén