Annotation of loncom/html/adm/spellcheck/js/jquery.spellchecker.js, revision 1.2
1.1 foxr 1: /*
2: * jquery.spellchecker.js - a simple jQuery Spell Checker
3: * Copyright (c) 2009 Richard Willis
4: * MIT license : http://www.opensource.org/licenses/mit-license.php
5: * Project : http://jquery-spellchecker.googlecode.com
6: * Contact : willis.rh@gmail.com
7: */
8:
9: (function($){
10:
11: $.fn.extend({
12:
13: spellchecker : function(options, callback){
14: return this.each(function(){
15: var obj = $(this).data('spellchecker');
16: if (obj && String === options.constructor && obj[options]) {
17: obj[options](callback);
18: } else if (obj) {
19: obj.init();
20: } else {
21: $(this).data('spellchecker', new SpellChecker(this, (Object === options.constructor ? options : null)));
22: (String === options.constructor) && $(this).data('spellchecker')[options](callback);
23: }
24: });
25: }
26: });
27:
28: var SpellChecker = function(domObj, options) {
29: this.options = $.extend({
30: url: 'checkspelling.php', // default spellcheck url
31: lang: 'en', // default language
32: engine: 'pspell', // pspell or google
33: addToDictionary: false, // display option to add word to dictionary (pspell only)
34: wordlist: {
35: action: 'after', // which jquery dom insert action
36: element: domObj // which object to apply above method
37: },
38: suggestBoxPosition: 'below', // position of suggest box; above or below the highlighted word
39: innerDocument: false // if you want the badwords highlighted in the html then set to true
40: }, options || {});
41: this.$domObj = $(domObj);
42: this.elements = {};
43: this.init();
44: };
45:
46: SpellChecker.prototype = {
47:
48: init : function(){
49: var self = this;
50: this.createElements();
51: this.$domObj.addClass('spellcheck-container');
52: // hide the suggest box on document click
53: $(document).bind('click', function(e){
54: (!$(e.target).hasClass('spellcheck-word-highlight') &&
55: !$(e.target).parents().filter('.spellcheck-suggestbox').length) &&
56: self.hideBox();
57: });
58: },
1.2 ! foxr 59: option : function(newopts) {
! 60: for (optname in newopts) {
! 61: this.options[optname] = newopts[optname];
! 62: }
! 63: return this;
! 64: },
1.1 foxr 65:
66: // checks a chunk of text for bad words, then either shows the words below the original element (if texarea) or highlights the bad words
67: check : function(callback){
68:
69: var self = this, node = this.$domObj.get(0).nodeName,
70: tagExp = '<[^>]+>',
71: puncExp = '^[^a-zA-Z\\u00A1-\\uFFFF]|[^a-zA-Z\\u00A1-\\uFFFF]+[^a-zA-Z\\u00A1-\\uFFFF]|[^a-zA-Z\\u00A1-\\uFFFF]$|\\n|\\t|\\s{2,}';
72:
73: if (node == 'TEXTAREA' || node == 'INPUT') {
74: this.type = 'textarea';
75: var text = $.trim(
76: this.$domObj.val()
77: .replace(new RegExp(tagExp, 'g'), '') // strip html tags
78: .replace(new RegExp(puncExp, 'g'), ' ') // strip punctuation
79: );
80: } else {
81: this.type = 'html';
82: var text = $.trim(
83: this.$domObj.text()
84: .replace(new RegExp(puncExp, 'g'), " ") // strip punctuation
85: );
86: }
87: this.postJson(this.options.url, {
88: text: encodeURIComponent(text).replace(/%20/g, '+')
89: }, function(json){
90: self.type == 'html' && self.options.innerDocument ?
91: self.highlightWords(json, callback) :
92: self.buildBadwordsBox(json, callback);
93: });
94: },
95:
96: highlightWords : function(json, callback) {
97: if (!json.length) { callback.call(this.$domObj, true); return; }
98:
99: var self = this, html = this.$domObj.html();
100:
101: $.each(json, function(key, replaceWord){
102: html = html.replace(
103: new RegExp('([^a-zA-Z\\u00A1-\\uFFFF])('+replaceWord+')([^a-zA-Z\\u00A1-\\uFFFF])', 'g'),
104: '$1<span class="spellcheck-word-highlight">$2</span>$3'
105: );
106: });
107: this.$domObj.html(html).find('.spellcheck-word-highlight').each(function(){
108: self.elements.highlightWords.push(
109: $(this).click(function(){
110: self.suggest(this);
111: })
112: );
113: });
114: (callback) && callback();
115: },
116:
117: buildBadwordsBox : function(json, callback){
118: if (!json.length) { callback.call(this.$domObj, true); return; }
119:
120: var self = this, words = [];
121:
122: // insert badwords list into dom
123: $(this.options.wordlist.element)[this.options.wordlist.action](this.elements.$badwords);
124:
125: // empty the badwords container
126: this.elements.$badwords.empty()
127:
128: // append incorrectly spelt words
129: $.each(json, function(key, badword) {
130: if ($.inArray(badword, words) === -1) {
131: self.elements.highlightWords.push(
132: $('<span class="spellcheck-word-highlight">'+badword+'</span>')
133: .click(function(){ self.suggest(this); })
134: .appendTo(self.elements.$badwords)
135: .after('<span class="spellcheck-sep">,</span> ')
136: );
137: words.push(badword);
138: }
139: });
140: $('.spellcheck-sep:last', self.elements.$badwords).addClass('spellcheck-sep-last');
141: (callback) && callback();
142: },
143:
144: // gets a list of suggested words, appends to the suggestbox and shows the suggestbox
145: suggest : function(word){
146:
147: var self = this, $word = $(word), offset = $word.offset();
148: this.$curWord = $word;
149:
150: if (this.options.innerDocument) {
151: this.elements.$suggestBox = this.elements.$body.find('.spellcheck-suggestbox');
152: this.elements.$suggestWords = this.elements.$body.find('.spellcheck-suggestbox-words');
153: this.elements.$suggestFoot = this.elements.$body.find('.spellcheck-suggestbox-foot');
154: }
155:
156: this.elements.$suggestFoot.hide();
157: this.elements.$suggestBox
158: .stop().hide()
159: .css({
160: opacity: 1,
161: width: "auto",
162: left: offset.left + "px",
163: top:
164: (this.options.suggestBoxPosition == "above" ?
165: (offset.top - ($word.outerHeight() + 10)) + "px" :
166: (offset.top + $word.outerHeight()) + "px")
167: }).fadeIn(200);
168:
169: this.elements.$suggestWords.html('<em>Loading..</em>');
170:
171: this.postJson(this.options.url, {
172: suggest: encodeURIComponent($.trim($word.text()))
173: }, function(json){
174: self.buildSuggestBox(json, offset);
175: });
176: },
177:
178: buildSuggestBox : function(json, offset){
179:
180: var self = this, $word = this.$curWord;
181:
182: this.elements.$suggestWords.empty();
183:
184: // build suggest word list
185: for(var i=0; i < (json.length < 5 ? json.length : 5); i++) {
186: this.elements.$suggestWords.append(
187: $('<a href="#">'+json[i]+'</a>')
188: .addClass((!i?'first':''))
189: .click(function(){ return false; })
190: .mousedown(function(e){
191: e.preventDefault();
192: self.replace(this.innerHTML);
193: self.hideBox();
194: })
195: );
196: }
197:
198: // no word suggestions
199: (!i) && this.elements.$suggestWords.append('<em>(no suggestions)</em>');
200:
201: // get browser viewport height
202: var viewportHeight = window.innerHeight ? window.innerHeight : $(window).height();
203:
204: this.elements.$suggestFoot.show();
205:
206: // position the suggest box
207: self.elements.$suggestBox
208: .css({
209: top : (this.options.suggestBoxPosition == 'above') ||
210: (offset.top + $word.outerHeight() + this.elements.$suggestBox.outerHeight() > viewportHeight + 10) ?
211: (offset.top - (this.elements.$suggestBox.height()+5)) + "px" :
212: (offset.top + $word.outerHeight() + "px"),
213: width : 'auto',
214: left : (this.elements.$suggestBox.outerWidth() + offset.left > $('body').width() ?
215: (offset.left - this.elements.$suggestBox.width()) + $word.outerWidth() + 'px' :
216: offset.left + 'px')
217: });
218:
219: },
220:
221: // hides the suggest box
222: hideBox : function(callback) {
223: this.elements.$suggestBox.fadeOut(250, function(){
224: (callback) && callback();
225: });
226: },
227:
228: // replace incorrectly spelt word with suggestion
229: replace : function(replace) {
230: switch(this.type) {
231: case 'textarea': this.replaceTextbox(replace); break;
232: case 'html': this.replaceHtml(replace); break;
233: }
234: },
235:
236: // replaces a word string in a chunk of text
237: replaceWord : function(text, replace){
238: return text
239: .replace(
240: new RegExp("([^a-zA-Z\\u00A1-\\uFFFF]?)("+this.$curWord.text()+")([^a-zA-Z\\u00A1-\\uFFFF]?)", "g"),
241: '$1'+replace+'$3'
242: )
243: .replace(
244: new RegExp("^("+this.$curWord.text()+")([^a-zA-Z\\u00A1-\\uFFFF])", "g"),
245: replace+'$2'
246: )
247: .replace(
248: new RegExp("([^a-zA-Z\\u00A1-\\uFFFF])("+this.$curWord.text()+")$", "g"),
249: '$1'+replace
250: );
251: },
252:
253: // replace word in a textarea
254: replaceTextbox : function(replace){
255: this.removeBadword(this.$curWord);
256: this.$domObj.val(
257: this.replaceWord(this.$domObj.val(), replace)
258: );
259: },
260:
261: // replace word in an HTML container
262: replaceHtml : function(replace){
263: var words = this.$domObj.find('.spellcheck-word-highlight:contains('+this.$curWord.text()+')')
264: if (words.length) {
265: words.after(replace).remove();
266: } else {
267: $(this.$domObj).html(
268: this.replaceWord($(this.$domObj).html(), replace)
269: );
270: this.removeBadword(this.$curWord);
271: }
272: },
273:
274: // remove spelling formatting from word to ignore in original element
275: ignore : function() {
276: if (this.type == 'textarea') {
277: this.removeBadword(this.$curWord);
278: } else {
279: this.$curWord.after(this.$curWord.html()).remove();
280: }
281: },
282:
283: // remove spelling formatting from all words to ignore in original element
284: ignoreAll : function() {
285: var self = this;
286: if (this.type == 'textarea') {
287: this.removeBadword(this.$curWord);
288: } else {
289: $('.spellcheck-word-highlight', this.$domObj).each(function(){
290: (new RegExp(self.$curWord.text(), 'i').test(this.innerHTML)) &&
291: $(this).after(this.innerHTML).remove(); // remove anchor
292: });
293: }
294: },
295:
296: removeBadword : function($domObj){
297: ($domObj.next().hasClass('spellcheck-sep')) && $domObj.next().remove();
298: $domObj.remove();
299: if (!$('.spellcheck-sep', this.elements.$badwords).length){
300: this.elements.$badwords.remove();
301: } else {
302: $('.spellcheck-sep:last', this.elements.$badwords).addClass('spellcheck-sep-last');
303: }
304: },
305:
306: // add word to personal dictionary (pspell only)
307: addToDictionary : function() {
308: var self= this;
309: this.hideBox(function(){
310: confirm('Are you sure you want to add the word "'+self.$curWord.text()+'" to the dictionary?') &&
311: self.postJson(self.options.url, { addtodictionary: self.$curWord.text() }, function(){
312: self.ignoreAll();
313: self.check();
314: });
315: });
316: },
317:
318: // remove spell check formatting
319: remove : function(destroy) {
320: destroy = destroy || true;
321: $.each(this.elements.highlightWords, function(val){
322: this.after(this.innerHTML).remove()
323: });
324: this.elements.$badwords.remove();
325: this.elements.$suggestBox.remove();
326: $(this.domObj).removeClass('spellcheck-container');
327: (destroy) && $(this.domObj).data('spellchecker', null);
328: },
329:
330: // sends post request, return JSON object
331: postJson : function(url, data, callback){
332: var xhr = $.ajax({
333: type : 'POST',
334: url : url,
335: data : $.extend(data, {
336: engine: this.options.engine,
337: lang: this.options.lang
338: }),
339: dataType : 'json',
340: cache : false,
341: error : function(XHR, status, error) {
342: alert('Sorry, there was an error processing the request.');
343: },
344: success : function(json){
345: (callback) && callback(json);
346: }
347: });
348: return xhr;
349: },
350:
351: // create the spellchecker elements, prepend to body
352: createElements : function(){
353: var self = this;
354:
355: this.elements.$body = this.options.innerDocument ? this.$domObj.parents().filter('html:first').find("body") : $('body');
356: this.elements.highlightWords = [];
357: this.elements.$suggestWords = this.elements.$suggestWords ||
358: $('<div></div>').addClass('spellcheck-suggestbox-words');
359: this.elements.$ignoreWord = this.elements.$ignoreWord ||
360: $('<a href="#">Ignore Word</a>')
361: .click(function(e){
362: e.preventDefault();
363: self.ignore();
364: self.hideBox();
365: });
366: this.elements.$ignoreAllWords = this.elements.$ignoreAllWords ||
367: $('<a href="#">Ignore all</a>')
368: .click(function(e){
369: e.preventDefault();
370: self.ignoreAll();
371: self.hideBox();
372: });
373: this.elements.$ignoreWordsForever = this.elements.$ignoreWordsForever ||
374: $('<a href="#" title="ignore word forever (add to dictionary)">Ignore forever</a>')
375: .click(function(e){
376: e.preventDefault();
377: self.addToDictionary();
378: self.hideBox();
379: });
380: this.elements.$suggestFoot = this.elements.$suggestFoot ||
381: $('<div></div>').addClass('spellcheck-suggestbox-foot')
382: .append(this.elements.$ignoreWord)
383: .append(this.elements.$ignoreAllWords)
384: .append(this.options.engine == "pspell" && self.options.addToDictionary ? this.elements.$ignoreWordsForever : false);
385: this.elements.$badwords = this.elements.$badwords ||
386: $('<div></div>').addClass('spellcheck-badwords');
387: this.elements.$suggestBox = this.elements.$suggestBox ||
388: $('<div></div>').addClass('spellcheck-suggestbox')
389: .append(this.elements.$suggestWords)
390: .append(this.elements.$suggestFoot)
391: .prependTo(this.elements.$body);
392: }
393: };
394:
395: })(jQuery);
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>