mirror of
https://github.com/devbridge/jQuery-Autocomplete.git
synced 2024-11-21 20:35:17 +00:00
Make plugin chainable, change suggestion event handling to improve performance, change suggestions format, update readme, add Jasmine test suites, add license.
This commit is contained in:
parent
8211e43cfd
commit
459719fc02
4
.gitignore
vendored
4
.gitignore
vendored
@ -1 +1,5 @@
|
||||
/*.user
|
||||
/*.sln
|
||||
/*.suo
|
||||
/.idea*
|
||||
/*.xml
|
@ -3,8 +3,8 @@
|
||||
|
||||
.autocomplete-w1 { position: absolute; top: 0px; left: 0px; }
|
||||
.autocomplete { border: 1px solid #999; background: #FFF; cursor: default; text-align: left; max-height: 350px; overflow: auto; /* IE6 specific: */ _height: 350px; _margin: 0; _overflow-x: hidden; }
|
||||
.autocomplete .selected { background: #F0F0F0; }
|
||||
.autocomplete-selected { background: #F0F0F0; }
|
||||
.autocomplete div { padding: 2px 5px; white-space: nowrap; overflow: hidden; }
|
||||
.autocomplete strong { font-weight: normal; color: #3399FF; }
|
||||
|
||||
#query { font-size: 28px; padding: 10px; border: 1px solid #CCC; display: block; margin: 40px; }
|
||||
input { font-size: 28px; padding: 10px; border: 1px solid #CCC; display: block; margin: 20px 0; }
|
12
demo.htm
12
demo.htm
@ -7,14 +7,24 @@
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Ajax Autocomplete Demo</h1>
|
||||
|
||||
<h2>Ajax Lookup</h2>
|
||||
<p>Type country name in english:</p>
|
||||
<div>
|
||||
<input type="text" name="query" id="query"/>
|
||||
<input type="text" name="country" id="autocomplete-ajax"/>
|
||||
</div>
|
||||
<div id="selction-ajax"></div>
|
||||
|
||||
<h2>Local Lookup</h2>
|
||||
<p>Type country name in english:</p>
|
||||
<div>
|
||||
<input type="text" name="country" id="autocomplete"/>
|
||||
</div>
|
||||
<div id="selection"></div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="scripts/jquery-1.8.2.min.js"></script>
|
||||
<script type="text/javascript" src="scripts/jquery.mockjax.js"></script>
|
||||
<script type="text/javascript" src="src/jquery.autocomplete.js"></script>
|
||||
<script type="text/javascript" src="scripts/demo.js"></script>
|
||||
</body>
|
||||
|
577
dist/jquery.autocomplete.js
vendored
Normal file
577
dist/jquery.autocomplete.js
vendored
Normal file
@ -0,0 +1,577 @@
|
||||
/**
|
||||
* Ajax Autocomplete for jQuery, version 1.2
|
||||
* (c) 2012 Tomas Kirda
|
||||
*
|
||||
* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
|
||||
* For details, see the web site: http://www.devbridge.com/projects/autocomplete/jquery/
|
||||
*
|
||||
* Last Review: 12/18/2012
|
||||
*/
|
||||
|
||||
/*jslint browser: true, white: true, plusplus: true, vars: true */
|
||||
/*global window: true, document: true, clearInterval: true, setInterval: true, jQuery: true */
|
||||
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
var utils = (function () {
|
||||
return {
|
||||
|
||||
extend: function (target, source) {
|
||||
return $.extend(target, source);
|
||||
},
|
||||
|
||||
addEvent: function (element, eventType, handler) {
|
||||
if (element.addEventListener) {
|
||||
element.addEventListener(eventType, handler, false);
|
||||
} else if (element.attachEvent) {
|
||||
element.attachEvent('on' + eventType, handler);
|
||||
} else {
|
||||
throw new Error('Browser doesn\'t support addEventListener or attachEvent');
|
||||
}
|
||||
},
|
||||
|
||||
removeEvent: function (element, eventType, handler) {
|
||||
if (element.removeEventListener) {
|
||||
element.removeEventListener(eventType, handler, false);
|
||||
} else if (element.detachEvent) {
|
||||
element.detachEvent('on' + eventType, handler);
|
||||
}
|
||||
},
|
||||
|
||||
createNode: function (html) {
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = html;
|
||||
return div.firstChild;
|
||||
}
|
||||
|
||||
};
|
||||
}());
|
||||
|
||||
function Autocomplete(el, options) {
|
||||
var that = this,
|
||||
defaults = {
|
||||
minChars: 1,
|
||||
maxHeight: 300,
|
||||
deferRequestBy: 0,
|
||||
width: 0,
|
||||
highlight: true,
|
||||
params: {},
|
||||
formatResult: Autocomplete.formatResult,
|
||||
delimiter: null,
|
||||
zIndex: 9999,
|
||||
type: 'GET',
|
||||
noCache: false,
|
||||
enforce: false
|
||||
};
|
||||
|
||||
// Shared variables:
|
||||
that.element = el;
|
||||
that.el = $(el);
|
||||
that.suggestions = [];
|
||||
that.badQueries = [];
|
||||
that.selectedIndex = -1;
|
||||
that.currentValue = that.element.value;
|
||||
that.intervalId = 0;
|
||||
that.cachedResponse = [];
|
||||
that.onChangeInterval = null;
|
||||
that.onChange = null;
|
||||
that.ignoreValueChange = false;
|
||||
that.isLocal = false;
|
||||
that.suggestionsContainer = null;
|
||||
that.options = defaults;
|
||||
that.classes = {
|
||||
selected: 'autocomplete-selected',
|
||||
suggestion: 'autocomplete-suggestion'
|
||||
};
|
||||
|
||||
// Initialize and set options:
|
||||
that.initialize();
|
||||
that.setOptions(options);
|
||||
}
|
||||
|
||||
Autocomplete.utils = utils;
|
||||
|
||||
$.Autocomplete = Autocomplete;
|
||||
|
||||
Autocomplete.formatResult = function (suggestion, currentValue) {
|
||||
var reEscape = new RegExp('(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'].join('|\\') + ')', 'g'),
|
||||
pattern = '(' + currentValue.replace(reEscape, '\\$1') + ')';
|
||||
|
||||
return suggestion.value.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
|
||||
};
|
||||
|
||||
Autocomplete.prototype = {
|
||||
|
||||
killerFn: null,
|
||||
|
||||
initialize: function () {
|
||||
var that = this,
|
||||
suggestionSelector = '.' + that.classes.suggestion;
|
||||
|
||||
// Remove autocomplete attribute to prevent native suggestions:
|
||||
this.element.setAttribute('autocomplete', 'off');
|
||||
|
||||
this.killerFn = function (e) {
|
||||
if ($(e.target).closest('.autocomplete').length === 0) {
|
||||
that.killSuggestions();
|
||||
that.disableKillerFn();
|
||||
}
|
||||
};
|
||||
|
||||
// Determine suggestions width:
|
||||
if (!this.options.width || this.options.width === 'auto') {
|
||||
this.options.width = this.el.outerWidth();
|
||||
}
|
||||
|
||||
this.suggestionsContainer = Autocomplete.utils.createNode('<div class="autocomplete" style="position: absolute; display: none;"></div>');
|
||||
|
||||
var container = $(this.suggestionsContainer);
|
||||
|
||||
container.appendTo('body').width(this.options.width);
|
||||
|
||||
// Listen for mouse over event on suggestions list:
|
||||
container.on('mouseover', suggestionSelector, function () {
|
||||
that.activate($(this).data('index'));
|
||||
});
|
||||
|
||||
// Listen for click event on suggestions list:
|
||||
container.on('click', suggestionSelector, function () {
|
||||
that.select($(this).data('index'));
|
||||
});
|
||||
|
||||
this.fixPosition();
|
||||
|
||||
// Opera does not like keydown:
|
||||
if (window.opera) {
|
||||
this.el.on('keypress', function (e) { that.onKeyPress(e); });
|
||||
} else {
|
||||
this.el.on('keydown', function (e) { that.onKeyPress(e); });
|
||||
}
|
||||
|
||||
this.el.on('keyup', function (e) { that.onKeyUp(e); });
|
||||
this.el.on('blur', function () { that.onBlur(); });
|
||||
this.el.on('focus', function () { that.fixPosition(); });
|
||||
},
|
||||
|
||||
onBlur: function () {
|
||||
this.enableKillerFn();
|
||||
},
|
||||
|
||||
setOptions: function (suppliedOptions) {
|
||||
var options = this.options;
|
||||
|
||||
utils.extend(options, suppliedOptions);
|
||||
|
||||
this.isLocal = $.isArray(options.lookup);
|
||||
|
||||
// Transform lookup array if it's string array:
|
||||
if (this.isLocal && typeof options.lookup[0] === 'string') {
|
||||
options.lookup = $.map(options.lookup, function (value) {
|
||||
return { value: value, data: null };
|
||||
});
|
||||
}
|
||||
|
||||
// Adjust height, width and z-index:
|
||||
$(this.suggestionsContainer).css({
|
||||
'max-height': options.maxHeight + 'px',
|
||||
'width': options.width,
|
||||
'z-index': options.zIndex
|
||||
});
|
||||
},
|
||||
|
||||
clearCache: function () {
|
||||
this.cachedResponse = [];
|
||||
this.badQueries = [];
|
||||
},
|
||||
|
||||
disable: function () {
|
||||
this.disabled = true;
|
||||
},
|
||||
|
||||
enable: function () {
|
||||
this.disabled = false;
|
||||
},
|
||||
|
||||
fixPosition: function () {
|
||||
var offset = this.el.offset();
|
||||
$(this.suggestionsContainer).css({
|
||||
top: (offset.top + this.el.outerHeight()) + 'px',
|
||||
left: offset.left + 'px'
|
||||
});
|
||||
},
|
||||
|
||||
enableKillerFn: function () {
|
||||
var that = this;
|
||||
$(document).on('click', that.killerFn);
|
||||
},
|
||||
|
||||
disableKillerFn: function () {
|
||||
var that = this;
|
||||
$(document).off('click', that.killerFn);
|
||||
},
|
||||
|
||||
killSuggestions: function () {
|
||||
var that = this;
|
||||
that.stopKillSuggestions();
|
||||
that.intervalId = window.setInterval(function () {
|
||||
that.hide();
|
||||
that.stopKillSuggestions();
|
||||
}, 300);
|
||||
},
|
||||
|
||||
stopKillSuggestions: function () {
|
||||
window.clearInterval(this.intervalId);
|
||||
},
|
||||
|
||||
onKeyPress: function (e) {
|
||||
// If suggestions are hidden and user presses arrow down, display suggestions:
|
||||
if (!this.disabled && !this.visible && e.keyCode === 40 && this.currentValue) {
|
||||
this.suggest();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.disabled || !this.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.keyCode) {
|
||||
case 27: //KEY_ESC:
|
||||
this.el.val(this.currentValue);
|
||||
this.hide();
|
||||
break;
|
||||
case 9: //KEY_TAB:
|
||||
case 13: //KEY_RETURN:
|
||||
if (this.selectedIndex === -1) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
this.select(this.selectedIndex);
|
||||
if (e.keyCode === 9) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 38: //KEY_UP:
|
||||
this.moveUp();
|
||||
break;
|
||||
case 40: //KEY_DOWN:
|
||||
this.moveDown();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel event if function did not return:
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
onKeyUp: function (e) {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.keyCode) {
|
||||
case 38: //KEY_UP:
|
||||
case 40: //KEY_DOWN:
|
||||
return;
|
||||
}
|
||||
|
||||
clearInterval(this.onChangeInterval);
|
||||
|
||||
if (this.currentValue !== this.el.val()) {
|
||||
if (this.options.deferRequestBy > 0) {
|
||||
// Defer lookup in case when value changes very quickly:
|
||||
var me = this;
|
||||
this.onChangeInterval = setInterval(function () {
|
||||
me.onValueChange();
|
||||
}, this.options.deferRequestBy);
|
||||
} else {
|
||||
this.onValueChange();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onValueChange: function () {
|
||||
clearInterval(this.onChangeInterval);
|
||||
this.currentValue = this.element.value;
|
||||
var q = this.getQuery(this.currentValue);
|
||||
this.selectedIndex = -1;
|
||||
|
||||
if (this.ignoreValueChange) {
|
||||
this.ignoreValueChange = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (q === '' || q.length < this.options.minChars) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.getSuggestions(q);
|
||||
}
|
||||
},
|
||||
|
||||
getQuery: function (value) {
|
||||
var delimiter = this.options.delimiter,
|
||||
parts;
|
||||
|
||||
if (!delimiter) {
|
||||
return $.trim(value);
|
||||
}
|
||||
parts = value.split(delimiter);
|
||||
return $.trim(parts[parts.length - 1]);
|
||||
},
|
||||
|
||||
getSuggestionsLocal: function (q) {
|
||||
q = q.toLowerCase();
|
||||
|
||||
return {
|
||||
suggestions: $.grep(this.options.lookup, function (suggestion) {
|
||||
return suggestion.value.toLowerCase().indexOf(q) !== -1;
|
||||
})
|
||||
};
|
||||
},
|
||||
|
||||
getSuggestions: function (q) {
|
||||
var response,
|
||||
that = this,
|
||||
options = that.options;
|
||||
|
||||
response = that.isLocal ? that.getSuggestionsLocal(q) : that.cachedResponse[q];
|
||||
|
||||
if (response && $.isArray(response.suggestions)) {
|
||||
that.suggestions = response.suggestions;
|
||||
that.suggest();
|
||||
} else if (!that.isBadQuery(q)) {
|
||||
that.options.params.query = q;
|
||||
$.ajax({
|
||||
url: options.serviceUrl,
|
||||
data: options.params,
|
||||
type: options.type,
|
||||
dataType: 'text'
|
||||
}).done(function (txt) {
|
||||
that.processResponse(txt);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
isBadQuery: function (q) {
|
||||
var badQueries = this.badQueries,
|
||||
i = badQueries.length;
|
||||
|
||||
while (i--) {
|
||||
if (q.indexOf(badQueries[i]) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
this.visible = false;
|
||||
this.selectedIndex = -1;
|
||||
$(this.suggestionsContainer).hide();
|
||||
},
|
||||
|
||||
suggest: function () {
|
||||
if (this.suggestions.length === 0) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var len = this.suggestions.length,
|
||||
formatResults = this.options.formatResult,
|
||||
value = this.getQuery(this.currentValue),
|
||||
suggestion,
|
||||
className = this.classes.suggestion,
|
||||
classSelected = this.classes.selected,
|
||||
container = $(this.suggestionsContainer),
|
||||
html = '',
|
||||
i;
|
||||
|
||||
// Build suggestions inner HTML:
|
||||
for (i = 0; i < len; i++) {
|
||||
suggestion = this.suggestions[i];
|
||||
html += '<div class="' + className + '" data-index="' + i + '">' + formatResults(suggestion, value) + '</div>';
|
||||
}
|
||||
|
||||
container.html(html).show();
|
||||
this.visible = true;
|
||||
|
||||
// Select first value by default:
|
||||
this.selectedIndex = 0;
|
||||
container.children().first().addClass(classSelected);
|
||||
},
|
||||
|
||||
processResponse: function (text) {
|
||||
var response = $.parseJSON(text);
|
||||
|
||||
// If suggestions is string array, convert them to supported format:
|
||||
if (typeof response.suggestions[0] === 'string') {
|
||||
response.suggestions = $.map(response.suggestions, function (value) {
|
||||
return { value: value, data: null };
|
||||
});
|
||||
}
|
||||
|
||||
// Cache results if cache is not disabled:
|
||||
if (!this.options.noCache) {
|
||||
this.cachedResponse[response.query] = response;
|
||||
if (response.suggestions.length === 0) {
|
||||
this.badQueries.push(response.query);
|
||||
}
|
||||
}
|
||||
|
||||
// Display suggestions only if returned query matches current value:
|
||||
if (response.query === this.getQuery(this.currentValue)) {
|
||||
this.suggestions = response.suggestions;
|
||||
this.suggest();
|
||||
}
|
||||
},
|
||||
|
||||
activate: function (index) {
|
||||
var activeItem,
|
||||
selected = this.classes.selected,
|
||||
container = $(this.suggestionsContainer),
|
||||
children = container.children();
|
||||
|
||||
container.children('.' + selected).removeClass(selected);
|
||||
|
||||
this.selectedIndex = index;
|
||||
|
||||
if (this.selectedIndex !== -1 && children.length > this.selectedIndex) {
|
||||
activeItem = children.get(this.selectedIndex);
|
||||
$(activeItem).addClass(selected);
|
||||
return activeItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
select: function (i) {
|
||||
var selectedValue = this.suggestions[i];
|
||||
|
||||
if (selectedValue) {
|
||||
this.el.val(selectedValue);
|
||||
this.ignoreValueChange = true;
|
||||
this.hide();
|
||||
this.onSelect(i);
|
||||
}
|
||||
},
|
||||
|
||||
change: function (i) {
|
||||
var onChange,
|
||||
me = this,
|
||||
selectedValue = this.suggestions[i],
|
||||
suggestion;
|
||||
|
||||
if (selectedValue) {
|
||||
suggestion = me.suggestions[i];
|
||||
me.el.val(me.getValue(suggestion.value));
|
||||
|
||||
onChange = me.options.onChange;
|
||||
if ($.isFunction(onChange)) {
|
||||
onChange(suggestion, me.el);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
moveUp: function () {
|
||||
if (this.selectedIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedIndex === 0) {
|
||||
$(this.suggestionsContainer).children().first().removeClass(this.classes.selected);
|
||||
this.selectedIndex = -1;
|
||||
this.el.val(this.currentValue);
|
||||
return;
|
||||
}
|
||||
|
||||
this.adjustScroll(this.selectedIndex - 1);
|
||||
},
|
||||
|
||||
moveDown: function () {
|
||||
if (this.selectedIndex === (this.suggestions.length - 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.adjustScroll(this.selectedIndex + 1);
|
||||
},
|
||||
|
||||
adjustScroll: function (index) {
|
||||
var activeItem = this.activate(index),
|
||||
offsetTop,
|
||||
upperBound,
|
||||
lowerBound,
|
||||
heightDelta = 25;
|
||||
|
||||
if (!activeItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
offsetTop = activeItem.offsetTop;
|
||||
upperBound = $(this.suggestionsContainer).scrollTop();
|
||||
lowerBound = upperBound + this.options.maxHeight - heightDelta;
|
||||
|
||||
if (offsetTop < upperBound) {
|
||||
$(this.suggestionsContainer).scrollTop(offsetTop);
|
||||
} else if (offsetTop > lowerBound) {
|
||||
$(this.suggestionsContainer).scrollTop(offsetTop - this.options.maxHeight + heightDelta);
|
||||
}
|
||||
|
||||
this.el.val(this.getValue(this.suggestions[index].value));
|
||||
},
|
||||
|
||||
onSelect: function (index) {
|
||||
var that = this,
|
||||
onSelectCallback = that.options.onSelect,
|
||||
suggestion = that.suggestions[index];
|
||||
|
||||
that.el.val(that.getValue(suggestion.value));
|
||||
|
||||
if ($.isFunction(onSelectCallback)) {
|
||||
onSelectCallback.call(that.element, suggestion);
|
||||
}
|
||||
},
|
||||
|
||||
getValue: function (value) {
|
||||
var that = this,
|
||||
delimiter = that.options.delimiter,
|
||||
currentValue,
|
||||
parts;
|
||||
|
||||
if (!delimiter) {
|
||||
return value;
|
||||
}
|
||||
|
||||
currentValue = that.currentValue;
|
||||
parts = currentValue.split(delimiter);
|
||||
|
||||
if (parts.length === 1) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
|
||||
}
|
||||
};
|
||||
|
||||
// Create chainable jQuery plugin:
|
||||
$.fn.autocomplete = function (options, args) {
|
||||
return this.each(function () {
|
||||
var dataKey = 'autocomplete',
|
||||
inputElement = $(this),
|
||||
instance;
|
||||
|
||||
if (typeof options === 'string') {
|
||||
instance = inputElement.data(dataKey);
|
||||
if (typeof instance[options] === 'function') {
|
||||
instance[options](args);
|
||||
}
|
||||
} else {
|
||||
instance = new Autocomplete(this, options);
|
||||
inputElement.data(dataKey, instance);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
}(jQuery));
|
2
dist/jquery.autocomplete.min.js
vendored
Normal file
2
dist/jquery.autocomplete.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
8
dist/jquery.autocomplete.min.js.map
vendored
Normal file
8
dist/jquery.autocomplete.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
21
license.txt
Normal file
21
license.txt
Normal file
@ -0,0 +1,21 @@
|
||||
Copyright 2012 DevBridge and other contributors
|
||||
http://www.devbridge.com/projects/autocomplete/jquery/
|
||||
|
||||
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.
|
64
readme.md
64
readme.md
@ -1 +1,63 @@
|
||||
# jQuery Autocomplete
|
||||
#Ajax AutoComplete for jQuery
|
||||
|
||||
Ajax Autocomplete for jQuery allows you to easily create
|
||||
autocomplete/autosuggest boxes for text input fields.
|
||||
|
||||
|
||||
##Usage
|
||||
|
||||
Html:
|
||||
|
||||
<input type="text" name="country" id="autocomplete"/>
|
||||
|
||||
Ajax lookup:
|
||||
|
||||
$('#autocomplete').autocomplete({
|
||||
serviceUrl: '/autocomplete/countries',
|
||||
onSelect: function (suggestion) {
|
||||
status.html('You selected: ' + suggestion);
|
||||
}
|
||||
});
|
||||
|
||||
Local lookup (no ajax):
|
||||
|
||||
$('#autocomplete').autocomplete({
|
||||
lookup: countries,
|
||||
onSelect: function (suggestion) {
|
||||
status.html('You selected: ' + suggestion);
|
||||
}
|
||||
});
|
||||
|
||||
##Response Format
|
||||
|
||||
Response from the server must be JSON formatted following JavaScript object:
|
||||
|
||||
{
|
||||
query: "Unit",
|
||||
suggestions: [
|
||||
{ value: "United Arab Emirates", data: "AE" },
|
||||
{ value: "United Kingdom", data: "UK" },
|
||||
{ value: "United States", data: "US" }
|
||||
]
|
||||
}
|
||||
|
||||
Data can be any value or object. Data object is passed to formatResults function and onSelect callback. Alternatively, if there is no data you can supply just a string array for suggestions:
|
||||
|
||||
{
|
||||
query: "Unit",
|
||||
suggestions: ["United Arab Emirates", "United Kingdom", "United States"]
|
||||
}
|
||||
|
||||
Important: query value must match original value in the input field, otherwise suggestions will not be displayed.
|
||||
|
||||
##License
|
||||
|
||||
Ajax Autocomplete for jQuery is freely distributable under the
|
||||
terms of an MIT-style [license](https://github.com/devbridge/jQuery-Autocomplete/dist/license.txt).
|
||||
|
||||
Copyright notice and permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
##Authors
|
||||
|
||||
Tomas Kirda / [@tkirda](https://twitter.com/tkirda)
|
||||
|
@ -4,20 +4,50 @@
|
||||
$(function () {
|
||||
'use strict';
|
||||
|
||||
// Load countries then initialize plugin:
|
||||
$.ajax({
|
||||
url: 'content/countries.txt',
|
||||
dataType: 'json'
|
||||
}).done(function (data) {
|
||||
var status = $('#selection'),
|
||||
countries = $.map(data, function (value) {
|
||||
return value;
|
||||
});
|
||||
}).done(function (source) {
|
||||
|
||||
$('#query').autocomplete({
|
||||
lookup: countries,
|
||||
onSelect: function (suggestion) {
|
||||
status.html('You selected: ' + suggestion);
|
||||
var countriesArray = $.map(source, function (value, key) { return { value: value, data: key }; }),
|
||||
countries = $.map(source, function (value) { return value; });
|
||||
|
||||
// Setup jQuery ajax mock:
|
||||
$.mockjax({
|
||||
url: '*',
|
||||
responseTime: 200,
|
||||
response: function (settings) {
|
||||
var query = settings.data.query,
|
||||
queryLowerCase = query.toLowerCase(),
|
||||
suggestions = $.grep(countries, function(country) {
|
||||
return country.toLowerCase().indexOf(queryLowerCase) !== -1;
|
||||
}),
|
||||
response = {
|
||||
query: query,
|
||||
suggestions: suggestions
|
||||
};
|
||||
|
||||
this.responseText = JSON.stringify(response);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize ajax autocomplete:
|
||||
$('#autocomplete-ajax').autocomplete({
|
||||
serviceUrl: '/autosuggest/service/url',
|
||||
onSelect: function(suggestion) {
|
||||
$('#selction-ajax').html('You selected: ' + suggestion.value + ', ' + suggestion.data);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize autocomplete with local lookup:
|
||||
$('#autocomplete').autocomplete({
|
||||
lookup: countriesArray,
|
||||
onSelect: function (suggestion) {
|
||||
$('#selection').html('You selected: ' + suggestion.value + ', ' + suggestion.data);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
523
scripts/jquery.mockjax.js
Normal file
523
scripts/jquery.mockjax.js
Normal file
@ -0,0 +1,523 @@
|
||||
/*!
|
||||
* MockJax - jQuery Plugin to Mock Ajax requests
|
||||
*
|
||||
* Version: 1.5.1
|
||||
* Released:
|
||||
* Home: http://github.com/appendto/jquery-mockjax
|
||||
* Author: Jonathan Sharp (http://jdsharp.com)
|
||||
* License: MIT,GPL
|
||||
*
|
||||
* Copyright (c) 2011 appendTo LLC.
|
||||
* Dual licensed under the MIT or GPL licenses.
|
||||
* http://appendto.com/open-source-licenses
|
||||
*/
|
||||
(function($) {
|
||||
var _ajax = $.ajax,
|
||||
mockHandlers = [],
|
||||
CALLBACK_REGEX = /=\?(&|$)/,
|
||||
jsc = (new Date()).getTime();
|
||||
|
||||
|
||||
// Parse the given XML string.
|
||||
function parseXML(xml) {
|
||||
if ( window['DOMParser'] == undefined && window.ActiveXObject ) {
|
||||
DOMParser = function() { };
|
||||
DOMParser.prototype.parseFromString = function( xmlString ) {
|
||||
var doc = new ActiveXObject('Microsoft.XMLDOM');
|
||||
doc.async = 'false';
|
||||
doc.loadXML( xmlString );
|
||||
return doc;
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
|
||||
if ( $.isXMLDoc( xmlDoc ) ) {
|
||||
var err = $('parsererror', xmlDoc);
|
||||
if ( err.length == 1 ) {
|
||||
throw('Error: ' + $(xmlDoc).text() );
|
||||
}
|
||||
} else {
|
||||
throw('Unable to parse XML');
|
||||
}
|
||||
} catch( e ) {
|
||||
var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
|
||||
$(document).trigger('xmlParseError', [ msg ]);
|
||||
return undefined;
|
||||
}
|
||||
return xmlDoc;
|
||||
}
|
||||
|
||||
// Trigger a jQuery event
|
||||
function trigger(s, type, args) {
|
||||
(s.context ? $(s.context) : $.event).trigger(type, args);
|
||||
}
|
||||
|
||||
// Check if the data field on the mock handler and the request match. This
|
||||
// can be used to restrict a mock handler to being used only when a certain
|
||||
// set of data is passed to it.
|
||||
function isMockDataEqual( mock, live ) {
|
||||
var identical = false;
|
||||
// Test for situations where the data is a querystring (not an object)
|
||||
if (typeof live === 'string') {
|
||||
// Querystring may be a regex
|
||||
return $.isFunction( mock.test ) ? mock.test(live) : mock == live;
|
||||
}
|
||||
$.each(mock, function(k, v) {
|
||||
if ( live[k] === undefined ) {
|
||||
identical = false;
|
||||
return identical;
|
||||
} else {
|
||||
identical = true;
|
||||
if ( typeof live[k] == 'object' ) {
|
||||
return isMockDataEqual(mock[k], live[k]);
|
||||
} else {
|
||||
if ( $.isFunction( mock[k].test ) ) {
|
||||
identical = mock[k].test(live[k]);
|
||||
} else {
|
||||
identical = ( mock[k] == live[k] );
|
||||
}
|
||||
return identical;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return identical;
|
||||
}
|
||||
|
||||
// Check the given handler should mock the given request
|
||||
function getMockForRequest( handler, requestSettings ) {
|
||||
// If the mock was registered with a function, let the function decide if we
|
||||
// want to mock this request
|
||||
if ( $.isFunction(handler) ) {
|
||||
return handler( requestSettings );
|
||||
}
|
||||
|
||||
// Inspect the URL of the request and check if the mock handler's url
|
||||
// matches the url for this ajax request
|
||||
if ( $.isFunction(handler.url.test) ) {
|
||||
// The user provided a regex for the url, test it
|
||||
if ( !handler.url.test( requestSettings.url ) ) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Look for a simple wildcard '*' or a direct URL match
|
||||
var star = handler.url.indexOf('*');
|
||||
if (handler.url !== requestSettings.url && star === -1 ||
|
||||
!new RegExp(handler.url.replace(/[-[\]{}()+?.,\\^$|#\s]/g, "\\$&").replace('*', '.+')).test(requestSettings.url)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Inspect the data submitted in the request (either POST body or GET query string)
|
||||
if ( handler.data && requestSettings.data ) {
|
||||
if ( !isMockDataEqual(handler.data, requestSettings.data) ) {
|
||||
// They're not identical, do not mock this request
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Inspect the request type
|
||||
if ( handler && handler.type &&
|
||||
handler.type.toLowerCase() != requestSettings.type.toLowerCase() ) {
|
||||
// The request type doesn't match (GET vs. POST)
|
||||
return null;
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
// If logging is enabled, log the mock to the console
|
||||
function logMock( mockHandler, requestSettings ) {
|
||||
var c = $.extend({}, $.mockjaxSettings, mockHandler);
|
||||
if ( c.log && $.isFunction(c.log) ) {
|
||||
c.log('MOCK ' + requestSettings.type.toUpperCase() + ': ' + requestSettings.url, $.extend({}, requestSettings));
|
||||
}
|
||||
}
|
||||
|
||||
// Process the xhr objects send operation
|
||||
function _xhrSend(mockHandler, requestSettings, origSettings) {
|
||||
|
||||
// This is a substitute for < 1.4 which lacks $.proxy
|
||||
var process = (function(that) {
|
||||
return function() {
|
||||
return (function() {
|
||||
// The request has returned
|
||||
this.status = mockHandler.status;
|
||||
this.statusText = mockHandler.statusText;
|
||||
this.readyState = 4;
|
||||
|
||||
// We have an executable function, call it to give
|
||||
// the mock handler a chance to update it's data
|
||||
if ( $.isFunction(mockHandler.response) ) {
|
||||
mockHandler.response(origSettings);
|
||||
}
|
||||
// Copy over our mock to our xhr object before passing control back to
|
||||
// jQuery's onreadystatechange callback
|
||||
if ( requestSettings.dataType == 'json' && ( typeof mockHandler.responseText == 'object' ) ) {
|
||||
this.responseText = JSON.stringify(mockHandler.responseText);
|
||||
} else if ( requestSettings.dataType == 'xml' ) {
|
||||
if ( typeof mockHandler.responseXML == 'string' ) {
|
||||
this.responseXML = parseXML(mockHandler.responseXML);
|
||||
} else {
|
||||
this.responseXML = mockHandler.responseXML;
|
||||
}
|
||||
} else {
|
||||
this.responseText = mockHandler.responseText;
|
||||
}
|
||||
if( typeof mockHandler.status == 'number' || typeof mockHandler.status == 'string' ) {
|
||||
this.status = mockHandler.status;
|
||||
}
|
||||
if( typeof mockHandler.statusText === "string") {
|
||||
this.statusText = mockHandler.statusText;
|
||||
}
|
||||
// jQuery < 1.4 doesn't have onreadystate change for xhr
|
||||
if ( $.isFunction(this.onreadystatechange) ) {
|
||||
if( mockHandler.isTimeout) {
|
||||
this.status = -1;
|
||||
}
|
||||
this.onreadystatechange( mockHandler.isTimeout ? 'timeout' : undefined );
|
||||
} else if ( mockHandler.isTimeout ) {
|
||||
// Fix for 1.3.2 timeout to keep success from firing.
|
||||
this.status = -1;
|
||||
}
|
||||
}).apply(that);
|
||||
};
|
||||
})(this);
|
||||
|
||||
if ( mockHandler.proxy ) {
|
||||
// We're proxying this request and loading in an external file instead
|
||||
_ajax({
|
||||
global: false,
|
||||
url: mockHandler.proxy,
|
||||
type: mockHandler.proxyType,
|
||||
data: mockHandler.data,
|
||||
dataType: requestSettings.dataType === "script" ? "text/plain" : requestSettings.dataType,
|
||||
complete: function(xhr, txt) {
|
||||
mockHandler.responseXML = xhr.responseXML;
|
||||
mockHandler.responseText = xhr.responseText;
|
||||
mockHandler.status = xhr.status;
|
||||
mockHandler.statusText = xhr.statusText;
|
||||
this.responseTimer = setTimeout(process, mockHandler.responseTime || 0);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// type == 'POST' || 'GET' || 'DELETE'
|
||||
if ( requestSettings.async === false ) {
|
||||
// TODO: Blocking delay
|
||||
process();
|
||||
} else {
|
||||
this.responseTimer = setTimeout(process, mockHandler.responseTime || 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct a mocked XHR Object
|
||||
function xhr(mockHandler, requestSettings, origSettings, origHandler) {
|
||||
// Extend with our default mockjax settings
|
||||
mockHandler = $.extend(true, {}, $.mockjaxSettings, mockHandler);
|
||||
|
||||
if (typeof mockHandler.headers === 'undefined') {
|
||||
mockHandler.headers = {};
|
||||
}
|
||||
if ( mockHandler.contentType ) {
|
||||
mockHandler.headers['content-type'] = mockHandler.contentType;
|
||||
}
|
||||
|
||||
return {
|
||||
status: mockHandler.status,
|
||||
statusText: mockHandler.statusText,
|
||||
readyState: 1,
|
||||
open: function() { },
|
||||
send: function() {
|
||||
origHandler.fired = true;
|
||||
_xhrSend.call(this, mockHandler, requestSettings, origSettings);
|
||||
},
|
||||
abort: function() {
|
||||
clearTimeout(this.responseTimer);
|
||||
},
|
||||
setRequestHeader: function(header, value) {
|
||||
mockHandler.headers[header] = value;
|
||||
},
|
||||
getResponseHeader: function(header) {
|
||||
// 'Last-modified', 'Etag', 'content-type' are all checked by jQuery
|
||||
if ( mockHandler.headers && mockHandler.headers[header] ) {
|
||||
// Return arbitrary headers
|
||||
return mockHandler.headers[header];
|
||||
} else if ( header.toLowerCase() == 'last-modified' ) {
|
||||
return mockHandler.lastModified || (new Date()).toString();
|
||||
} else if ( header.toLowerCase() == 'etag' ) {
|
||||
return mockHandler.etag || '';
|
||||
} else if ( header.toLowerCase() == 'content-type' ) {
|
||||
return mockHandler.contentType || 'text/plain';
|
||||
}
|
||||
},
|
||||
getAllResponseHeaders: function() {
|
||||
var headers = '';
|
||||
$.each(mockHandler.headers, function(k, v) {
|
||||
headers += k + ': ' + v + "\n";
|
||||
});
|
||||
return headers;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Process a JSONP mock request.
|
||||
function processJsonpMock( requestSettings, mockHandler, origSettings ) {
|
||||
// Handle JSONP Parameter Callbacks, we need to replicate some of the jQuery core here
|
||||
// because there isn't an easy hook for the cross domain script tag of jsonp
|
||||
|
||||
processJsonpUrl( requestSettings );
|
||||
|
||||
requestSettings.dataType = "json";
|
||||
if(requestSettings.data && CALLBACK_REGEX.test(requestSettings.data) || CALLBACK_REGEX.test(requestSettings.url)) {
|
||||
createJsonpCallback(requestSettings, mockHandler);
|
||||
|
||||
// We need to make sure
|
||||
// that a JSONP style response is executed properly
|
||||
|
||||
var rurl = /^(\w+:)?\/\/([^\/?#]+)/,
|
||||
parts = rurl.exec( requestSettings.url ),
|
||||
remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
|
||||
|
||||
requestSettings.dataType = "script";
|
||||
if(requestSettings.type.toUpperCase() === "GET" && remote ) {
|
||||
var newMockReturn = processJsonpRequest( requestSettings, mockHandler, origSettings );
|
||||
|
||||
// Check if we are supposed to return a Deferred back to the mock call, or just
|
||||
// signal success
|
||||
if(newMockReturn) {
|
||||
return newMockReturn;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Append the required callback parameter to the end of the request URL, for a JSONP request
|
||||
function processJsonpUrl( requestSettings ) {
|
||||
if ( requestSettings.type.toUpperCase() === "GET" ) {
|
||||
if ( !CALLBACK_REGEX.test( requestSettings.url ) ) {
|
||||
requestSettings.url += (/\?/.test( requestSettings.url ) ? "&" : "?") +
|
||||
(requestSettings.jsonp || "callback") + "=?";
|
||||
}
|
||||
} else if ( !requestSettings.data || !CALLBACK_REGEX.test(requestSettings.data) ) {
|
||||
requestSettings.data = (requestSettings.data ? requestSettings.data + "&" : "") + (requestSettings.jsonp || "callback") + "=?";
|
||||
}
|
||||
}
|
||||
|
||||
// Process a JSONP request by evaluating the mocked response text
|
||||
function processJsonpRequest( requestSettings, mockHandler, origSettings ) {
|
||||
// Synthesize the mock request for adding a script tag
|
||||
var callbackContext = origSettings && origSettings.context || requestSettings,
|
||||
newMock = null;
|
||||
|
||||
|
||||
// If the response handler on the moock is a function, call it
|
||||
if ( mockHandler.response && $.isFunction(mockHandler.response) ) {
|
||||
mockHandler.response(origSettings);
|
||||
} else {
|
||||
|
||||
// Evaluate the responseText javascript in a global context
|
||||
if( typeof mockHandler.responseText === 'object' ) {
|
||||
$.globalEval( '(' + JSON.stringify( mockHandler.responseText ) + ')');
|
||||
} else {
|
||||
$.globalEval( '(' + mockHandler.responseText + ')');
|
||||
}
|
||||
}
|
||||
|
||||
// Successful response
|
||||
jsonpSuccess( requestSettings, mockHandler );
|
||||
jsonpComplete( requestSettings, mockHandler );
|
||||
|
||||
// If we are running under jQuery 1.5+, return a deferred object
|
||||
if($.Deferred){
|
||||
newMock = new $.Deferred();
|
||||
if(typeof mockHandler.responseText == "object"){
|
||||
newMock.resolveWith( callbackContext, [mockHandler.responseText] );
|
||||
}
|
||||
else{
|
||||
newMock.resolveWith( callbackContext, [$.parseJSON( mockHandler.responseText )] );
|
||||
}
|
||||
}
|
||||
return newMock;
|
||||
}
|
||||
|
||||
|
||||
// Create the required JSONP callback function for the request
|
||||
function createJsonpCallback( requestSettings, mockHandler ) {
|
||||
jsonp = requestSettings.jsonpCallback || ("jsonp" + jsc++);
|
||||
|
||||
// Replace the =? sequence both in the query string and the data
|
||||
if ( requestSettings.data ) {
|
||||
requestSettings.data = (requestSettings.data + "").replace(CALLBACK_REGEX, "=" + jsonp + "$1");
|
||||
}
|
||||
|
||||
requestSettings.url = requestSettings.url.replace(CALLBACK_REGEX, "=" + jsonp + "$1");
|
||||
|
||||
|
||||
// Handle JSONP-style loading
|
||||
window[ jsonp ] = window[ jsonp ] || function( tmp ) {
|
||||
data = tmp;
|
||||
jsonpSuccess( requestSettings, mockHandler );
|
||||
jsonpComplete( requestSettings, mockHandler );
|
||||
// Garbage collect
|
||||
window[ jsonp ] = undefined;
|
||||
|
||||
try {
|
||||
delete window[ jsonp ];
|
||||
} catch(e) {}
|
||||
|
||||
if ( head ) {
|
||||
head.removeChild( script );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// The JSONP request was successful
|
||||
function jsonpSuccess(requestSettings, mockHandler) {
|
||||
// If a local callback was specified, fire it and pass it the data
|
||||
if ( requestSettings.success ) {
|
||||
requestSettings.success.call( callbackContext, ( mockHandler.response ? mockHandler.response.toString() : mockHandler.responseText || ''), status, {} );
|
||||
}
|
||||
|
||||
// Fire the global callback
|
||||
if ( requestSettings.global ) {
|
||||
trigger(requestSettings, "ajaxSuccess", [{}, requestSettings] );
|
||||
}
|
||||
}
|
||||
|
||||
// The JSONP request was completed
|
||||
function jsonpComplete(requestSettings, mockHandler) {
|
||||
// Process result
|
||||
if ( requestSettings.complete ) {
|
||||
requestSettings.complete.call( callbackContext, {} , status );
|
||||
}
|
||||
|
||||
// The request was completed
|
||||
if ( requestSettings.global ) {
|
||||
trigger( "ajaxComplete", [{}, requestSettings] );
|
||||
}
|
||||
|
||||
// Handle the global AJAX counter
|
||||
if ( requestSettings.global && ! --$.active ) {
|
||||
$.event.trigger( "ajaxStop" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The core $.ajax replacement.
|
||||
function handleAjax( url, origSettings ) {
|
||||
var mockRequest, requestSettings, mockHandler;
|
||||
|
||||
// If url is an object, simulate pre-1.5 signature
|
||||
if ( typeof url === "object" ) {
|
||||
origSettings = url;
|
||||
url = undefined;
|
||||
} else {
|
||||
// work around to support 1.5 signature
|
||||
origSettings.url = url;
|
||||
}
|
||||
|
||||
// Extend the original settings for the request
|
||||
requestSettings = $.extend(true, {}, $.ajaxSettings, origSettings);
|
||||
|
||||
// Iterate over our mock handlers (in registration order) until we find
|
||||
// one that is willing to intercept the request
|
||||
for(var k = 0; k < mockHandlers.length; k++) {
|
||||
if ( !mockHandlers[k] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mockHandler = getMockForRequest( mockHandlers[k], requestSettings );
|
||||
if(!mockHandler) {
|
||||
// No valid mock found for this request
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle console logging
|
||||
logMock( mockHandler, requestSettings );
|
||||
|
||||
|
||||
if ( requestSettings.dataType === "jsonp" ) {
|
||||
if ((mockRequest = processJsonpMock( requestSettings, mockHandler, origSettings ))) {
|
||||
// This mock will handle the JSONP request
|
||||
return mockRequest;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Removed to fix #54 - keep the mocking data object intact
|
||||
//mockHandler.data = requestSettings.data;
|
||||
|
||||
mockHandler.cache = requestSettings.cache;
|
||||
mockHandler.timeout = requestSettings.timeout;
|
||||
mockHandler.global = requestSettings.global;
|
||||
|
||||
(function(mockHandler, requestSettings, origSettings, origHandler) {
|
||||
mockRequest = _ajax.call($, $.extend(true, {}, origSettings, {
|
||||
// Mock the XHR object
|
||||
xhr: function() { return xhr( mockHandler, requestSettings, origSettings, origHandler ) }
|
||||
}));
|
||||
})(mockHandler, requestSettings, origSettings, mockHandlers[k]);
|
||||
|
||||
return mockRequest;
|
||||
}
|
||||
|
||||
// We don't have a mock request, trigger a normal request
|
||||
return _ajax.apply($, [origSettings]);
|
||||
}
|
||||
|
||||
|
||||
// Public
|
||||
|
||||
$.extend({
|
||||
ajax: handleAjax
|
||||
});
|
||||
|
||||
$.mockjaxSettings = {
|
||||
//url: null,
|
||||
//type: 'GET',
|
||||
log: function( msg ) {
|
||||
if ( window[ 'console' ] && window.console.log ) {
|
||||
window.console.log.apply( console, arguments );
|
||||
}
|
||||
},
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
responseTime: 500,
|
||||
isTimeout: false,
|
||||
contentType: 'text/plain',
|
||||
response: '',
|
||||
responseText: '',
|
||||
responseXML: '',
|
||||
proxy: '',
|
||||
proxyType: 'GET',
|
||||
|
||||
lastModified: null,
|
||||
etag: '',
|
||||
headers: {
|
||||
etag: 'IJF@H#@923uf8023hFO@I#H#',
|
||||
'content-type' : 'text/plain'
|
||||
}
|
||||
};
|
||||
|
||||
$.mockjax = function(settings) {
|
||||
var i = mockHandlers.length;
|
||||
mockHandlers[i] = settings;
|
||||
return i;
|
||||
};
|
||||
$.mockjaxClear = function(i) {
|
||||
if ( arguments.length == 1 ) {
|
||||
mockHandlers[i] = null;
|
||||
} else {
|
||||
mockHandlers = [];
|
||||
}
|
||||
};
|
||||
$.mockjax.handler = function(i) {
|
||||
if ( arguments.length == 1 ) {
|
||||
return mockHandlers[i];
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
60
spec/autocompleteBehavior.js
Normal file
60
spec/autocompleteBehavior.js
Normal file
@ -0,0 +1,60 @@
|
||||
/*jslint vars: true*/
|
||||
/*global describe, it, expect, $*/
|
||||
|
||||
describe('Autocomplete', function () {
|
||||
'use strict';
|
||||
|
||||
it('Should initialize autocomplete options', function () {
|
||||
var input = document.createElement('input'),
|
||||
options = { serviceUrl: '/autocomplete/service/url' },
|
||||
autocomplete = new $.Autocomplete(input, options);
|
||||
|
||||
expect(autocomplete.options.serviceUrl).toEqual(options.serviceUrl);
|
||||
expect(autocomplete.suggestionsContainer).not.toBeNull();
|
||||
});
|
||||
|
||||
it('Should set autocomplete attribute to "off"', function () {
|
||||
var input = document.createElement('input'),
|
||||
autocomplete = new $.Autocomplete(input, {});
|
||||
|
||||
expect(autocomplete).not.toBeNull();
|
||||
expect(input.getAttribute('autocomplete')).toEqual('off');
|
||||
});
|
||||
|
||||
it('Should get current value', function () {
|
||||
var input = document.createElement('input'),
|
||||
autocomplete = new $.Autocomplete(input, {
|
||||
lookup: [{ value: 'Jamaica', data: 'B' }]
|
||||
});
|
||||
|
||||
input.value = 'Jam';
|
||||
autocomplete.onValueChange();
|
||||
|
||||
expect(autocomplete.visible).toBe(true);
|
||||
expect(autocomplete.currentValue).toEqual('Jam');
|
||||
});
|
||||
|
||||
it('Verify onSelect callback', function () {
|
||||
var input = document.createElement('input'),
|
||||
context,
|
||||
value,
|
||||
data,
|
||||
autocomplete = new $.Autocomplete(input, {
|
||||
lookup: [{ value: 'A', data: 'B' }],
|
||||
onSelect: function (suggestion) {
|
||||
context = this;
|
||||
value = suggestion.value;
|
||||
data = suggestion.data;
|
||||
}
|
||||
});
|
||||
|
||||
input.value = 'A';
|
||||
autocomplete.onValueChange();
|
||||
autocomplete.select(0);
|
||||
|
||||
expect(context).toEqual(input);
|
||||
expect(value).toEqual('A');
|
||||
expect(data).toEqual('B');
|
||||
});
|
||||
|
||||
});
|
20
spec/lib/jasmine-1.3.1/MIT.LICENSE
Normal file
20
spec/lib/jasmine-1.3.1/MIT.LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright (c) 2008-2011 Pivotal Labs
|
||||
|
||||
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.
|
681
spec/lib/jasmine-1.3.1/jasmine-html.js
Normal file
681
spec/lib/jasmine-1.3.1/jasmine-html.js
Normal file
@ -0,0 +1,681 @@
|
||||
jasmine.HtmlReporterHelpers = {};
|
||||
|
||||
jasmine.HtmlReporterHelpers.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.HtmlReporterHelpers.getSpecStatus = function(child) {
|
||||
var results = child.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.skipped) {
|
||||
status = 'skipped';
|
||||
}
|
||||
|
||||
return status;
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
|
||||
var parentDiv = this.dom.summary;
|
||||
var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
|
||||
var parent = child[parentSuite];
|
||||
|
||||
if (parent) {
|
||||
if (typeof this.views.suites[parent.id] == 'undefined') {
|
||||
this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
|
||||
}
|
||||
parentDiv = this.views.suites[parent.id].element;
|
||||
}
|
||||
|
||||
parentDiv.appendChild(childElement);
|
||||
};
|
||||
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
|
||||
for(var fn in jasmine.HtmlReporterHelpers) {
|
||||
ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter = function(_doc) {
|
||||
var self = this;
|
||||
var doc = _doc || window.document;
|
||||
|
||||
var reporterView;
|
||||
|
||||
var dom = {};
|
||||
|
||||
// Jasmine Reporter Public Interface
|
||||
self.logRunningSpecs = false;
|
||||
|
||||
self.reportRunnerStarting = function(runner) {
|
||||
var specs = runner.specs() || [];
|
||||
|
||||
if (specs.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
createReporterDom(runner.env.versionString());
|
||||
doc.body.appendChild(dom.reporter);
|
||||
setExceptionHandling();
|
||||
|
||||
reporterView = new jasmine.HtmlReporter.ReporterView(dom);
|
||||
reporterView.addSpecs(specs, self.specFilter);
|
||||
};
|
||||
|
||||
self.reportRunnerResults = function(runner) {
|
||||
reporterView && reporterView.complete();
|
||||
};
|
||||
|
||||
self.reportSuiteResults = function(suite) {
|
||||
reporterView.suiteComplete(suite);
|
||||
};
|
||||
|
||||
self.reportSpecStarting = function(spec) {
|
||||
if (self.logRunningSpecs) {
|
||||
self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
|
||||
}
|
||||
};
|
||||
|
||||
self.reportSpecResults = function(spec) {
|
||||
reporterView.specComplete(spec);
|
||||
};
|
||||
|
||||
self.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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.specFilter = function(spec) {
|
||||
if (!focusedSpecName()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return spec.getFullName().indexOf(focusedSpecName()) === 0;
|
||||
};
|
||||
|
||||
return self;
|
||||
|
||||
function focusedSpecName() {
|
||||
var specName;
|
||||
|
||||
(function memoizeFocusedSpec() {
|
||||
if (specName) {
|
||||
return;
|
||||
}
|
||||
|
||||
var paramMap = [];
|
||||
var params = jasmine.HtmlReporter.parameters(doc);
|
||||
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var p = params[i].split('=');
|
||||
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
|
||||
}
|
||||
|
||||
specName = paramMap.spec;
|
||||
})();
|
||||
|
||||
return specName;
|
||||
}
|
||||
|
||||
function createReporterDom(version) {
|
||||
dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
|
||||
dom.banner = self.createDom('div', { className: 'banner' },
|
||||
self.createDom('span', { className: 'title' }, "Jasmine "),
|
||||
self.createDom('span', { className: 'version' }, version)),
|
||||
|
||||
dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
|
||||
dom.alert = self.createDom('div', {className: 'alert'},
|
||||
self.createDom('span', { className: 'exceptions' },
|
||||
self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'),
|
||||
self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))),
|
||||
dom.results = self.createDom('div', {className: 'results'},
|
||||
dom.summary = self.createDom('div', { className: 'summary' }),
|
||||
dom.details = self.createDom('div', { id: 'details' }))
|
||||
);
|
||||
}
|
||||
|
||||
function noTryCatch() {
|
||||
return window.location.search.match(/catch=false/);
|
||||
}
|
||||
|
||||
function searchWithCatch() {
|
||||
var params = jasmine.HtmlReporter.parameters(window.document);
|
||||
var removed = false;
|
||||
var i = 0;
|
||||
|
||||
while (!removed && i < params.length) {
|
||||
if (params[i].match(/catch=/)) {
|
||||
params.splice(i, 1);
|
||||
removed = true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (jasmine.CATCH_EXCEPTIONS) {
|
||||
params.push("catch=false");
|
||||
}
|
||||
|
||||
return params.join("&");
|
||||
}
|
||||
|
||||
function setExceptionHandling() {
|
||||
var chxCatch = document.getElementById('no_try_catch');
|
||||
|
||||
if (noTryCatch()) {
|
||||
chxCatch.setAttribute('checked', true);
|
||||
jasmine.CATCH_EXCEPTIONS = false;
|
||||
}
|
||||
chxCatch.onclick = function() {
|
||||
window.location.search = searchWithCatch();
|
||||
};
|
||||
}
|
||||
};
|
||||
jasmine.HtmlReporter.parameters = function(doc) {
|
||||
var paramStr = doc.location.search.substring(1);
|
||||
var params = [];
|
||||
|
||||
if (paramStr.length > 0) {
|
||||
params = paramStr.split('&');
|
||||
}
|
||||
return params;
|
||||
}
|
||||
jasmine.HtmlReporter.sectionLink = function(sectionName) {
|
||||
var link = '?';
|
||||
var params = [];
|
||||
|
||||
if (sectionName) {
|
||||
params.push('spec=' + encodeURIComponent(sectionName));
|
||||
}
|
||||
if (!jasmine.CATCH_EXCEPTIONS) {
|
||||
params.push("catch=false");
|
||||
}
|
||||
if (params.length > 0) {
|
||||
link += params.join("&");
|
||||
}
|
||||
|
||||
return link;
|
||||
};
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
|
||||
jasmine.HtmlReporter.ReporterView = function(dom) {
|
||||
this.startedAt = new Date();
|
||||
this.runningSpecCount = 0;
|
||||
this.completeSpecCount = 0;
|
||||
this.passedCount = 0;
|
||||
this.failedCount = 0;
|
||||
this.skippedCount = 0;
|
||||
|
||||
this.createResultsMenu = function() {
|
||||
this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
|
||||
this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
|
||||
' | ',
|
||||
this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
|
||||
|
||||
this.summaryMenuItem.onclick = function() {
|
||||
dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
|
||||
};
|
||||
|
||||
this.detailsMenuItem.onclick = function() {
|
||||
showDetails();
|
||||
};
|
||||
};
|
||||
|
||||
this.addSpecs = function(specs, specFilter) {
|
||||
this.totalSpecCount = specs.length;
|
||||
|
||||
this.views = {
|
||||
specs: {},
|
||||
suites: {}
|
||||
};
|
||||
|
||||
for (var i = 0; i < specs.length; i++) {
|
||||
var spec = specs[i];
|
||||
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
|
||||
if (specFilter(spec)) {
|
||||
this.runningSpecCount++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.specComplete = function(spec) {
|
||||
this.completeSpecCount++;
|
||||
|
||||
if (isUndefined(this.views.specs[spec.id])) {
|
||||
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
|
||||
}
|
||||
|
||||
var specView = this.views.specs[spec.id];
|
||||
|
||||
switch (specView.status()) {
|
||||
case 'passed':
|
||||
this.passedCount++;
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
this.failedCount++;
|
||||
break;
|
||||
|
||||
case 'skipped':
|
||||
this.skippedCount++;
|
||||
break;
|
||||
}
|
||||
|
||||
specView.refresh();
|
||||
this.refresh();
|
||||
};
|
||||
|
||||
this.suiteComplete = function(suite) {
|
||||
var suiteView = this.views.suites[suite.id];
|
||||
if (isUndefined(suiteView)) {
|
||||
return;
|
||||
}
|
||||
suiteView.refresh();
|
||||
};
|
||||
|
||||
this.refresh = function() {
|
||||
|
||||
if (isUndefined(this.resultsMenu)) {
|
||||
this.createResultsMenu();
|
||||
}
|
||||
|
||||
// currently running UI
|
||||
if (isUndefined(this.runningAlert)) {
|
||||
this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" });
|
||||
dom.alert.appendChild(this.runningAlert);
|
||||
}
|
||||
this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
|
||||
|
||||
// skipped specs UI
|
||||
if (isUndefined(this.skippedAlert)) {
|
||||
this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" });
|
||||
}
|
||||
|
||||
this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
|
||||
|
||||
if (this.skippedCount === 1 && isDefined(dom.alert)) {
|
||||
dom.alert.appendChild(this.skippedAlert);
|
||||
}
|
||||
|
||||
// passing specs UI
|
||||
if (isUndefined(this.passedAlert)) {
|
||||
this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" });
|
||||
}
|
||||
this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
|
||||
|
||||
// failing specs UI
|
||||
if (isUndefined(this.failedAlert)) {
|
||||
this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
|
||||
}
|
||||
this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
|
||||
|
||||
if (this.failedCount === 1 && isDefined(dom.alert)) {
|
||||
dom.alert.appendChild(this.failedAlert);
|
||||
dom.alert.appendChild(this.resultsMenu);
|
||||
}
|
||||
|
||||
// summary info
|
||||
this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
|
||||
this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
|
||||
};
|
||||
|
||||
this.complete = function() {
|
||||
dom.alert.removeChild(this.runningAlert);
|
||||
|
||||
this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
|
||||
|
||||
if (this.failedCount === 0) {
|
||||
dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
|
||||
} else {
|
||||
showDetails();
|
||||
}
|
||||
|
||||
dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
function showDetails() {
|
||||
if (dom.reporter.className.search(/showDetails/) === -1) {
|
||||
dom.reporter.className += " showDetails";
|
||||
}
|
||||
}
|
||||
|
||||
function isUndefined(obj) {
|
||||
return typeof obj === 'undefined';
|
||||
}
|
||||
|
||||
function isDefined(obj) {
|
||||
return !isUndefined(obj);
|
||||
}
|
||||
|
||||
function specPluralizedFor(count) {
|
||||
var str = count + " spec";
|
||||
if (count > 1) {
|
||||
str += "s"
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
|
||||
|
||||
|
||||
jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
|
||||
this.spec = spec;
|
||||
this.dom = dom;
|
||||
this.views = views;
|
||||
|
||||
this.symbol = this.createDom('li', { className: 'pending' });
|
||||
this.dom.symbolSummary.appendChild(this.symbol);
|
||||
|
||||
this.summary = this.createDom('div', { className: 'specSummary' },
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
|
||||
title: this.spec.getFullName()
|
||||
}, this.spec.description)
|
||||
);
|
||||
|
||||
this.detail = this.createDom('div', { className: 'specDetail' },
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
|
||||
title: this.spec.getFullName()
|
||||
}, this.spec.getFullName())
|
||||
);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.status = function() {
|
||||
return this.getSpecStatus(this.spec);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
|
||||
this.symbol.className = this.status();
|
||||
|
||||
switch (this.status()) {
|
||||
case 'skipped':
|
||||
break;
|
||||
|
||||
case 'passed':
|
||||
this.appendSummaryToSuiteDiv();
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
this.appendSummaryToSuiteDiv();
|
||||
this.appendFailureDetail();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
|
||||
this.summary.className += ' ' + this.status();
|
||||
this.appendToSummary(this.spec, this.summary);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
|
||||
this.detail.className += ' ' + this.status();
|
||||
|
||||
var resultItems = this.spec.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) {
|
||||
this.detail.appendChild(messagesDiv);
|
||||
this.dom.details.appendChild(this.detail);
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
|
||||
this.suite = suite;
|
||||
this.dom = dom;
|
||||
this.views = views;
|
||||
|
||||
this.element = this.createDom('div', { className: 'suite' },
|
||||
this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description)
|
||||
);
|
||||
|
||||
this.appendToSummary(this.suite, this.element);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SuiteView.prototype.status = function() {
|
||||
return this.getSpecStatus(this.suite);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
|
||||
this.element.className += " " + this.status();
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
|
||||
|
||||
/* @deprecated Use jasmine.HtmlReporter instead
|
||||
*/
|
||||
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', { id: 'TrivialReporter', 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;
|
||||
};
|
82
spec/lib/jasmine-1.3.1/jasmine.css
Normal file
82
spec/lib/jasmine-1.3.1/jasmine.css
Normal file
@ -0,0 +1,82 @@
|
||||
body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
|
||||
|
||||
#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
|
||||
#HTMLReporter a { text-decoration: none; }
|
||||
#HTMLReporter a:hover { text-decoration: underline; }
|
||||
#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
|
||||
#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
|
||||
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
|
||||
#HTMLReporter .version { color: #aaaaaa; }
|
||||
#HTMLReporter .banner { margin-top: 14px; }
|
||||
#HTMLReporter .duration { color: #aaaaaa; float: right; }
|
||||
#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
|
||||
#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
|
||||
#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
|
||||
#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
|
||||
#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
|
||||
#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
|
||||
#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
|
||||
#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
|
||||
#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
|
||||
#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
|
||||
#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
|
||||
#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
|
||||
#HTMLReporter .runningAlert { background-color: #666666; }
|
||||
#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
|
||||
#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
|
||||
#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
|
||||
#HTMLReporter .passingAlert { background-color: #a6b779; }
|
||||
#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
|
||||
#HTMLReporter .failingAlert { background-color: #cf867e; }
|
||||
#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
|
||||
#HTMLReporter .results { margin-top: 14px; }
|
||||
#HTMLReporter #details { display: none; }
|
||||
#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
|
||||
#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
|
||||
#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
|
||||
#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
|
||||
#HTMLReporter.showDetails .summary { display: none; }
|
||||
#HTMLReporter.showDetails #details { display: block; }
|
||||
#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
|
||||
#HTMLReporter .summary { margin-top: 14px; }
|
||||
#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
|
||||
#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
|
||||
#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
|
||||
#HTMLReporter .description + .suite { margin-top: 0; }
|
||||
#HTMLReporter .suite { margin-top: 14px; }
|
||||
#HTMLReporter .suite a { color: #333333; }
|
||||
#HTMLReporter #details .specDetail { margin-bottom: 28px; }
|
||||
#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
|
||||
#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
|
||||
#HTMLReporter .resultMessage span.result { display: block; }
|
||||
#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
|
||||
|
||||
#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
|
||||
#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
|
||||
#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
|
||||
#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
|
||||
#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
|
||||
#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
|
||||
#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
|
||||
#TrivialReporter .runner.running { background-color: yellow; }
|
||||
#TrivialReporter .options { text-align: right; font-size: .8em; }
|
||||
#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
|
||||
#TrivialReporter .suite .suite { margin: 5px; }
|
||||
#TrivialReporter .suite.passed { background-color: #dfd; }
|
||||
#TrivialReporter .suite.failed { background-color: #fdd; }
|
||||
#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
|
||||
#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
|
||||
#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
|
||||
#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
|
||||
#TrivialReporter .spec.skipped { background-color: #bbb; }
|
||||
#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
|
||||
#TrivialReporter .passed { background-color: #cfc; display: none; }
|
||||
#TrivialReporter .failed { background-color: #fbb; }
|
||||
#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
|
||||
#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
|
||||
#TrivialReporter .resultMessage .mismatch { color: black; }
|
||||
#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
|
||||
#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
|
||||
#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
|
||||
#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
|
||||
#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
|
2600
spec/lib/jasmine-1.3.1/jasmine.js
Normal file
2600
spec/lib/jasmine-1.3.1/jasmine.js
Normal file
File diff suppressed because it is too large
Load Diff
32
spec/runner.html
Normal file
32
spec/runner.html
Normal file
@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Autocomplete Spec</title>
|
||||
<!-- jasmine -->
|
||||
<link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css" />
|
||||
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script>
|
||||
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="../scripts/jquery-1.8.2.min.js"></script>
|
||||
|
||||
<!-- Autocomplete -->
|
||||
<script src="../src/jquery.autocomplete.js"></script>
|
||||
<!-- specs -->
|
||||
<script type="text/javascript" src="autocompleteBehavior.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
/*jslint vars: true; */
|
||||
(function () {
|
||||
var jasmineEnv = jasmine.getEnv();
|
||||
jasmineEnv.updateInterval = 500;
|
||||
|
||||
var htmlReporter = new jasmine.HtmlReporter();
|
||||
jasmineEnv.addReporter(htmlReporter);
|
||||
jasmineEnv.specFilter = htmlReporter.specFilter;
|
||||
jasmineEnv.execute();
|
||||
}());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,71 +1,104 @@
|
||||
/**
|
||||
* Ajax Autocomplete for jQuery, version 1.1.5
|
||||
* (c) 2012 Tomas Kirda, Vytautas Pranskunas
|
||||
* Ajax Autocomplete for jQuery, version 1.2
|
||||
* (c) 2012 Tomas Kirda
|
||||
*
|
||||
* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
|
||||
* For details, see the web site: http://www.devbridge.com/projects/autocomplete/jquery/
|
||||
*
|
||||
* Last Review: 11/08/2012
|
||||
* Last Review: 12/18/2012
|
||||
*/
|
||||
|
||||
/*jslint browser: true, white: true, plusplus: true */
|
||||
/*jslint browser: true, white: true, plusplus: true, vars: true */
|
||||
/*global window: true, document: true, clearInterval: true, setInterval: true, jQuery: true */
|
||||
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
var reEscape = new RegExp('(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'].join('|\\') + ')', 'g');
|
||||
var utils = (function () {
|
||||
return {
|
||||
|
||||
function fnFormatResult(value, data, currentValue) {
|
||||
var pattern = '(' + currentValue.replace(reEscape, '\\$1') + ')';
|
||||
return value.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
|
||||
}
|
||||
extend: function (target, source) {
|
||||
return $.extend(target, source);
|
||||
},
|
||||
|
||||
addEvent: function (element, eventType, handler) {
|
||||
if (element.addEventListener) {
|
||||
element.addEventListener(eventType, handler, false);
|
||||
} else if (element.attachEvent) {
|
||||
element.attachEvent('on' + eventType, handler);
|
||||
} else {
|
||||
throw new Error('Browser doesn\'t support addEventListener or attachEvent');
|
||||
}
|
||||
},
|
||||
|
||||
removeEvent: function (element, eventType, handler) {
|
||||
if (element.removeEventListener) {
|
||||
element.removeEventListener(eventType, handler, false);
|
||||
} else if (element.detachEvent) {
|
||||
element.detachEvent('on' + eventType, handler);
|
||||
}
|
||||
},
|
||||
|
||||
createNode: function (html) {
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = html;
|
||||
return div.firstChild;
|
||||
}
|
||||
|
||||
};
|
||||
}());
|
||||
|
||||
function Autocomplete(el, options) {
|
||||
this.el = $(el);
|
||||
this.el.attr('autocomplete', 'off');
|
||||
this.suggestions = [];
|
||||
this.data = [];
|
||||
this.badQueries = [];
|
||||
this.selectedIndex = -1;
|
||||
this.currentValue = this.el.val();
|
||||
this.intervalId = 0;
|
||||
this.cachedResponse = [];
|
||||
this.onChangeInterval = null;
|
||||
this.onChange = null;
|
||||
this.ignoreValueChange = false;
|
||||
this.serviceUrl = options.serviceUrl;
|
||||
this.isLocal = false;
|
||||
this.options = {
|
||||
autoSubmit: false,
|
||||
minChars: 1,
|
||||
maxHeight: 300,
|
||||
deferRequestBy: 0,
|
||||
width: 0,
|
||||
highlight: true,
|
||||
params: {},
|
||||
fnFormatResult: fnFormatResult,
|
||||
delimiter: null,
|
||||
zIndex: 9999
|
||||
var that = this,
|
||||
defaults = {
|
||||
minChars: 1,
|
||||
maxHeight: 300,
|
||||
deferRequestBy: 0,
|
||||
width: 0,
|
||||
highlight: true,
|
||||
params: {},
|
||||
formatResult: Autocomplete.formatResult,
|
||||
delimiter: null,
|
||||
zIndex: 9999,
|
||||
type: 'GET',
|
||||
noCache: false,
|
||||
enforce: false
|
||||
};
|
||||
|
||||
// Shared variables:
|
||||
that.element = el;
|
||||
that.el = $(el);
|
||||
that.suggestions = [];
|
||||
that.badQueries = [];
|
||||
that.selectedIndex = -1;
|
||||
that.currentValue = that.element.value;
|
||||
that.intervalId = 0;
|
||||
that.cachedResponse = [];
|
||||
that.onChangeInterval = null;
|
||||
that.onChange = null;
|
||||
that.ignoreValueChange = false;
|
||||
that.isLocal = false;
|
||||
that.suggestionsContainer = null;
|
||||
that.options = defaults;
|
||||
that.classes = {
|
||||
selected: 'autocomplete-selected',
|
||||
suggestion: 'autocomplete-suggestion'
|
||||
};
|
||||
this.initialize();
|
||||
this.setOptions(options);
|
||||
this.el.data('autocomplete', this);
|
||||
|
||||
// Initialize and set options:
|
||||
that.initialize();
|
||||
that.setOptions(options);
|
||||
}
|
||||
|
||||
$.fn.autocomplete = function (options, args) {
|
||||
var instance;
|
||||
Autocomplete.utils = utils;
|
||||
|
||||
if (typeof options === 'string') {
|
||||
instance = this.data('autocomplete');
|
||||
if (typeof instance[options] === 'function') {
|
||||
instance[options](args);
|
||||
}
|
||||
} else {
|
||||
instance = new Autocomplete(this.get(0) || $('<input />'), options);
|
||||
}
|
||||
$.Autocomplete = Autocomplete;
|
||||
|
||||
return instance;
|
||||
Autocomplete.formatResult = function (suggestion, currentValue) {
|
||||
var reEscape = new RegExp('(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'].join('|\\') + ')', 'g'),
|
||||
pattern = '(' + currentValue.replace(reEscape, '\\$1') + ')';
|
||||
|
||||
return suggestion.value.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
|
||||
};
|
||||
|
||||
Autocomplete.prototype = {
|
||||
@ -73,60 +106,78 @@
|
||||
killerFn: null,
|
||||
|
||||
initialize: function () {
|
||||
var me = this,
|
||||
uid = Math.floor(Math.random() * 0x100000).toString(16),
|
||||
autocompleteElId = 'Autocomplete_' + uid,
|
||||
onKeyPress = function (e) {
|
||||
me.onKeyPress(e);
|
||||
};
|
||||
var that = this,
|
||||
suggestionSelector = '.' + that.classes.suggestion;
|
||||
|
||||
// Remove autocomplete attribute to prevent native suggestions:
|
||||
this.element.setAttribute('autocomplete', 'off');
|
||||
|
||||
this.killerFn = function (e) {
|
||||
if ($(e.target).parents('.autocomplete').size() === 0) {
|
||||
me.killSuggestions();
|
||||
me.disableKillerFn();
|
||||
if ($(e.target).closest('.autocomplete').length === 0) {
|
||||
that.killSuggestions();
|
||||
that.disableKillerFn();
|
||||
}
|
||||
};
|
||||
|
||||
if (!this.options.width) {
|
||||
// Determine suggestions width:
|
||||
if (!this.options.width || this.options.width === 'auto') {
|
||||
this.options.width = this.el.outerWidth();
|
||||
}
|
||||
|
||||
this.mainContainerId = 'AutocompleteContainter_' + uid;
|
||||
this.suggestionsContainer = Autocomplete.utils.createNode('<div class="autocomplete" style="position: absolute; display: none;"></div>');
|
||||
|
||||
$('<div id="' + this.mainContainerId + '" style="position:absolute;z-index:9999;"><div class="autocomplete-w1"><div class="autocomplete" id="' + autocompleteElId + '" style="display:none; width:300px;"></div></div></div>').appendTo('body');
|
||||
var container = $(this.suggestionsContainer);
|
||||
|
||||
container.appendTo('body').width(this.options.width);
|
||||
|
||||
// Listen for mouse over event on suggestions list:
|
||||
container.on('mouseover', suggestionSelector, function () {
|
||||
that.activate($(this).data('index'));
|
||||
});
|
||||
|
||||
// Listen for click event on suggestions list:
|
||||
container.on('click', suggestionSelector, function () {
|
||||
that.select($(this).data('index'));
|
||||
});
|
||||
|
||||
this.container = $('#' + autocompleteElId);
|
||||
this.fixPosition();
|
||||
|
||||
// Opera does not like keydown:
|
||||
if (window.opera) {
|
||||
this.el.keypress(onKeyPress);
|
||||
this.el.on('keypress', function (e) { that.onKeyPress(e); });
|
||||
} else {
|
||||
this.el.keydown(onKeyPress);
|
||||
this.el.on('keydown', function (e) { that.onKeyPress(e); });
|
||||
}
|
||||
|
||||
this.el.keyup(function (e) { me.onKeyUp(e); });
|
||||
this.el.blur(function () { me.enableKillerFn(); });
|
||||
this.el.focus(function () { me.fixPosition(); });
|
||||
this.el.change(function () { me.onValueChanged(); });
|
||||
this.el.on('keyup', function (e) { that.onKeyUp(e); });
|
||||
this.el.on('blur', function () { that.onBlur(); });
|
||||
this.el.on('focus', function () { that.fixPosition(); });
|
||||
},
|
||||
|
||||
extendOptions: function (options) {
|
||||
$.extend(this.options, options);
|
||||
onBlur: function () {
|
||||
this.enableKillerFn();
|
||||
},
|
||||
|
||||
setOptions: function (options) {
|
||||
var o = this.options;
|
||||
setOptions: function (suppliedOptions) {
|
||||
var options = this.options;
|
||||
|
||||
this.extendOptions(options);
|
||||
utils.extend(options, suppliedOptions);
|
||||
|
||||
if (o.lookup || o.isLocal) {
|
||||
this.isLocal = true;
|
||||
if ($.isArray(o.lookup)) { o.lookup = { suggestions: o.lookup, data: [] }; }
|
||||
this.isLocal = $.isArray(options.lookup);
|
||||
|
||||
// Transform lookup array if it's string array:
|
||||
if (this.isLocal && typeof options.lookup[0] === 'string') {
|
||||
options.lookup = $.map(options.lookup, function (value) {
|
||||
return { value: value, data: null };
|
||||
});
|
||||
}
|
||||
|
||||
$('#' + this.mainContainerId).css({ zIndex: o.zIndex });
|
||||
|
||||
this.container.css({ maxHeight: o.maxHeight + 'px', width: o.width });
|
||||
// Adjust height, width and z-index:
|
||||
$(this.suggestionsContainer).css({
|
||||
'max-height': options.maxHeight + 'px',
|
||||
'width': options.width,
|
||||
'z-index': options.zIndex
|
||||
});
|
||||
},
|
||||
|
||||
clearCache: function () {
|
||||
@ -144,25 +195,28 @@
|
||||
|
||||
fixPosition: function () {
|
||||
var offset = this.el.offset();
|
||||
$('#' + this.mainContainerId).css({ top: (offset.top + this.el.outerHeight()) + 'px', left: offset.left + 'px' });
|
||||
$(this.suggestionsContainer).css({
|
||||
top: (offset.top + this.el.outerHeight()) + 'px',
|
||||
left: offset.left + 'px'
|
||||
});
|
||||
},
|
||||
|
||||
enableKillerFn: function () {
|
||||
var me = this;
|
||||
$(document).bind('click', me.killerFn);
|
||||
var that = this;
|
||||
$(document).on('click', that.killerFn);
|
||||
},
|
||||
|
||||
disableKillerFn: function () {
|
||||
var me = this;
|
||||
$(document).unbind('click', me.killerFn);
|
||||
var that = this;
|
||||
$(document).off('click', that.killerFn);
|
||||
},
|
||||
|
||||
killSuggestions: function () {
|
||||
var me = this;
|
||||
me.stopKillSuggestions();
|
||||
me.intervalId = window.setInterval(function () {
|
||||
me.hide();
|
||||
me.stopKillSuggestions();
|
||||
var that = this;
|
||||
that.stopKillSuggestions();
|
||||
that.intervalId = window.setInterval(function () {
|
||||
that.hide();
|
||||
that.stopKillSuggestions();
|
||||
}, 300);
|
||||
},
|
||||
|
||||
@ -170,12 +224,14 @@
|
||||
window.clearInterval(this.intervalId);
|
||||
},
|
||||
|
||||
onValueChanged: function () {
|
||||
this.change(this.selectedIndex);
|
||||
},
|
||||
|
||||
onKeyPress: function (e) {
|
||||
if (this.disabled || !this.enabled) {
|
||||
// If suggestions are hidden and user presses arrow down, display suggestions:
|
||||
if (!this.disabled && !this.visible && e.keyCode === 40 && this.currentValue) {
|
||||
this.suggest();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.disabled || !this.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -227,7 +283,9 @@
|
||||
if (this.options.deferRequestBy > 0) {
|
||||
// Defer lookup in case when value changes very quickly:
|
||||
var me = this;
|
||||
this.onChangeInterval = setInterval(function () { me.onValueChange(); }, this.options.deferRequestBy);
|
||||
this.onChangeInterval = setInterval(function () {
|
||||
me.onValueChange();
|
||||
}, this.options.deferRequestBy);
|
||||
} else {
|
||||
this.onValueChange();
|
||||
}
|
||||
@ -236,7 +294,7 @@
|
||||
|
||||
onValueChange: function () {
|
||||
clearInterval(this.onChangeInterval);
|
||||
this.currentValue = this.el.val();
|
||||
this.currentValue = this.element.value;
|
||||
var q = this.getQuery(this.currentValue);
|
||||
this.selectedIndex = -1;
|
||||
|
||||
@ -252,58 +310,56 @@
|
||||
}
|
||||
},
|
||||
|
||||
getQuery: function (val) {
|
||||
var d, arr;
|
||||
d = this.options.delimiter;
|
||||
if (!d) {
|
||||
return $.trim(val);
|
||||
getQuery: function (value) {
|
||||
var delimiter = this.options.delimiter,
|
||||
parts;
|
||||
|
||||
if (!delimiter) {
|
||||
return $.trim(value);
|
||||
}
|
||||
arr = val.split(d);
|
||||
return $.trim(arr[arr.length - 1]);
|
||||
parts = value.split(delimiter);
|
||||
return $.trim(parts[parts.length - 1]);
|
||||
},
|
||||
|
||||
getSuggestionsLocal: function (q) {
|
||||
var ret, arr, len, val, i;
|
||||
|
||||
arr = this.options.lookup;
|
||||
len = arr.suggestions.length;
|
||||
ret = { suggestions: [], data: [] };
|
||||
q = q.toLowerCase();
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
val = arr.suggestions[i];
|
||||
if (val.toLowerCase().indexOf(q) === 0) {
|
||||
ret.suggestions.push(val);
|
||||
ret.data.push(arr.data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return {
|
||||
suggestions: $.grep(this.options.lookup, function (suggestion) {
|
||||
return suggestion.value.toLowerCase().indexOf(q) !== -1;
|
||||
})
|
||||
};
|
||||
},
|
||||
|
||||
getSuggestions: function (q) {
|
||||
var cr, me;
|
||||
var response,
|
||||
that = this,
|
||||
options = that.options;
|
||||
|
||||
cr = this.isLocal ? this.getSuggestionsLocal(q) : this.cachedResponse[q];
|
||||
response = that.isLocal ? that.getSuggestionsLocal(q) : that.cachedResponse[q];
|
||||
|
||||
if (cr && $.isArray(cr.suggestions)) {
|
||||
this.suggestions = cr.suggestions;
|
||||
this.data = cr.data;
|
||||
this.suggest();
|
||||
} else if (!this.isBadQuery(q)) {
|
||||
me = this;
|
||||
me.options.params.query = q;
|
||||
$.get(this.serviceUrl, me.options.params, function (txt) {
|
||||
me.processResponse(txt);
|
||||
}, 'text');
|
||||
if (response && $.isArray(response.suggestions)) {
|
||||
that.suggestions = response.suggestions;
|
||||
that.suggest();
|
||||
} else if (!that.isBadQuery(q)) {
|
||||
that.options.params.query = q;
|
||||
$.ajax({
|
||||
url: options.serviceUrl,
|
||||
data: options.params,
|
||||
type: options.type,
|
||||
dataType: 'text'
|
||||
}).done(function (txt) {
|
||||
that.processResponse(txt);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
isBadQuery: function (q) {
|
||||
var i = this.badQueries.length;
|
||||
var badQueries = this.badQueries,
|
||||
i = badQueries.length;
|
||||
|
||||
while (i--) {
|
||||
if (q.indexOf(this.badQueries[i]) === 0) {
|
||||
if (q.indexOf(badQueries[i]) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -312,9 +368,9 @@
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
this.enabled = false;
|
||||
this.visible = false;
|
||||
this.selectedIndex = -1;
|
||||
this.container.hide();
|
||||
$(this.suggestionsContainer).hide();
|
||||
},
|
||||
|
||||
suggest: function () {
|
||||
@ -323,53 +379,41 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var me, len, div, f, v, i, s, mOver, mClick;
|
||||
|
||||
me = this;
|
||||
len = this.suggestions.length;
|
||||
f = this.options.fnFormatResult;
|
||||
v = this.getQuery(this.currentValue);
|
||||
|
||||
mOver = function (xi) {
|
||||
return function () {
|
||||
me.activate(xi);
|
||||
};
|
||||
};
|
||||
|
||||
mClick = function (xi) {
|
||||
return function () {
|
||||
me.select(xi);
|
||||
};
|
||||
};
|
||||
|
||||
this.container.hide().empty();
|
||||
var len = this.suggestions.length,
|
||||
formatResults = this.options.formatResult,
|
||||
value = this.getQuery(this.currentValue),
|
||||
suggestion,
|
||||
className = this.classes.suggestion,
|
||||
classSelected = this.classes.selected,
|
||||
container = $(this.suggestionsContainer),
|
||||
html = '',
|
||||
i;
|
||||
|
||||
// Build suggestions inner HTML:
|
||||
for (i = 0; i < len; i++) {
|
||||
s = this.suggestions[i];
|
||||
div = $((me.selectedIndex === i ? '<div class="selected"' : '<div') + ' title="' + s + '">' + f(s, this.data[i], v) + '</div>');
|
||||
div.mouseover(mOver(i));
|
||||
div.click(mClick(i));
|
||||
this.container.append(div);
|
||||
suggestion = this.suggestions[i];
|
||||
html += '<div class="' + className + '" data-index="' + i + '">' + formatResults(suggestion, value) + '</div>';
|
||||
}
|
||||
|
||||
this.enabled = true;
|
||||
this.container.show();
|
||||
container.html(html).show();
|
||||
this.visible = true;
|
||||
|
||||
// Select first value by default:
|
||||
this.selectedIndex = 0;
|
||||
container.children().first().addClass(classSelected);
|
||||
},
|
||||
|
||||
processResponse: function (text) {
|
||||
/*jslint evil: true */
|
||||
var response;
|
||||
var response = $.parseJSON(text);
|
||||
|
||||
try {
|
||||
response = eval('(' + text + ')');
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$.isArray(response.data)) {
|
||||
response.data = [];
|
||||
// If suggestions is string array, convert them to supported format:
|
||||
if (typeof response.suggestions[0] === 'string') {
|
||||
response.suggestions = $.map(response.suggestions, function (value) {
|
||||
return { value: value, data: null };
|
||||
});
|
||||
}
|
||||
|
||||
// Cache results if cache is not disabled:
|
||||
if (!this.options.noCache) {
|
||||
this.cachedResponse[response.query] = response;
|
||||
if (response.suggestions.length === 0) {
|
||||
@ -377,53 +421,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Display suggestions only if returned query matches current value:
|
||||
if (response.query === this.getQuery(this.currentValue)) {
|
||||
this.suggestions = response.suggestions;
|
||||
this.data = response.data;
|
||||
this.suggest();
|
||||
}
|
||||
},
|
||||
|
||||
activate: function (index) {
|
||||
var divs, activeItem;
|
||||
var activeItem,
|
||||
selected = this.classes.selected,
|
||||
container = $(this.suggestionsContainer),
|
||||
children = container.children();
|
||||
|
||||
divs = this.container.children();
|
||||
// Clear previous selection:
|
||||
if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) {
|
||||
$(divs.get(this.selectedIndex)).removeClass();
|
||||
}
|
||||
container.children('.' + selected).removeClass(selected);
|
||||
|
||||
this.selectedIndex = index;
|
||||
|
||||
if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) {
|
||||
activeItem = divs.get(this.selectedIndex);
|
||||
$(activeItem).addClass('selected');
|
||||
if (this.selectedIndex !== -1 && children.length > this.selectedIndex) {
|
||||
activeItem = children.get(this.selectedIndex);
|
||||
$(activeItem).addClass(selected);
|
||||
return activeItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
deactivate: function (div, index) {
|
||||
div.className = '';
|
||||
if (this.selectedIndex === index) {
|
||||
this.selectedIndex = -1;
|
||||
}
|
||||
},
|
||||
|
||||
select: function (i) {
|
||||
var selectedValue, f;
|
||||
|
||||
selectedValue = this.suggestions[i];
|
||||
var selectedValue = this.suggestions[i];
|
||||
|
||||
if (selectedValue) {
|
||||
this.el.val(selectedValue);
|
||||
if (this.options.autoSubmit) {
|
||||
f = this.el.parents('form');
|
||||
if (f.length > 0) {
|
||||
f.get(0).submit();
|
||||
}
|
||||
}
|
||||
this.ignoreValueChange = true;
|
||||
this.hide();
|
||||
this.onSelect(i);
|
||||
@ -431,23 +459,19 @@
|
||||
},
|
||||
|
||||
change: function (i) {
|
||||
var selectedValue, onChange, me, s, d;
|
||||
|
||||
me = this;
|
||||
selectedValue = this.suggestions[i];
|
||||
var onChange,
|
||||
me = this,
|
||||
selectedValue = this.suggestions[i],
|
||||
suggestion;
|
||||
|
||||
if (selectedValue) {
|
||||
s = me.suggestions[i];
|
||||
d = me.data[i];
|
||||
me.el.val(me.getValue(s));
|
||||
} else {
|
||||
s = '';
|
||||
d = -1;
|
||||
}
|
||||
suggestion = me.suggestions[i];
|
||||
me.el.val(me.getValue(suggestion.value));
|
||||
|
||||
onChange = me.options.onChange;
|
||||
if ($.isFunction(onChange)) {
|
||||
onChange(s, d, me.el);
|
||||
onChange = me.options.onChange;
|
||||
if ($.isFunction(onChange)) {
|
||||
onChange(suggestion, me.el);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -457,7 +481,7 @@
|
||||
}
|
||||
|
||||
if (this.selectedIndex === 0) {
|
||||
this.container.children().get(0).className = '';
|
||||
$(this.suggestionsContainer).children().first().removeClass(this.classes.selected);
|
||||
this.selectedIndex = -1;
|
||||
this.el.val(this.currentValue);
|
||||
return;
|
||||
@ -474,54 +498,80 @@
|
||||
this.adjustScroll(this.selectedIndex + 1);
|
||||
},
|
||||
|
||||
adjustScroll: function (i) {
|
||||
var activeItem, offsetTop, upperBound, lowerBound;
|
||||
adjustScroll: function (index) {
|
||||
var activeItem = this.activate(index),
|
||||
offsetTop,
|
||||
upperBound,
|
||||
lowerBound,
|
||||
heightDelta = 25;
|
||||
|
||||
activeItem = this.activate(i);
|
||||
if (!activeItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
offsetTop = activeItem.offsetTop;
|
||||
upperBound = this.container.scrollTop();
|
||||
lowerBound = upperBound + this.options.maxHeight - 25;
|
||||
upperBound = $(this.suggestionsContainer).scrollTop();
|
||||
lowerBound = upperBound + this.options.maxHeight - heightDelta;
|
||||
|
||||
if (offsetTop < upperBound) {
|
||||
this.container.scrollTop(offsetTop);
|
||||
$(this.suggestionsContainer).scrollTop(offsetTop);
|
||||
} else if (offsetTop > lowerBound) {
|
||||
this.container.scrollTop(offsetTop - this.options.maxHeight + 25);
|
||||
$(this.suggestionsContainer).scrollTop(offsetTop - this.options.maxHeight + heightDelta);
|
||||
}
|
||||
|
||||
this.el.val(this.getValue(this.suggestions[i]));
|
||||
this.el.val(this.getValue(this.suggestions[index].value));
|
||||
},
|
||||
|
||||
onSelect: function (i) {
|
||||
var me = this,
|
||||
callback = me.options.onSelect,
|
||||
sugestion = me.suggestions[i],
|
||||
data = me.data[i];
|
||||
onSelect: function (index) {
|
||||
var that = this,
|
||||
onSelectCallback = that.options.onSelect,
|
||||
suggestion = that.suggestions[index];
|
||||
|
||||
me.el.val(me.getValue(sugestion));
|
||||
that.el.val(that.getValue(suggestion.value));
|
||||
|
||||
if ($.isFunction(callback)) {
|
||||
callback(sugestion, data, me.el);
|
||||
if ($.isFunction(onSelectCallback)) {
|
||||
onSelectCallback.call(that.element, suggestion);
|
||||
}
|
||||
},
|
||||
|
||||
getValue: function (value) {
|
||||
var me = this,
|
||||
separator = me.options.delimiter,
|
||||
var that = this,
|
||||
delimiter = that.options.delimiter,
|
||||
currentValue,
|
||||
array;
|
||||
parts;
|
||||
|
||||
if (!separator) {
|
||||
if (!delimiter) {
|
||||
return value;
|
||||
}
|
||||
|
||||
currentValue = me.currentValue;
|
||||
array = currentValue.split(separator);
|
||||
currentValue = that.currentValue;
|
||||
parts = currentValue.split(delimiter);
|
||||
|
||||
if (array.length === 1) {
|
||||
if (parts.length === 1) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return currentValue.substr(0, currentValue.length - array[array.length - 1].length) + value;
|
||||
return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
|
||||
}
|
||||
};
|
||||
|
||||
// Create chainable jQuery plugin:
|
||||
$.fn.autocomplete = function (options, args) {
|
||||
return this.each(function () {
|
||||
var dataKey = 'autocomplete',
|
||||
inputElement = $(this),
|
||||
instance;
|
||||
|
||||
if (typeof options === 'string') {
|
||||
instance = inputElement.data(dataKey);
|
||||
if (typeof instance[options] === 'function') {
|
||||
instance[options](args);
|
||||
}
|
||||
} else {
|
||||
instance = new Autocomplete(this, options);
|
||||
inputElement.data(dataKey, instance);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
|
Loading…
Reference in New Issue
Block a user