InsertAnchor.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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["dojox.editor.plugins.InsertAnchor"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.editor.plugins.InsertAnchor"] = true;
  8. dojo.provide("dojox.editor.plugins.InsertAnchor");
  9. dojo.require("dojo.string");
  10. dojo.require("dijit._Widget");
  11. dojo.require("dijit._editor.range");
  12. dojo.require("dijit._Templated");
  13. dojo.require("dijit.TooltipDialog");
  14. dojo.require("dijit.form.ValidationTextBox");
  15. dojo.require("dijit.form.Select");
  16. dojo.require("dijit._editor._Plugin");
  17. dojo.require("dijit.form.Button");
  18. dojo.require("dojox.editor.plugins.ToolbarLineBreak");
  19. dojo.require("dojo.i18n");
  20. dojo.requireLocalization("dojox.editor.plugins", "InsertAnchor", null, "ROOT,ar,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");
  21. dojo.requireLocalization("dijit", "common", 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");
  22. dojo.declare("dojox.editor.plugins.InsertAnchor", dijit._editor._Plugin, {
  23. // summary:
  24. // This plugin provides the basis for an insert anchor dialog for the
  25. // dijit.Editor
  26. //
  27. // description:
  28. // The command provided by this plugin is:
  29. // * insertAnchor
  30. // htmlTemplate: [protected] String
  31. // String used for templating the HTML to insert at the desired point.
  32. htmlTemplate: "<a name=\"${anchorInput}\" class=\"dijitEditorPluginInsertAnchorStyle\">${textInput}</a>",
  33. // iconClassPrefix: [const] String
  34. // The CSS class name for the button node icon.
  35. iconClassPrefix: "dijitAdditionalEditorIcon",
  36. // linkDialogTemplate: [private] String
  37. // Template for contents of TooltipDialog to pick URL
  38. _template: [
  39. "<table><tr><td>",
  40. "<label for='${id}_anchorInput'>${anchor}</label>",
  41. "</td><td>",
  42. "<input dojoType='dijit.form.ValidationTextBox' required='true' " +
  43. "id='${id}_anchorInput' name='anchorInput' intermediateChanges='true'>",
  44. "</td></tr><tr><td>",
  45. "<label for='${id}_textInput'>${text}</label>",
  46. "</td><td>",
  47. "<input dojoType='dijit.form.ValidationTextBox' required='true' id='${id}_textInput' " +
  48. "name='textInput' intermediateChanges='true'>",
  49. "</td></tr>",
  50. "<tr><td colspan='2'>",
  51. "<button dojoType='dijit.form.Button' type='submit' id='${id}_setButton'>${set}</button>",
  52. "<button dojoType='dijit.form.Button' type='button' id='${id}_cancelButton'>${cancel}</button>",
  53. "</td></tr></table>"
  54. ].join(""),
  55. _initButton: function(){
  56. // Override _Plugin._initButton() to initialize DropDownButton and TooltipDialog.
  57. var _this = this;
  58. var messages = dojo.i18n.getLocalization("dojox.editor.plugins", "InsertAnchor", this.lang);
  59. // Build the dropdown dialog we'll use for the button
  60. var dropDown = (this.dropDown = new dijit.TooltipDialog({
  61. title: messages["title"],
  62. execute: dojo.hitch(this, "setValue"),
  63. onOpen: function(){
  64. _this._onOpenDialog();
  65. dijit.TooltipDialog.prototype.onOpen.apply(this, arguments);
  66. },
  67. onCancel: function(){
  68. setTimeout(dojo.hitch(_this, "_onCloseDialog"),0);
  69. }
  70. }));
  71. this.button = new dijit.form.DropDownButton({
  72. label: messages["insertAnchor"],
  73. showLabel: false,
  74. iconClass: this.iconClassPrefix + " " + this.iconClassPrefix + "InsertAnchor",
  75. tabIndex: "-1",
  76. dropDown: this.dropDown
  77. });
  78. messages.id = dijit.getUniqueId(this.editor.id);
  79. this._uniqueId = messages.id;
  80. this.dropDown.set('content', dropDown.title +
  81. "<div style='border-bottom: 1px black solid;padding-bottom:2pt;margin-bottom:4pt'></div>" +
  82. dojo.string.substitute(this._template, messages));
  83. dropDown.startup();
  84. this._anchorInput = dijit.byId(this._uniqueId + "_anchorInput");
  85. this._textInput = dijit.byId(this._uniqueId + "_textInput");
  86. this._setButton = dijit.byId(this._uniqueId + "_setButton");
  87. this.connect(dijit.byId(this._uniqueId + "_cancelButton"), "onClick", function(){
  88. this.dropDown.onCancel();
  89. });
  90. if(this._anchorInput){
  91. this.connect(this._anchorInput, "onChange", "_checkInput");
  92. }
  93. if(this._textInput){
  94. this.connect(this._anchorInput, "onChange", "_checkInput");
  95. }
  96. //Register some filters to handle setting/removing the class tags on anchors.
  97. this.editor.contentDomPreFilters.push(dojo.hitch(this, this._preDomFilter));
  98. this.editor.contentDomPostFilters.push(dojo.hitch(this, this._postDomFilter));
  99. this._setup();
  100. },
  101. updateState: function(){
  102. // summary:
  103. // Over-ride for button state control for disabled to work.
  104. this.button.set("disabled", this.get("disabled"));
  105. },
  106. setEditor: function(editor){
  107. // summary:
  108. // Over-ride for the setting of the editor.
  109. // editor: Object
  110. // The editor to configure for this plugin to use.
  111. this.editor = editor;
  112. this._initButton();
  113. },
  114. _checkInput: function(){
  115. // summary:
  116. // Function to check the input to the dialog is valid
  117. // and enable/disable set button
  118. // tags:
  119. // private
  120. var disable = true;
  121. if(this._anchorInput.isValid()){
  122. disable = false;
  123. }
  124. this._setButton.set("disabled", disable);
  125. },
  126. _setup: function(){
  127. // summary:
  128. // Over-ridable function that connects tag specific events.
  129. this.editor.onLoadDeferred.addCallback(dojo.hitch(this, function(){
  130. this.connect(this.editor.editNode, "ondblclick", this._onDblClick);
  131. setTimeout(dojo.hitch(this, function() {
  132. this._applyStyles();
  133. }), 100);
  134. }));
  135. },
  136. getAnchorStyle: function(){
  137. // summary:
  138. // Over-ridable function for getting the style to apply to the anchor.
  139. // The default is a dashed border with an anchor symbol.
  140. // tags:
  141. // public
  142. var style = "@media screen {\n" +
  143. "\t.dijitEditorPluginInsertAnchorStyle {\n" +
  144. "\t\tbackground-image: url({MODURL}/images/anchor.gif);\n" +
  145. "\t\tbackground-repeat: no-repeat;\n" +
  146. "\t\tbackground-position: top left;\n" +
  147. "\t\tborder-width: 1px;\n" +
  148. "\t\tborder-style: dashed;\n" +
  149. "\t\tborder-color: #D0D0D0;\n" +
  150. "\t\tpadding-left: 20px;\n" +
  151. "\t}\n" +
  152. "}\n";
  153. //Finally associate in the image locations based off the module url.
  154. var modurl = dojo.moduleUrl(dojox._scopeName, "editor/plugins/resources").toString();
  155. if(!(modurl.match(/^https?:\/\//i)) &&
  156. !(modurl.match(/^file:\/\//i))){
  157. // We have to root it to the page location on webkit for some nutball reason.
  158. // Probably has to do with how iframe was loaded.
  159. var bUrl;
  160. if(modurl.charAt(0) === "/"){
  161. //Absolute path on the server, so lets handle...
  162. var proto = dojo.doc.location.protocol;
  163. var hostn = dojo.doc.location.host;
  164. bUrl = proto + "//" + hostn;
  165. }else{
  166. bUrl = this._calcBaseUrl(dojo.global.location.href);
  167. }
  168. if(bUrl[bUrl.length - 1] !== "/" && modurl.charAt(0) !== "/"){
  169. bUrl += "/";
  170. }
  171. modurl = bUrl + modurl;
  172. }
  173. return style.replace(/\{MODURL\}/gi, modurl);
  174. },
  175. _applyStyles: function(){
  176. // summary:
  177. // Function to apply a style to inserted anchor tags so that
  178. // they are obviously anchors.
  179. if(!this._styled){
  180. try{
  181. //Attempt to inject our specialized style rules for doing this.
  182. this._styled = true;
  183. var doc = this.editor.document;
  184. var style = this.getAnchorStyle();
  185. if(!dojo.isIE){
  186. var sNode = doc.createElement("style");
  187. sNode.appendChild(doc.createTextNode(style));
  188. doc.getElementsByTagName("head")[0].appendChild(sNode);
  189. }else{
  190. var ss = doc.createStyleSheet("");
  191. ss.cssText = style;
  192. }
  193. }catch(e){ /* Squelch */ }
  194. }
  195. },
  196. _calcBaseUrl: function(fullUrl) {
  197. // summary:
  198. // Internal function used to figure out the full root url (no relatives)
  199. // for loading images in the styles in the iframe.
  200. // fullUrl: String
  201. // The full url to tear down to the base.
  202. // tags:
  203. // private
  204. var baseUrl = null;
  205. if (fullUrl !== null) {
  206. // Check to see if we need to strip off any query parameters from the Url.
  207. var index = fullUrl.indexOf("?");
  208. if (index != -1) {
  209. fullUrl = fullUrl.substring(0,index);
  210. }
  211. // Now we need to trim if necessary. If it ends in /, then we don't
  212. // have a filename to trim off so we can return.
  213. index = fullUrl.lastIndexOf("/");
  214. if (index > 0 && index < fullUrl.length) {
  215. baseUrl = fullUrl.substring(0,index);
  216. }else{
  217. baseUrl = fullUrl;
  218. }
  219. }
  220. return baseUrl; //String
  221. },
  222. _checkValues: function(args){
  223. // summary:
  224. // Function to check the values in args and 'fix' them up as needed.
  225. // args: Object
  226. // Content being set.
  227. // tags:
  228. // protected
  229. if(args){
  230. if(args.anchorInput){
  231. args.anchorInput = args.anchorInput.replace(/"/g, "&quot;");
  232. }
  233. if(!args.textInput){
  234. // WebKit doesn't work with double-click select unless there's
  235. // a space in the anchor text, so put a in the case of
  236. // empty desc.
  237. args.textInput = "&nbsp;";
  238. }
  239. }
  240. return args;
  241. },
  242. setValue: function(args){
  243. // summary:
  244. // Callback from the dialog when user presses "set" button.
  245. // tags:
  246. // private
  247. this._onCloseDialog();
  248. if(!this.editor.window.getSelection){
  249. // IE check without using user agent string.
  250. var sel = dijit.range.getSelection(this.editor.window);
  251. var range = sel.getRangeAt(0);
  252. var a = range.endContainer;
  253. if(a.nodeType === 3){
  254. // Text node, may be the link contents, so check parent.
  255. // This plugin doesn't really support nested HTML elements
  256. // in the link, it assumes all link content is text.
  257. a = a.parentNode;
  258. }
  259. if(a && (a.nodeName && a.nodeName.toLowerCase() !== "a")){
  260. // Stll nothing, one last thing to try on IE, as it might be 'img'
  261. // and thus considered a control.
  262. a = dojo.withGlobal(this.editor.window,
  263. "getSelectedElement", dijit._editor.selection, ["a"]);
  264. }
  265. if(a && (a.nodeName && a.nodeName.toLowerCase() === "a")){
  266. // Okay, we do have a match. IE, for some reason, sometimes pastes before
  267. // instead of removing the targetted paste-over element, so we unlink the
  268. // old one first. If we do not the <a> tag remains, but it has no content,
  269. // so isn't readily visible (but is wrong for the action).
  270. if(this.editor.queryCommandEnabled("unlink")){
  271. // Select all the link childent, then unlink. The following insert will
  272. // then replace the selected text.
  273. dojo.withGlobal(this.editor.window,
  274. "selectElementChildren", dijit._editor.selection, [a]);
  275. this.editor.execCommand("unlink");
  276. }
  277. }
  278. }
  279. // make sure values are properly escaped, etc.
  280. args = this._checkValues(args);
  281. this.editor.execCommand('inserthtml',
  282. dojo.string.substitute(this.htmlTemplate, args));
  283. },
  284. _onCloseDialog: function(){
  285. // summary:
  286. // Handler for close event on the dialog
  287. this.editor.focus();
  288. },
  289. _getCurrentValues: function(a){
  290. // summary:
  291. // Over-ride for getting the values to set in the dropdown.
  292. // a:
  293. // The anchor/link to process for data for the dropdown.
  294. // tags:
  295. // protected
  296. var anchor, text;
  297. if(a && a.tagName.toLowerCase() === "a" && dojo.attr(a, "name")){
  298. anchor = dojo.attr(a, "name");
  299. text = a.textContent || a.innerText;
  300. dojo.withGlobal(this.editor.window, "selectElement", dijit._editor.selection, [a, true]);
  301. }else{
  302. text = dojo.withGlobal(this.editor.window, dijit._editor.selection.getSelectedText);
  303. }
  304. return {anchorInput: anchor || '', textInput: text || ''}; //Object;
  305. },
  306. _onOpenDialog: function(){
  307. // summary:
  308. // Handler for when the dialog is opened.
  309. // If the caret is currently in a URL then populate the URL's info into the dialog.
  310. var a;
  311. if(!this.editor.window.getSelection){
  312. // IE is difficult to select the element in, using the range unified
  313. // API seems to work reasonably well.
  314. var sel = dijit.range.getSelection(this.editor.window);
  315. var range = sel.getRangeAt(0);
  316. a = range.endContainer;
  317. if(a.nodeType === 3){
  318. // Text node, may be the link contents, so check parent.
  319. // This plugin doesn't really support nested HTML elements
  320. // in the link, it assumes all link content is text.
  321. a = a.parentNode;
  322. }
  323. if(a && (a.nodeName && a.nodeName.toLowerCase() !== "a")){
  324. // Stll nothing, one last thing to try on IE, as it might be 'img'
  325. // and thus considered a control.
  326. a = dojo.withGlobal(this.editor.window,
  327. "getSelectedElement", dijit._editor.selection, ["a"]);
  328. }
  329. }else{
  330. a = dojo.withGlobal(this.editor.window,
  331. "getAncestorElement", dijit._editor.selection, ["a"]);
  332. }
  333. this.dropDown.reset();
  334. this._setButton.set("disabled", true);
  335. this.dropDown.set("value", this._getCurrentValues(a));
  336. },
  337. _onDblClick: function(e){
  338. // summary:
  339. // Function to define a behavior on double clicks on the element
  340. // type this dialog edits to select it and pop up the editor
  341. // dialog.
  342. // e: Object
  343. // The double-click event.
  344. // tags:
  345. // protected.
  346. if(e && e.target){
  347. var t = e.target;
  348. var tg = t.tagName? t.tagName.toLowerCase() : "";
  349. if(tg === "a" && dojo.attr(t, "name")){
  350. this.editor.onDisplayChanged();
  351. dojo.withGlobal(this.editor.window,
  352. "selectElement",
  353. dijit._editor.selection, [t]);
  354. setTimeout(dojo.hitch(this, function(){
  355. // Focus shift outside the event handler.
  356. // IE doesn't like focus changes in event handles.
  357. this.button.set("disabled", false);
  358. this.button.openDropDown();
  359. }), 10);
  360. }
  361. }
  362. },
  363. _preDomFilter: function(node){
  364. // summary:
  365. // A filter to identify the 'a' tags and if they're anchors,
  366. // apply the right style to them.
  367. // node:
  368. // The node to search from.
  369. // tags:
  370. // private
  371. var ed = this.editor;
  372. dojo.withGlobal(ed.window, function(){
  373. dojo.query("a", ed.editNode).forEach(function(a){
  374. if(dojo.attr(a, "name") && !dojo.attr(a, "href")){
  375. if(!dojo.hasClass(a,"dijitEditorPluginInsertAnchorStyle")){
  376. dojo.addClass(a, "dijitEditorPluginInsertAnchorStyle");
  377. }
  378. }
  379. });
  380. });
  381. },
  382. _postDomFilter: function(node){
  383. // summary:
  384. // A filter to identify the 'a' tags and if they're anchors,
  385. // remove the class style that shows up in the editor from
  386. // them.
  387. // node:
  388. // The node to search from.
  389. // tags:
  390. // private
  391. var ed = this.editor;
  392. dojo.withGlobal(ed.window, function(){
  393. dojo.query("a", node).forEach(function(a){
  394. if(dojo.attr(a, "name") && !dojo.attr(a, "href")){
  395. if(dojo.hasClass(a,"dijitEditorPluginInsertAnchorStyle")){
  396. dojo.removeClass(a, "dijitEditorPluginInsertAnchorStyle");
  397. }
  398. }
  399. });
  400. });
  401. return node;
  402. }
  403. });
  404. // Register this plugin.
  405. dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){
  406. if(o.plugin){ return; }
  407. var name = o.args.name;
  408. if(name) { name = name.toLowerCase(); }
  409. if(name === "insertanchor"){
  410. o.plugin = new dojox.editor.plugins.InsertAnchor();
  411. }
  412. });
  413. }