394 lines
18 KiB
JavaScript
394 lines
18 KiB
JavaScript
/*
|
|
* jquery.spellchecker.js - a simple jQuery Spell Checker
|
|
* Copyright (c) 2009 Richard Willis
|
|
* MIT license : http://www.opensource.org/licenses/mit-license.php
|
|
* Project : http://jquery-spellchecker.googlecode.com
|
|
* Contact : willis.rh@gmail.com
|
|
*/
|
|
|
|
(function ($) {
|
|
|
|
$.fn.extend({
|
|
|
|
spellchecker: function (options, callback) {
|
|
return this.each(function () {
|
|
var obj = $(this).data('spellchecker');
|
|
if (obj && String === options.constructor && obj[options]) {
|
|
obj[options](callback);
|
|
} else if (obj) {
|
|
obj.init();
|
|
} else {
|
|
$(this).data('spellchecker', new SpellChecker(this, (Object === options.constructor ? options : null)));
|
|
(String === options.constructor) && $(this).data('spellchecker')[options](callback);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
var SpellChecker = function (domObj, options) {
|
|
this.options = $.extend({
|
|
url: 'checkspelling.php', // default spellcheck url
|
|
lang: 'en', // default language
|
|
engine: 'pspell', // pspell or google
|
|
addToDictionary: false, // display option to add word to dictionary (pspell only)
|
|
wordlist: {
|
|
action: 'after', // which jquery dom insert action
|
|
element: domObj // which object to apply above method
|
|
},
|
|
suggestBoxPosition: 'below', // position of suggest box; above or below the highlighted word
|
|
innerDocument: false // if you want the badwords highlighted in the html then set to true
|
|
}, options || {});
|
|
this.$domObj = $(domObj);
|
|
this.elements = {};
|
|
this.init();
|
|
};
|
|
|
|
SpellChecker.prototype = {
|
|
|
|
init: function () {
|
|
var self = this;
|
|
this.createElements();
|
|
this.$domObj.addClass('spellcheck-container');
|
|
// hide the suggest box on document click
|
|
$(document).bind('click', function (e) {
|
|
(!$(e.target).hasClass('spellcheck-word-highlight') &&
|
|
!$(e.target).parents().filter('.spellcheck-suggestbox').length) &&
|
|
self.hideBox();
|
|
});
|
|
},
|
|
|
|
// checks a chunk of text for bad words, then either shows the words below the original element (if texarea) or highlights the bad words
|
|
check: function (callback) {
|
|
|
|
var self = this, node = this.$domObj.get(0).nodeName,
|
|
tagExp = '<[^>]+>',
|
|
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,}';
|
|
|
|
if (node == 'TEXTAREA' || node == 'INPUT') {
|
|
this.type = 'textarea';
|
|
var text = $.trim(
|
|
this.$domObj.val()
|
|
.replace(new RegExp(tagExp, 'g'), '') // strip html tags
|
|
.replace(new RegExp(puncExp, 'g'), ' ') // strip punctuation
|
|
);
|
|
} else {
|
|
this.type = 'html';
|
|
var text = $.trim(
|
|
this.$domObj.text()
|
|
.replace(new RegExp(puncExp, 'g'), " ") // strip punctuation
|
|
);
|
|
}
|
|
this.postJson(this.options.url, {
|
|
text: encodeURIComponent(text).replace(/%20/g, '+')
|
|
}, function (json) {
|
|
self.type == 'html' && self.options.innerDocument ?
|
|
self.highlightWords(json, callback) :
|
|
self.buildBadwordsBox(json, callback);
|
|
});
|
|
},
|
|
|
|
highlightWords: function (json, callback) {
|
|
if (!json.length) { callback.call(this.$domObj, true); return; }
|
|
|
|
var self = this, html = this.$domObj.html();
|
|
|
|
$.each(json, function (key, replaceWord) {
|
|
html = html.replace(
|
|
new RegExp('([^a-zA-Z\\u00A1-\\uFFFF])(' + replaceWord + ')([^a-zA-Z\\u00A1-\\uFFFF])', 'g'),
|
|
'$1<span class="spellcheck-word-highlight">$2</span>$3'
|
|
);
|
|
});
|
|
this.$domObj.html(html).find('.spellcheck-word-highlight').each(function () {
|
|
self.elements.highlightWords.push(
|
|
$(this).click(function () {
|
|
self.suggest(this);
|
|
})
|
|
);
|
|
});
|
|
(callback) && callback();
|
|
},
|
|
|
|
buildBadwordsBox: function (json, callback) {
|
|
|
|
// empty the badwords container
|
|
this.elements.$badwords.empty();
|
|
|
|
if (!json.length) { callback.call(this.$domObj, true); return; }
|
|
|
|
var self = this, words = [];
|
|
|
|
// insert badwords list into dom
|
|
$(this.options.wordlist.element)[this.options.wordlist.action](this.elements.$badwords);
|
|
|
|
// empty the badwords container
|
|
//this.elements.$badwords.empty()
|
|
|
|
// append incorrectly spelt words
|
|
$.each(json, function (key, badword) {
|
|
if ($.inArray(badword, words) === -1) {
|
|
self.elements.highlightWords.push(
|
|
$('<span class="spellcheck-word-highlight">' + badword + '</span>')
|
|
.click(function () { self.suggest(this); }).bind("contextmenu", function (e) { self.suggest(this); return false; })
|
|
.appendTo(self.elements.$badwords)
|
|
.after('<span class="spellcheck-sep">,</span> ')
|
|
);
|
|
words.push(badword);
|
|
}
|
|
});
|
|
$('.spellcheck-sep:last', self.elements.$badwords).addClass('spellcheck-sep-last');
|
|
(callback) && callback();
|
|
},
|
|
|
|
// gets a list of suggested words, appends to the suggestbox and shows the suggestbox
|
|
suggest: function (word) {
|
|
|
|
var self = this, $word = $(word), offset = $word.offset();
|
|
this.$curWord = $word;
|
|
|
|
if (this.options.innerDocument) {
|
|
this.elements.$suggestBox = this.elements.$body.find('.spellcheck-suggestbox');
|
|
this.elements.$suggestWords = this.elements.$body.find('.spellcheck-suggestbox-words');
|
|
this.elements.$suggestFoot = this.elements.$body.find('.spellcheck-suggestbox-foot');
|
|
}
|
|
|
|
this.elements.$suggestFoot.hide();
|
|
this.elements.$suggestBox
|
|
.stop().hide()
|
|
.css({
|
|
opacity: 1,
|
|
width: "auto",
|
|
left: offset.left + "px",
|
|
top:
|
|
(this.options.suggestBoxPosition == "above" ?
|
|
(offset.top - ($word.outerHeight() + 10)) + "px" :
|
|
(offset.top + $word.outerHeight()) + "px")
|
|
}).fadeIn(200);
|
|
|
|
this.elements.$suggestWords.html('<em>Loading..</em>');
|
|
|
|
this.postJson(this.options.url, {
|
|
suggest: encodeURIComponent($.trim($word.text()))
|
|
}, function (json) {
|
|
self.buildSuggestBox(json, offset);
|
|
});
|
|
},
|
|
|
|
buildSuggestBox: function (json, offset) {
|
|
|
|
var self = this, $word = this.$curWord;
|
|
|
|
this.elements.$suggestWords.empty();
|
|
|
|
// build suggest word list
|
|
for (var i = 0; i < (json.length < 5 ? json.length : 5) ; i++) {
|
|
this.elements.$suggestWords.append(
|
|
$('<a href="#">' + json[i] + '</a>')
|
|
.addClass((!i ? 'first' : ''))
|
|
.click(function () { return false; })
|
|
.mousedown(function (e) {
|
|
e.preventDefault();
|
|
self.replace(this.innerHTML);
|
|
self.hideBox();
|
|
})
|
|
);
|
|
}
|
|
|
|
// no word suggestions
|
|
(!i) && this.elements.$suggestWords.append('<em>(no suggestions)</em>');
|
|
|
|
// get browser viewport height
|
|
var viewportHeight = window.innerHeight ? window.innerHeight : $(window).height();
|
|
|
|
this.elements.$suggestFoot.show();
|
|
|
|
// position the suggest box
|
|
self.elements.$suggestBox
|
|
.css({
|
|
top: (this.options.suggestBoxPosition == 'above') ||
|
|
(offset.top + $word.outerHeight() + this.elements.$suggestBox.outerHeight() > viewportHeight + 10) ?
|
|
(offset.top - (this.elements.$suggestBox.height() + 5)) + "px" :
|
|
(offset.top + $word.outerHeight() + "px"),
|
|
width: 'auto',
|
|
left: (this.elements.$suggestBox.outerWidth() + offset.left > $('body').width() ?
|
|
(offset.left - this.elements.$suggestBox.width()) + $word.outerWidth() + 'px' :
|
|
offset.left + 'px')
|
|
});
|
|
|
|
},
|
|
|
|
// hides the suggest box
|
|
hideBox: function (callback) {
|
|
this.elements.$suggestBox.fadeOut(250, function () {
|
|
(callback) && callback();
|
|
});
|
|
},
|
|
|
|
// replace incorrectly spelt word with suggestion
|
|
replace: function (replace) {
|
|
switch (this.type) {
|
|
case 'textarea': this.replaceTextbox(replace); break;
|
|
case 'html': this.replaceHtml(replace); break;
|
|
}
|
|
},
|
|
|
|
// replaces a word string in a chunk of text
|
|
replaceWord: function (text, replace) {
|
|
return text
|
|
.replace(
|
|
new RegExp("([^a-zA-Z\\u00A1-\\uFFFF]?)(" + this.$curWord.text() + ")([^a-zA-Z\\u00A1-\\uFFFF]?)", "g"),
|
|
'$1' + replace + '$3'
|
|
)
|
|
.replace(
|
|
new RegExp("^(" + this.$curWord.text() + ")([^a-zA-Z\\u00A1-\\uFFFF])", "g"),
|
|
replace + '$2'
|
|
)
|
|
.replace(
|
|
new RegExp("([^a-zA-Z\\u00A1-\\uFFFF])(" + this.$curWord.text() + ")$", "g"),
|
|
'$1' + replace
|
|
);
|
|
},
|
|
|
|
// replace word in a textarea
|
|
replaceTextbox: function (replace) {
|
|
this.removeBadword(this.$curWord);
|
|
this.$domObj.val(
|
|
this.replaceWord(this.$domObj.val(), replace)
|
|
);
|
|
},
|
|
|
|
// replace word in an HTML container
|
|
replaceHtml: function (replace) {
|
|
var words = this.$domObj.find('.spellcheck-word-highlight:contains(' + this.$curWord.text() + ')')
|
|
if (words.length) {
|
|
words.after(replace).remove();
|
|
} else {
|
|
$(this.$domObj).html(
|
|
this.replaceWord($(this.$domObj).html(), replace)
|
|
);
|
|
this.removeBadword(this.$curWord);
|
|
}
|
|
},
|
|
|
|
// remove spelling formatting from word to ignore in original element
|
|
ignore: function () {
|
|
if (this.type == 'textarea') {
|
|
this.removeBadword(this.$curWord);
|
|
} else {
|
|
this.$curWord.after(this.$curWord.html()).remove();
|
|
}
|
|
},
|
|
|
|
// remove spelling formatting from all words to ignore in original element
|
|
ignoreAll: function () {
|
|
var self = this;
|
|
if (this.type == 'textarea') {
|
|
this.removeBadword(this.$curWord);
|
|
} else {
|
|
$('.spellcheck-word-highlight', this.$domObj).each(function () {
|
|
(new RegExp(self.$curWord.text(), 'i').test(this.innerHTML)) &&
|
|
$(this).after(this.innerHTML).remove(); // remove anchor
|
|
});
|
|
}
|
|
},
|
|
|
|
removeBadword: function ($domObj) {
|
|
($domObj.next().hasClass('spellcheck-sep')) && $domObj.next().remove();
|
|
$domObj.remove();
|
|
if (!$('.spellcheck-sep', this.elements.$badwords).length) {
|
|
this.elements.$badwords.remove();
|
|
} else {
|
|
$('.spellcheck-sep:last', this.elements.$badwords).addClass('spellcheck-sep-last');
|
|
}
|
|
},
|
|
|
|
// add word to personal dictionary (pspell only)
|
|
addToDictionary: function () {
|
|
var self = this;
|
|
this.hideBox(function () {
|
|
confirm('Are you sure you want to add the word "' + self.$curWord.text() + '" to the dictionary?') &&
|
|
self.postJson(self.options.url, { addtodictionary: self.$curWord.text() }, function () {
|
|
self.ignoreAll();
|
|
self.check();
|
|
});
|
|
});
|
|
},
|
|
|
|
// remove spell check formatting
|
|
remove: function (destroy) {
|
|
destroy = destroy || true;
|
|
$.each(this.elements.highlightWords, function (val) {
|
|
this.after(this.innerHTML).remove()
|
|
});
|
|
this.elements.$badwords.remove();
|
|
this.elements.$suggestBox.remove();
|
|
this.$domObj.removeClass('spellcheck-container');
|
|
(destroy) && this.$domObj.data('spellchecker', null);
|
|
},
|
|
|
|
// sends post request, return JSON object
|
|
postJson: function (url, data, callback) {
|
|
var xhr = $.ajax({
|
|
type: 'POST',
|
|
url: url,
|
|
data: $.extend(data, {
|
|
engine: this.options.engine,
|
|
lang: this.options.lang
|
|
}),
|
|
dataType: 'json',
|
|
cache: false,
|
|
error: function (XHR, status, error) {
|
|
alert('Sorry, there was an error processing spellchecker request to Google service.');
|
|
},
|
|
success: function (json) {
|
|
(callback) && callback(json);
|
|
}
|
|
});
|
|
return xhr;
|
|
},
|
|
|
|
// create the spellchecker elements, prepend to body
|
|
createElements: function () {
|
|
var self = this;
|
|
|
|
this.elements.$body = this.options.innerDocument ? this.$domObj.parents().filter('html:first').find("body") : $('body');
|
|
this.elements.highlightWords = [];
|
|
this.elements.$suggestWords = this.elements.$suggestWords ||
|
|
$('<div></div>').addClass('spellcheck-suggestbox-words');
|
|
this.elements.$ignoreWord = this.elements.$ignoreWord ||
|
|
$('<a href="#">Ignore Word</a>')
|
|
.click(function (e) {
|
|
e.preventDefault();
|
|
self.ignore();
|
|
self.hideBox();
|
|
});
|
|
this.elements.$ignoreAllWords = this.elements.$ignoreAllWords ||
|
|
$('<a href="#">Ignore all</a>')
|
|
.click(function (e) {
|
|
e.preventDefault();
|
|
self.ignoreAll();
|
|
self.hideBox();
|
|
});
|
|
this.elements.$ignoreWordsForever = this.elements.$ignoreWordsForever ||
|
|
$('<a href="#" title="ignore word forever (add to dictionary)">Ignore forever</a>')
|
|
.click(function (e) {
|
|
e.preventDefault();
|
|
self.addToDictionary();
|
|
self.hideBox();
|
|
});
|
|
this.elements.$suggestFoot = this.elements.$suggestFoot ||
|
|
$('<div></div>').addClass('spellcheck-suggestbox-foot')
|
|
.append(this.elements.$ignoreWord)
|
|
.append(this.elements.$ignoreAllWords)
|
|
.append(this.options.engine == "pspell" && self.options.addToDictionary ? this.elements.$ignoreWordsForever : false);
|
|
this.elements.$badwords = this.elements.$badwords ||
|
|
$('<div></div>').addClass('spellcheck-badwords');
|
|
this.elements.$suggestBox = this.elements.$suggestBox ||
|
|
$('<div></div>').addClass('spellcheck-suggestbox')
|
|
.append(this.elements.$suggestWords)
|
|
.append(this.elements.$suggestFoot)
|
|
.prependTo(this.elements.$body);
|
|
}
|
|
};
|
|
|
|
})(jQuery);
|