FindReplace.js 26 KB

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