Skip to content
forked from kentcdodds/genie

Magic. GenieJS was built to bring behavior found in Alfred to JavaScript. Register functions associated with one or more keywords and then you can do a search with keywords and GenieJS will miraculously return them in a logical order depending on the keyword given. Genie also learns about the user over time and brings even more relevant results …

License

Notifications You must be signed in to change notification settings

asanjeevak/genie

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

76 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GenieJS

Genie |ˈjēnē| (noun): a spirit of Arabian folklore, as traditionally depicted imprisoned within a bottle or oil lamp, and capable of granting wishes when summoned.

Demo

Tests

Watered Down Explanation

GenieJS is a simple library to emulate the same kind of behavior seen in apps like Alfred. Essentially, you register actions associated with keywords. Then you can request the genie to perform that action based on the best keyword match for a given keyword.

Over time, the genie will learn the actions more associated with specific keywords and those will be come first when a list of matching actions is requested. If that didn't make sense, don't worry, hopefully the tutorial, tests, and demo will help explain how it works.

Vernacular

Wish: An object with an id, action, and magic words.

Action: What to call when this wish is to be executed.

Magic Word: Keywords for a wish used to match it with given magic words.

On Deck: The second wish of preference for a certain magic word which will be King of the Hill if chosen again.

King of the Hill: The wish which gets preference for a certain magic word until the On Deck wish is chosen again (it then becomes On Deck).

How to use it

Include the regular script tag:

<script src="./vendor/genie.js"></script>

This will place genie on the global namespace for your delight. genie is a function with a few useful functions as properties of genie. The flow of using GenieJS is simple:

/* Register wishes */
// One magic word
var trashWish = genie('Take out the trash', function() {
  console.log('Yes! I love taking out the trash!');
});
// Multiple magic words
var vacuumWish = genie(['Get dust out of the carpet', 'vacuum'], function() {
  console.log('Can NOT wait to get that dust out of that carpet!');
});

/* Get wishes based on magic word matches */
genie.getMatchingWishes('vacuum'); // returns [vacuumWish];
genie.getMatchingWishes('out'); // returns [trashWish, vacuumWish];

// Make wish based on wish object or id of wish object
genie.makeWish(trashWish.id); // logs: 'Yes! I love taking out the trash!'
genie.makeWish(vacuumWish); // logs: 'Can NOT wait to get that dust out of that carpet!'

So far it doesn't look too magical, but the true magic comes in the form of genie giving preference to wishes that were recently chosen with a given keyword. To do this, you need to provide genie with a magic word to associate the wish with, like so:

genie.makeWish(vacuumWish, 'out'); // logs as above
genie.getMatchingWishes('out'); // returns [vacuumWish, trashWish]; <-- Notice difference from above

As you'll notice, the order of the two wishes is changed because genie gave preference to the vacuumWish because the last time makeWish was called with the the 'out' magic word, vacuumWish was the wish given.

This behavior simulates apps such as Alfred which is the goal of this library!

API

There are a few internal objects you may want to be aware of:

var wishObject = {
  id: 'string',
  data: object,
  context: 'string',
  keywords: ['string'],
  action: function() { }
};

var enteredMagicWords = {
  'Any Magic Word': ['wishId1', 'wishId2', 'wishId3'],
  'Another magic word': ['wishId1', 'wishId2', 'wishId3']
};

You have the following api to use at your discretion:

// If no id is provided, one will be auto-generated via the previousId + 1
// Returns the wish object
genie(magicWords [string || array | required], action [function | required], data [object | optional], id [string | optional]);
// You may also register wishes with an object for convenience, like so:
genie({
  id: string | optional,
  data: object | optional,
  context: string | optional,
  action: function | required,
  magicWords: string || [string] | required
});

// Removes the wish from the registered wishes and the enteredMagicWords
// Returns the deregisteredWish
genie.deregisterWish(id [string || wishObject | required]);

// calls options() with default options and returns the old options
genie.reset();

/* 
 * Returns an array of wishes which match in order:
 *  1. Most recently made wishes with the given magicWord
 *  2. Following the order of their initial registration
 */
genie.getMatchingWishes(magicWord [string | required]);

/* 
 * Executes the given wish's action.
 * If a magicWord is provided, adds the given wish to the enteredMagicWords
 *   to be given preferential treatment of order in the array returned
 *   by the getMatchingWishes method.
 * Returns the wish object.
 */
genie.makeWish(id [string || wishObject | required], magicWord [string | optional]);

/*
 * Allows you to set the attributes of genie and returns the current genie options.
 *  1. wishes: All wishes (wishObject described above) currently registered
 *  2. noWishMerge: Instead of adding wishes, replace the current list of wishes with the given list.
 *    More about this below...
 *  3. previousId: The number used to auto-generate wish Ids if an id is not
 *    provided when a wish is registered.
 *  4. enteredMagicWords: All magicWords which have been associated with wishes
 *    to give preferential treatment in the order of wishes returned by getMatchingWishes
 *  5. context: The current context of the genie. See below about how context affects wishes
 *  6. enabled: Control whether genie's functions will actually run
 *  7. returnOnDisabled: If enabled is set to false and this is true, will return an empty
 *    object/array/string to prevent the need to do null/undefined checking wherever genie
 *    is used.
 */
