├── .gitignore ├── README.md ├── bower.json ├── media └── octopi.png ├── octopi.js ├── plugins ├── delete.js └── has.js └── test ├── test.html ├── test.js ├── test_delete.js └── test_has.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # octopi.js 2 | 3 | 4 | 5 | Micro (0.5kB) trie based suggestion generating 6 | library, made with autocompletion, control, and 7 | performance on mobile browsers in mind. Check out 8 | the [demo](https://eugene-eeo.github.io/octopi/demo.html). 9 | 10 | ```js 11 | var oct = new Octopi(['bird', 'boy']); 12 | oct.add('bid', {'word': 'BID'}); 13 | oct.get('bi'); // => ['bird', {'word':'BID'}] 14 | ``` 15 | 16 | ### Do you even normalise? 17 | 18 | Octopi allows for arbitrary data to be associated 19 | with words added to the tree. This means that you 20 | have full control over normalisation of the words. 21 | For example: 22 | 23 | ```js 24 | word = 'CoolUseRName'; 25 | oct.add(word.toLowerCase(), word); 26 | oct.get(word.toLowerCase()); 27 | ``` 28 | 29 | ### Deleting entries 30 | 31 | You can use the [delete](plugins/delete.js) plugin: 32 | 33 | ```js 34 | oct.delete('bid'); 35 | ``` 36 | 37 | ### Installation 38 | 39 | ```sh 40 | $ bower install eugenee-eeo/octopi 41 | ``` 42 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "octopi", 3 | "main": "octopi.js", 4 | "authors": [ 5 | "Eeo Jun <141bytes@gmail.com>" 6 | ], 7 | "description": "micro client-side suggestions library", 8 | "moduleType": [], 9 | "keywords": [ 10 | "trie" 11 | ], 12 | "license": "MIT", 13 | "homepage": "github.com/eugene-eeo/octopi", 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "bower_components", 18 | "test", 19 | "tests", 20 | "media" 21 | ], 22 | "devDependencies": { 23 | "mocha": "~2.3.4", 24 | "chai": "~3.4.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /media/octopi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene-eeo/octopi/ade8c4d742a0494327455c0845cf944baf691f72/media/octopi.png -------------------------------------------------------------------------------- /octopi.js: -------------------------------------------------------------------------------- 1 | Octopi = function(words) { 2 | this.uid = 0; 3 | this.tree = {$$: []}; 4 | this.table = {}; 5 | words = (words || []); 6 | for (var i = 0; i < words.length; i++) 7 | this.add(words[i]); 8 | }; 9 | 10 | /** 11 | * Take serialized Octopi data as string and initialize the Octopi object 12 | * @param json String The serialized Octopi trie 13 | */ 14 | Octopi.load = function(json){ 15 | var oct = new Octopi(); 16 | var o = JSON.parse(json); 17 | oct.uid = o.uid; 18 | oct.tree = o.tree; 19 | oct.table = o.table; 20 | return oct; 21 | } 22 | 23 | Octopi.prototype = { 24 | constructor: Octopi, 25 | /** 26 | * Add a new element to the trie 27 | * @param key String prefix to look up 28 | * @param data Object returned by trie 29 | */ 30 | add: function(key, data) { 31 | var id = ++this.uid; 32 | var sub = this.tree; 33 | 34 | this.table[id] = data || key; 35 | sub.$$.push(id); 36 | 37 | for (var i = 0; i < key.length; i++) { 38 | var c = key[i]; 39 | sub = sub[c] || (sub[c] = {$$:[]}); 40 | sub.$$.push(id); 41 | } 42 | }, 43 | 44 | /** 45 | * Return the list of elements in the trie for a given query 46 | * @param key String The prefix to lookup 47 | */ 48 | get: function(key) { 49 | var sub = this.tree; 50 | var tbl = this.table; 51 | for (var i = 0; i < key.length; i++) 52 | if (!(sub = sub[key[i]])) 53 | return []; 54 | 55 | return sub.$$.map(function(id) { 56 | return tbl[id]; 57 | }); 58 | }, 59 | /** 60 | * Serialize the Octopi trie as string 61 | * 62 | * @return String 63 | */ 64 | serialize: function(){ 65 | var o = { uid: this.uid, 66 | tree: this.tree, 67 | table: this.table 68 | } 69 | return JSON.stringify(o); 70 | 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /plugins/delete.js: -------------------------------------------------------------------------------- 1 | Octopi.prototype.delete = function(key) { 2 | var sub = this.tree; 3 | var trees = [sub]; 4 | var length = key.length; 5 | 6 | for (var i = 0; i < length; i++) { 7 | if (!(sub = sub[key[i]])) 8 | return; 9 | trees.push(sub); 10 | } 11 | 12 | var toDelete = sub.$$; 13 | 14 | for (var i = toDelete.length; i--;) { 15 | var id = toDelete[i]; 16 | 17 | for (var j = trees.length; j--;) { 18 | var ids = trees[j].$$; 19 | ids.splice(ids.indexOf(id), 1); 20 | if (j && !ids.length) 21 | delete trees[j-1][key[j-1]]; 22 | } 23 | 24 | delete this.table[id]; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /plugins/has.js: -------------------------------------------------------------------------------- 1 | Octopi.prototype.has = function(key) { 2 | return !!this.get(key).length; 3 | }; 4 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Mocha Tests 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var assert = chai.assert; 2 | 3 | describe('Octopi(words)', function() { 4 | it('indexes the words', function() { 5 | var o = new Octopi(['bi', 'bir', 'bird']); 6 | assert.deepEqual(o.get('b'), ['bi', 'bir', 'bird']); 7 | }); 8 | }); 9 | 10 | describe('Octopi constructor', function() { 11 | it('returns the constructor function', function() { 12 | var o = new Octopi(); 13 | assert.deepEqual(o.constructor, Octopi); 14 | }); 15 | }); 16 | 17 | describe('Octopi.add', function() { 18 | it('allows arbitrary data to be added', function() { 19 | var o = new Octopi(); 20 | o.add('bird', 1); 21 | assert.deepEqual(o.get('bird'), [1]); 22 | }); 23 | }); 24 | 25 | describe('Octopi.get', function() { 26 | var o = new Octopi(); 27 | o.add('bird'); 28 | o.add('bid'); 29 | o.add('ata'); 30 | 31 | it('returns all words starting with the given word', function() { 32 | var s1 = o.get('bi'); 33 | var s2 = o.get('b'); 34 | assert.deepEqual(s1, s2); 35 | 36 | var s3 = o.get('a'); 37 | assert.deepEqual(s3, ['ata']); 38 | }); 39 | 40 | it('returns the words in the order they were added', function() { 41 | var s = o.get(''); 42 | assert.deepEqual(s, ['bird', 'bid', 'ata']); 43 | }); 44 | 45 | it('returns an empty array if there are no matches', function() { 46 | assert.deepEqual(o.get('c'), []); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/test_delete.js: -------------------------------------------------------------------------------- 1 | describe('Octopi.delete', function() { 2 | var oct = new Octopi(['bid', 'bi', 'b']); 3 | 4 | it('deletes the entries from the table', function() { 5 | oct.delete('bi'); 6 | for (var i in this.table) 7 | assert.notOk(/bi/.test(this.table[i])); 8 | }); 9 | 10 | it(".get doesn't include the entry", function() { 11 | assert.deepEqual(oct.get(''), ['b']); 12 | }); 13 | 14 | it('compacts the tree', function() { 15 | assert.notOk('i' in oct.tree['b']); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/test_has.js: -------------------------------------------------------------------------------- 1 | describe('Octopi.has', function() { 2 | var oct = new Octopi(['abc', 'def']); 3 | 4 | it('returns true if the key is present', function() { 5 | assert(oct.has('abc') === true); 6 | assert(oct.has('def') === true); 7 | }); 8 | 9 | it('returns false if the key is not present', function() { 10 | assert(oct.has('ghi') === false); 11 | }); 12 | 13 | it('returns true for partial matches', function() { 14 | assert(oct.has('a') === true); 15 | }); 16 | }); 17 | --------------------------------------------------------------------------------