/src/main/resources/META-INF/resources/primefaces/editor/editor.js

http://primefaces.googlecode.com/ · JavaScript · 1307 lines · 882 code · 219 blank · 206 comment · 163 complexity · 8c94b0b4b72ab7c6708a45fb30fdece2 MD5 · raw file

  1. /*!
  2. CLEditor WYSIWYG HTML Editor v1.4.5
  3. http://premiumsoftware.net/CLEditor
  4. requires jQuery v1.4.2 or later
  5. Copyright 2010, Chris Landowski, Premium Software, LLC
  6. Dual licensed under the MIT or GPL Version 2 licenses.
  7. */
  8. (function ($) {
  9. //==============
  10. // jQuery Plugin
  11. //==============
  12. $.cleditor = {
  13. // Define the defaults used for all new cleditor instances
  14. defaultOptions: {
  15. width: 'auto', // width not including margins, borders or padding
  16. height: 250, // height not including margins, borders or padding
  17. controls: // controls to add to the toolbar
  18. "bold italic underline strikethrough subscript superscript | font size " +
  19. "style | color highlight removeformat | bullets numbering | outdent " +
  20. "indent | alignleft center alignright justify | undo redo | " +
  21. "rule image link unlink | cut copy paste pastetext | print source",
  22. colors: // colors in the color popup
  23. "FFF FCC FC9 FF9 FFC 9F9 9FF CFF CCF FCF " +
  24. "CCC F66 F96 FF6 FF3 6F9 3FF 6FF 99F F9F " +
  25. "BBB F00 F90 FC6 FF0 3F3 6CC 3CF 66C C6C " +
  26. "999 C00 F60 FC3 FC0 3C0 0CC 36F 63F C3C " +
  27. "666 900 C60 C93 990 090 399 33F 60C 939 " +
  28. "333 600 930 963 660 060 366 009 339 636 " +
  29. "000 300 630 633 330 030 033 006 309 303",
  30. fonts: // font names in the font popup
  31. "Arial,Arial Black,Comic Sans MS,Courier New,Narrow,Garamond," +
  32. "Georgia,Impact,Sans Serif,Serif,Tahoma,Trebuchet MS,Verdana",
  33. sizes: // sizes in the font size popup
  34. "1,2,3,4,5,6,7",
  35. styles: // styles in the style popup
  36. [["Paragraph", "<p>"], ["Header 1", "<h1>"], ["Header 2", "<h2>"],
  37. ["Header 3", "<h3>"], ["Header 4", "<h4>"], ["Header 5", "<h5>"],
  38. ["Header 6", "<h6>"]],
  39. useCSS: true, // use CSS to style HTML when possible (not supported in ie)
  40. docType: // Document type contained within the editor
  41. '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
  42. docCSSFile: // CSS file used to style the document contained within the editor
  43. "",
  44. bodyStyle: // style to assign to document body contained within the editor
  45. "margin:4px; font:10pt Arial,Verdana; cursor:text"
  46. },
  47. // Define all usable toolbar buttons - the init string property is
  48. // expanded during initialization back into the buttons object and
  49. // separate object properties are created for each button.
  50. // e.g. buttons.size.title = "Font Size"
  51. buttons: {
  52. // name,title,command,popupName (""=use name)
  53. init:
  54. "bold,,|" +
  55. "italic,,|" +
  56. "underline,,|" +
  57. "strikethrough,,|" +
  58. "subscript,,|" +
  59. "superscript,,|" +
  60. "font,,fontname,|" +
  61. "size,Font Size,fontsize,|" +
  62. "style,,formatblock,|" +
  63. "color,Font Color,forecolor,|" +
  64. "highlight,Text Highlight Color,hilitecolor,color|" +
  65. "removeformat,Remove Formatting,|" +
  66. "bullets,,insertunorderedlist|" +
  67. "numbering,,insertorderedlist|" +
  68. "outdent,,|" +
  69. "indent,,|" +
  70. "alignleft,Align Text Left,justifyleft|" +
  71. "center,,justifycenter|" +
  72. "alignright,Align Text Right,justifyright|" +
  73. "justify,,justifyfull|" +
  74. "undo,,|" +
  75. "redo,,|" +
  76. "rule,Insert Horizontal Rule,inserthorizontalrule|" +
  77. "image,Insert Image,insertimage,url|" +
  78. "link,Insert Hyperlink,createlink,url|" +
  79. "unlink,Remove Hyperlink,|" +
  80. "cut,,|" +
  81. "copy,,|" +
  82. "paste,,|" +
  83. "pastetext,Paste as Text,inserthtml,|" +
  84. "print,,|" +
  85. "source,Show Source"
  86. },
  87. // imagesPath - returns the path to the images folder
  88. imagesPath: function () { return imagesPath(); }
  89. };
  90. // cleditor - creates a new editor for each of the matched textareas
  91. $.fn.cleditor = function (options) {
  92. // Create a new jQuery object to hold the results
  93. var $result = $([]);
  94. // Loop through all matching textareas and create the editors
  95. this.each(function (idx, elem) {
  96. if (elem.tagName.toUpperCase() === "TEXTAREA") {
  97. var data = $.data(elem, CLEDITOR);
  98. if (!data) data = new cleditor(elem, options);
  99. $result = $result.add(data);
  100. }
  101. });
  102. // return the new jQuery object
  103. return $result;
  104. };
  105. //==================
  106. // Private Variables
  107. //==================
  108. var
  109. // Misc constants
  110. BACKGROUND_COLOR = "backgroundColor",
  111. BLURRED = "blurred",
  112. BUTTON = "button",
  113. BUTTON_NAME = "buttonName",
  114. CHANGE = "change",
  115. CLEDITOR = "cleditor",
  116. CLICK = "click",
  117. DISABLED = "disabled",
  118. DIV_TAG = "<div>",
  119. FOCUSED = "focused",
  120. TRANSPARENT = "transparent",
  121. UNSELECTABLE = "unselectable",
  122. // Class name constants
  123. MAIN_CLASS = "ui-editor ui-widget-content", // main containing div
  124. TOOLBAR_CLASS = "ui-editor-toolbar", // toolbar div inside main div
  125. GROUP_CLASS = "ui-editor-group", // group divs inside the toolbar div
  126. BUTTON_CLASS = "ui-editor-button", // button divs inside group div
  127. DISABLED_CLASS = "ui-editor-disabled",// disabled button divs
  128. DIVIDER_CLASS = "ui-editor-divider", // divider divs inside group div
  129. POPUP_CLASS = "ui-editor-popup", // popup divs inside body
  130. LIST_CLASS = "ui-editor-list", // list popup divs inside body
  131. COLOR_CLASS = "ui-editor-color", // color popup div inside body
  132. PROMPT_CLASS = "ui-editor-prompt", // prompt popup divs inside body
  133. MSG_CLASS = "ui-editor-message", // message popup div inside body
  134. // Browser detection
  135. ua = navigator.userAgent.toLowerCase(),
  136. ie = /msie/.test(ua),
  137. ie6 = /msie\s6/.test(ua),
  138. iege11 = /(trident)(?:.*rv:([\w.]+))?/.test(ua),
  139. webkit = /webkit/.test(ua),
  140. // Test for iPhone/iTouch/iPad
  141. iOS = /iPhone|iPad|iPod/i.test(ua),
  142. // Popups are created once as needed and shared by all editor instances
  143. popups = {},
  144. // Used to prevent the document click event from being bound more than once
  145. documentClickAssigned,
  146. // Local copy of the buttons object
  147. buttons = $.cleditor.buttons;
  148. //===============
  149. // Initialization
  150. //===============
  151. // Expand the buttons.init string back into the buttons object
  152. // and create seperate object properties for each button.
  153. // e.g. buttons.size.title = "Font Size"
  154. $.each(buttons.init.split("|"), function (idx, button) {
  155. var items = button.split(","), name = items[0];
  156. buttons[name] = {
  157. stripIndex: idx,
  158. name: name,
  159. title: items[1] === "" ? name.charAt(0).toUpperCase() + name.substr(1) : items[1],
  160. command: items[2] === "" ? name : items[2],
  161. popupName: items[3] === "" ? name : items[3]
  162. };
  163. });
  164. delete buttons.init;
  165. //============
  166. // Constructor
  167. //============
  168. // cleditor - creates a new editor for the passed in textarea element
  169. cleditor = function (area, options) {
  170. var editor = this;
  171. // Get the defaults and override with options
  172. editor.options = options = $.extend({}, $.cleditor.defaultOptions, options);
  173. // Hide the textarea and associate it with this editor
  174. var $area = editor.$area = $(area)
  175. .css({ border: "none", margin: 0, padding: 0 }) // Needed for IE6 & 7 (won't work in CSS file)
  176. .hide()
  177. .data(CLEDITOR, editor)
  178. .blur(function () {
  179. // Update the iframe when the textarea loses focus
  180. updateFrame(editor, true);
  181. });
  182. // Create the main container
  183. var $main = editor.$main = $(DIV_TAG)
  184. .addClass(MAIN_CLASS)
  185. .width(options.width)
  186. .height(options.height);
  187. // Create the toolbar
  188. var $toolbar = editor.$toolbar = $(DIV_TAG)
  189. .addClass(TOOLBAR_CLASS)
  190. .appendTo($main);
  191. // Add the first group to the toolbar
  192. var $group = $(DIV_TAG)
  193. .addClass(GROUP_CLASS)
  194. .appendTo($toolbar);
  195. // Initialize the group width
  196. var groupWidth = 0;
  197. // Add the buttons to the toolbar
  198. $.each(options.controls.split(" "), function (idx, buttonName) {
  199. if (buttonName === "") return true;
  200. // Divider
  201. if (buttonName === "|") {
  202. // Add a new divider to the group
  203. var $div = $(DIV_TAG)
  204. .addClass(DIVIDER_CLASS)
  205. .appendTo($group);
  206. // Update the group width
  207. $group.width(groupWidth + 1);
  208. groupWidth = 0;
  209. // Create a new group
  210. $group = $(DIV_TAG)
  211. .addClass(GROUP_CLASS)
  212. .appendTo($toolbar);
  213. }
  214. // Button
  215. else {
  216. // Get the button definition
  217. var button = buttons[buttonName];
  218. // Add a new button to the group
  219. var $buttonDiv = $(DIV_TAG)
  220. .data(BUTTON_NAME, button.name)
  221. .addClass(BUTTON_CLASS)
  222. .attr("title", button.title)
  223. .bind(CLICK, $.proxy(buttonClick, editor))
  224. .appendTo($group)
  225. .hover(hoverEnter, hoverLeave);
  226. // Update the group width
  227. groupWidth += 24;
  228. $group.width(groupWidth + 1);
  229. // Prepare the button image
  230. var map = {};
  231. if (button.css) map = button.css;
  232. else if (button.image) map.backgroundImage = imageUrl(button.image);
  233. if (button.stripIndex) map.backgroundPosition = button.stripIndex * -24;
  234. $buttonDiv.css(map);
  235. // Add the unselectable attribute for ie
  236. if (ie)
  237. $buttonDiv.attr(UNSELECTABLE, "on");
  238. // Create the popup
  239. if (button.popupName)
  240. createPopup(button.popupName, options, button.popupClass,
  241. button.popupContent, button.popupHover);
  242. }
  243. });
  244. // Add the main div to the DOM and append the textarea
  245. $main.insertBefore($area)
  246. .append($area);
  247. // Bind the document click event handler
  248. if (!documentClickAssigned) {
  249. $(document).click(function (e) {
  250. // Dismiss all non-prompt popups
  251. var $target = $(e.target);
  252. if (!$target.add($target.parents()).is("." + PROMPT_CLASS))
  253. hidePopups();
  254. });
  255. documentClickAssigned = true;
  256. }
  257. // Bind the window resize event when the width or height is auto or %
  258. if (/auto|%/.test("" + options.width + options.height))
  259. $(window).bind("resize.cleditor", function () { refresh(editor); });
  260. // Create the iframe and resize the controls
  261. refresh(editor);
  262. };
  263. //===============
  264. // Public Methods
  265. //===============
  266. var fn = cleditor.prototype,
  267. // Expose the following private functions as methods on the cleditor object.
  268. // The closure compiler will rename the private functions. However, the
  269. // exposed method names on the cleditor object will remain fixed.
  270. methods = [
  271. ["clear", clear],
  272. ["disable", disable],
  273. ["execCommand", execCommand],
  274. ["focus", focus],
  275. ["hidePopups", hidePopups],
  276. ["sourceMode", sourceMode, true],
  277. ["refresh", refresh],
  278. ["select", select],
  279. ["selectedHTML", selectedHTML, true],
  280. ["selectedText", selectedText, true],
  281. ["showMessage", showMessage],
  282. ["updateFrame", updateFrame],
  283. ["updateTextArea", updateTextArea]
  284. ];
  285. $.each(methods, function (idx, method) {
  286. fn[method[0]] = function () {
  287. var editor = this, args = [editor];
  288. // using each here would cast booleans into objects!
  289. for (var x = 0; x < arguments.length; x++) { args.push(arguments[x]); }
  290. var result = method[1].apply(editor, args);
  291. if (method[2]) return result;
  292. return editor;
  293. };
  294. });
  295. // blurred - shortcut for .bind("blurred", handler) or .trigger("blurred")
  296. fn.blurred = function (handler) {
  297. var $this = $(this);
  298. return handler ? $this.bind(BLURRED, handler) : $this.trigger(BLURRED);
  299. };
  300. // change - shortcut for .bind("change", handler) or .trigger("change")
  301. fn.change = function change(handler) {
  302. var $this = $(this);
  303. return handler ? $this.bind(CHANGE, handler) : $this.trigger(CHANGE);
  304. };
  305. // focused - shortcut for .bind("focused", handler) or .trigger("focused")
  306. fn.focused = function (handler) {
  307. var $this = $(this);
  308. return handler ? $this.bind(FOCUSED, handler) : $this.trigger(FOCUSED);
  309. };
  310. //===============
  311. // Event Handlers
  312. //===============
  313. // buttonClick - click event handler for toolbar buttons
  314. function buttonClick(e) {
  315. var editor = this,
  316. buttonDiv = e.target,
  317. buttonName = $.data(buttonDiv, BUTTON_NAME),
  318. button = buttons[buttonName],
  319. popupName = button.popupName,
  320. popup = popups[popupName];
  321. // Check if disabled
  322. if (editor.disabled || $(buttonDiv).attr(DISABLED) === DISABLED)
  323. return;
  324. // Fire the buttonClick event
  325. var data = {
  326. editor: editor,
  327. button: buttonDiv,
  328. buttonName: buttonName,
  329. popup: popup,
  330. popupName: popupName,
  331. command: button.command,
  332. useCSS: editor.options.useCSS
  333. };
  334. if (button.buttonClick && button.buttonClick(e, data) === false)
  335. return false;
  336. // Toggle source
  337. if (buttonName === "source") {
  338. // Show the iframe
  339. if (sourceMode(editor)) {
  340. delete editor.range;
  341. editor.$area.hide();
  342. editor.$frame.show();
  343. buttonDiv.title = button.title;
  344. }
  345. // Show the textarea
  346. else {
  347. editor.$frame.hide();
  348. editor.$area.show();
  349. buttonDiv.title = "Show Rich Text";
  350. }
  351. }
  352. // Check for rich text mode
  353. else if (!sourceMode(editor)) {
  354. // Handle popups
  355. if (popupName) {
  356. var $popup = $(popup);
  357. // URL
  358. if (popupName === "url") {
  359. // Check for selection before showing the link url popup
  360. if (buttonName === "link" && selectedText(editor) === "") {
  361. showMessage(editor, "A selection is required when inserting a link.", buttonDiv);
  362. return false;
  363. }
  364. // Wire up the submit button click event handler
  365. $popup.children(":button")
  366. .unbind(CLICK)
  367. .bind(CLICK, function () {
  368. // Insert the image or link if a url was entered
  369. var $text = $popup.find(":text"),
  370. url = $.trim($text.val());
  371. if (url !== "")
  372. execCommand(editor, data.command, url, null, data.button);
  373. // Reset the text, hide the popup and set focus
  374. $text.val("http://");
  375. hidePopups();
  376. focus(editor);
  377. });
  378. }
  379. // Paste as Text
  380. else if (popupName === "pastetext") {
  381. // Wire up the submit button click event handler
  382. $popup.children(":button")
  383. .unbind(CLICK)
  384. .bind(CLICK, function () {
  385. // Insert the unformatted text replacing new lines with break tags
  386. var $textarea = $popup.find("textarea"),
  387. text = $textarea.val().replace(/\n/g, "<br />");
  388. if (text !== "")
  389. execCommand(editor, data.command, text, null, data.button);
  390. // Reset the text, hide the popup and set focus
  391. $textarea.val("");
  392. hidePopups();
  393. focus(editor);
  394. });
  395. }
  396. // Show the popup if not already showing for this button
  397. if (buttonDiv !== $.data(popup, BUTTON)) {
  398. showPopup(editor, popup, buttonDiv);
  399. return false; // stop propagination to document click
  400. }
  401. // propaginate to document click
  402. return;
  403. }
  404. // Print
  405. else if (buttonName === "print")
  406. editor.$frame[0].contentWindow.print();
  407. // All other buttons
  408. else if (!execCommand(editor, data.command, data.value, data.useCSS, buttonDiv))
  409. return false;
  410. }
  411. // Focus the editor
  412. focus(editor);
  413. }
  414. // hoverEnter - mouseenter event handler for buttons and popup items
  415. function hoverEnter(e) {
  416. var $div = $(e.target).closest("div");
  417. $div.css(BACKGROUND_COLOR, $div.data(BUTTON_NAME) ? "#FFF" : "#FFC");
  418. }
  419. // hoverLeave - mouseleave event handler for buttons and popup items
  420. function hoverLeave(e) {
  421. $(e.target).closest("div").css(BACKGROUND_COLOR, "transparent");
  422. }
  423. // popupClick - click event handler for popup items
  424. function popupClick(e) {
  425. var editor = this,
  426. popup = e.data.popup,
  427. target = e.target;
  428. // Check for message and prompt popups
  429. if (popup === popups.msg || $(popup).hasClass(PROMPT_CLASS))
  430. return;
  431. // Get the button info
  432. var buttonDiv = $.data(popup, BUTTON),
  433. buttonName = $.data(buttonDiv, BUTTON_NAME),
  434. button = buttons[buttonName],
  435. command = button.command,
  436. value,
  437. useCSS = editor.options.useCSS;
  438. // Get the command value
  439. if (buttonName === "font")
  440. // Opera returns the fontfamily wrapped in quotes
  441. value = target.style.fontFamily.replace(/"/g, "");
  442. else if (buttonName === "size") {
  443. if (target.tagName.toUpperCase() === "DIV")
  444. target = target.children[0];
  445. value = target.innerHTML;
  446. }
  447. else if (buttonName === "style")
  448. value = "<" + target.tagName + ">";
  449. else if (buttonName === "color")
  450. value = hex(target.style.backgroundColor);
  451. else if (buttonName === "highlight") {
  452. value = hex(target.style.backgroundColor);
  453. if (ie) command = 'backcolor';
  454. else useCSS = true;
  455. }
  456. // Fire the popupClick event
  457. var data = {
  458. editor: editor,
  459. button: buttonDiv,
  460. buttonName: buttonName,
  461. popup: popup,
  462. popupName: button.popupName,
  463. command: command,
  464. value: value,
  465. useCSS: useCSS
  466. };
  467. if (button.popupClick && button.popupClick(e, data) === false)
  468. return;
  469. // Execute the command
  470. if (data.command && !execCommand(editor, data.command, data.value, data.useCSS, buttonDiv))
  471. return false;
  472. // Hide the popup and focus the editor
  473. hidePopups();
  474. focus(editor);
  475. }
  476. //==================
  477. // Private Functions
  478. //==================
  479. // checksum - returns a checksum using the Adler-32 method
  480. function checksum(text) {
  481. var a = 1, b = 0;
  482. for (var index = 0; index < text.length; ++index) {
  483. a = (a + text.charCodeAt(index)) % 65521;
  484. b = (b + a) % 65521;
  485. }
  486. return (b << 16) | a;
  487. }
  488. // clear - clears the contents of the editor
  489. function clear(editor) {
  490. editor.$area.val("");
  491. updateFrame(editor);
  492. }
  493. // createPopup - creates a popup and adds it to the body
  494. function createPopup(popupName, options, popupTypeClass, popupContent, popupHover) {
  495. // Check if popup already exists
  496. if (popups[popupName])
  497. return popups[popupName];
  498. // Create the popup
  499. var $popup = $(DIV_TAG)
  500. .hide()
  501. .addClass(POPUP_CLASS)
  502. .appendTo("body");
  503. // Add the content
  504. // Custom popup
  505. if (popupContent)
  506. $popup.html(popupContent);
  507. // Color
  508. else if (popupName === "color") {
  509. var colors = options.colors.split(" ");
  510. if (colors.length < 10)
  511. $popup.width("auto");
  512. $.each(colors, function (idx, color) {
  513. $(DIV_TAG).appendTo($popup)
  514. .css(BACKGROUND_COLOR, "#" + color);
  515. });
  516. popupTypeClass = COLOR_CLASS;
  517. }
  518. // Font
  519. else if (popupName === "font")
  520. $.each(options.fonts.split(","), function (idx, font) {
  521. $(DIV_TAG).appendTo($popup)
  522. .css("fontFamily", font)
  523. .html(font);
  524. });
  525. // Size
  526. else if (popupName === "size")
  527. $.each(options.sizes.split(","), function (idx, size) {
  528. $(DIV_TAG).appendTo($popup)
  529. .html('<font size="' + size + '">' + size + '</font>');
  530. });
  531. // Style
  532. else if (popupName === "style")
  533. $.each(options.styles, function (idx, style) {
  534. $(DIV_TAG).appendTo($popup)
  535. .html(style[1] + style[0] + style[1].replace("<", "</"));
  536. });
  537. // URL
  538. else if (popupName === "url") {
  539. $popup.html('<label>Enter URL:<br /><input type="text" value="http://" style="width:200px" /></label><br /><input type="button" value="Submit" />');
  540. popupTypeClass = PROMPT_CLASS;
  541. }
  542. // Paste as Text
  543. else if (popupName === "pastetext") {
  544. $popup.html('<label>Paste your content here:<br /><textarea rows="3" style="width:200px"></textarea></label><br /><input type="button" value="Submit" />');
  545. popupTypeClass = PROMPT_CLASS;
  546. }
  547. // Add the popup type class name
  548. if (!popupTypeClass && !popupContent)
  549. popupTypeClass = LIST_CLASS;
  550. $popup.addClass(popupTypeClass);
  551. // Add the unselectable attribute to all items
  552. if (ie) {
  553. $popup.attr(UNSELECTABLE, "on")
  554. .find("div,font,p,h1,h2,h3,h4,h5,h6")
  555. .attr(UNSELECTABLE, "on");
  556. }
  557. // Add the hover effect to all items
  558. if ($popup.hasClass(LIST_CLASS) || popupHover === true)
  559. $popup.children().hover(hoverEnter, hoverLeave);
  560. // Add the popup to the array and return it
  561. popups[popupName] = $popup[0];
  562. return $popup[0];
  563. }
  564. // disable - enables or disables the editor
  565. function disable(editor, disabled) {
  566. // Update the textarea and save the state
  567. if (disabled) {
  568. editor.$area.attr(DISABLED, DISABLED);
  569. editor.disabled = true;
  570. }
  571. else {
  572. editor.$area.removeAttr(DISABLED);
  573. delete editor.disabled;
  574. }
  575. // Switch the iframe into design mode.
  576. // ie6 does not support designMode.
  577. // ie7 & ie8 do not properly support designMode="off".
  578. try {
  579. if (ie) editor.doc.body.contentEditable = !disabled;
  580. else editor.doc.designMode = !disabled ? "on" : "off";
  581. }
  582. // Firefox 1.5 throws an exception that can be ignored
  583. // when toggling designMode from off to on.
  584. catch (err) { }
  585. // Enable or disable the toolbar buttons
  586. refreshButtons(editor);
  587. }
  588. // execCommand - executes a designMode command
  589. function execCommand(editor, command, value, useCSS, button) {
  590. // Restore the current ie selection
  591. restoreRange(editor);
  592. // Set the styling method
  593. if (!ie) {
  594. if (useCSS === undefined || useCSS === null)
  595. useCSS = editor.options.useCSS;
  596. editor.doc.execCommand("styleWithCSS", 0, useCSS.toString());
  597. }
  598. // Execute the command and check for error
  599. var inserthtml = command.toLowerCase() === "inserthtml";
  600. if (ie && inserthtml) {
  601. /*
  602. Despite having access to pasteHTML, IE8 will produce an 'unspecified error'
  603. if it is invoked. The only way to detect this bug is via try catch.
  604. */
  605. try{
  606. getRange(editor).pasteHTML(value);
  607. }
  608. catch(e){
  609. // An empty document needs selection beforehand
  610. if(/^\s*$/.test(editor.doc.body.innerText)){
  611. editor.doc.execCommand('selectAll',false, null);
  612. }
  613. // execCommand is the standard method for contentEditable elements
  614. editor.doc.execCommand("Paste", 0, value || null);
  615. }
  616. }
  617. else if (iege11 && inserthtml) {
  618. var selection = getSelection(editor),
  619. range = selection.getRangeAt(0);
  620. range.deleteContents();
  621. range.insertNode(range.createContextualFragment(value));
  622. selection.removeAllRanges();
  623. selection.addRange(range);
  624. }
  625. else {
  626. var success = true, message;
  627. try { success = editor.doc.execCommand(command, 0, value || null); }
  628. catch (err) { message = err.message; success = false; }
  629. if (!success) {
  630. if ("cutcopypaste".indexOf(command) > -1)
  631. showMessage(editor, "For security reasons, your browser does not support the " +
  632. command + " command. Try using the keyboard shortcut or context menu instead.",
  633. button);
  634. else
  635. showMessage(editor,
  636. (message ? message : "Error executing the " + command + " command."),
  637. button);
  638. }
  639. }
  640. // Enable the buttons and update the textarea
  641. refreshButtons(editor);
  642. updateTextArea(editor, true);
  643. return success;
  644. }
  645. // focus - sets focus to either the textarea or iframe
  646. function focus(editor) {
  647. setTimeout(function () {
  648. if (sourceMode(editor)) editor.$area.focus();
  649. else editor.$frame[0].contentWindow.focus();
  650. refreshButtons(editor);
  651. }, 0);
  652. }
  653. // getRange - gets the current text range object
  654. function getRange(editor) {
  655. if (ie) return getSelection(editor).createRange();
  656. return getSelection(editor).getRangeAt(0);
  657. }
  658. // getSelection - gets the current text range object
  659. function getSelection(editor) {
  660. if (ie) return editor.doc.selection;
  661. return editor.$frame[0].contentWindow.getSelection();
  662. }
  663. // hex - returns the hex value for the passed in color string
  664. function hex(s) {
  665. // hex("rgb(255, 0, 0)") returns #FF0000
  666. var m = /rgba?\((\d+), (\d+), (\d+)/.exec(s);
  667. if (m) {
  668. s = (m[1] << 16 | m[2] << 8 | m[3]).toString(16);
  669. while (s.length < 6)
  670. s = "0" + s;
  671. return "#" + s;
  672. }
  673. // hex("#F00") returns #FF0000
  674. var c = s.split("");
  675. if (s.length === 4)
  676. return "#" + c[1] + c[1] + c[2] + c[2] + c[3] + c[3];
  677. // hex("#FF0000") returns #FF0000
  678. return s;
  679. }
  680. // hidePopups - hides all popups
  681. function hidePopups() {
  682. $.each(popups, function (idx, popup) {
  683. $(popup)
  684. .hide()
  685. .unbind(CLICK)
  686. .removeData(BUTTON);
  687. });
  688. }
  689. // imagesPath - returns the path to the images folder
  690. function imagesPath() {
  691. var href = $("link[href*=cleditor]").attr("href");
  692. return href.replace(/^(.*\/)[^\/]+$/, '$1') + "images/";
  693. }
  694. // imageUrl - Returns the css url string for a filemane
  695. function imageUrl(filename) {
  696. return "url(" + imagesPath() + filename + ")";
  697. }
  698. // refresh - creates the iframe and resizes the controls
  699. function refresh(editor) {
  700. var $main = editor.$main,
  701. options = editor.options;
  702. // Remove the old iframe
  703. if (editor.$frame)
  704. editor.$frame.remove();
  705. // Create a new iframe
  706. var $frame = editor.$frame = $('<iframe frameborder="0" src="javascript:true;" />')
  707. .hide()
  708. .appendTo($main);
  709. // Load the iframe document content
  710. var contentWindow = $frame[0].contentWindow,
  711. doc = editor.doc = contentWindow.document,
  712. $doc = $(doc);
  713. doc.open();
  714. doc.write(
  715. options.docType +
  716. '<html>' +
  717. ((options.docCSSFile === '') ? '' : '<head><link rel="stylesheet" type="text/css" href="' + options.docCSSFile + '" /></head>') +
  718. '<body style="' + options.bodyStyle + '"></body></html>'
  719. );
  720. doc.close();
  721. // Work around for bug in IE which causes the editor to lose
  722. // focus when clicking below the end of the document.
  723. if (ie || iege11)
  724. $doc.click(function () { focus(editor); });
  725. // Load the content
  726. updateFrame(editor);
  727. // Bind the ie specific iframe event handlers
  728. if (ie || iege11) {
  729. // Save the current user selection. This code is needed since IE will
  730. // reset the selection just after the beforedeactivate event and just
  731. // before the beforeactivate event.
  732. $doc.bind("beforedeactivate beforeactivate selectionchange keypress keyup", function (e) {
  733. // Flag the editor as inactive
  734. if (e.type === "beforedeactivate")
  735. editor.inactive = true;
  736. // Get rid of the bogus selection and flag the editor as active
  737. else if (e.type === "beforeactivate") {
  738. if (!editor.inactive && editor.range && editor.range.length > 1)
  739. editor.range.shift();
  740. delete editor.inactive;
  741. }
  742. // Save the selection when the editor is active
  743. else if (!editor.inactive) {
  744. if (!editor.range)
  745. editor.range = [];
  746. editor.range.unshift(getRange(editor));
  747. // We only need the last 2 selections
  748. while (editor.range.length > 2)
  749. editor.range.pop();
  750. }
  751. });
  752. // Restore the text range and trigger focused event when the iframe gains focus
  753. $frame.focus(function () {
  754. restoreRange(editor);
  755. $(editor).triggerHandler(FOCUSED);
  756. });
  757. // Trigger blurred event when the iframe looses focus
  758. $frame.blur(function () {
  759. $(editor).triggerHandler(BLURRED);
  760. });
  761. }
  762. // Trigger focused and blurred events for all other browsers
  763. else {
  764. $($frame[0].contentWindow)
  765. .focus(function () { $(editor).triggerHandler(FOCUSED); })
  766. .blur(function () { $(editor).triggerHandler(BLURRED); });
  767. }
  768. // Enable the toolbar buttons and update the textarea as the user types or clicks
  769. $doc.click(hidePopups)
  770. .keydown(function (e) {
  771. // Prevent Internet Explorer from going to prior page when an image
  772. // is selected and the backspace key is pressed.
  773. if (ie && getSelection(editor).type == "Control" && e.keyCode == 8) {
  774. getSelection(editor).clear();
  775. e.preventDefault();
  776. }
  777. })
  778. .bind("keyup mouseup", function () {
  779. refreshButtons(editor);
  780. updateTextArea(editor, true);
  781. });
  782. // Show the textarea for iPhone/iTouch/iPad or
  783. // the iframe when design mode is supported.
  784. if (iOS) editor.$area.show();
  785. else $frame.show();
  786. // Wait for the layout to finish - shortcut for $(document).ready()
  787. $(function () {
  788. var $toolbar = editor.$toolbar,
  789. $group = $toolbar.children("div:last"),
  790. wid = $main.width();
  791. // Resize the toolbar
  792. var hgt = $group.offset().top + $group.outerHeight() - $toolbar.offset().top + 1;
  793. $toolbar.height(hgt);
  794. // Resize the iframe
  795. hgt = (/%/.test("" + options.height) ? $main.height() : parseInt(options.height, 10)) - hgt;
  796. $frame.width(wid).height(hgt);
  797. // Resize the textarea. IE6 textareas have a 1px top
  798. // & bottom margin that cannot be removed using css.
  799. editor.$area.width(wid).height(ie6 ? hgt - 2 : hgt);
  800. // Switch the iframe into design mode if enabled
  801. disable(editor, editor.disabled);
  802. // Enable or disable the toolbar buttons
  803. refreshButtons(editor);
  804. });
  805. }
  806. // refreshButtons - enables or disables buttons based on availability
  807. function refreshButtons(editor) {
  808. // Webkit requires focus before queryCommandEnabled will return anything but false
  809. if (!iOS && webkit && !editor.focused) {
  810. editor.$frame[0].contentWindow.focus();
  811. window.focus();
  812. editor.focused = true;
  813. }
  814. // Get the object used for checking queryCommandEnabled
  815. var queryObj = editor.doc;
  816. if (ie) queryObj = getRange(editor);
  817. // Loop through each button
  818. var inSourceMode = sourceMode(editor);
  819. $.each(editor.$toolbar.find("." + BUTTON_CLASS), function (idx, elem) {
  820. var $elem = $(elem),
  821. button = $.cleditor.buttons[$.data(elem, BUTTON_NAME)],
  822. command = button.command,
  823. enabled = true;
  824. // Determine the state
  825. if (editor.disabled)
  826. enabled = false;
  827. else if (button.getEnabled) {
  828. var data = {
  829. editor: editor,
  830. button: elem,
  831. buttonName: button.name,
  832. popup: popups[button.popupName],
  833. popupName: button.popupName,
  834. command: button.command,
  835. useCSS: editor.options.useCSS
  836. };
  837. enabled = button.getEnabled(data);
  838. if (enabled === undefined)
  839. enabled = true;
  840. }
  841. else if (((inSourceMode || iOS) && button.name !== "source") ||
  842. (ie && (command === "undo" || command === "redo")))
  843. enabled = false;
  844. else if (command && command !== "print") {
  845. if (ie && command === "hilitecolor")
  846. command = "backcolor";
  847. // IE does not support inserthtml, so it's always enabled
  848. if ((!ie && !iege11) || command !== "inserthtml") {
  849. try { enabled = queryObj.queryCommandEnabled(command); }
  850. catch (err) { enabled = false; }
  851. }
  852. }
  853. // Enable or disable the button
  854. if (enabled) {
  855. $elem.removeClass(DISABLED_CLASS);
  856. $elem.removeAttr(DISABLED);
  857. }
  858. else {
  859. $elem.addClass(DISABLED_CLASS);
  860. $elem.attr(DISABLED, DISABLED);
  861. }
  862. });
  863. }
  864. // restoreRange - restores the current ie selection
  865. function restoreRange(editor) {
  866. if (editor.range) {
  867. if (ie)
  868. editor.range[0].select();
  869. else if (iege11)
  870. getSelection(editor).addRange(editor.range[0]);
  871. }
  872. }
  873. // select - selects all the text in either the textarea or iframe
  874. function select(editor) {
  875. setTimeout(function () {
  876. if (sourceMode(editor)) editor.$area.select();
  877. else execCommand(editor, "selectall");
  878. }, 0);
  879. }
  880. // selectedHTML - returns the current HTML selection or and empty string
  881. function selectedHTML(editor) {
  882. restoreRange(editor);
  883. var range = getRange(editor);
  884. if (ie)
  885. return range.htmlText;
  886. var layer = $("<layer>")[0];
  887. layer.appendChild(range.cloneContents());
  888. var html = layer.innerHTML;
  889. layer = null;
  890. return html;
  891. }
  892. // selectedText - returns the current text selection or and empty string
  893. function selectedText(editor) {
  894. restoreRange(editor);
  895. if (ie) return getRange(editor).text;
  896. return getSelection(editor).toString();
  897. }
  898. // showMessage - alert replacement
  899. function showMessage(editor, message, button) {
  900. var popup = createPopup("msg", editor.options, MSG_CLASS);
  901. popup.innerHTML = message;
  902. showPopup(editor, popup, button);
  903. }
  904. // showPopup - shows a popup
  905. function showPopup(editor, popup, button) {
  906. var offset, left, top, $popup = $(popup);
  907. // Determine the popup location
  908. if (button) {
  909. var $button = $(button);
  910. offset = $button.offset();
  911. left = --offset.left;
  912. top = offset.top + $button.height();
  913. }
  914. else {
  915. var $toolbar = editor.$toolbar;
  916. offset = $toolbar.offset();
  917. left = Math.floor(($toolbar.width() - $popup.width()) / 2) + offset.left;
  918. top = offset.top + $toolbar.height() - 2;
  919. }
  920. // Position and show the popup
  921. hidePopups();
  922. $popup.css({ left: left, top: top })
  923. .show();
  924. // Assign the popup button and click event handler
  925. if (button) {
  926. $.data(popup, BUTTON, button);
  927. $popup.bind(CLICK, { popup: popup }, $.proxy(popupClick, editor));
  928. }
  929. // Focus the first input element if any
  930. setTimeout(function () {
  931. $popup.find(":text,textarea").eq(0).focus().select();
  932. }, 100);
  933. }
  934. // sourceMode - returns true if the textarea is showing
  935. function sourceMode(editor) {
  936. return editor.$area.is(":visible");
  937. }
  938. // updateFrame - updates the iframe with the textarea contents
  939. function updateFrame(editor, checkForChange) {
  940. var code = editor.$area.val(),
  941. options = editor.options,
  942. updateFrameCallback = options.updateFrame,
  943. $body = $(editor.doc.body);
  944. // Check for textarea change to avoid unnecessary firing
  945. // of potentially heavy updateFrame callbacks.
  946. if (updateFrameCallback) {
  947. var sum = checksum(code);
  948. if (checkForChange && editor.areaChecksum === sum)
  949. return;
  950. editor.areaChecksum = sum;
  951. }
  952. // Convert the textarea source code into iframe html
  953. var html = updateFrameCallback ? updateFrameCallback(code) : code;
  954. // Prevent script injection attacks by html encoding script tags
  955. html = html.replace(/<(?=\/?script)/ig, "&lt;");
  956. // Update the iframe checksum
  957. if (options.updateTextArea)
  958. editor.frameChecksum = checksum(html);
  959. // Update the iframe and trigger the change event
  960. if (html !== $body.html()) {
  961. $body.html(html);
  962. $(editor).triggerHandler(CHANGE);
  963. }
  964. }
  965. // updateTextArea - updates the textarea with the iframe contents
  966. function updateTextArea(editor, checkForChange) {
  967. var html = $(editor.doc.body).html(),
  968. options = editor.options,
  969. updateTextAreaCallback = options.updateTextArea,
  970. $area = editor.$area;
  971. // Check for iframe change to avoid unnecessary firing
  972. // of potentially heavy updateTextArea callbacks.
  973. if (updateTextAreaCallback) {
  974. var sum = checksum(html);
  975. if (checkForChange && editor.frameChecksum === sum)
  976. return;
  977. editor.frameChecksum = sum;
  978. }
  979. // Convert the iframe html into textarea source code
  980. var code = updateTextAreaCallback ? updateTextAreaCallback(html) : html;
  981. // Update the textarea checksum
  982. if (options.updateFrame)
  983. editor.areaChecksum = checksum(code);
  984. // Update the textarea and trigger the change event
  985. if (code !== $area.val()) {
  986. $area.val(code);
  987. $(editor).triggerHandler(CHANGE);
  988. }
  989. }
  990. })(jQuery);
  991. /**
  992. * PrimeFaces Editor Widget
  993. */
  994. PrimeFaces.widget.Editor = PrimeFaces.widget.DeferredWidget.extend({
  995. init: function(cfg) {
  996. this._super(cfg);
  997. this.jqInput = $(this.jqId + '_input');
  998. this.renderDeferred();
  999. },
  1000. _render: function() {
  1001. this.editor = this.jqInput.cleditor(this.cfg)[0];
  1002. if(this.cfg.disabled) {
  1003. this.disable();
  1004. }
  1005. if(this.cfg.invalid) {
  1006. this.invalidate();
  1007. }
  1008. if(this.cfg.change) {
  1009. this.editor.change(this.cfg.change);
  1010. }
  1011. if(this.cfg.maxlength) {
  1012. this.bindMaxlength();
  1013. }
  1014. this.jq.css('visibility', '');
  1015. },
  1016. bindMaxlength: function() {
  1017. var $this = this,
  1018. frameDoc = this.editor.$frame[0].contentWindow.document;
  1019. $(frameDoc).bind('keydown.editor', function(event){
  1020. $this.editor.updateTextArea();
  1021. var text = $this.editor.$area.val();
  1022. if(text.length >= $this.cfg.maxlength &&
  1023. event.which != 8 && // back
  1024. event.which != 46 && // cancel
  1025. event.which != 37 && // left
  1026. event.which != 38 && // up
  1027. event.which != 39 && // right
  1028. event.which != 16 && // shift
  1029. event.which != 20 && // caps lock
  1030. event.which != 91 && // os special
  1031. event.which != 18 // alt
  1032. ) {
  1033. $this.editor.$area.val(text.substr(0, $this.cfg.maxlength));
  1034. return false;
  1035. }
  1036. else{
  1037. $this.editor.updateTextArea();
  1038. return true;
  1039. }
  1040. });
  1041. },
  1042. saveHTML: function() {
  1043. this.editor.updateTextArea();
  1044. },
  1045. clear: function() {
  1046. this.editor.clear();
  1047. },
  1048. enable: function() {
  1049. this.editor.disable(false);
  1050. },
  1051. disable: function() {
  1052. this.editor.disable(true);
  1053. },
  1054. invalidate: function() {
  1055. this.jq.children('div.ui-editor').addClass('ui-state-error');
  1056. },
  1057. focus: function() {
  1058. this.editor.focus();
  1059. },
  1060. selectAll: function() {
  1061. this.editor.select();
  1062. },
  1063. getSelectedHTML: function() {
  1064. return this.editor.selectedHTML();
  1065. },
  1066. getSelectedText: function() {
  1067. return this.editor.selectedText();
  1068. }
  1069. });