5fc852b37ffaf58ede42bbe1e6ad6984a43855fd.svn-base 25 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007
  1. /*
  2. * Ext JS Library 2.0 Copyright(c) 2006-2007, Ext JS, LLC. licensing@extjs.com
  3. *
  4. * http://extjs.com/license
  5. */
  6. /**
  7. * @class Ext.form.HtmlEditor
  8. * @extends Ext.form.Field Provides a lightweight HTML Editor component. <br>
  9. * <br>
  10. * <b>Note: The focus/blur and validation marking functionality
  11. * inherited from Ext.form.Field is NOT supported by this editor.</b><br/><br/>
  12. * An Editor is a sensitive component that can't be used in all spots
  13. * standard fields can be used. Putting an Editor within any element
  14. * that has display set to 'none' can cause problems in Safari and
  15. * Firefox due to their default iframe reloading bugs.
  16. */
  17. Ext.form.HtmlEditor = Ext.extend(Ext.form.Field, {
  18. /**
  19. * @cfg {Boolean} enableFormat Enable the bold, italic and underline buttons
  20. * (defaults to true)
  21. */
  22. enableFormat : true,
  23. /**
  24. * @cfg {Boolean} enableFontSize Enable the increase/decrease font size
  25. * buttons (defaults to true)
  26. */
  27. enableFontSize : true,
  28. /**
  29. * @cfg {Boolean} enableColors Enable the fore/highlight color buttons
  30. * (defaults to true)
  31. */
  32. enableColors : true,
  33. /**
  34. * @cfg {Boolean} enableAlignments Enable the left, center, right alignment
  35. * buttons (defaults to true)
  36. */
  37. enableAlignments : true,
  38. /**
  39. * @cfg {Boolean} enableLists Enable the bullet and numbered list buttons.
  40. * Not available in Safari. (defaults to true)
  41. */
  42. enableLists : true,
  43. /**
  44. * @cfg {Boolean} enableSourceEdit Enable the switch to source edit button.
  45. * Not available in Safari. (defaults to true)
  46. */
  47. enableSourceEdit : true,
  48. /**
  49. * @cfg {Boolean} enableLinks Enable the create link button. Not available
  50. * in Safari. (defaults to true)
  51. */
  52. enableLinks : true,
  53. /**
  54. * @cfg {Boolean} enableFont Enable font selection. Not available in Safari.
  55. * (defaults to true)
  56. */
  57. enableFont : true,
  58. /**
  59. * @cfg {String} createLinkText The default text for the create link prompt
  60. */
  61. createLinkText : 'Please enter the URL for the link:',
  62. /**
  63. * @cfg {String} defaultLinkValue The default value for the create link
  64. * prompt (defaults to http:/ /)
  65. */
  66. defaultLinkValue : 'http:/' + '/',
  67. /**
  68. * @cfg {Array} fontFamilies An array of available font families
  69. */
  70. fontFamilies : ['Arial', 'Courier New', 'Tahoma', 'Times New Roman',
  71. 'Verdana'],
  72. defaultFont : 'tahoma',
  73. // private properties
  74. validationEvent : false,
  75. deferHeight : true,
  76. initialized : false,
  77. activated : false,
  78. sourceEditMode : false,
  79. onFocus : Ext.emptyFn,
  80. iframePad : 3,
  81. hideMode : 'offsets',
  82. defaultAutoCreate : {
  83. tag : "textarea",
  84. style : "width:500px;height:300px;",
  85. autocomplete : "off"
  86. },
  87. // private
  88. initComponent : function() {
  89. this.addEvents(
  90. /**
  91. * @event initialize Fires when the editor is fully initialized
  92. * (including the iframe)
  93. * @param {HtmlEditor}
  94. * this
  95. */
  96. 'initialize',
  97. /**
  98. * @event activate Fires when the editor is first receives the
  99. * focus. Any insertion must wait until after this event.
  100. * @param {HtmlEditor}
  101. * this
  102. */
  103. 'activate',
  104. /**
  105. * @event beforesync Fires before the textarea is updated with
  106. * content from the editor iframe. Return false to cancel
  107. * the sync.
  108. * @param {HtmlEditor}
  109. * this
  110. * @param {String}
  111. * html
  112. */
  113. 'beforesync',
  114. /**
  115. * @event beforepush Fires before the iframe editor is updated
  116. * with content from the textarea. Return false to cancel
  117. * the push.
  118. * @param {HtmlEditor}
  119. * this
  120. * @param {String}
  121. * html
  122. */
  123. 'beforepush',
  124. /**
  125. * @event sync Fires when the textarea is updated with content
  126. * from the editor iframe.
  127. * @param {HtmlEditor}
  128. * this
  129. * @param {String}
  130. * html
  131. */
  132. 'sync',
  133. /**
  134. * @event push Fires when the iframe editor is updated with
  135. * content from the textarea.
  136. * @param {HtmlEditor}
  137. * this
  138. * @param {String}
  139. * html
  140. */
  141. 'push',
  142. /**
  143. * @event editmodechange Fires when the editor switches edit
  144. * modes
  145. * @param {HtmlEditor}
  146. * this
  147. * @param {Boolean}
  148. * sourceEdit True if source edit, false if standard
  149. * editing.
  150. */
  151. 'editmodechange')
  152. },
  153. createFontOptions : function() {
  154. var buf = [], fs = this.fontFamilies, ff, lc;
  155. for (var i = 0, len = fs.length; i < len; i++) {
  156. ff = fs[i];
  157. lc = ff.toLowerCase();
  158. buf.push('<option value="', lc, '" style="font-family:', ff, ';"',
  159. (this.defaultFont == lc ? ' selected="true">' : '>'), ff,
  160. '</option>');
  161. }
  162. return buf.join('');
  163. },
  164. /**
  165. * Protected method that will not generally be called directly. It is called
  166. * when the editor creates its toolbar. Override this method if you need to
  167. * add custom toolbar buttons.
  168. *
  169. * @param {HtmlEditor}
  170. * editor
  171. */
  172. createToolbar : function(editor) {
  173. function btn(id, toggle, handler) {
  174. return {
  175. itemId : id,
  176. cls : 'x-btn-icon x-edit-' + id,
  177. enableToggle : toggle !== false,
  178. scope : editor,
  179. handler : handler || editor.relayBtnCmd,
  180. clickEvent : 'mousedown',
  181. tooltip : editor.buttonTips[id] || undefined,
  182. tabIndex : -1
  183. };
  184. }
  185. // build the toolbar
  186. var tb = new Ext.Toolbar({
  187. renderTo : this.wrap.dom.firstChild
  188. });
  189. // stop form submits
  190. tb.el.on('click', function(e) {
  191. e.preventDefault();
  192. });
  193. if (this.enableFont && !Ext.isSafari) {
  194. this.fontSelect = tb.el.createChild({
  195. tag : 'select',
  196. cls : 'x-font-select',
  197. html : this.createFontOptions()
  198. });
  199. this.fontSelect.on('change', function() {
  200. var font = this.fontSelect.dom.value;
  201. this.relayCmd('fontname', font);
  202. this.deferFocus();
  203. }, this);
  204. tb.add(this.fontSelect.dom, '-');
  205. };
  206. if (this.enableFormat) {
  207. tb.add(btn('bold'), btn('italic'), btn('underline'));
  208. };
  209. if (this.enableFontSize) {
  210. tb.add('-', btn('increasefontsize', false, this.adjustFont), btn(
  211. 'decreasefontsize', false, this.adjustFont));
  212. };
  213. if (this.enableColors) {
  214. tb.add('-', {
  215. itemId : 'forecolor',
  216. cls : 'x-btn-icon x-edit-forecolor',
  217. clickEvent : 'mousedown',
  218. tooltip : editor.buttonTips['forecolor'] || undefined,
  219. tabIndex : -1,
  220. menu : new Ext.menu.ColorMenu({
  221. allowReselect : true,
  222. focus : Ext.emptyFn,
  223. value : '000000',
  224. plain : true,
  225. selectHandler : function(cp, color) {
  226. this.execCmd('forecolor', Ext.isSafari
  227. || Ext.isIE ? '#'
  228. + color : color);
  229. this.deferFocus();
  230. },
  231. scope : this,
  232. clickEvent : 'mousedown'
  233. })
  234. }, {
  235. itemId : 'backcolor',
  236. cls : 'x-btn-icon x-edit-backcolor',
  237. clickEvent : 'mousedown',
  238. tooltip : editor.buttonTips['backcolor'] || undefined,
  239. tabIndex : -1,
  240. menu : new Ext.menu.ColorMenu({
  241. focus : Ext.emptyFn,
  242. value : 'FFFFFF',
  243. plain : true,
  244. allowReselect : true,
  245. selectHandler : function(cp, color) {
  246. if (Ext.isGecko) {
  247. this.execCmd('useCSS', false);
  248. this.execCmd('hilitecolor', color);
  249. this.execCmd('useCSS', true);
  250. this.deferFocus();
  251. } else {
  252. this.execCmd(Ext.isOpera
  253. ? 'hilitecolor'
  254. : 'backcolor',
  255. Ext.isSafari || Ext.isIE
  256. ? '#' + color
  257. : color);
  258. this.deferFocus();
  259. }
  260. },
  261. scope : this,
  262. clickEvent : 'mousedown'
  263. })
  264. });
  265. };
  266. if (this.enableAlignments) {
  267. tb.add('-', btn('justifyleft'), btn('justifycenter'),
  268. btn('justifyright'));
  269. };
  270. if (!Ext.isSafari) {
  271. if (this.enableLinks) {
  272. tb.add('-', btn('createlink', false, this.createLink));
  273. };
  274. if (this.enableLists) {
  275. tb.add('-', btn('insertorderedlist'),
  276. btn('insertunorderedlist'));
  277. }
  278. if (this.enableSourceEdit) {
  279. tb.add('-', btn('sourceedit', true, function(btn) {
  280. this.toggleSourceEdit(btn.pressed);
  281. }));
  282. }
  283. }
  284. this.tb = tb;
  285. },
  286. /**
  287. * Protected method that will not generally be called directly. It is called
  288. * when the editor initializes the iframe with HTML contents. Override this
  289. * method if you want to change the initialization markup of the iframe
  290. * (e.g. to add stylesheets).
  291. */
  292. getDocMarkup : function() {
  293. return '<html><head><style type="text/css">body{border:0;margin:0;padding:3px;height:98%;cursor:text;}</style></head><body></body></html>';
  294. },
  295. getEditorBody : function() {
  296. return this.doc.body || this.doc.documentElement;
  297. },
  298. // private
  299. onRender : function(ct, position) {
  300. Ext.form.HtmlEditor.superclass.onRender.call(this, ct, position);
  301. this.el.dom.style.border = '0 none';
  302. this.el.dom.setAttribute('tabIndex', -1);
  303. this.el.addClass('x-hidden');
  304. if (Ext.isIE) { // fix IE 1px bogus margin
  305. this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
  306. }
  307. this.wrap = this.el.wrap({
  308. cls : 'x-html-editor-wrap',
  309. cn : {
  310. cls : 'x-html-editor-tb'
  311. }
  312. });
  313. this.createToolbar(this);
  314. this.tb.items.each(function(item) {
  315. if (item.itemId != 'sourceedit') {
  316. item.disable();
  317. }
  318. });
  319. var iframe = document.createElement('iframe');
  320. iframe.name = Ext.id();
  321. iframe.frameBorder = 'no';
  322. iframe.src = (Ext.SSL_SECURE_URL || "javascript:false");
  323. this.wrap.dom.appendChild(iframe);
  324. this.iframe = iframe;
  325. if (Ext.isIE) {
  326. iframe.contentWindow.document.designMode = 'on';
  327. this.doc = iframe.contentWindow.document;
  328. this.win = iframe.contentWindow;
  329. } else {
  330. this.doc = (iframe.contentDocument || window.frames[iframe.name].document);
  331. this.win = window.frames[iframe.name];
  332. this.doc.designMode = 'on';
  333. }
  334. this.doc.open();
  335. this.doc.write(this.getDocMarkup())
  336. this.doc.close();
  337. var task = { // must defer to wait for browser to be ready
  338. run : function() {
  339. if (this.doc.body || this.doc.readyState == 'complete') {
  340. Ext.TaskMgr.stop(task);
  341. this.doc.designMode = "on";
  342. this.initEditor.defer(10, this);
  343. }
  344. },
  345. interval : 10,
  346. duration : 10000,
  347. scope : this
  348. };
  349. Ext.TaskMgr.start(task);
  350. if (!this.width) {
  351. this.setSize(this.el.getSize());
  352. }
  353. },
  354. // private
  355. onResize : function(w, h) {
  356. Ext.form.HtmlEditor.superclass.onResize.apply(this, arguments);
  357. if (this.el && this.iframe) {
  358. if (typeof w == 'number') {
  359. var aw = w - this.wrap.getFrameWidth('lr');
  360. this.el.setWidth(this.adjustWidth('textarea', aw));
  361. this.iframe.style.width = aw + 'px';
  362. }
  363. if (typeof h == 'number') {
  364. var ah = h - this.wrap.getFrameWidth('tb')
  365. - this.tb.el.getHeight();
  366. this.el.setHeight(this.adjustWidth('textarea', ah));
  367. this.iframe.style.height = ah + 'px';
  368. if (this.doc) {
  369. this.getEditorBody().style.height = (ah - (this.iframePad * 2))
  370. + 'px';
  371. }
  372. }
  373. }
  374. },
  375. /**
  376. * Toggles the editor between standard and source edit mode.
  377. *
  378. * @param {Boolean}
  379. * sourceEdit (optional) True for source edit, false for standard
  380. */
  381. toggleSourceEdit : function(sourceEditMode) {
  382. if (sourceEditMode === undefined) {
  383. sourceEditMode = !this.sourceEditMode;
  384. }
  385. this.sourceEditMode = sourceEditMode === true;
  386. var btn = this.tb.items.get('sourceedit');
  387. if (btn.pressed !== this.sourceEditMode) {
  388. btn.toggle(this.sourceEditMode);
  389. return;
  390. }
  391. if (this.sourceEditMode) {
  392. this.tb.items.each(function(item) {
  393. if (item.itemId != 'sourceedit') {
  394. item.disable();
  395. }
  396. });
  397. this.syncValue();
  398. this.iframe.className = 'x-hidden';
  399. this.el.removeClass('x-hidden');
  400. this.el.dom.removeAttribute('tabIndex');
  401. this.el.focus();
  402. } else {
  403. if (this.initialized) {
  404. this.tb.items.each(function(item) {
  405. item.enable();
  406. });
  407. }
  408. this.pushValue();
  409. this.iframe.className = '';
  410. this.el.addClass('x-hidden');
  411. this.el.dom.setAttribute('tabIndex', -1);
  412. this.deferFocus();
  413. }
  414. var lastSize = this.lastSize;
  415. if (lastSize) {
  416. delete this.lastSize;
  417. this.setSize(lastSize);
  418. }
  419. this.fireEvent('editmodechange', this, this.sourceEditMode);
  420. },
  421. // private used internally
  422. createLink : function() {
  423. var url = prompt(this.createLinkText, this.defaultLinkValue);
  424. if (url && url != 'http:/' + '/') {
  425. this.relayCmd('createlink', url);
  426. }
  427. },
  428. // private (for BoxComponent)
  429. adjustSize : Ext.BoxComponent.prototype.adjustSize,
  430. // private (for BoxComponent)
  431. getResizeEl : function() {
  432. return this.wrap;
  433. },
  434. // private (for BoxComponent)
  435. getPositionEl : function() {
  436. return this.wrap;
  437. },
  438. // private
  439. initEvents : function() {
  440. this.originalValue = this.getValue();
  441. },
  442. /**
  443. * Overridden and disabled. The editor element does not support standard
  444. * valid/invalid marking.
  445. *
  446. * @hide
  447. * @method
  448. */
  449. markInvalid : Ext.emptyFn,
  450. /**
  451. * Overridden and disabled. The editor element does not support standard
  452. * valid/invalid marking.
  453. *
  454. * @hide
  455. * @method
  456. */
  457. clearInvalid : Ext.emptyFn,
  458. setValue : function(v) {
  459. Ext.form.HtmlEditor.superclass.setValue.call(this, v);
  460. this.pushValue();
  461. },
  462. /**
  463. * Protected method that will not generally be called directly. If you
  464. * need/want custom HTML cleanup, this is the method you should override.
  465. *
  466. * @param {String}
  467. * html The HTML to be cleaned return {String} The cleaned HTML
  468. */
  469. cleanHtml : function(html) {
  470. html = String(html);
  471. if (html.length > 5) {
  472. if (Ext.isSafari) { // strip safari nonsense
  473. html = html
  474. .replace(
  475. /\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi,
  476. '');
  477. }
  478. }
  479. if (html == '&nbsp;') {
  480. html = '';
  481. }
  482. return html;
  483. },
  484. /**
  485. * Protected method that will not generally be called directly. Syncs the
  486. * contents of the editor iframe with the textarea.
  487. */
  488. syncValue : function() {
  489. if (this.initialized) {
  490. var bd = this.getEditorBody();
  491. var html = bd.innerHTML;
  492. if (Ext.isSafari) {
  493. var bs = bd.getAttribute('style'); // Safari puts text-align
  494. // styles on the body
  495. // element!
  496. var m = bs.match(/text-align:(.*?);/i);
  497. if (m && m[1]) {
  498. html = '<div style="' + m[0] + '">' + html + '</div>';
  499. }
  500. }
  501. html = this.cleanHtml(html);
  502. if (this.fireEvent('beforesync', this, html) !== false) {
  503. this.el.dom.value = html;
  504. this.fireEvent('sync', this, html);
  505. }
  506. }
  507. },
  508. /**
  509. * Protected method that will not generally be called directly. Pushes the
  510. * value of the textarea into the iframe editor.
  511. */
  512. pushValue : function() {
  513. if (this.initialized) {
  514. var v = this.el.dom.value;
  515. if (!this.activated && v.length < 1) {
  516. v = '&nbsp;';
  517. }
  518. if (this.fireEvent('beforepush', this, v) !== false) {
  519. this.getEditorBody().innerHTML = v;
  520. this.fireEvent('push', this, v);
  521. }
  522. }
  523. },
  524. // private
  525. deferFocus : function() {
  526. this.focus.defer(10, this);
  527. },
  528. // doc'ed in Field
  529. focus : function() {
  530. if (this.win && !this.sourceEditMode) {
  531. this.win.focus();
  532. } else {
  533. this.el.focus();
  534. }
  535. },
  536. // private
  537. initEditor : function() {
  538. var dbody = this.getEditorBody();
  539. var ss = this.el.getStyles('font-size', 'font-family',
  540. 'background-image', 'background-repeat');
  541. ss['background-attachment'] = 'fixed'; // w3c
  542. dbody.bgProperties = 'fixed'; // ie
  543. Ext.DomHelper.applyStyles(dbody, ss);
  544. Ext.EventManager.on(this.doc, {
  545. 'mousedown' : this.onEditorEvent,
  546. 'dblclick' : this.onEditorEvent,
  547. 'click' : this.onEditorEvent,
  548. 'keyup' : this.onEditorEvent,
  549. buffer : 100,
  550. scope : this
  551. });
  552. if (Ext.isGecko) {
  553. Ext.EventManager.on(this.doc, 'keypress', this.applyCommand, this);
  554. }
  555. if (Ext.isIE || Ext.isSafari || Ext.isOpera) {
  556. Ext.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
  557. }
  558. this.initialized = true;
  559. this.fireEvent('initialize', this);
  560. this.pushValue();
  561. },
  562. // private
  563. onDestroy : function() {
  564. if (this.rendered) {
  565. this.tb.items.each(function(item) {
  566. if (item.menu) {
  567. item.menu.removeAll();
  568. if (item.menu.el) {
  569. item.menu.el.destroy();
  570. }
  571. }
  572. item.destroy();
  573. });
  574. this.wrap.dom.innerHTML = '';
  575. this.wrap.remove();
  576. }
  577. },
  578. // private
  579. onFirstFocus : function() {
  580. this.activated = true;
  581. this.tb.items.each(function(item) {
  582. item.enable();
  583. });
  584. if (Ext.isGecko) { // prevent silly gecko errors
  585. this.win.focus();
  586. var s = this.win.getSelection();
  587. if (!s.focusNode || s.focusNode.nodeType != 3) {
  588. var r = s.getRangeAt(0);
  589. r.selectNodeContents(this.getEditorBody());
  590. r.collapse(true);
  591. this.deferFocus();
  592. }
  593. try {
  594. this.execCmd('useCSS', true);
  595. this.execCmd('styleWithCSS', false);
  596. } catch (e) {
  597. }
  598. }
  599. this.fireEvent('activate', this);
  600. },
  601. // private
  602. adjustFont : function(btn) {
  603. var adjust = btn.itemId == 'increasefontsize' ? 1 : -1;
  604. if (Ext.isSafari) { // safari
  605. adjust *= 2;
  606. }
  607. var v = parseInt(this.doc.queryCommandValue('FontSize') || 3, 10);
  608. v = Math.max(1, v + adjust);
  609. this.execCmd('FontSize', v + (Ext.isSafari ? 'px' : 0));
  610. },
  611. onEditorEvent : function(e) {
  612. this.updateToolbar();
  613. },
  614. /**
  615. * Protected method that will not generally be called directly. It triggers
  616. * a toolbar update by reading the markup state of the current selection in
  617. * the editor.
  618. */
  619. updateToolbar : function() {
  620. if (!this.activated) {
  621. this.onFirstFocus();
  622. return;
  623. }
  624. var btns = this.tb.items.map, doc = this.doc;
  625. if (this.enableFont && !Ext.isSafari) {
  626. var name = (this.doc.queryCommandValue('FontName') || this.defaultFont)
  627. .toLowerCase();
  628. if (name != this.fontSelect.dom.value) {
  629. this.fontSelect.dom.value = name;
  630. }
  631. }
  632. if (this.enableFormat) {
  633. btns.bold.toggle(doc.queryCommandState('bold'));
  634. btns.italic.toggle(doc.queryCommandState('italic'));
  635. btns.underline.toggle(doc.queryCommandState('underline'));
  636. }
  637. if (this.enableAlignments) {
  638. btns.justifyleft.toggle(doc.queryCommandState('justifyleft'));
  639. btns.justifycenter.toggle(doc.queryCommandState('justifycenter'));
  640. btns.justifyright.toggle(doc.queryCommandState('justifyright'));
  641. }
  642. if (!Ext.isSafari && this.enableLists) {
  643. btns.insertorderedlist.toggle(doc
  644. .queryCommandState('insertorderedlist'));
  645. btns.insertunorderedlist.toggle(doc
  646. .queryCommandState('insertunorderedlist'));
  647. }
  648. Ext.menu.MenuMgr.hideAll();
  649. this.syncValue();
  650. },
  651. // private
  652. relayBtnCmd : function(btn) {
  653. this.relayCmd(btn.itemId);
  654. },
  655. /**
  656. * Executes a Midas editor command on the editor document and performs
  657. * necessary focus and toolbar updates. <b>This should only be called after
  658. * the editor is initialized.</b>
  659. *
  660. * @param {String}
  661. * cmd The Midas command
  662. * @param {String/Boolean}
  663. * value (optional) The value to pass to the command (defaults to
  664. * null)
  665. */
  666. relayCmd : function(cmd, value) {
  667. this.win.focus();
  668. this.execCmd(cmd, value);
  669. this.updateToolbar();
  670. this.deferFocus();
  671. },
  672. /**
  673. * Executes a Midas editor command directly on the editor document. For
  674. * visual commands, you should use {@link #relayCmd} instead. <b>This should
  675. * only be called after the editor is initialized.</b>
  676. *
  677. * @param {String}
  678. * cmd The Midas command
  679. * @param {String/Boolean}
  680. * value (optional) The value to pass to the command (defaults to
  681. * null)
  682. */
  683. execCmd : function(cmd, value) {
  684. this.doc.execCommand(cmd, false, value === undefined ? null : value);
  685. this.syncValue();
  686. },
  687. // private
  688. applyCommand : function(e) {
  689. if (e.ctrlKey) {
  690. var c = e.getCharCode(), cmd;
  691. if (c > 0) {
  692. c = String.fromCharCode(c);
  693. switch (c) {
  694. case 'b' :
  695. cmd = 'bold';
  696. break;
  697. case 'i' :
  698. cmd = 'italic';
  699. break;
  700. case 'u' :
  701. cmd = 'underline';
  702. break;
  703. }
  704. if (cmd) {
  705. this.win.focus();
  706. this.execCmd(cmd);
  707. this.deferFocus();
  708. e.preventDefault();
  709. }
  710. }
  711. }
  712. },
  713. /**
  714. * Inserts the passed text at the current cursor position. Note: the editor
  715. * must be initialized and activated to insert text.
  716. *
  717. * @param {String}
  718. * text
  719. */
  720. insertAtCursor : function(text) {
  721. if (!this.activated) {
  722. return;
  723. }
  724. if (Ext.isIE) {
  725. this.win.focus();
  726. var r = this.doc.selection.createRange();
  727. if (r) {
  728. r.collapse(true);
  729. r.pasteHTML(text);
  730. this.syncValue();
  731. this.deferFocus();
  732. }
  733. } else if (Ext.isGecko || Ext.isOpera) {
  734. this.win.focus();
  735. this.execCmd('InsertHTML', text);
  736. this.deferFocus();
  737. } else if (Ext.isSafari) {
  738. this.execCmd('InsertText', text);
  739. this.deferFocus();
  740. }
  741. },
  742. // private
  743. fixKeys : function() { // load time branching for fastest keydown
  744. // performance
  745. if (Ext.isIE) {
  746. return function(e) {
  747. var k = e.getKey(), r;
  748. if (k == e.TAB) {
  749. e.stopEvent();
  750. r = this.doc.selection.createRange();
  751. if (r) {
  752. r.collapse(true);
  753. r.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
  754. this.deferFocus();
  755. }
  756. } else if (k == e.ENTER) {
  757. r = this.doc.selection.createRange();
  758. if (r) {
  759. var target = r.parentElement();
  760. if (!target || target.tagName.toLowerCase() != 'li') {
  761. e.stopEvent();
  762. r.pasteHTML('<br />');
  763. r.collapse(false);
  764. r.select();
  765. }
  766. }
  767. }
  768. };
  769. } else if (Ext.isOpera) {
  770. return function(e) {
  771. var k = e.getKey();
  772. if (k == e.TAB) {
  773. e.stopEvent();
  774. this.win.focus();
  775. this.execCmd('InsertHTML', '&nbsp;&nbsp;&nbsp;&nbsp;');
  776. this.deferFocus();
  777. }
  778. };
  779. } else if (Ext.isSafari) {
  780. return function(e) {
  781. var k = e.getKey();
  782. if (k == e.TAB) {
  783. e.stopEvent();
  784. this.execCmd('InsertText', '\t');
  785. this.deferFocus();
  786. }
  787. };
  788. }
  789. }(),
  790. /**
  791. * Returns the editor's toolbar. <b>This is only available after the editor
  792. * has been rendered.</b>
  793. *
  794. * @return {Ext.Toolbar}
  795. */
  796. getToolbar : function() {
  797. return this.tb;
  798. },
  799. /**
  800. * Object collection of toolbar tooltips for the buttons in the editor. The
  801. * key is the command id associated with that button and the value is a
  802. * valid QuickTips object. For example:
  803. *
  804. * <pre><code>
  805. * {
  806. * bold : {
  807. * title: 'Bold (Ctrl+B)',
  808. * text: 'Make the selected text bold.',
  809. * cls: 'x-html-editor-tip'
  810. * },
  811. * italic : {
  812. * title: 'Italic (Ctrl+I)',
  813. * text: 'Make the selected text italic.',
  814. * cls: 'x-html-editor-tip'
  815. * },
  816. * ...
  817. *
  818. * </code></pre>
  819. *
  820. * @type Object
  821. */
  822. buttonTips : {
  823. bold : {
  824. title : 'Bold (Ctrl+B)',
  825. text : 'Make the selected text bold.',
  826. cls : 'x-html-editor-tip'
  827. },
  828. italic : {
  829. title : 'Italic (Ctrl+I)',
  830. text : 'Make the selected text italic.',
  831. cls : 'x-html-editor-tip'
  832. },
  833. underline : {
  834. title : 'Underline (Ctrl+U)',
  835. text : 'Underline the selected text.',
  836. cls : 'x-html-editor-tip'
  837. },
  838. increasefontsize : {
  839. title : 'Grow Text',
  840. text : 'Increase the font size.',
  841. cls : 'x-html-editor-tip'
  842. },
  843. decreasefontsize : {
  844. title : 'Shrink Text',
  845. text : 'Decrease the font size.',
  846. cls : 'x-html-editor-tip'
  847. },
  848. backcolor : {
  849. title : 'Text Highlight Color',
  850. text : 'Change the background color of the selected text.',
  851. cls : 'x-html-editor-tip'
  852. },
  853. forecolor : {
  854. title : 'Font Color',
  855. text : 'Change the color of the selected text.',
  856. cls : 'x-html-editor-tip'
  857. },
  858. justifyleft : {
  859. title : 'Align Text Left',
  860. text : 'Align text to the left.',
  861. cls : 'x-html-editor-tip'
  862. },
  863. justifycenter : {
  864. title : 'Center Text',
  865. text : 'Center text in the editor.',
  866. cls : 'x-html-editor-tip'
  867. },
  868. justifyright : {
  869. title : 'Align Text Right',
  870. text : 'Align text to the right.',
  871. cls : 'x-html-editor-tip'
  872. },
  873. insertunorderedlist : {
  874. title : 'Bullet List',
  875. text : 'Start a bulleted list.',
  876. cls : 'x-html-editor-tip'
  877. },
  878. insertorderedlist : {
  879. title : 'Numbered List',
  880. text : 'Start a numbered list.',
  881. cls : 'x-html-editor-tip'
  882. },
  883. createlink : {
  884. title : 'Hyperlink',
  885. text : 'Make the selected text a hyperlink.',
  886. cls : 'x-html-editor-tip'
  887. },
  888. sourceedit : {
  889. title : 'Source Edit',
  890. text : 'Switch to source editing mode.',
  891. cls : 'x-html-editor-tip'
  892. }
  893. }
  894. // hide stuff that is not compatible
  895. /**
  896. * @event blur
  897. * @hide
  898. */
  899. /**
  900. * @event change
  901. * @hide
  902. */
  903. /**
  904. * @event focus
  905. * @hide
  906. */
  907. /**
  908. * @event specialkey
  909. * @hide
  910. */
  911. /**
  912. * @cfg {String} fieldClass
  913. * @hide
  914. */
  915. /**
  916. * @cfg {String} focusClass
  917. * @hide
  918. */
  919. /**
  920. * @cfg {String} autoCreate
  921. * @hide
  922. */
  923. /**
  924. * @cfg {String} inputType
  925. * @hide
  926. */
  927. /**
  928. * @cfg {String} invalidClass
  929. * @hide
  930. */
  931. /**
  932. * @cfg {String} invalidText
  933. * @hide
  934. */
  935. /**
  936. * @cfg {String} msgFx
  937. * @hide
  938. */
  939. /**
  940. * @cfg {String} validateOnBlur
  941. * @hide
  942. */
  943. });
  944. Ext.reg('htmleditor', Ext.form.HtmlEditor);