FindReplace.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  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.FindReplace"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.editor.plugins.FindReplace"] = true;
  8. dojo.provide("dojox.editor.plugins.FindReplace");
  9. dojo.require("dojo.string");
  10. dojo.require("dijit.TooltipDialog");
  11. dojo.require("dijit.Toolbar");
  12. dojo.require("dijit.form.CheckBox");
  13. dojo.require("dijit.form.TextBox");
  14. dojo.require("dijit._editor._Plugin");
  15. dojo.require("dijit.form.Button");
  16. dojo.require("dojox.editor.plugins.ToolbarLineBreak");
  17. dojo.require("dojo.i18n");
  18. dojo.requireLocalization("dojox.editor.plugins", "FindReplace", 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");
  19. dojo.experimental("dojox.editor.plugins.FindReplace");
  20. dojo.declare("dojox.editor.plugins._FindReplaceCloseBox", [dijit._Widget, dijit._Templated], {
  21. // summary:
  22. // Base class for widgets that contains a button labeled X
  23. // to close the tool bar.
  24. btnId: "",
  25. widget: null,
  26. widgetsInTemplate: true,
  27. templateString:
  28. "<span style='float: right' class='dijitInline' tabindex='-1'>" +
  29. "<button class='dijit dijitReset dijitInline' " +
  30. "id='${btnId}' dojoAttachPoint='button' dojoType='dijit.form.Button' tabindex='-1' iconClass='dijitEditorIconsFindReplaceClose' showLabel='false'>X</button>" +
  31. "</span>",
  32. postMixInProperties: function(){
  33. // Set some substitution variables used in the template
  34. this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_"));
  35. this.btnId = this.id + "_close";
  36. this.inherited(arguments);
  37. },
  38. startup: function(){
  39. this.connect(this.button, "onClick", "onClick");
  40. },
  41. onClick: function(){
  42. }
  43. });
  44. dojo.declare("dojox.editor.plugins._FindReplaceTextBox",
  45. [dijit._Widget, dijit._Templated],{
  46. // summary:
  47. // Base class for widgets that contains a label (like "Font:")
  48. // and a TextBox to pick a value.
  49. // Used as Toolbar entry.
  50. // textId: [public] String
  51. // The id of the enhanced textbox
  52. textId: "",
  53. // label: [public] String
  54. // The label of the enhanced textbox
  55. label: "",
  56. // tooltip: [public] String
  57. // The tooltip of the enhanced textbox when the mouse is hovering on it
  58. toolTip: "",
  59. widget: null,
  60. widgetsInTemplate: true,
  61. templateString:
  62. "<span style='white-space: nowrap' class='dijit dijitReset dijitInline dijitEditorFindReplaceTextBox' " +
  63. "title='${tooltip}' tabindex='-1'>" +
  64. "<label class='dijitLeft dijitInline' for='${textId}' tabindex='-1'>${label}</label>" +
  65. "<input dojoType='dijit.form.TextBox' required='false' intermediateChanges='true' class='focusTextBox'" +
  66. "tabIndex='0' id='${textId}' dojoAttachPoint='textBox, focusNode' value='' dojoAttachEvent='onKeyPress: _onKeyPress'/>" +
  67. "</span>",
  68. postMixInProperties: function(){
  69. // Set some substitution variables used in the template
  70. this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_"));
  71. this.textId = this.id + "_text";
  72. this.inherited(arguments);
  73. },
  74. postCreate: function(){
  75. this.textBox.set("value", "");
  76. this.disabled = this.textBox.get("disabled");
  77. this.connect(this.textBox, "onChange", "onChange");
  78. },
  79. _setValueAttr: function(/*String*/ value){
  80. //If the value is not a permitted value, just set empty string to prevent showing the warning icon
  81. this.value = value;
  82. this.textBox.set("value", value);
  83. },
  84. focus: function(){
  85. this.textBox.focus();
  86. },
  87. _setDisabledAttr: function(/*Boolean*/ value){
  88. // summary:
  89. // Over-ride for the textbox's 'disabled' attribute so that it can be
  90. // disabled programmatically.
  91. // value:
  92. // The boolean value to indicate if the textbox should be disabled or not
  93. // tags:
  94. // private
  95. this.disabled = value;
  96. this.textBox.set("disabled", value);
  97. },
  98. onChange: function(/*String*/ val){
  99. // summary:
  100. // Stub function for change events on the box.
  101. // tags:
  102. // public
  103. this.value= val;
  104. },
  105. _onKeyPress: function(/*Event*/ evt){
  106. // summary:
  107. // Handle the arrow key events
  108. // evt:
  109. // Event object passed to this handler
  110. // tags:
  111. // private
  112. var start = 0;
  113. var end = 0;
  114. // If CTRL, ALT or SHIFT is not held on
  115. if(evt.target && !evt.ctrlKey && !evt.altKey && !evt.shiftKey){
  116. if(evt.keyCode == dojo.keys.LEFT_ARROW){
  117. start = evt.target.selectionStart;
  118. end = evt.target.selectionEnd;
  119. if(start < end){
  120. dijit.selectInputText(evt.target, start, start);
  121. dojo.stopEvent(evt);
  122. }
  123. }else if(evt.keyCode == dojo.keys.RIGHT_ARROW){
  124. start = evt.target.selectionStart;
  125. end = evt.target.selectionEnd;
  126. if(start < end){
  127. dijit.selectInputText(evt.target, end, end);
  128. dojo.stopEvent(evt);
  129. }
  130. }
  131. }
  132. }
  133. });
  134. dojo.declare("dojox.editor.plugins._FindReplaceCheckBox",
  135. [dijit._Widget, dijit._Templated],{
  136. // summary:
  137. // Base class for widgets that contains a label (like "Match case: ")
  138. // and a checkbox to indicate if it is checked or not.
  139. // Used as Toolbar entry.
  140. // checkId: [public] String
  141. // The id of the enhanced checkbox
  142. checkId: "",
  143. // label: [public] String
  144. // The label of the enhanced checkbox
  145. label: "",
  146. // tooltip: [public] String
  147. // The tooltip of the enhanced checkbox when the mouse is hovering it
  148. tooltip: "",
  149. widget: null,
  150. widgetsInTemplate: true,
  151. templateString:
  152. "<span style='white-space: nowrap' tabindex='-1' " +
  153. "class='dijit dijitReset dijitInline dijitEditorFindReplaceCheckBox' title='${tooltip}' >" +
  154. "<input dojoType='dijit.form.CheckBox' required=false " +
  155. "tabIndex='0' id='${checkId}' dojoAttachPoint='checkBox, focusNode' value=''/>" +
  156. "<label tabindex='-1' class='dijitLeft dijitInline' for='${checkId}'>${label}</label>" +
  157. "</span>",
  158. postMixInProperties: function(){
  159. // Set some substitution variables used in the template
  160. this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_"));
  161. this.checkId = this.id + "_check";
  162. this.inherited(arguments);
  163. },
  164. postCreate: function(){
  165. this.checkBox.set("checked", false);
  166. this.disabled = this.checkBox.get("disabled");
  167. this.checkBox.isFocusable = function(){ return false; };
  168. },
  169. _setValueAttr: function(/*Boolean*/ value){
  170. // summary:
  171. // Passthrough for checkbox.
  172. // tags:
  173. // private
  174. this.checkBox.set('value', value);
  175. },
  176. _getValueAttr: function(){
  177. // summary:
  178. // Passthrough for checkbox.
  179. // tags:
  180. // private
  181. return this.checkBox.get('value');
  182. },
  183. focus: function(){
  184. // summary:
  185. // Handle the focus event when this widget gets focused
  186. // tags:
  187. // private
  188. this.checkBox.focus();
  189. },
  190. _setDisabledAttr: function(/*Boolean*/ value){
  191. // summary:
  192. // Over-ride for the button's 'disabled' attribute so that it can be
  193. // disabled programmatically.
  194. // value:
  195. // The flag that indicates if the checkbox is disabled or not.
  196. // tags:
  197. // private
  198. this.disabled = value;
  199. this.checkBox.set("disabled", value);
  200. }
  201. });
  202. dojo.declare("dojox.editor.plugins._FindReplaceToolbar", dijit.Toolbar, {
  203. // summary:
  204. // A toolbar that derived from dijit.Toolbar, which
  205. // eliminates some unnecessary event response such as LEFT_ARROW pressing
  206. // and click bubbling.
  207. postCreate: function(){
  208. this.connectKeyNavHandlers([], []); // Prevent arrow key navigation
  209. this.connect(this.containerNode, "onclick", "_onToolbarEvent");
  210. this.connect(this.containerNode, "onkeydown", "_onToolbarEvent");
  211. dojo.addClass(this.domNode, "dijitToolbar");
  212. },
  213. addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){
  214. // summary:
  215. // Add a child to our _Container and prevent the default
  216. // arrow key navigation function. This function may bring in
  217. // side effect
  218. dijit._KeyNavContainer.superclass.addChild.apply(this, arguments);
  219. },
  220. _onToolbarEvent: function(/*Event*/ evt){
  221. // Editor may have special treatment to some events, so stop the bubbling.
  222. // evt:
  223. // The Event object
  224. // tages:
  225. // private
  226. evt.stopPropagation();
  227. }
  228. });
  229. dojo.declare("dojox.editor.plugins.FindReplace",[dijit._editor._Plugin],{
  230. // summary:
  231. // This plugin provides a Find/Replace cabability for the editor.
  232. // Note that this plugin is NOT supported on Opera currently, as opera
  233. // does not implement a window.find or equiv function.
  234. // buttonClass: [protected]
  235. // Define the class of button the editor uses.
  236. buttonClass: dijit.form.ToggleButton,
  237. // iconClassPrefix: [const] String
  238. // The CSS class name for the button node is formed from `iconClassPrefix` and `command`
  239. iconClassPrefix: "dijitEditorIconsFindReplace",
  240. // editor: [protected]
  241. // The editor this plugin belongs to
  242. editor: null,
  243. // button: [protected]
  244. // The toggle button
  245. button: null,
  246. // _frToolbar: [private]
  247. // The toolbar that contain all the entries and buttons
  248. _frToolbar: null,
  249. // _closeBox: [private]
  250. // The close button of the F/R toolbar
  251. _closeBox: null,
  252. // _findField: [private]
  253. // The Find field of the F/R toolbar
  254. _findField: null,
  255. // _replaceField: [private]
  256. // The Replace field of the F/R toolbar
  257. _replaceField: null,
  258. // _findButton: [private]
  259. // The Find button of the F/R toolbar
  260. _findButton: null,
  261. // _replaceButton: [private]
  262. // The Replace button of the F/R toolbar
  263. _replaceButton: null,
  264. // _replaceAllButton: [private]
  265. // The ReplaceAll button of the F/R toolbar
  266. _replaceAllButton: null,
  267. // _caseSensitive: [private]
  268. // The case sensitive checkbox
  269. _caseSensitive: null,
  270. // _backwards: [private]
  271. // The backwards checkbox
  272. _backwards: null,
  273. // _promDialog: [private]
  274. // The prompt message box that shows the user some messages
  275. // such as the end of a search, the end of a replacement, etc.
  276. _promDialog: null,
  277. _promDialogTimeout: null,
  278. // _strings: [private]
  279. // The array that contains globalized strings
  280. _strings: null,
  281. _initButton: function(){
  282. // summary:
  283. // Over-ride for creation of the resize button.
  284. this._strings = dojo.i18n.getLocalization("dojox.editor.plugins", "FindReplace");
  285. this.button = new dijit.form.ToggleButton({
  286. label: this._strings["findReplace"],
  287. showLabel: false,
  288. iconClass: this.iconClassPrefix + " dijitEditorIconFindString",
  289. tabIndex: "-1",
  290. onChange: dojo.hitch(this, "_toggleFindReplace")
  291. });
  292. if(dojo.isOpera){
  293. // Not currently supported on Opera!
  294. this.button.set("disabled", true);
  295. }
  296. //Link up so that if the toggle is disabled, then the view of Find/Replace is closed.
  297. this.connect(this.button, "set", dojo.hitch(this, function(attr, val){
  298. if(attr === "disabled"){
  299. this._toggleFindReplace((!val && this._displayed), true, true);
  300. }
  301. }));
  302. },
  303. setEditor: function(editor){
  304. // summary:
  305. // This is a callback handler that set a reference to the editor this plugin
  306. // hosts in
  307. this.editor = editor;
  308. this._initButton();
  309. },
  310. toggle: function(){
  311. // summary:
  312. // Function to allow programmatic toggling of the find toolbar.
  313. // tags:
  314. // public
  315. this.button.set("checked", !this.button.get("checked"));
  316. },
  317. _toggleFindReplace: function(/*Boolean*/ show, /*Boolean?*/ ignoreState, /*Boolean?*/ buttonDisabled){
  318. // summary:
  319. // Function to toggle whether or not find/replace is displayed.
  320. // show:
  321. // Indicate if the toolbar is shown or not
  322. // ignoreState:
  323. // Indicate if the status should be ignored or not
  324. // blurEditor:
  325. // Indicate if the focus should be removed from the editor or not
  326. // tags:
  327. // private
  328. var size = dojo.marginBox(this.editor.domNode);
  329. if(show && !dojo.isOpera){
  330. dojo.style(this._frToolbar.domNode, "display", "block");
  331. // Auto populate the Find field
  332. this._populateFindField();
  333. if(!ignoreState){
  334. this._displayed = true;
  335. }
  336. }else{
  337. dojo.style(this._frToolbar.domNode, "display", "none");
  338. if(!ignoreState){
  339. this._displayed = false;
  340. }
  341. // If the toggle button is disabled, it is most likely that
  342. // another plugin such as ViewSource disables it.
  343. // So we do not need to focus the text area of the editor to
  344. // prevent the editor from an invalid status.
  345. // Please refer to dijit._editor.plugins.ViewSource for more details.
  346. if(!buttonDisabled){
  347. this.editor.focus();
  348. }
  349. }
  350. // Resize the editor.
  351. this.editor.resize({h: size.h});
  352. },
  353. _populateFindField: function(){
  354. // summary:
  355. // Populate the Find field with selected text when dialog initially displayed.
  356. // Auto-select text in Find field after it is populated.
  357. // If nothing selected, restore previous entry from the same session.
  358. // tags:
  359. // private
  360. var ed = this.editor;
  361. var win = ed.window;
  362. var selectedTxt = dojo.withGlobal(ed.window, "getSelectedText", dijit._editor.selection, [null]);
  363. if(this._findField && this._findField.textBox){
  364. if(selectedTxt){
  365. this._findField.textBox.set("value", selectedTxt);
  366. }
  367. this._findField.textBox.focus();
  368. dijit.selectInputText(this._findField.textBox.focusNode);
  369. }
  370. },
  371. setToolbar: function(/*dijit.Toolbar*/ toolbar){
  372. // summary:
  373. // Over-ride so that find/replace toolbar is appended after the current toolbar.
  374. // toolbar:
  375. // The current toolbar of the editor
  376. // tags:
  377. // public
  378. this.inherited(arguments);
  379. if(!dojo.isOpera){
  380. var _tb = this._frToolbar = new dojox.editor.plugins._FindReplaceToolbar();
  381. dojo.style(_tb.domNode, "display", "none");
  382. dojo.place(_tb.domNode, toolbar.domNode, "after");
  383. _tb.startup();
  384. // IE6 will put the close box in a new line when its style is "float: right".
  385. // So place the close box ahead of the other fields, which makes it align with
  386. // the other components.
  387. this._closeBox = new dojox.editor.plugins._FindReplaceCloseBox();
  388. _tb.addChild(this._closeBox);
  389. // Define the search/replace fields.
  390. this._findField = new dojox.editor.plugins._FindReplaceTextBox(
  391. {label: this._strings["findLabel"], tooltip: this._strings["findTooltip"]});
  392. _tb.addChild(this._findField);
  393. this._replaceField = new dojox.editor.plugins._FindReplaceTextBox(
  394. {label: this._strings["replaceLabel"], tooltip: this._strings["replaceTooltip"]});
  395. _tb.addChild(this._replaceField);
  396. // Define the Find/Replace/ReplaceAll buttons.
  397. _tb.addChild(new dojox.editor.plugins.ToolbarLineBreak());
  398. this._findButton = new dijit.form.Button({label: this._strings["findButton"], showLabel: true,
  399. iconClass: this.iconClassPrefix + " dijitEditorIconFind"});
  400. this._findButton.titleNode.title = this._strings["findButtonTooltip"];
  401. _tb.addChild(this._findButton);
  402. this._replaceButton = new dijit.form.Button({label: this._strings["replaceButton"], showLabel: true,
  403. iconClass: this.iconClassPrefix + " dijitEditorIconReplace"});
  404. this._replaceButton.titleNode.title = this._strings["replaceButtonTooltip"];
  405. _tb.addChild(this._replaceButton);
  406. this._replaceAllButton = new dijit.form.Button({label: this._strings["replaceAllButton"], showLabel: true,
  407. iconClass: this.iconClassPrefix + " dijitEditorIconReplaceAll"});
  408. this._replaceAllButton.titleNode.title = this._strings["replaceAllButtonTooltip"];
  409. _tb.addChild(this._replaceAllButton);
  410. // Define the option checkboxes.
  411. this._caseSensitive = new dojox.editor.plugins._FindReplaceCheckBox(
  412. {label: this._strings["matchCase"], tooltip: this._strings["matchCaseTooltip"]});
  413. _tb.addChild(this._caseSensitive);
  414. this._backwards = new dojox.editor.plugins._FindReplaceCheckBox(
  415. {label: this._strings["backwards"], tooltip: this._strings["backwardsTooltip"]});
  416. _tb.addChild(this._backwards);
  417. // Set initial states, buttons should be disabled unless content is
  418. // present in the fields.
  419. this._findButton.set("disabled", true);
  420. this._replaceButton.set("disabled", true);
  421. this._replaceAllButton.set("disabled", true);
  422. // Connect the event to the status of the items
  423. this.connect(this._findField, "onChange", "_checkButtons");
  424. this.connect(this._findField, "onKeyDown", "_onFindKeyDown");
  425. this.connect(this._replaceField, "onKeyDown", "_onReplaceKeyDown");
  426. // Connect up the actual search events.
  427. this.connect(this._findButton, "onClick", "_find");
  428. this.connect(this._replaceButton, "onClick", "_replace");
  429. this.connect(this._replaceAllButton, "onClick", "_replaceAll");
  430. // Connect up the close event
  431. this.connect(this._closeBox, "onClick", "toggle");
  432. // Prompt for the message
  433. this._promDialog = new dijit.TooltipDialog();
  434. this._promDialog.startup();
  435. this._promDialog.set("content", "");
  436. }
  437. },
  438. _checkButtons: function(){
  439. // summary:
  440. // Ensure that all the buttons are in a correct status
  441. // when certain events are fired.
  442. var fText = this._findField.get("value");
  443. if(fText){
  444. // Only enable if find text is not empty or just blank/spaces.
  445. this._findButton.set("disabled", false);
  446. this._replaceButton.set("disabled", false);
  447. this._replaceAllButton.set("disabled", false);
  448. }else{
  449. this._findButton.set("disabled", true);
  450. this._replaceButton.set("disabled", true);
  451. this._replaceAllButton.set("disabled", true);
  452. }
  453. },
  454. _onFindKeyDown: function(evt){
  455. if(evt.keyCode == dojo.keys.ENTER){
  456. // Perform the default search action
  457. this._find();
  458. dojo.stopEvent(evt);
  459. }
  460. },
  461. _onReplaceKeyDown: function(evt){
  462. if(evt.keyCode == dojo.keys.ENTER){
  463. // Perform the default replace action
  464. if(!this._replace()) this._replace();
  465. dojo.stopEvent(evt);
  466. }
  467. },
  468. _find: function(/*Boolean?*/ showMessage){
  469. // summary:
  470. // This function invokes a find on the editor document with the noted options for
  471. // find.
  472. // showMessage:
  473. // Indicated whether the tooltip is shown or not when the search reaches the end
  474. // tags:
  475. // private.
  476. // returns:
  477. // Boolean indicating if the content was found or not.
  478. var txt = this._findField.get("value") || "";
  479. if(txt){
  480. var caseSensitive = this._caseSensitive.get("value");
  481. var backwards = this._backwards.get("value");
  482. var isFound = this._findText(txt, caseSensitive, backwards);
  483. if(!isFound && showMessage){
  484. this._promDialog.set("content", dojo.string.substitute(
  485. this._strings["eofDialogText"], {"0": this._strings["eofDialogTextFind"]}));
  486. dijit.popup.open({popup: this._promDialog, around: this._findButton.domNode});
  487. this._promDialogTimeout = setTimeout(dojo.hitch(this, function(){
  488. clearTimeout(this._promDialogTimeout);
  489. this._promDialogTimeout = null;
  490. dijit.popup.close(this._promDialog);
  491. }), 3000);
  492. setTimeout(dojo.hitch(this, function(){
  493. this.editor.focus();
  494. }), 0);
  495. }
  496. return isFound;
  497. }
  498. return false;
  499. },
  500. _replace: function(/*Boolean?*/ showMessage){
  501. // summary:
  502. // This function invokes a replace on the editor document with the noted options for replace
  503. // showMessage:
  504. // Indicate if the prompt message is shown or not when the replacement
  505. // reaches the end
  506. // tags:
  507. // private
  508. // returns:
  509. // Boolean indicating if the content was replaced or not.
  510. var isReplaced = false;
  511. var ed = this.editor;
  512. ed.focus();
  513. var txt = this._findField.get("value") || "";
  514. var repTxt = this._replaceField.get("value") || "";
  515. if(txt){
  516. var caseSensitive = this._caseSensitive.get("value");
  517. // Check if it is forced to be forwards or backwards
  518. var backwards = this._backwards.get("value");
  519. //Replace the current selected text if it matches the pattern
  520. var selected = dojo.withGlobal(ed.window, "getSelectedText", dijit._editor.selection, [null]);
  521. // Handle checking/replacing current selection. For some reason on Moz
  522. // leading whitespace is trimmed, so we have to trim it down on this check
  523. // or we don't always replace. Moz bug!
  524. if(dojo.isMoz){
  525. txt = dojo.trim(txt);
  526. selected = dojo.trim(selected);
  527. }
  528. var regExp = this._filterRegexp(txt, !caseSensitive);
  529. if(selected && regExp.test(selected)){
  530. ed.execCommand("inserthtml", repTxt);
  531. isReplaced = true;
  532. if(backwards){
  533. // Move to the beginning of the replaced text
  534. // to avoid the infinite recursive replace
  535. this._findText(repTxt, caseSensitive, backwards);
  536. dojo.withGlobal(ed.window, "collapse", dijit._editor.selection, [true]);
  537. }
  538. }
  539. if(!this._find(false) && showMessage){ // Find the next
  540. this._promDialog.set("content", dojo.string.substitute(
  541. this._strings["eofDialogText"], {"0": this._strings["eofDialogTextReplace"]}));
  542. dijit.popup.open({popup: this._promDialog, around: this._replaceButton.domNode});
  543. this._promDialogTimeout = setTimeout(dojo.hitch(this, function(){
  544. clearTimeout(this._promDialogTimeout);
  545. this._promDialogTimeout = null;
  546. dijit.popup.close(this._promDialog);
  547. }), 3000);
  548. setTimeout(dojo.hitch(this, function(){
  549. this.editor.focus();
  550. }), 0);
  551. }
  552. return isReplaced;
  553. }
  554. return null;
  555. },
  556. _replaceAll: function(/*Boolean?*/ showMessage){
  557. // summary:
  558. // This function replaces all the matched content on the editor document
  559. // with the noted options for replace
  560. // showMessage:
  561. // Indicate if the prompt message is shown or not when the action is done.
  562. // tags:
  563. // private
  564. var replaced = 0;
  565. var backwards = this._backwards.get("value");
  566. if(backwards){
  567. this.editor.placeCursorAtEnd();
  568. }else{
  569. this.editor.placeCursorAtStart();
  570. }
  571. // The _replace will return false if the current selection deos not match
  572. // the searched text. So try the first attempt so that the selection
  573. // is always the searched text if there is one that matches
  574. if(this._replace(false)) { replaced++; }
  575. // Do the replace via timeouts to avoid locking the browser up for a lot of replaces.
  576. var loopBody = dojo.hitch(this, function(){
  577. if(this._replace(false)){
  578. replaced++;
  579. setTimeout(loopBody, 10);
  580. }else{
  581. if(showMessage){
  582. this._promDialog.set("content", dojo.string.substitute(
  583. this._strings["replaceDialogText"], {"0": "" + replaced}));
  584. dijit.popup.open({
  585. popup: this._promDialog,
  586. around: this._replaceAllButton.domNode
  587. });
  588. this._promDialogTimeout = setTimeout(dojo.hitch(this, function(){
  589. clearTimeout(this._promDialogTimeout);
  590. this._promDialogTimeout = null;
  591. dijit.popup.close(this._promDialog);
  592. }), 3000);
  593. setTimeout(dojo.hitch(this, function(){
  594. this._findField.focus();
  595. this._findField.textBox.focusNode.select();
  596. }), 0);
  597. }
  598. }
  599. });
  600. loopBody();
  601. },
  602. _findText: function(/*String*/ txt, /*Boolean*/ caseSensitive, /*Boolean*/ backwards){
  603. // summary:
  604. // This function invokes a find with specific options
  605. // txt: String
  606. // The text to locate in the document.
  607. // caseSensitive: Boolean
  608. // Whether or ot to search case-sensitively.
  609. // backwards: Boolean
  610. // Whether or not to search backwards in the document.
  611. // tags:
  612. // private.
  613. // returns:
  614. // Boolean indicating if the content was found or not.
  615. var ed = this.editor;
  616. var win = ed.window;
  617. var found = false;
  618. if(txt){
  619. if(win.find){
  620. found = win.find(txt, caseSensitive, backwards, false, false, false, false);
  621. }else{
  622. var doc = ed.document;
  623. if(doc.selection){
  624. /* IE */
  625. // Focus to restore position/selection,
  626. // then shift to search from current position.
  627. this.editor.focus();
  628. var txtRg = doc.body.createTextRange();
  629. var curPos = doc.selection?doc.selection.createRange():null;
  630. if(curPos){
  631. if(backwards){
  632. txtRg.setEndPoint("EndToStart", curPos);
  633. }else{
  634. txtRg.setEndPoint("StartToEnd", curPos);
  635. }
  636. }
  637. var flags = caseSensitive?4:0;
  638. if(backwards){
  639. flags = flags | 1;
  640. }
  641. //flags = flags |
  642. found = txtRg.findText(txt,txtRg.text.length,flags);
  643. if(found){
  644. txtRg.select();
  645. }
  646. }
  647. }
  648. }
  649. return found;
  650. },
  651. _filterRegexp: function(/*String*/ pattern, /*Boolean*/ ignoreCase){
  652. // summary:
  653. // Helper function to convert a simple pattern to a regular expression for matching.
  654. // description:
  655. // Returns a regular expression object that conforms to the defined conversion rules.
  656. // For example:
  657. // ca* -> /^ca.*$/
  658. // *ca* -> /^.*ca.*$/
  659. // *c\*a* -> /^.*c\*a.*$/
  660. // *c\*a?* -> /^.*c\*a..*$/
  661. // and so on.
  662. //
  663. // pattern: string
  664. // A simple matching pattern to convert that follows basic rules:
  665. // * Means match anything, so ca* means match anything starting with ca
  666. // ? Means match single character. So, b?b will match to bob and bab, and so on.
  667. // \ is an escape character. So for example, \* means do not treat * as a match, but literal character *.
  668. // To use a \ as a character in the string, it must be escaped. So in the pattern it should be
  669. // represented by \\ to be treated as an ordinary \ character instead of an escape.
  670. //
  671. // ignoreCase:
  672. // An optional flag to indicate if the pattern matching should be treated as case-sensitive or not when comparing
  673. // By default, it is assumed case sensitive.
  674. // tags:
  675. // private
  676. var rxp = "";
  677. var c = null;
  678. for(var i = 0; i < pattern.length; i++){
  679. c = pattern.charAt(i);
  680. switch(c){
  681. case '\\':
  682. rxp += c;
  683. i++;
  684. rxp += pattern.charAt(i);
  685. break;
  686. case '$':
  687. case '^':
  688. case '/':
  689. case '+':
  690. case '.':
  691. case '|':
  692. case '(':
  693. case ')':
  694. case '{':
  695. case '}':
  696. case '[':
  697. case ']':
  698. rxp += "\\"; //fallthrough
  699. default:
  700. rxp += c;
  701. }
  702. }
  703. rxp = "^" + rxp + "$";
  704. if(ignoreCase){
  705. return new RegExp(rxp,"mi"); //RegExp
  706. }else{
  707. return new RegExp(rxp,"m"); //RegExp
  708. }
  709. },
  710. updateState: function(){
  711. // summary:
  712. // Over-ride for button state control for disabled to work.
  713. this.button.set("disabled", this.get("disabled"));
  714. },
  715. destroy: function(){
  716. // summary:
  717. // Cleanup of our custom toolbar.
  718. this.inherited(arguments);
  719. if(this._promDialogTimeout){
  720. clearTimeout(this._promDialogTimeout);
  721. this._promDialogTimeout = null;
  722. dijit.popup.close(this._promDialog);
  723. }
  724. if(this._frToolbar){
  725. this._frToolbar.destroyRecursive();
  726. this._frToolbar = null;
  727. }
  728. if(this._promDialog){
  729. this._promDialog.destroyRecursive();
  730. this._promDialog = null;
  731. }
  732. }
  733. });
  734. // Register this plugin.
  735. dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){
  736. if(o.plugin){ return; }
  737. var name = o.args.name.toLowerCase();
  738. if(name === "findreplace"){
  739. o.plugin = new dojox.editor.plugins.FindReplace({});
  740. }
  741. });
  742. }