Skip to content

Instantly share code, notes, and snippets.

@serebro
Forked from mattmccray/Loot-ReadMe.md
Created November 10, 2015 00:09
Show Gist options
  • Save serebro/a77a096f7d579c09241a to your computer and use it in GitHub Desktop.
Save serebro/a77a096f7d579c09241a to your computer and use it in GitHub Desktop.
Loot.js - Smart stores for Riot (like flux, based on ideas from RiotControl)

Loot.js!

Smart stores for Riot (like flux, based on ideas from RiotControl)

Small (<1.5Kb) flux-like store container.

Example

Make sure loot.js is included in your page after riot.js:

<script src="riot.js"></script>
<script src="loot.js"></script>

Then you can create your stores:

stores.js

riot.loot.register('auth', function controller(store){
    store.isLoggedIn = false
    store.message = null

    store.onAction({
        authenticate: function(username, password) {
            if (username === 'bob' && password === 'smith' ) {
                store.isLoggedIn = true
                store.message = "Welcome, Bob!"
            }
            else {
                store.isLoggedIn = false
                store.message = "Username or password invalid, please try again."
            }
            // return false if you DON'T want to trigger a 'change' event.
        },

        logout: function() {
            store.isLoggedIn = false
            store.message = "You've been logged out."
        }
    })
})

login-page.tag

<login-page>
    <form onsubmit={login}>
        <div if={auth.message}>
            <div class="message">{ auth.message }</div>
        </div>
        <div>
            <input name="username" placeholder="Username">
        </div>
        <div>
            <input name="password" placeholder="Password" type="password">
        </div>
        <div>
            <button disabled={auth.isLoggedIn || !username.value || !password.value}>
                Login
            </button>
            <button disabled={!auth.isLoggedIn} onclick={logout} type="button">
                Logout
            </button>
        </div>
    </form>

    <script>
        this.mixin('loot')

        // this.getStore( storeName ) -- provided by the mixin -- will setup
        // this component to automatically call this.update() whenever the store
        // triggers a 'change' event.
        this.auth = this.getStore('auth')

        // This event handler will be cleaned up when the component unmounts.
        this.auth.on('success', () => {
            // on auth store event 'success'
        })

        login(e) {
            this.sendAction('authenticate', {
                username: this.username.value,
                password: this.password.value
            })
            this.password.value = ''
        }

        logout(e) {
            this.sendAction('logout')
        }
    </script>

    <style>
        .message {
            background-color: dodgerblue;
            color: white;
        }
    </style>
</login-page>
(function(libName, riot, global) {
var CHANGE_EVENT = 'change'
var storeList = []
var storeMap = {}
var api = {
register: register,
reset: reset,
stores: storeMap
}
function register(name, controller) {
var store = new Store(controller)
storeList.push(store)
storeMap[name] = store
}
function reset() {
storeList.forEach(function(store){
store.off('*')
})
storeList = []
storeMap = {}
}
function Store(controller) {
var self = this
riot.observable(this)
self.hasChanged = function() {
self.trigger(CHANGE_EVENT)
}
self.onAction = function(action, handler) {
if (arguments.length === 2) {
self.on(action, _changeChecker(self, handler))
}
else if (typeof action === 'object') {
Object.keys(action).forEach(function(name){
self.on(name, _changeChecker(self, action[name]))
})
}
}
self._controller = controller
controller(self, self.onAction, self.hasChanged)
}
function _changeChecker( store, actionHandler ){
return function() {
var args = [].slice.call(arguments),
results = actionHandler.apply(store, args)
if (results !== false) {
store.trigger(CHANGE_EVENT)
}
}
}
function _autoDispose(view, on, off) {
return function(event, handler) {
handler = handler.bind(view)
view.autoDispose(function() {
off(event, handler)
})
return on(event, handler)
}
}
;['on','one','off','trigger'].forEach(function(name){
api[name] = function() {
var args = [].slice.call(arguments)
storeList.forEach(function(store){
store[name].apply(store, args)
})
}
})
riot.mixin(libName, {
init: function() {
this.disposables = []
this.on('unmount', function() {
this.disposables.forEach(function(unsubscribe){
unsubscribe()
})
})
},
autoDispose: function(fn) {
this.disposables.push(fn)
},
getStore: function(name, readOnly) {
var store = storeMap[name]
if (!store) {
throw new Error("Store not found: "+ name)
}
if (readOnly !== true) {
var _store = store
store = Object.create(_store, {
on: { value: _autoDispose(this, _store.on, _store.off) },
one:{ value: _autoDispose(this, _store.one, _store.off) }
})
store.on(CHANGE_EVENT, this.update)
}
return store
},
sendAction: function(action) {
var args = [].slice.call(arguments)
try {
api.trigger.apply(api, args)
}
catch (error) {
console.error('Dispatch error:', error)
}
}
})
riot.loot = api
if (typeof module != 'undefined') {
module.exports = api
}
else if (global) {
global[libName] = api
}
})('loot', riot, window)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment