diff options
author | Lea Verou <lea@verou.me> | 2015-02-18 16:25:29 -0500 |
---|---|---|
committer | Lea Verou <lea@verou.me> | 2015-02-18 16:25:29 -0500 |
commit | 718adf1d1ffb5b47589eb86bba4574a7f872424a (patch) | |
tree | 03a13ce93f2d1942f8f818d768ec6655cfa88d81 | |
parent | 3ccf512d969a8dce346110fbb616aec7f669329a (diff) | |
download | awesomplete-718adf1d1ffb5b47589eb86bba4574a7f872424a.zip awesomplete-718adf1d1ffb5b47589eb86bba4574a7f872424a.tar.gz awesomplete-718adf1d1ffb5b47589eb86bba4574a7f872424a.tar.bz2 |
First commit
-rw-r--r-- | awesomplete.css | 91 | ||||
-rw-r--r-- | awesomplete.js | 267 | ||||
-rw-r--r-- | index.html | 168 | ||||
-rw-r--r-- | style.css | 193 |
4 files changed, 719 insertions, 0 deletions
diff --git a/awesomplete.css b/awesomplete.css new file mode 100644 index 0000000..6466a0c --- /dev/null +++ b/awesomplete.css @@ -0,0 +1,91 @@ +[hidden] { display: none; } + +div.awesomplete { + display: inline-block; + position: relative; +} + +div.awesomplete > input { + display: block; +} + +div.awesomplete > ul { + position: absolute; + left: 0; + z-index: 1; + min-width: 100%; + box-sizing: border-box; + list-style: none; + padding: 0; + border-radius: .3em; + margin: .2em 0 0; + background: hsla(0,0%,100%,.9); + background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8)); + border: 1px solid rgba(0,0,0,.3); + box-shadow: .05em .2em .6em rgba(0,0,0,.2); + text-shadow: none; +} + +div.awesomplete > ul[hidden], +div.awesomplete > ul:empty { + display: none; +} + +@supports (transform: scale(0)) { + div.awesomplete > ul { + transition: .3s cubic-bezier(.4,.2,.5,1.4); + transform-origin: 1em -.43em; + } + + div.awesomplete > ul[hidden], + div.awesomplete > ul:empty { + opacity: 0; + transform: scale(0); + display: block; + transition-timing-function: ease; + } +} + + /* Pointer */ + div.awesomplete > ul:before { + content: ""; + position: absolute; + top: -.43em; + left: 1em; + width: 0; height: 0; + padding: .4em; + background: linear-gradient(to bottom right,white 50%, transparent 0); + border: inherit; + border-right: 0; + border-bottom: 0; + transform: rotate(45deg); + } + + div.awesomplete > ul > li { + position: relative; + padding: .2em .5em; + cursor: pointer; + } + + div.awesomplete > ul > li:hover { + background: hsl(200, 40%, 80%); + color: black; + } + + div.awesomplete > ul > li[aria-selected="true"] { + background: hsl(205, 40%, 40%); + color: white; + } + + div.awesomplete mark { + background: yellow; + } + + div.awesomplete li:hover mark { + background: hsl(68, 101%, 41%); + } + + div.awesomplete li[aria-selected="true"] mark { + background: hsl(86, 102%, 21%); + color: inherit; + }
\ No newline at end of file diff --git a/awesomplete.js b/awesomplete.js new file mode 100644 index 0000000..ffe6a14 --- /dev/null +++ b/awesomplete.js @@ -0,0 +1,267 @@ +/** + * Simple, leightweight, usable local autocomplete library for modern browsers + * Because there weren’t enough autocomplete scripts in the world? Because I’m completely insane and have NIH syndrome? Probably both. :P + * @author Lea Verou + * MIT license + */ + +(function () { + +function $(expr, con) { + if (!expr) return null; + return typeof expr === 'string'? (con || document).querySelector(expr) : expr; +} + +function $$(expr, con) { + return Array.prototype.slice.call((con || document).querySelectorAll(expr)); +} + +$.create = function(tag, o) { + if (arguments.length == 1) { + o = tag; + tag = o.tag; + delete o.tag; + } + + var element = o.element || document.createElement(tag); + delete o.element; + + for (var i in o) { + var val = o[i]; + + if (i == "inside") { + $(val).appendChild(element); + } + else if (i == "around") { + var ref = $(val); + ref.parentNode.insertBefore(element, ref); + element.appendChild(ref); + } + else if (i in element) { + element[i] = val; + } + else { + element.setAttribute(i, val); + } + } + + return element; +} + +$.bind = function(element, o) { + if (element) { + for (var event in o) { + var callback = o[event]; + + event.split(/\s+/).forEach(function (event) { + element.addEventListener(event, callback); + }); + } + } +} + +var _ = self.Awesomplete = function (input, o) { + var me = this; + + // Setup environment + o = o || {}; + + this.input = input; + input.setAttribute("aria-autocomplete", "list"); + + this.minChars = +input.getAttribute("data-minchars") || o.minChars || 2; + this.maxItems = +input.getAttribute("data-maxitems") || o.maxItems || 20; + + var list = input.getAttribute("data-list") || o.list || []; + + if (Array.isArray(list)) { + this.list = list; + } + else { + if (typeof list == "string" && list.indexOf(",") > -1) { + this.list = list.split(/\s*,\s*/); + } + else { + list = $(list); + + if (list && list.children) { + list.setAttribute("hidden", ""); + + this.list = [].slice.apply(list.children).map(function (el) { + return el.innerHTML.trim(); + }); + } + } + } + + this.filter = o.filter || _.FILTER_CONTAINS; + this.item = o.item || function (text, input) { + return $.create("li", { + innerHTML: text.replace(RegExp(regEscape(input.trim()), "gi"), "<mark>$&</mark>"), + "aria-selected": "false" + }); + } + + this.index = -1; + + this.container = $.create("div", { + className: "awesomplete", + around: input + }); + + this.ul = $.create("ul", { + hidden: "", + inside: this.container + }); + + // Bind events + + $.bind(this.input, { + "input focus": checkInput, + "blur": function () { + if (document.activeElement != this && document.activeElement != document.body) { + me.close(); + } + }, + "keydown": function(evt) { + switch (evt.keyCode) { + case 13: // Enter + if (me.index > -1) { + evt.preventDefault(); + me.select(); + } + + break; + case 27: // Esc + me.close(); + break; + case 40: // up arrow + me.next(); + evt.preventDefault(); + break; + case 38: // down arrow + me.previous(); + evt.preventDefault(); + break; + } + } + }); + + $.bind(this.input.form, {"submit": function(event) { + me.close(); + }}); + + $.bind(this.ul, {"click": function(evt) { + var li = evt.target; + + if (li != this) { + + while (li && !/li/i.test(li.nodeName)) { + li = li.parentNode; + } + + if (li) { + me.select(li); + } + } + }}); + + // Private functions + + function checkInput() { + var value = me.input.value; + + if (value.length >= me.minChars && me.list.length > 0) { + // Populate list with options that match + me.ul.innerHTML = ""; + + me.list.filter(function(item) { + return me.filter(item, value); + }).every(function(text, i) { + me.ul.appendChild(me.item(text, value)); + + return i < me.maxItems - 1; + }); + + me.open(); + } + else { + me.close(); + } + } +}; + +_.prototype = { + close: function () { + this.ul.setAttribute("hidden", ""); + this.index = -1; + }, + + open: function () { + this.ul.removeAttribute("hidden"); + + var me = this; + + document.addEventListener("click", function(evt) { + if (!me.container.contains(evt.target)) { + document.removeEventListener(arguments.callee); + me.close(); + } + }); + }, + + next: function () { + var count = this.ul.children.length; + + this.goto(this.index < count - 1? this.index + 1 : -1); + }, + + previous: function () { + var count = this.ul.children.length; + + this.goto(this.index > -1? this.index - 1 : count - 1); + }, + + // Should not be used, highlights specific item without any checks! + goto: function (i) { + var lis = this.ul.children; + + if (this.index > -1) { + lis[this.index].setAttribute("aria-selected", "false"); + } + + this.index = i; + + if (i > -1) { + lis[i].setAttribute("aria-selected", "true"); + } + }, + + select: function (selected) { + selected = selected || this.ul.children[this.index]; + + if (selected) { + this.input.value = selected.textContent; + this.close(); + } + } +}; + +_.FILTER_CONTAINS = function (text, input) { + return RegExp(regEscape(input.trim()), "i").test(text); +}; + +_.FILTER_STARTSWITH = function (text, input) { + return RegExp("^" + regEscape(input.trim()), "i").test(text); +}; + +function regEscape(s) { return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); } + +$$("input.awesomplete").forEach(function (input) { + new Awesomplete(input); +}); + +_.$ = $; +_.$$ = $$; + +})();
\ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..fd25c96 --- /dev/null +++ b/index.html @@ -0,0 +1,168 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + +<meta charset="utf-8" /> +<title>Awesomplete: Lightweight autocomplete</title> +<link rel="stylesheet" href="http://prismjs.com/themes/prism.css" /> +<link rel="stylesheet" href="awesomplete.css" /> +<link rel="stylesheet" href="style.css" /> + +</head> +<body class="language-markup"> + +<header> +<h1>Awesomplete</h1> + +<p class="size"><strong>1.5KB</strong> minified <span class="amp">&</span> gzipped!</p> + +<p>Lightweight, simple, customizable autocomplete, built with modern standards. Because <code><datalist></code> still doesn’t cut it.</p> +</header> + +<section> + +<h1>Demo (no JS, minimal options)</h1> +<label> + Pick a programming language:<br /> + <input class="awesomplete" data-list="Ada, Java, JavaScript, Brainfuck, LOLCODE, Node.js, Ruby on Rails" xautofocus /> +</label> + +<p>With Awesomplete, making something like this can be as simple as:</p> + +<pre class="language-markup"><code><input class="awesomplete" + data-list="Ada, Java, JavaScript, Brainfuck, LOLCODE, Node.js, Ruby on Rails" /></code></pre> + +<pre class="language-javascript"><code>// No extra JS needed for basic usage!</code></pre> + +</section> + +<section id="basic-usage"> + +<h1>Basic usage</h1> +<p>Before you try anything, you need to include <code>awesomplete.css</code> and <code>awesomplete.js</code> in your page, via the usual <code><link rel="stylesheet" href="awesomplete.css" /></code> and <code><script src="awesomplete.js"></script></code> tags.</p> + +<p>For the autocomplete, you just need an <code><input></code> or <code><textarea></code> box. Add <code>class="awsomplete"</code> for it to be <strong>automatically processed</strong> (you can still specify most options via HTML attributes), otherwise you can instantiate with a few lines of JS code, which allow for more customization.</p> +<p>There are many ways <strong>to link an input to a list of suggestions</strong>. The simple example above could have also been made with the following code:</p> + +<pre class="language-markup"><code><input class="awesomplete" data-list="#mylist" /> +<ul id="mylist"> + <li>Ada</li> + <li>Java</li> + <li>JavaScript</li> + <li>Brainfuck</li> + <li>LOLCODE</li> + <li>Node.js</li> + <li>Ruby on Rails</li> +</ul></code></pre> + +<pre class="language-javascript"><code>// None!</code></pre> + +<p>Or the following, if we want to instantiate in JS:</p> + +<pre class="language-markup"><code><input id="myinput" /> +<ul id="mylist"> + <li>Ada</li> + <li>Java</li> + <li>JavaScript</li> + <li>Brainfuck</li> + <li>LOLCODE</li> + <li>Node.js</li> + <li>Ruby on Rails</li> +</ul></code></pre> +<pre class="language-javascript"><code>var input = document.getElementById("myinput"); +new Awesomplete(input, {list: "#mylist"});</code></pre> + +<p>We can use an <strong>element reference</strong> for the list instead of a selector:</p> + +<pre class="language-markup"><code><input id="myinput" /> +<ul id="mylist"> + <li>Ada</li> + <li>Java</li> + <li>JavaScript</li> + <li>Brainfuck</li> + <li>LOLCODE</li> + <li>Node.js</li> + <li>Ruby on Rails</li> +</ul></code></pre> +<pre class="language-javascript"><code>var input = document.getElementById("myinput"); +new Awesomplete(input, {list: document.querySelector("#mylist")});</code></pre> + +<p>We can also directly use an <strong>array of strings</strong>:</p> + +<pre class="language-markup"><code><input id="myinput" /></code></pre> + <pre class="language-javascript"><code>var input = document.getElementById("myinput"); +new Awesomplete(input, { + list: ["Ada", "Java", "JavaScript", "Brainfuck", "LOLCODE", "Node.js", "Ruby on Rails"] +});</code></pre> + +</section> + +<section id="customization"> + + <h1>More customization</h1> + + <p>Except the properties whose value needs to be a function, every property is settable via either a <code>data-</code> attribute on the <code><input></code> element or a JS property on the second argument of the <code>Awesomplete</code> constructor, like so:</p> + + <pre class="language-javascript"><code>new Awesomplete(inputReference, { + minChars: 3, + maxItems: 15, + ... +});</code></pre> + <p>You can of course combine both HTML attributes and JS properties. <strong>In case of conflict</strong> (e.g. you’ve specified both a <code>data-minchars</code> on the text field and a <code>minChars</code> JS property, <strong>the HTML attribute wins</strong>.</p> + + <table> + <thead> + <tr> + <th>JS property</th> + <th>HTML attribute</th> + <th>Description</th> + <th>Value</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>list</code></td> + <td><code>data-list</code></td> + <td>Where to find the list of suggestions. Described in more detail in the “<a href="#basic-usage">Basic usage</a>” section above.</td> + <td>Array of strings, CSS selector (no commas), comma-separated list of items</td> + </tr> + <tr> + <td><code>minChars</code></td> + <td><code>data-minchars</code></td> + <td>Minimum characters the user has to type before the autocomplete popup shows up.</td> + <td>Number</td> + </tr> + <tr> + <td><code>maxItems</code></td> + <td><code>data-maxitems</code></td> + <td>Maximum number of suggestions to display.</td> + <td>Number</td> + </tr> + <tr> + <td><code>filter</code></td> + <td>-</td> + <td>Controls how entries get matched. By default, the input can match anywhere in the string and it’s a case insensitive match.</td> + <td>Function that takes two parameters, the first one being the current suggestion text that’s being tested and the second a string with the user’s input it’s matched against. Returns <code>true</code> if the match is successful and <code>false</code> if it is not. For example, to only match strings that <strong>start with the user’s input</strong>, <strong>case sensitive</strong>, we can do this: +<pre class="language-javascript"><code>filter: function (text, input) { + return item.indexOf(input) === 0; +}</code></pre> For case-<strong>in</strong>sensitive matching from the start of the word, there is a predefined filter that you can use, <code class="language-javascript">Awesomplete.FILTER_STARTSWITH</code> + </td> + </tr> + <tr> + <td><code>item</code></td> + <td>-</td> + <td>Controls how list items are generated.</td> + <td>Function that takes two parameters, the first one being the suggestion text and the second one the user’s input and returns a list item.</td> + </tr> + </tbody> + </table> + +</section> + +<footer>By <a href="http://lea.verou.me">Lea Verou</a></footer> + +<script src="awesomplete.js"></script> +<script src="http://prismjs.com/prism.js" defer></script> + +</body> +</html>
\ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..30d5a52 --- /dev/null +++ b/style.css @@ -0,0 +1,193 @@ +body { + max-width: 50rem; + padding: 1rem; + margin: auto; + background: hsl(35, 80%, 94%); + color: hsl(35, 50%, 20%); + text-shadow: 0 1px 1px white; + font: 120%/1.6 Baskerville, Palatino Linotype, Palatino, serif; +} + +a { + color: #58a; +} + +h1 { + text-align: center; + font-weight: 600; +} + + section > h1 { + margin-top: 2em; + } + +h2 { + color: hsl(35, 50%, 40%); + font-weight: 600; + font-size: 120%; +} + +strong { + font-weight: 600; +} + +input, button { + font: inherit; + text-shadow: inherit; +} + +button { + padding: .1em .5em; + border-radius: .3em; + background: hsl(80, 80%, 80%); + background: linear-gradient(hsl(40, 70%, 80%), hsl(40, 70%, 70%)); + border: 1px solid rgba(0,0,0,.3); + box-shadow: 0 1px white inset, 0 .3em .3em -.3em rgba(0,0,0,.3); +} + +input { + width: 12em; + padding: 0 .3em; + border: 0; + border: 1px solid hsl(35, 80%, 60%); + background: hsla(0,0%,100%,.2); + border-radius: .3em; + box-shadow: .05em .1em .3em rgba(0,0,0,.3) inset; +} + +@keyframes pulsate { + to { + box-shadow: .05em .1em .3em rgba(0,0,0,.3) inset, 0 0 .3em .1em #58a; + } +} + +input:focus { + outline: none; + border: 1px solid #58a; + animation: pulsate 2s infinite alternate linear; + background: hsla(0,0%,100%,.5); +} + +header { + max-width: 37rem; + margin: 0 auto 2em; + text-align: center; + font-size: 150%; + font-style: italic; +} + + header h1 { + margin: .1em 0; + text-align: center; + color: hsl(35, 50%, 40%); + font-size: 400%; + line-height: 1; + font-style: italic; + font-weight: normal; + } + + header p { + margin: 0; + } + + .size { + position: absolute; + top: 1em; + right: 1em; + width: 4em; + padding: .7em .5em; + background: hsl(40, 80%, 40%); + color: hsl(35, 80%, 94%); + text-shadow: none; + text-align: center; + line-height: 1.1; + text-indent: -.1em; + box-shadow: 0 0 0 .2em hsl(40, 80%, 40%) inset, 0 0 0 .25em currentcolor inset; + } + + .size::before { + content: ""; + position: absolute; + top: -.5em; + left: -.5em; + padding: .5em; + background: currentColor; + border-radius: 50%; + box-shadow: 5em 0, 0 4.9em, 5em 4.9em; + overflow: hidden; + } + + .size strong { + display: block; + font-weight: 900; + margin-bottom: .1em; + font-size: 110%; + font-style: normal; + } + + .size .amp { + position: absolute; + left: 0; right: 0; + bottom: -.1em; + color: hsla(35, 80%, 94%,.3); + font-size: 350%; + } + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: hsl(35, 70%, 88%); + font-size: 80%; + border-radius: .3em; +} + +footer { + padding: .6em; + border-top: 1px solid rgba(0,0,0,.3); + margin-top: 1em; + text-align: center; + font-size: 75%; +} + +pre { + position: relative; +} + +pre::before { + position: absolute; + top: 0; + right: .5em; + padding: .3em .4em; + border-radius: 0 0 .3em .3em; + background: hsl(35, 50%, 60%); + color: hsl(35, 80%, 90%); + text-shadow: none; + font: bold 100%/1 Baskerville, Palatino Linotype, Palatino, serif; +} + +pre.language-markup::before { + content: "HTML"; +} + +pre.language-javascript::before { + content: "JS"; +} + +table { + table-layout: fixed; + border-spacing: 0; + border-collapse: collapse; +} + +th { + font-size: 75%; +} + +td { + vertical-align: top; + padding: 0 .5em; + border: 1px solid rgba(0,0,0,.2); +} + +:not(pre) > code[class*="language-"] { + background: none; +}
\ No newline at end of file |