File:  [LON-CAPA] / loncom / html / adm / spellcheck / js / jquery.spellchecker.js
Revision 1.2: download - view: text, annotated - select for diffs
Mon Sep 24 10:31:45 2012 UTC (12 years, 5 months ago) by foxr
Branches: MAIN
CVS tags: version_2_12_X, version_2_11_X, version_2_11_6_msu, version_2_11_6, version_2_11_5_msu, version_2_11_5, version_2_11_4_uiuc, version_2_11_4_msu, version_2_11_4, version_2_11_3_uiuc, version_2_11_3_msu, version_2_11_3, version_2_11_2_uiuc, version_2_11_2_msu, version_2_11_2_educog, version_2_11_2, version_2_11_1, version_2_11_0_RC3, version_2_11_0_RC2, version_2_11_0_RC1, version_2_11_0, HEAD
Add option setting method

    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: 		option : function(newopts) {
   60: 		    for (optname in newopts) {
   61: 		        this.options[optname] = newopts[optname];
   62: 		    }
   63: 		    return this;
   64: 	    },
   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>