/** * @class Roo.bootstrap.form.HtmlEditorToolbar.Context * @parent Roo.bootstrap.form.HtmlEditor * @extends Roo.bootstrap.nav.Simplebar * Basic Toolbar * * @example * Usage: * new Roo.bootstrap.form.HtmlEditor({ .... toolbars : [ { xtyle: 'Standard', disable : { fonts: 1 , format: 1, ..., ... , ...], btns : [ .... ] }, { xtyle : 'Context', .... } } * * */ Roo.bootstrap.form.HtmlEditorToolbar.Context = function(config) { Roo.apply(this, config); Roo.bootstrap.form.HtmlEditorToolbar.Context.superclass.constructor.call(this, config); this.editor = config.editor; this.editorcore = config.editor.editorcore; this.buttons = new Roo.util.MixedCollection(false, function(o) { return o.cmd; }); } Roo.bootstrap.form.HtmlEditorToolbar.Context.types = { 'IMG' : [ { name : 'width', title: "Width", width: 40 }, { name : 'height', title: "Height", width: 40 }, { name : 'align', title: "Align", opts : [ [""],[ "left"],[ "right"],[ "center"],[ "top"]], width : 80 }, { name : 'border', title: "Border", width: 40 }, { name : 'alt', title: "Alt", width: 120 }, { name : 'src', title: "Src", width: 220 } ], /* 'A' : [ { name : 'name', title: "Name", width: 50 }, { name : 'target', title: "Target", width: 120 }, { name : 'href', title: "Href", width: 220 } // border? ], */ /* 'INPUT' : [ { name : 'name', title: "name", width: 120 }, { name : 'value', title: "Value", width: 120 }, { name : 'width', title: "Width", width: 40 } ], 'LABEL' : [ { name : 'for', title: "For", width: 120 } ], 'TEXTAREA' : [ { name : 'name', title: "name", width: 120 }, { name : 'rows', title: "Rows", width: 20 }, { name : 'cols', title: "Cols", width: 20 } ], 'SELECT' : [ { name : 'name', title: "name", width: 120 }, { name : 'selectoptions', title: "Options", width: 200 } ], // should we really allow this?? // should this just be 'BODY' : [ { name : 'title', title: "Title", width: 200, disabled : true } ], */ '*' : [ // empty. ] }; Roo.extend(Roo.bootstrap.form.HtmlEditorToolbar.Context, Roo.bootstrap.nav.Simplebar, { editor : false, editorcore : false, buttons : false, button_groups : false, // subtoolbars... - buttson? active_group : false, selectedNode : false, onRender : function(ct, position) { // Roo.log("Call onRender: " + this.xtype); this.constructor.superclass.onRender.call(this, ct, position); // disable everything... var ty = this.constructor.types; this.button_groups = {}; // block toolbars are built in updateToolbar when needed. for (var i in ty) { this.button_groups[i] = this.buildToolbarGroup(ty[i],i); } this.buildToolbarDelete(); this.hide(); // the all the btns; this.editor.on('editorevent', this.updateToolbar, this); }, onFirstFocus: function() { }, buildToolbarGroup: function(tlist, key ) { var editor = this.editor; var editorcore = this.editorcore; var tb = this; var ret = []; for (var i = 0; i < tlist.length; i++) { // newer versions will use xtype cfg to create menus. if (typeof(tlist[i].xtype) != 'undefined') { tb[typeof(tlist[i].name)== 'undefined' ? 'add' : 'addField'](Roo.factory(tlist[i])); continue; } var item = tlist[i]; ret.push( this.addxtypeChild({ xtype : 'Element', xns : Roo.bootstrap, cls : 'roo-htmleditor-context-label-' + key + '-' + item.name, html : item.title }) ); // add a text entry!? ret.push( this.addxtypeChild({ xtype : 'Input', xns : Roo.bootstrap.form, cls : 'roo-htmleditor-context-entry-' + key + '-' + item.name, name: '-roo-edit-' + item.name, attrname : item.name, width: item.width, //allowBlank:true, value: '', listeners: { 'change' : function(f, nv, ov) { tb.selectedNode.setAttribute(f.attrname, nv); editorcore.syncValue(); } } }) ); } // hide them all.. ret.forEach(function(e) { e.hide(); }); ret.name = key; return ret; }, buildToolbarDelete : function() { this.addxtypeChild({ xtype : 'Element', xns : Roo.bootstrap, cls : 'roo-htmleditor-fill' }); this.deleteBtn = this.addxtypeChild({ size : 'sm', xtype: 'Button', xns: Roo.bootstrap, fa: 'trash', listeners : { click : this.onDelete.createDelegate(this) } }); this.deleteBtn.hide(); }, onDelete : function() { var range = this.editorcore.createRange(); var selection = this.editorcore.getSelection(); var sn = this.selectedNode; range.setStart(sn,0); range.setEnd(sn,0); if (sn.hasAttribute('data-block')) { var block = Roo.htmleditor.Block.factory(tb.selectedNode); if (block) { block.removeNode(); selection.removeAllRanges(); selection.addRange(range); this.updateToolbar(null, null, null); } } if (!sn) { return; // should not really happen.. } if (sn && sn.tagName == 'BODY') { return; } var stn = sn.childNodes[0] || sn.nextSibling || sn.previousSibling || sn.parentNode; // remove and keep parents. a = new Roo.htmleditor.FilterKeepChildren({tag : false}); a.replaceTag(sn); selection.removeAllRanges(); selection.addRange(range); this.editorcore.fireEditorEvent(false); }, /** * Protected method that will not generally be called directly. It triggers * a toolbar update by reading the markup state of the current selection in the editor. * * Note you can force an update by calling on('editorevent', scope, false) */ updateToolbar: function(editor ,ev, sel) { var ty = this.constructor.types; if (ev) { ev.stopEvent(); // se if we can stop this looping with mutiple events. } // capture mouse up - this is handy for selecting images.. // perhaps should go somewhere else... if(!this.editorcore.activated){ this.editor.onFirstFocus(); return; } //Roo.log(ev ? ev.target : 'NOTARGET'); // http://developer.yahoo.com/yui/docs/simple-editor.js.html // selectNode - might want to handle IE? if (ev && (ev.type == 'mouseup' || ev.type == 'click' ) && ev.target && ev.target.tagName != 'BODY' ) { // && ev.target.tagName == 'IMG') { // they have click on an image... // let's see if we can change the selection... sel = ev.target; } // this forces an id.. Array.from(this.editorcore.doc.body.querySelectorAll('.roo-ed-selection')).forEach(function(e) { e.classList.remove('roo-ed-selection'); }); var ans = this.editorcore.getAllAncestors(); if (!sel) { sel = ans.length ? (ans[0] ? ans[0] : ans[1]) : this.editorcore.doc.body; sel = sel ? sel : this.editorcore.doc.body; sel = sel.tagName.length ? sel : this.editorcore.doc.body; } var tn = sel.tagName.toUpperCase(); var lastSel = this.selectedNode; this.selectedNode = sel; var left_label = tn; // ok see if we are editing a block? var db = false; // you are not actually selecting the block. if (sel && sel.hasAttribute('data-block')) { db = sel; } else if (sel && sel.closest('[data-block]')) { db = sel.closest('[data-block]'); } var block = false; if (db && this.editorcore.enableBlocks) { block = Roo.htmleditor.Block.factory(db); if (block) { db.className = ( db.classList.length > 0 ? db.className + ' ' : '' ) + 'roo-ed-selection'; // since we removed it earlier... its not there.. tn = 'BLOCK.' + db.getAttribute('data-block'); //this.editorcore.selectNode(db); if (typeof(this.button_groups[tn]) == 'undefined') { this.button_groups[tn] = this.buildBlockToolbar( block ); } this.selectedNode = db; left_label = block.friendly_name; } } if ( this.active_group !== false && this.active_group.name == tn && lastSel == this.selectedNode && ev !== false) { return; // no change? } if (tn == 'BODY') { this.deleteBtn.hide(); this.hide(); this.hideActiveGroup(); return; } if (this.active_group) { this.hideActiveGroup(); } this.showActiveGroup(tn); this.show(); this.deleteBtn.show(); }, hideActiveGroup : function() { this.hide(); if (this.active_group === false) { return; } this.active_group.forEach(function(e) { e.hide(); }); this.active_group = false; }, showActiveGroup : function(tn) { if (typeof(this.button_groups[tn]) == 'undefined') { return; } this.active_group = this.button_groups[tn]; this.active_group.forEach(function(e) { e.show(); }); // update attributes if (this.selectedNode.hasAttribute('data-block') ) { var block = Roo.htmleditor.Block.factory(this.selectedNode); this.active_group.forEach(function(e) { e.setValue(this.selectedNode.getAttribute(block[e.name])); }, this); return; } // based on attributes... this.active_group.forEach(function(e) { if (typeof(e.attrname) == 'undefined') { return; } e.setValue(this.selectedNode.getAttribute(e.attrname)); }, this); } });