Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
with
3,176 additions
and 0 deletions.
- +20 −0 LICENSE
- +88 −0 README.md
- BIN doc/example.png
- +190 −0 spec/jasmine/jasmine-html.js
- +166 −0 spec/jasmine/jasmine.css
- +2,476 −0 spec/jasmine/jasmine.js
- BIN spec/jasmine/jasmine_favicon.png
- +84 −0 spec/mailcheckSpec.js
- +55 −0 spec/spec_runner.html
- +97 −0 src/jquery.mailcheck.js
@@ -0,0 +1,20 @@ | ||
The MIT License (MIT) | ||
Copyright © 2012 Received Inc, http://kicksend.com | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the “Software”), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. |
@@ -0,0 +1,88 @@ | ||
mailcheck.js | ||
========= | ||
|
||
A jQuery plugin that suggests a right domain when your users misspell it in an email address. | ||
|
||
What does it do? | ||
---------------- | ||
|
||
When your user types in "user@hotnail.con", Mailcheck will suggest "user@hotmail.com". | ||
|
||
At [Kicksend](http://kicksend.com), we use Mailcheck to help reduce typos in email addresses during sign ups. | ||
|
||
 | ||
|
||
See it live in action [here](http://kicksend.com/join). | ||
|
||
Installation | ||
------------ | ||
|
||
- For instant use, download `src/jquery.mailcheck.min.js` into javascripts directory. Use `src/jquery.mailcheck.js` if you want to hack on it, or you are using your own minimizer. | ||
|
||
- For hacking, fork the repo or git clone it. | ||
|
||
Usage | ||
----- | ||
First, include jQuery and Mailcheck into the page. | ||
|
||
<script type="text/javascript" src="jquery.min.js"></script> | ||
<script type="text/javascript" src="jquery.mailcheck.min.js"></script> | ||
|
||
Have a text field. | ||
|
||
<input id="email" name="email" type="text" /> | ||
|
||
Now, attach Mailcheck to the text field. Remember to declare an array of domains you want to check against. | ||
|
||
<script type="text/javascript"> | ||
var domains = ['hotmail.com', 'gmail.com', 'aol.com']; | ||
|
||
$('input#email').mailcheck(domains, { | ||
suggested: function(element, suggestion) { | ||
// callback code | ||
}, | ||
empty: function(element) { | ||
// callback code | ||
} | ||
}) | ||
</script> | ||
|
||
Mailcheck takes in two callbacks, `suggested` and `empty`. We recommend you supply both. | ||
|
||
`suggested` is called when there's a suggestion. Mailcheck passes in the target element and the suggestion. The suggestion is an object with the following members: | ||
|
||
{ | ||
address: 'test', // the address; part before the @ sign | ||
domain: 'hotmail.com', // the suggested domain | ||
full: 'test@hotmail.com' // the full suggested email | ||
} | ||
|
||
`empty` is called when there's no suggestion. Mailcheck just passes in the target element. | ||
|
||
You can use the callbacks to display the appropriate visual feedback to the user. | ||
|
||
Customization | ||
------------- | ||
The Mailcheck jQuery plugin wraps Kicksend.mailcheck. The prime candidates for customization are the methods | ||
`Kicksend.mailcheck.findClosestDomain` and `Kicksend.mailcheck.stringDistance`. | ||
|
||
Mailcheck currently uses the [sift3](http://siderite.blogspot.com/2007/04/super-fast-and-accurate-string-distance.html) string similarity algorithm by [Siderite](http://siderite.blogspot.com/). | ||
|
||
Since Mailcheck is a client-side operation, keep in mind file size, memory usage, and performance. | ||
|
||
Tests | ||
----- | ||
|
||
Mailcheck is tested with [Jasmine](http://pivotal.github.com/jasmine/). Load `spec/spec_runner.html` in your browser to run the tests. | ||
|
||
Author | ||
------- | ||
|
||
Derrick Ko ([@derrickko](http://twitter.com/derrickko)) | ||
|
||
License | ||
------- | ||
|
||
Copyright (c) 2012 [Receivd, Inc.](http://kicksend.com) | ||
|
||
Licensed under the MIT License. |
Binary file not shown.
@@ -0,0 +1,190 @@ | ||
jasmine.TrivialReporter = function(doc) { | ||
this.document = doc || document; | ||
this.suiteDivs = {}; | ||
this.logRunningSpecs = false; | ||
}; | ||
|
||
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { | ||
var el = document.createElement(type); | ||
|
||
for (var i = 2; i < arguments.length; i++) { | ||
var child = arguments[i]; | ||
|
||
if (typeof child === 'string') { | ||
el.appendChild(document.createTextNode(child)); | ||
} else { | ||
if (child) { el.appendChild(child); } | ||
} | ||
} | ||
|
||
for (var attr in attrs) { | ||
if (attr == "className") { | ||
el[attr] = attrs[attr]; | ||
} else { | ||
el.setAttribute(attr, attrs[attr]); | ||
} | ||
} | ||
|
||
return el; | ||
}; | ||
|
||
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { | ||
var showPassed, showSkipped; | ||
|
||
this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, | ||
this.createDom('div', { className: 'banner' }, | ||
this.createDom('div', { className: 'logo' }, | ||
this.createDom('span', { className: 'title' }, "Jasmine"), | ||
this.createDom('span', { className: 'version' }, runner.env.versionString())), | ||
this.createDom('div', { className: 'options' }, | ||
"Show ", | ||
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), | ||
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), | ||
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), | ||
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") | ||
) | ||
), | ||
|
||
this.runnerDiv = this.createDom('div', { className: 'runner running' }, | ||
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), | ||
this.runnerMessageSpan = this.createDom('span', {}, "Running..."), | ||
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) | ||
); | ||
|
||
this.document.body.appendChild(this.outerDiv); | ||
|
||
var suites = runner.suites(); | ||
for (var i = 0; i < suites.length; i++) { | ||
var suite = suites[i]; | ||
var suiteDiv = this.createDom('div', { className: 'suite' }, | ||
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), | ||
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); | ||
this.suiteDivs[suite.id] = suiteDiv; | ||
var parentDiv = this.outerDiv; | ||
if (suite.parentSuite) { | ||
parentDiv = this.suiteDivs[suite.parentSuite.id]; | ||
} | ||
parentDiv.appendChild(suiteDiv); | ||
} | ||
|
||
this.startedAt = new Date(); | ||
|
||
var self = this; | ||
showPassed.onclick = function(evt) { | ||
if (showPassed.checked) { | ||
self.outerDiv.className += ' show-passed'; | ||
} else { | ||
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); | ||
} | ||
}; | ||
|
||
showSkipped.onclick = function(evt) { | ||
if (showSkipped.checked) { | ||
self.outerDiv.className += ' show-skipped'; | ||
} else { | ||
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); | ||
} | ||
}; | ||
}; | ||
|
||
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { | ||
var results = runner.results(); | ||
var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; | ||
this.runnerDiv.setAttribute("class", className); | ||
//do it twice for IE | ||
this.runnerDiv.setAttribute("className", className); | ||
var specs = runner.specs(); | ||
var specCount = 0; | ||
for (var i = 0; i < specs.length; i++) { | ||
if (this.specFilter(specs[i])) { | ||
specCount++; | ||
} | ||
} | ||
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); | ||
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; | ||
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); | ||
|
||
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); | ||
}; | ||
|
||
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { | ||
var results = suite.results(); | ||
var status = results.passed() ? 'passed' : 'failed'; | ||
if (results.totalCount === 0) { // todo: change this to check results.skipped | ||
status = 'skipped'; | ||
} | ||
this.suiteDivs[suite.id].className += " " + status; | ||
}; | ||
|
||
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { | ||
if (this.logRunningSpecs) { | ||
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); | ||
} | ||
}; | ||
|
||
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { | ||
var results = spec.results(); | ||
var status = results.passed() ? 'passed' : 'failed'; | ||
if (results.skipped) { | ||
status = 'skipped'; | ||
} | ||
var specDiv = this.createDom('div', { className: 'spec ' + status }, | ||
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), | ||
this.createDom('a', { | ||
className: 'description', | ||
href: '?spec=' + encodeURIComponent(spec.getFullName()), | ||
title: spec.getFullName() | ||
}, spec.description)); | ||
|
||
|
||
var resultItems = results.getItems(); | ||
var messagesDiv = this.createDom('div', { className: 'messages' }); | ||
for (var i = 0; i < resultItems.length; i++) { | ||
var result = resultItems[i]; | ||
|
||
if (result.type == 'log') { | ||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); | ||
} else if (result.type == 'expect' && result.passed && !result.passed()) { | ||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); | ||
|
||
if (result.trace.stack) { | ||
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); | ||
} | ||
} | ||
} | ||
|
||
if (messagesDiv.childNodes.length > 0) { | ||
specDiv.appendChild(messagesDiv); | ||
} | ||
|
||
this.suiteDivs[spec.suite.id].appendChild(specDiv); | ||
}; | ||
|
||
jasmine.TrivialReporter.prototype.log = function() { | ||
var console = jasmine.getGlobal().console; | ||
if (console && console.log) { | ||
if (console.log.apply) { | ||
console.log.apply(console, arguments); | ||
} else { | ||
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie | ||
} | ||
} | ||
}; | ||
|
||
jasmine.TrivialReporter.prototype.getLocation = function() { | ||
return this.document.location; | ||
}; | ||
|
||
jasmine.TrivialReporter.prototype.specFilter = function(spec) { | ||
var paramMap = {}; | ||
var params = this.getLocation().search.substring(1).split('&'); | ||
for (var i = 0; i < params.length; i++) { | ||
var p = params[i].split('='); | ||
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); | ||
} | ||
|
||
if (!paramMap.spec) { | ||
return true; | ||
} | ||
return spec.getFullName().indexOf(paramMap.spec) === 0; | ||
}; |
Oops, something went wrong.