FontChoice.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dijit._editor.plugins.FontChoice"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit._editor.plugins.FontChoice"] = true;
  8. dojo.provide("dijit._editor.plugins.FontChoice");
  9. dojo.require("dijit._editor._Plugin");
  10. dojo.require("dijit._editor.range");
  11. dojo.require("dijit._editor.selection");
  12. dojo.require("dijit.form.FilteringSelect");
  13. dojo.require("dojo.data.ItemFileReadStore");
  14. dojo.require("dojo.i18n");
  15. dojo.requireLocalization("dijit._editor", "FontChoice", null, "ROOT,ar,az,bg,ca,cs,da,de,el,es,fi,fr,he,hr,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw");
  16. dojo.declare("dijit._editor.plugins._FontDropDown",
  17. [dijit._Widget, dijit._Templated],{
  18. // summary:
  19. // Base class for widgets that contains a label (like "Font:")
  20. // and a FilteringSelect drop down to pick a value.
  21. // Used as Toolbar entry.
  22. // label: [public] String
  23. // The label to apply to this particular FontDropDown.
  24. label: "",
  25. // widgetsInTemplate: [public] boolean
  26. // Over-ride denoting the template has widgets to parse.
  27. widgetsInTemplate: true,
  28. // plainText: [public] boolean
  29. // Flag to indicate that the returned label should be plain text
  30. // instead of an example.
  31. plainText: false,
  32. // templateString: [public] String
  33. // The template used to construct the labeled dropdown.
  34. templateString:
  35. "<span style='white-space: nowrap' class='dijit dijitReset dijitInline'>" +
  36. "<label class='dijitLeft dijitInline' for='${selectId}'>${label}</label>" +
  37. "<input dojoType='dijit.form.FilteringSelect' required='false' labelType='html' labelAttr='label' searchAttr='name' " +
  38. "tabIndex='-1' id='${selectId}' dojoAttachPoint='select' value=''/>" +
  39. "</span>",
  40. postMixInProperties: function(){
  41. // summary:
  42. // Over-ride to set specific properties.
  43. this.inherited(arguments);
  44. this.strings = dojo.i18n.getLocalization("dijit._editor", "FontChoice");
  45. // Set some substitution variables used in the template
  46. this.label = this.strings[this.command];
  47. this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_"));
  48. this.selectId = this.id + "_select";
  49. this.inherited(arguments);
  50. },
  51. postCreate: function(){
  52. // summary:
  53. // Over-ride for the default postCreate action
  54. // This establishes the filtering selects and the like.
  55. // Initialize the list of items in the drop down by creating data store with items like:
  56. // {value: 1, name: "xx-small", label: "<font size=1>xx-small</font-size>" }
  57. var items = dojo.map(this.values, function(value){
  58. var name = this.strings[value] || value;
  59. return {
  60. label: this.getLabel(value, name),
  61. name: name,
  62. value: value
  63. };
  64. }, this);
  65. this.select.store = new dojo.data.ItemFileReadStore({
  66. data: {
  67. identifier: "value",
  68. items: items
  69. }
  70. });
  71. this.select.set("value", "", false);
  72. this.disabled = this.select.get("disabled");
  73. },
  74. _setValueAttr: function(value, priorityChange){
  75. // summary:
  76. // Over-ride for the default action of setting the
  77. // widget value, maps the input to known values
  78. // value: Object|String
  79. // The value to set in the select.
  80. // priorityChange:
  81. // Optional parameter used to tell the select whether or not to fire
  82. // onChange event.
  83. //if the value is not a permitted value, just set empty string to prevent showing the warning icon
  84. priorityChange = priorityChange !== false?true:false;
  85. this.select.set('value', dojo.indexOf(this.values,value) < 0 ? "" : value, priorityChange);
  86. if(!priorityChange){
  87. // Clear the last state in case of updateState calls. Ref: #10466
  88. this.select._lastValueReported=null;
  89. }
  90. },
  91. _getValueAttr: function(){
  92. // summary:
  93. // Allow retreiving the value from the composite select on
  94. // call to button.get("value");
  95. return this.select.get('value');
  96. },
  97. focus: function(){
  98. // summary:
  99. // Over-ride for focus control of this widget. Delegates focus down to the
  100. // filtering select.
  101. this.select.focus();
  102. },
  103. _setDisabledAttr: function(value){
  104. // summary:
  105. // Over-ride for the button's 'disabled' attribute so that it can be
  106. // disabled programmatically.
  107. // Save off ths disabled state so the get retrieves it correctly
  108. //without needing to have a function proxy it.
  109. this.disabled = value;
  110. this.select.set("disabled", value);
  111. }
  112. });
  113. dojo.declare("dijit._editor.plugins._FontNameDropDown", dijit._editor.plugins._FontDropDown, {
  114. // summary:
  115. // Dropdown to select a font; goes in editor toolbar.
  116. // generic: Boolean
  117. // Use generic (web standard) font names
  118. generic: false,
  119. // command: [public] String
  120. // The editor 'command' implemented by this plugin.
  121. command: "fontName",
  122. postMixInProperties: function(){
  123. // summary:
  124. // Over-ride for the default posr mixin control
  125. if(!this.values){
  126. this.values = this.generic ?
  127. ["serif", "sans-serif", "monospace", "cursive", "fantasy"] : // CSS font-family generics
  128. ["Arial", "Times New Roman", "Comic Sans MS", "Courier New"];
  129. }
  130. this.inherited(arguments);
  131. },
  132. getLabel: function(value, name){
  133. // summary:
  134. // Function used to generate the labels of the format dropdown
  135. // will return a formatted, or plain label based on the value
  136. // of the plainText option.
  137. // value: String
  138. // The 'insert value' associated with a name
  139. // name: String
  140. // The text name of the value
  141. if(this.plainText){
  142. return name;
  143. }else{
  144. return "<div style='font-family: "+value+"'>" + name + "</div>";
  145. }
  146. },
  147. _setValueAttr: function(value, priorityChange){
  148. // summary:
  149. // Over-ride for the default action of setting the
  150. // widget value, maps the input to known values
  151. priorityChange = priorityChange !== false?true:false;
  152. if(this.generic){
  153. var map = {
  154. "Arial": "sans-serif",
  155. "Helvetica": "sans-serif",
  156. "Myriad": "sans-serif",
  157. "Times": "serif",
  158. "Times New Roman": "serif",
  159. "Comic Sans MS": "cursive",
  160. "Apple Chancery": "cursive",
  161. "Courier": "monospace",
  162. "Courier New": "monospace",
  163. "Papyrus": "fantasy",
  164. "Estrangelo Edessa": "cursive",
  165. "Gabriola": "fantasy"
  166. };
  167. value = map[value] || value;
  168. }
  169. this.inherited(arguments, [value, priorityChange]);
  170. }
  171. });
  172. dojo.declare("dijit._editor.plugins._FontSizeDropDown", dijit._editor.plugins._FontDropDown, {
  173. // summary:
  174. // Dropdown to select a font size; goes in editor toolbar.
  175. // command: [public] String
  176. // The editor 'command' implemented by this plugin.
  177. command: "fontSize",
  178. // values: [public] Number[]
  179. // The HTML font size values supported by this plugin
  180. values: [1,2,3,4,5,6,7], // sizes according to the old HTML FONT SIZE
  181. getLabel: function(value, name){
  182. // summary:
  183. // Function used to generate the labels of the format dropdown
  184. // will return a formatted, or plain label based on the value
  185. // of the plainText option.
  186. // We're stuck using the deprecated FONT tag to correspond
  187. // with the size measurements used by the editor
  188. // value: String
  189. // The 'insert value' associated with a name
  190. // name: String
  191. // The text name of the value
  192. if(this.plainText){
  193. return name;
  194. }else{
  195. return "<font size=" + value + "'>" + name + "</font>";
  196. }
  197. },
  198. _setValueAttr: function(value, priorityChange){
  199. // summary:
  200. // Over-ride for the default action of setting the
  201. // widget value, maps the input to known values
  202. priorityChange = priorityChange !== false?true:false;
  203. if(value.indexOf && value.indexOf("px") != -1){
  204. var pixels = parseInt(value, 10);
  205. value = {10:1, 13:2, 16:3, 18:4, 24:5, 32:6, 48:7}[pixels] || value;
  206. }
  207. this.inherited(arguments, [value, priorityChange]);
  208. }
  209. });
  210. dojo.declare("dijit._editor.plugins._FormatBlockDropDown", dijit._editor.plugins._FontDropDown, {
  211. // summary:
  212. // Dropdown to select a format (like paragraph or heading); goes in editor toolbar.
  213. // command: [public] String
  214. // The editor 'command' implemented by this plugin.
  215. command: "formatBlock",
  216. // values: [public] Array
  217. // The HTML format tags supported by this plugin
  218. values: ["noFormat", "p", "h1", "h2", "h3", "pre"],
  219. postCreate: function(){
  220. // Init and set the default value to no formatting. Update state will adjust it
  221. // as needed.
  222. this.inherited(arguments);
  223. this.set("value", "noFormat", false);
  224. },
  225. getLabel: function(value, name){
  226. // summary:
  227. // Function used to generate the labels of the format dropdown
  228. // will return a formatted, or plain label based on the value
  229. // of the plainText option.
  230. // value: String
  231. // The 'insert value' associated with a name
  232. // name: String
  233. // The text name of the value
  234. if(this.plainText || value == "noFormat"){
  235. return name;
  236. }else{
  237. return "<" + value + ">" + name + "</" + value + ">";
  238. }
  239. },
  240. _execCommand: function(editor, command, choice){
  241. // summary:
  242. // Over-ride for default exec-command label.
  243. // Allows us to treat 'none' as special.
  244. if(choice === "noFormat"){
  245. var start;
  246. var end;
  247. var sel = dijit.range.getSelection(editor.window);
  248. if(sel && sel.rangeCount > 0){
  249. var range = sel.getRangeAt(0);
  250. var node, tag;
  251. if(range){
  252. start = range.startContainer;
  253. end = range.endContainer;
  254. // find containing nodes of start/end.
  255. while(start && start !== editor.editNode &&
  256. start !== editor.document.body &&
  257. start.nodeType !== 1){
  258. start = start.parentNode;
  259. }
  260. while(end && end !== editor.editNode &&
  261. end !== editor.document.body &&
  262. end.nodeType !== 1){
  263. end = end.parentNode;
  264. }
  265. var processChildren = dojo.hitch(this, function(node, array){
  266. if(node.childNodes && node.childNodes.length){
  267. var i;
  268. for(i = 0; i < node.childNodes.length; i++){
  269. var c = node.childNodes[i];
  270. if(c.nodeType == 1){
  271. if(dojo.withGlobal(editor.window, "inSelection", dijit._editor.selection, [c])){
  272. var tag = c.tagName? c.tagName.toLowerCase(): "";
  273. if(dojo.indexOf(this.values, tag) !== -1){
  274. array.push(c);
  275. }
  276. processChildren(c,array);
  277. }
  278. }
  279. }
  280. }
  281. });
  282. var unformatNodes = dojo.hitch(this, function(nodes){
  283. // summary:
  284. // Internal function to clear format nodes.
  285. // nodes:
  286. // The array of nodes to strip formatting from.
  287. if(nodes && nodes.length){
  288. editor.beginEditing();
  289. while(nodes.length){
  290. this._removeFormat(editor, nodes.pop());
  291. }
  292. editor.endEditing();
  293. }
  294. });
  295. var clearNodes = [];
  296. if(start == end){
  297. //Contained within the same block, may be collapsed, but who cares, see if we
  298. // have a block element to remove.
  299. var block;
  300. node = start;
  301. while(node && node !== editor.editNode && node !== editor.document.body){
  302. if(node.nodeType == 1){
  303. tag = node.tagName? node.tagName.toLowerCase(): "";
  304. if(dojo.indexOf(this.values, tag) !== -1){
  305. block = node;
  306. break;
  307. }
  308. }
  309. node = node.parentNode;
  310. }
  311. //Also look for all child nodes in the selection that may need to be
  312. //cleared of formatting
  313. processChildren(start, clearNodes);
  314. if(block) { clearNodes = [block].concat(clearNodes); }
  315. unformatNodes(clearNodes);
  316. }else{
  317. // Probably a multi select, so we have to process it. Whee.
  318. node = start;
  319. while(dojo.withGlobal(editor.window, "inSelection", dijit._editor.selection, [node])){
  320. if(node.nodeType == 1){
  321. tag = node.tagName? node.tagName.toLowerCase(): "";
  322. if(dojo.indexOf(this.values, tag) !== -1){
  323. clearNodes.push(node);
  324. }
  325. processChildren(node,clearNodes);
  326. }
  327. node = node.nextSibling;
  328. }
  329. unformatNodes(clearNodes);
  330. }
  331. editor.onDisplayChanged();
  332. }
  333. }
  334. }else{
  335. editor.execCommand(command, choice);
  336. }
  337. },
  338. _removeFormat: function(editor, node){
  339. // summary:
  340. // function to remove the block format node.
  341. // node:
  342. // The block format node to remove (and leave the contents behind)
  343. if(editor.customUndo){
  344. // So of course IE doesn't work right with paste-overs.
  345. // We have to do this manually, which is okay since IE already uses
  346. // customUndo and we turned it on for WebKit. WebKit pasted funny,
  347. // so couldn't use the execCommand approach
  348. while(node.firstChild){
  349. dojo.place(node.firstChild, node, "before");
  350. }
  351. node.parentNode.removeChild(node);
  352. }else{
  353. // Everyone else works fine this way, a paste-over and is native
  354. // undo friendly.
  355. dojo.withGlobal(editor.window,
  356. "selectElementChildren", dijit._editor.selection, [node]);
  357. var html = dojo.withGlobal(editor.window,
  358. "getSelectedHtml", dijit._editor.selection, [null]);
  359. dojo.withGlobal(editor.window,
  360. "selectElement", dijit._editor.selection, [node]);
  361. editor.execCommand("inserthtml", html||"");
  362. }
  363. }
  364. });
  365. // TODO: for 2.0, split into FontChoice plugin into three separate classes,
  366. // one for each command (and change registry below)
  367. dojo.declare("dijit._editor.plugins.FontChoice", dijit._editor._Plugin,{
  368. // summary:
  369. // This plugin provides three drop downs for setting style in the editor
  370. // (font, font size, and format block), as controlled by command.
  371. //
  372. // description:
  373. // The commands provided by this plugin are:
  374. //
  375. // * fontName
  376. // | Provides a drop down to select from a list of font names
  377. // * fontSize
  378. // | Provides a drop down to select from a list of font sizes
  379. // * formatBlock
  380. // | Provides a drop down to select from a list of block styles
  381. // |
  382. //
  383. // which can easily be added to an editor by including one or more of the above commands
  384. // in the `plugins` attribute as follows:
  385. //
  386. // | plugins="['fontName','fontSize',...]"
  387. //
  388. // It is possible to override the default dropdown list by providing an Array for the `custom` property when
  389. // instantiating this plugin, e.g.
  390. //
  391. // | plugins="[{name:'dijit._editor.plugins.FontChoice', command:'fontName', custom:['Verdana','Myriad','Garamond']},...]"
  392. //
  393. // Alternatively, for `fontName` only, `generic:true` may be specified to provide a dropdown with
  394. // [CSS generic font families](http://www.w3.org/TR/REC-CSS2/fonts.html#generic-font-families)
  395. //
  396. // Note that the editor is often unable to properly handle font styling information defined outside
  397. // the context of the current editor instance, such as pre-populated HTML.
  398. // useDefaultCommand: [protected] booleam
  399. // Override _Plugin.useDefaultCommand...
  400. // processing is handled by this plugin, not by dijit.Editor.
  401. useDefaultCommand: false,
  402. _initButton: function(){
  403. // summary:
  404. // Overrides _Plugin._initButton(), to initialize the FilteringSelect+label in toolbar,
  405. // rather than a simple button.
  406. // tags:
  407. // protected
  408. // Create the widget to go into the toolbar (the so-called "button")
  409. var clazz = {
  410. fontName: dijit._editor.plugins._FontNameDropDown,
  411. fontSize: dijit._editor.plugins._FontSizeDropDown,
  412. formatBlock: dijit._editor.plugins._FormatBlockDropDown
  413. }[this.command],
  414. params = this.params;
  415. // For back-compat reasons support setting custom values via "custom" parameter
  416. // rather than "values" parameter
  417. if(this.params.custom){
  418. params.values = this.params.custom;
  419. }
  420. var editor = this.editor;
  421. this.button = new clazz(dojo.delegate({dir: editor.dir, lang: editor.lang}, params));
  422. // Reflect changes to the drop down in the editor
  423. this.connect(this.button.select, "onChange", function(choice){
  424. // User invoked change, since all internal updates set priorityChange to false and will
  425. // not trigger an onChange event.
  426. this.editor.focus();
  427. if(this.command == "fontName" && choice.indexOf(" ") != -1){ choice = "'" + choice + "'"; }
  428. // Invoke, the editor already normalizes commands called through its
  429. // execCommand.
  430. if(this.button._execCommand){
  431. this.button._execCommand(this.editor, this.command, choice);
  432. }else{
  433. this.editor.execCommand(this.command, choice);
  434. }
  435. });
  436. },
  437. updateState: function(){
  438. // summary:
  439. // Overrides _Plugin.updateState(). This controls updating the menu
  440. // options to the right values on state changes in the document (that trigger a
  441. // test of the actions.)
  442. // It set value of drop down in toolbar to reflect font/font size/format block
  443. // of text at current caret position.
  444. // tags:
  445. // protected
  446. var _e = this.editor;
  447. var _c = this.command;
  448. if(!_e || !_e.isLoaded || !_c.length){ return; }
  449. if(this.button){
  450. var disabled = this.get("disabled");
  451. this.button.set("disabled", disabled);
  452. if(disabled){ return; }
  453. var value;
  454. try{
  455. value = _e.queryCommandValue(_c) || "";
  456. }catch(e){
  457. //Firefox may throw error above if the editor is just loaded, ignore it
  458. value = "";
  459. }
  460. // strip off single quotes, if any
  461. var quoted = dojo.isString(value) && value.match(/'([^']*)'/);
  462. if(quoted){ value = quoted[1]; }
  463. if(_c === "formatBlock"){
  464. if(!value || value == "p"){
  465. // Some browsers (WebKit) doesn't actually get the tag info right.
  466. // and IE returns paragraph when in a DIV!, so incorrect a lot,
  467. // so we have double-check it.
  468. value = null;
  469. var elem;
  470. // Try to find the current element where the caret is.
  471. var sel = dijit.range.getSelection(this.editor.window);
  472. if(sel && sel.rangeCount > 0){
  473. var range = sel.getRangeAt(0);
  474. if(range){
  475. elem = range.endContainer;
  476. }
  477. }
  478. // Okay, now see if we can find one of the formatting types we're in.
  479. while(elem && elem !== _e.editNode && elem !== _e.document){
  480. var tg = elem.tagName?elem.tagName.toLowerCase():"";
  481. if(tg && dojo.indexOf(this.button.values, tg) > -1){
  482. value = tg;
  483. break;
  484. }
  485. elem = elem.parentNode;
  486. }
  487. if(!value){
  488. // Still no value, so lets select 'none'.
  489. value = "noFormat";
  490. }
  491. }else{
  492. // Check that the block format is one allowed, if not,
  493. // null it so that it gets set to empty.
  494. if(dojo.indexOf(this.button.values, value) < 0){
  495. value = "noFormat";
  496. }
  497. }
  498. }
  499. if(value !== this.button.get("value")){
  500. // Set the value, but denote it is not a priority change, so no
  501. // onchange fires.
  502. this.button.set('value', value, false);
  503. }
  504. }
  505. }
  506. });
  507. // Register this plugin.
  508. dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){
  509. if(o.plugin){ return; }
  510. switch(o.args.name){
  511. case "fontName": case "fontSize": case "formatBlock":
  512. o.plugin = new dijit._editor.plugins.FontChoice({
  513. command: o.args.name,
  514. plainText: o.args.plainText?o.args.plainText:false
  515. });
  516. }
  517. });
  518. }