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