genie.options({
  wishes: object | optional,
  noWishMerge: boolean | optional,
  previousId: number | optional,
  enteredMagicWords: object | optional,
  context: string | optional,
  enabled: boolean | optional,
  returnOnDisabled: boolean | optional
});

// Merges the given wishes with existing wishes. (See Merging Wishes below)
genie.mergeWishes(wishes);

// Sets and returns the current context to newContext if provided
// Also sets an internal variable: _previousContext for the revertContext function
genie.context(newContext [string || array | optional]);

// Sets and returns the current context to the default context (universe)
genie.restoreContext();

// Sets and returns the current context to the previous context
genie.revertContext();

// Sets and returns the enabled state
genie.enabled(boolean | optional);

// Sets and returns the returnOnDisabled state
genie.returnOnDisabled(boolean | optional);

About Matching Priority

The wishes returned from getMatchingWishes are ordered with the following priority

  1. In order of most recently executed (makeWish) with the given magic word
  2. If the given magic word is equal to any magic words of a wish
  3. If the given magic word is the start to any magic word of a wish (i.e. 'he' in 'hello');
  4. If the given magic word is the start to any word in a magic word (i.e. 'wo' in 'hello world');
  5. If the given magic word is contained in any magic words of a wish
  6. If the given magic word is an acronym of any magic words of a wish
  7. If the given magic word matches the order of characters in any magic words of a wish.

Just trust the genie. He knows best. And if you think otherwise, let me know or (even better) contribute :)

About Context

Genie has a concept of context that allows you to switch between sets of wishes easily. Each wish is given the default context which is universe unless one is provided when it is registered. Wishes will only behave normally in getMatchingWishes and makeWish` when they are in context.

There are a few ways for a wish to be in context:

  1. Genie's current context is the default context
  2. The wish's context is the default context
  3. The current context and the wish's context are equal
  4. The current context is a decendent of the wish's context

There's something to be said about the last one. Context can be an array as well as a string. If it is an array, Genie will iterate through the wish's context array and compare it with genie's context to determine whether each item in genie's context is equal to the corresponding item in the wish's array. For example:

genie.context = ['grandparent', 'parent', 'child'];
wish1.context = ['grandparent']; // In context
wish2.context = ['grandparent', 'parent']; // In context
wish3.context = ['grandparent', 'parent', 'child']; // In context
wish4.context = ['grandparent', 'parent', 'child', 'great-grandchild']; // Out of context
wish5.context = ['grandparent', 'child']; // Out of context

Enabling & Disabiling

To give you a little more control, you can enable and disable genie globally. All genie functions go through a check to make sure genie is enabled. If it is enabled, everything works as expected. If it is disabled, then genie will return an empty object/array/string depending on what the function you're calling is expecting. This behavior is to prevent the need to do null/undefined checking everywhere you use genie and can be disabled as well via the returnOnDisabled function.

Merging Wishes

To persist the user's experience, you may want to store the result of genie.options() in localStorage or even a database associated with the user. Then after you have registered all the wishes for the user you load the options by calling genie.options({wishes: usersOptions}). The problem with this is that usersOptions wont have the actions for wishes, so this would overwrite the wishes with a bunch that don't have actions.

To prevent this, by default when you call genie.options genie will merge the wishes. So any new wishes provided will either overwrite wishes with the same ID, but preserve the action of the old version if the new version doesn't have an action already. It will also preserve wishes which existed before and don't have matching ids.

To completely overwrite the existing wishes, simply pass in noWishMerge along with the wishes.

Note: Genie provides direct access to the mergeWishes function as well.

UX-Genie

There is a AngularJS directive available for GenieJS. Genie is awesome, but it's not too useful without a way to interact with it. It is used in the demo for GenieJS. Feel free to go to that project here.

Contributing

I'd love to accept pull requests. Please make sure that any new functionality is fully tested in /test/index.html and that all tests pass! Also, please uglify genie.js to genie.min.js using UglifyJS2 and this command: uglifyjs genie.js -o genie.min.js --comments thanks!

Issues

If you have a problem with GenieJS please don't hesitate to use GitHub's issue tracker to report it. I'll do my best to get it resolved as quickly as I can.

The Future...

... is as bright as your faith. And I plan on adding the following features in the future

  • Finished... Ideas? Pull request or add an issue

License

The MIT License (MIT)

Copyright (c) 2013 Kent C. Dodds

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.

About

Magic. GenieJS was built to bring behavior found in Alfred to JavaScript. Register functions associated with one or more keywords and then you can do a search with keywords and GenieJS will miraculously return them in a logical order depending on the keyword given. Genie also learns about the user over time and brings even more relevant results …

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published