Skip to content

Instantly share code, notes, and snippets.

@noblethrasher
Last active December 27, 2015 10:39
Show Gist options
  • Save noblethrasher/7313193 to your computer and use it in GitHub Desktop.
Save noblethrasher/7313193 to your computer and use it in GitHub Desktop.
Yet another JavaScript library...
window.addHandler = window.addEventListener || window.attachEvent;
window.removeHandler = window.removeEventListener || window.detachEvent;
Object.prototype.isArray = function ()
{
return this.hasOwnProperty('length') && this.pop != null && this.push != null && this.join != null;
}
var Random = new (function ()
{
var get_upper = function (rnd)
{
return String.fromCharCode((Math.random() * 26) + 65)
}
var get_lower = function ()
{
return String.fromCharCode((Math.random() * 26) + 96)
}
var get_number = function ()
{
return String.fromCharCode((Math.random() * 10) + 48)
}
var fns = [get_upper, get_lower, get_number];
this.getRandomString = function (n)
{
if (n == null)
n = 8;
var chars = new Array();
for (var i = 0; i < n; i++)
chars.push(fns[Math.floor(Math.random() * 3)]());
return chars.join('');
}
});
function Xhr(url, success, failure)
{
var xhr = new XMLHttpRequest();
this.responseType = 'json';
var that = this;
xhr.onreadystatechange = function ()
{
if (success != null)
{
if (xhr.readyState == 4 && xhr.statusText == 'OK')
{
var data = null;
switch (that.responseType)
{
case 'json':
case 'application/json':
case 'text/json':
case 'application/javascript':
{
eval('data = ' + xhr.responseText);
break;
}
case 'text':
case 'text/html':
{
data = xhr.responseText;
break;
}
case 'xml':
case 'text/xml':
case 'application/xml':
case 'application/xhtml+xml':
{
data = xhr.responseXML;
break;
}
default:
{
data = xhr.responseText;
break;
}
}
success(data);
}
}
}
this.exec = function (method)
{
var args = new Array();
for (var p in this)
{
if (p == 'exec' || p == 'responseType')
continue;
if (this.hasOwnProperty(p) && this[p] != null)
args.push(p + '=' + encodeURIComponent(this[p]));
}
var query = null;
if (args.length > 0)
{
if (method == null)
method = 'POST';
query = args.join('&');
xhr.open(method, url);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
console.log(query);
}
else
{
if (method == null)
method = 'GET';
xhr.open(method, url);
}
xhr.responseType = this.responseType;
xhr.send(query);
}
}
if (window.attachEvent)
{
HTMLElement.prototype.addHandler = function (event, fn)
{
if (event.substring(0, 2).toLowerCase() != 'on')
event = 'on' + event;
this.attachEvent(event, fn);
}
HTMLElement.prototype.removeHandler = function (event, fn)
{
if (event.substring(0, 2).toLowerCase() != 'on')
event = 'on' + event;
this.detachEvent(event, fn);
}
}
if (window.addEventListener)
{
HTMLElement.prototype.addHandler = function (event, fn, capture)
{
if (event.substring(0, 2).toLowerCase() == 'on')
event = event.substring(2);
this.addEventListener(event, fn, capture);
}
HTMLElement.prototype.removeHandler = function (event, fn, capture)
{
if (event.substring(0, 2).toLowerCase() == 'on')
event = event.substring(2);
this.removeEventListener(event, fn, capture);
}
}
//credit and thanks to http://stackoverflow.com/a/3471664/3927
HTMLElement.prototype.getOffset = function findPos()
{
var obj = this;
var obj2 = obj;
var curtop = 0;
var curleft = 0;
if (document.getElementById || document.all)
{
do
{
curleft += obj.offsetLeft - obj.scrollLeft;
curtop += obj.offsetTop - obj.scrollTop;
obj = obj.offsetParent;
obj2 = obj2.parentNode;
while (obj2 != obj)
{
curleft -= obj2.scrollLeft;
curtop -= obj2.scrollTop;
obj2 = obj2.parentNode;
}
} while (obj.offsetParent)
} else if (document.layers)
{
curtop += obj.y;
curleft += obj.x;
}
return { x: curleft, y: curtop };
}
function find(id)
{
return document.getElementById(id);
}
function make(tag)
{
return document.createElement(tag);
}
function autoSuggest(input, src)
{
if (typeof (input) == 'string')
input = document.getElementById(input);
if (typeof (src) == 'string')
{
var url = src;
src = function () { return url; }
}
if (typeof (src) != 'function' || typeof(src()) != 'string')
throw "'src' parameter must either be a string or a function that returns a string";
if (input == null)
throw "No input element specified";
//we need a way to determine whether the newly focused element should keep the suggest list open whenever either the input or any of the (dynamically added) suggested items loses focus
//if the newly focused element has the secret_context_key (of if it's the input element), we keep the suggest list open, otherwise we close it
var secret_context_key = Random.getRandomString(8);
var input_container = make('span');
input.parentNode.replaceChild(input_container, input);
input_container.appendChild(input);
input.addHandler('keydown', function (e)
{
if (e.which == 40) //down key
{
var anchors = container.getElementsByTagName('a');
if (anchors.length > 0)
anchors[0].focus();
}
if (e.which == 27) //escape key
{
var list = container.getElementsByTagName('ul');
if (list.length > 0)
container.removeChild(list[0]);
}
});
var container = make('div'); //the container for our autosuggest list
container.style.position = 'absolute';
container.style.marginTop = '1px';
container.className = '___autosuggest___';
var style = make('style');
var rules = document.createTextNode('.___autosuggest___ ul { margin: 0; padding:0; list-style-type:none;');
if (style.styleSheet)
style.styleSheet.cssText = rules.nodeValue;
else
style.appendChild(rules);
document.getElementsByTagName('head')[0].appendChild(style);
function clear_container()
{
var list = container.getElementsByTagName('ul');
if (list.length > 0)
container.removeChild(list[0]);
}
function compute_container_offset()
{
var offset = input.getOffset();
container.style.top = offset.y + input.offsetHeight + 'px';
container.style.left = offset.x + 'px';
console.log(offset);
}
compute_container_offset();
document.body.appendChild(container);
if (window.addEventListener)
{
window.addEventListener('resize', compute_container_offset);
}
else
{
if (window.attachEvent)
{
window.attachEvent('onresize', compute_container_offset);
}
}
function handle_results(data)
{
var existing = container.getElementsByTagName('ul');
if (existing.length > 0)
container.removeChild(existing[0]);
var ul = make('ul');
var array = null;
if (!data.isArray())
array = [data];
else
array = data;
var len = array.length;
var anchors = new Array();
var navigation = function (n)
{
return function (e)
{
if (e.which == 40)
{
anchors[(n + 1) % len].focus();
}
if (e.which == 38)
{
if (n == 0)
input.focus();
else
anchors[n - 1].focus();
}
if (e.which == 27)
{
container.removeChild(ul);
}
}
}
if (len > 0)
{
for (var i = 0; i < len; i++)
{
var obj = array[i];
var a = make('a');
var li = make('li');
anchors.push(a);
a.href = '#';
a[secret_context_key] = true;
a.innerHTML = array[i].fullname + (new Date());
li.appendChild(a);
ul.appendChild(li);
a.addHandler('keydown', navigation(i));
a.addHandler('click', function (e)
{
clear_container();
e.stopPropagation();
});
var kill_container_on_blur = function()
{
if(!document.activeElement[secret_context_key] && document.activeElement != input)
{
console.log(document.activeElement);
clear_container();
}
};
a.addHandler('blur', function (e)
{
setTimeout(kill_container_on_blur, 10);
});
}
container.appendChild(ul);
}
}
function create_poll()
{
var value = null;
return function ()
{
var d;
if (input.value.length < 3)
{
var list = container.getElementsByTagName('ul');
if(list.length > 0)
container.removeChild(list[0]);
return;
}
if (value == null || input.value != value)
{
value = input.value;
new Xhr(src(), function (data)
{
console.log(src());
if (typeof (data) != 'array')
{
handle_results(data);
}
}).exec();
}
}
}
input.addHandler('focus', function (e)
{
var poll = create_poll();
console.log('start polling...');
poll();
var interval_id = window.setInterval(poll, 500);
var on_blur = function (e)
{
window.clearInterval(interval_id);
input.removeHandler('blur', on_blur);
setTimeout(function ()
{
var active = document.activeElement;
if (!active[secret_context_key])
{
clear_container();
}
}, 20);
}
input.addHandler('blur', on_blur);
});
var all_children = document.body.getElementsByTagName('*');
var count = all_children.length;
var seen_input = false;
for (var i = 0; i < count; i++)
{
if (seen_input)
{
//we want to ensure that the dynamically added controls are next in the tab order after the input element
all_children[i].tabIndex = all_children[i].tabIndex + 50;
continue;
}
if (all_children[i] == input)
seen_input = true;
}
}
function make_draggable(elem, init_drag, drop_fn)
{
elem.style.cursor = 'move';
function mouse_down(e)
{
var pos = { x: null, y: null }
if (init_drag)
init_drag();
var offset = elem.getOffset();
var delta_x = e.clientX - offset.x;
var delta_y = e.clientY - offset.y;
elem.style.position = 'absolute';
var old_pos = { x: null, y: null };
function drag_state()
{
if (pos.x == null)
return;
if (old_pos.x != pos.x || old_pos.y != pos.y)
{
old_pos.x = pos.x;
old_pos.y = pos.y;
elem.style.top = (pos.y - delta_y) + 'px';
elem.style.left = (pos.x - delta_x) + 'px';
}
}
function mouse_up(e)
{
window.removeHandler('mouseup', mouse_up);
window.removeHandler('mousemove', mouse_move);
if (drop_fn)
drop_fn(e);
}
function mouse_move(e)
{
e = e || window.event;
pos.x = e.clientX;
pos.y = e.clientY;
elem.style.top = (pos.y - delta_y) + 'px';
elem.style.left = (pos.x - delta_x) + 'px';
}
window.addHandler('mousemove', mouse_move);
window.addHandler('mouseup', mouse_up);
}
elem.addHandler('mousedown', mouse_down);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment