/** * Router class */ function Router() { this.$ = [] observable(this) // make it observable central.on('stop', this.s.bind(this)) central.on('emit', this.e.bind(this)) }
import observable from 'riot-observable'; export default observable({});
;define(function(require, exports, module) { 'use strict' /** * Simple client-side router * @module riot-route */ var observable = require('riot-observable') var RE_ORIGIN = /^.+?\/+[^\/]+/, EVENT_LISTENER = 'EventListener', REMOVE_EVENT_LISTENER = 'remove' + EVENT_LISTENER, ADD_EVENT_LISTENER = 'add' + EVENT_LISTENER, HAS_ATTRIBUTE = 'hasAttribute', REPLACE = 'replace', POPSTATE = 'popstate', HASHCHANGE = 'hashchange', TRIGGER = 'trigger', MAX_EMIT_STACK_LEVEL = 3, win = typeof window != 'undefined' && window, doc = typeof document != 'undefined' && document, hist = win && history, loc = win && (hist.location || win.location), // see html5-history-api prot = Router.prototype, // to minify more clickEvent = doc && doc.ontouchstart ? 'touchstart' : 'click', started = false, central = observable(), routeFound = false, debouncedEmit, base, current, parser, secondParser, emitStack = [], emitStackLevel = 0 /** * Default parser. You can replace it via router.parser method. * @param {string} path - current path (normalized) * @returns {array} array */ function DEFAULT_PARSER(path) { return path.split(/[/?#]/) } /** * Default parser (second). You can replace it via router.parser method. * @param {string} path - current path (normalized) * @param {string} filter - filter string (normalized) * @returns {array} array */ function DEFAULT_SECOND_PARSER(path, filter) { var re = new RegExp('^' + filter[REPLACE](/\*/g, '([^/?#]+?)')[REPLACE](/\.\./, '.*') + '$'), args = path.match(re) if (args) return args.slice(1) } /** * Simple/cheap debounce implementation * @param {function} fn - callback * @param {number} delay - delay in seconds * @returns {function} debounced function */ function debounce(fn, delay) { var t return function () { clearTimeout(t) t = setTimeout(fn, delay) } } /** * Set the window listeners to trigger the routes * @param {boolean} autoExec - see route.start */ function start(autoExec) { debouncedEmit = debounce(emit, 1) win[ADD_EVENT_LISTENER](POPSTATE, debouncedEmit) win[ADD_EVENT_LISTENER](HASHCHANGE, debouncedEmit) doc[ADD_EVENT_LISTENER](clickEvent, click) if (autoExec) emit(true) } /** * Router class */ function Router() { this.$ = [] observable(this) // make it observable central.on('stop', this.s.bind(this)) central.on('emit', this.e.bind(this)) } function normalize(path) { return path[REPLACE](/^\/|\/$/, '') } function isString(str) { return typeof str == 'string' } /** * Get the part after domain name * @param {string} href - fullpath * @returns {string} path from root */ function getPathFromRoot(href) { return (href || loc.href || '')[REPLACE](RE_ORIGIN, '') } /** * Get the part after base * @param {string} href - fullpath * @returns {string} path from base */ function getPathFromBase(href) { return base[0] == '#' ? (href || loc.href || '').split(base)[1] || '' : getPathFromRoot(href)[REPLACE](base, '') } function emit(force) { // the stack is needed for redirections var isRoot = emitStackLevel == 0 if (MAX_EMIT_STACK_LEVEL <= emitStackLevel) return emitStackLevel++ emitStack.push(function() { var path = getPathFromBase() if (force || path != current) { central[TRIGGER]('emit', path) current = path } }) if (isRoot) { while (emitStack.length) { emitStack[0]() emitStack.shift() } emitStackLevel = 0 } } function click(e) { if ( e.which != 1 // not left click || e.metaKey || e.ctrlKey || e.shiftKey // or meta keys || e.defaultPrevented // or default prevented ) return var el = e.target while (el && el.nodeName != 'A') el = el.parentNode if ( !el || el.nodeName != 'A' // not A tag || el[HAS_ATTRIBUTE]('download') // has download attr || !el[HAS_ATTRIBUTE]('href') // has no href attr || el.target && el.target != '_self' // another window or frame || el.href.indexOf(loc.href.match(RE_ORIGIN)[0]) == -1 // cross origin ) return if (el.href != loc.href) { if ( el.href.split('#')[0] == loc.href.split('#')[0] // internal jump || base != '#' && getPathFromRoot(el.href).indexOf(base) !== 0 // outside of base || !go(getPathFromBase(el.href), el.title || doc.title) // route not found ) return } e.preventDefault() } /** * Go to the path * @param {string} path - destination path * @param {string} title - page title * @param {boolean} shouldReplace - use replaceState or pushState * @returns {boolean} - route not found flag */ function go(path, title, shouldReplace) { if (hist) { // if a browser path = base + normalize(path) title = title || doc.title // browsers ignores the second parameter `title` shouldReplace ? hist.replaceState(null, title, path) : hist.pushState(null, title, path) // so we need to set it manually doc.title = title routeFound = false emit() return routeFound } // Server-side usage: directly execute handlers for the path return central[TRIGGER]('emit', getPathFromBase(path)) } /** * Go to path or set action * a single string: go there * two strings: go there with setting a title * two strings and boolean: replace history with setting a title * a single function: set an action on the default route * a string/RegExp and a function: set an action on the route * @param {(string|function)} first - path / action / filter * @param {(string|RegExp|function)} second - title / action * @param {boolean} third - replace flag */ prot.m = function(first, second, third) { if (isString(first) && (!second || isString(second))) go(first, second, third || false) else if (second) this.r(first, second) else this.r('@', first) } /** * Stop routing */ prot.s = function() { this.off('*') this.$ = [] } /** * Emit * @param {string} path - path */ prot.e = function(path) { this.$.concat('@').some(function(filter) { var args = (filter == '@' ? parser : secondParser)(normalize(path), normalize(filter)) if (typeof args != 'undefined') { this[TRIGGER].apply(null, [filter].concat(args)) return routeFound = true // exit from loop } }, this) } /** * Register route * @param {string} filter - filter for matching to url * @param {function} action - action to register */ prot.r = function(filter, action) { if (filter != '@') { filter = '/' + normalize(filter) this.$.push(filter) } this.on(filter, action) } var mainRouter = new Router() var route = mainRouter.m.bind(mainRouter) /** * Create a sub router * @returns {function} the method of a new Router object */ route.create = function() { var newSubRouter = new Router() // stop only this sub-router newSubRouter.m.stop = newSubRouter.s.bind(newSubRouter) // return sub-router's main method return newSubRouter.m.bind(newSubRouter) } /** * Set the base of url * @param {(str|RegExp)} arg - a new base or '#' or '#!' */ route.base = function(arg) { base = arg || '#' current = getPathFromBase() // recalculate current path } /** Exec routing right now **/ route.exec = function() { emit(true) } /** * Replace the default router to yours * @param {function} fn - your parser function * @param {function} fn2 - your secondParser function */ route.parser = function(fn, fn2) { if (!fn && !fn2) { // reset parser for testing... parser = DEFAULT_PARSER secondParser = DEFAULT_SECOND_PARSER } if (fn) parser = fn if (fn2) secondParser = fn2 } /** * Helper function to get url query as an object * @returns {object} parsed query */ route.query = function() { var q = {} var href = loc.href || current href[REPLACE](/[?&](.+?)=([^&]*)/g, function(_, k, v) { q[k] = v }) return q } /** Stop routing **/ route.stop = function () { if (started) { if (win) { win[REMOVE_EVENT_LISTENER](POPSTATE, debouncedEmit) win[REMOVE_EVENT_LISTENER](HASHCHANGE, debouncedEmit) doc[REMOVE_EVENT_LISTENER](clickEvent, click) } central[TRIGGER]('stop') started = false } } /** * Start routing * @param {boolean} autoExec - automatically exec after starting if true */ route.start = function (autoExec) { if (!started) { if (win) { if (document.readyState == 'complete') start(autoExec) // the timeout is needed to solve // a weird safari bug https://github.com/riot/route/issues/33 else win[ADD_EVENT_LISTENER]('load', function() { setTimeout(function() { start(autoExec) }, 1) }) } started = true } } /** Prepare the router **/ route.base() route.parser() module.exports = route });
var RE_ORIGIN = /^.+?\/\/+[^\/]+/; var EVENT_LISTENER = 'EventListener'; var REMOVE_EVENT_LISTENER = 'remove' + EVENT_LISTENER; var ADD_EVENT_LISTENER = 'add' + EVENT_LISTENER; var HAS_ATTRIBUTE = 'hasAttribute'; var POPSTATE = 'popstate'; var HASHCHANGE = 'hashchange'; var TRIGGER = 'trigger'; var MAX_EMIT_STACK_LEVEL = 3; var win = typeof window != 'undefined' && window; var doc = typeof document != 'undefined' && document; var hist = win && history; var loc = win && (hist.location || win.location); var prot = Router.prototype; var clickEvent = doc && doc.ontouchstart ? 'touchstart' : 'click'; var central = observable(); var started = false; var routeFound = false; var debouncedEmit; var base; var current; var parser; var secondParser; var emitStack = []; var emitStackLevel = 0; /** * Default parser. You can replace it via router.parser method. * @param {string} path - current path (normalized) * @returns {array} array
import GET_BY_CATEGORY from 'api/GET_BY_CATEGORY'; import GET_INDEX_ITEMS from 'api/GET_INDEX_ITEMS'; import observable from 'riot-observable'; import cartStore from 'store/cart'; let store = {}; observable( store ); // ============= Data ============= let items = []; let loading = true; let activeTabIndex = 0; store.getItems = () => { return items; }; store.getLoading = () => { return loading; }; store.getActiveTabIndex = () => { return activeTabIndex; }; // ============ Actions ============= let fetchPromise = {}; store.on('GET_BY_CATEGORY', async ( category ) => {
REMOVE_EVENT_LISTENER = 'remove' + EVENT_LISTENER, ADD_EVENT_LISTENER = 'add' + EVENT_LISTENER, HAS_ATTRIBUTE = 'hasAttribute', REPLACE = 'replace', POPSTATE = 'popstate', HASHCHANGE = 'hashchange', TRIGGER = 'trigger', MAX_EMIT_STACK_LEVEL = 3, win = typeof window != 'undefined' && window, doc = typeof document != 'undefined' && document, hist = win && history, loc = win && (hist.location || win.location), // see html5-history-api prot = Router.prototype, // to minify more clickEvent = doc && doc.ontouchstart ? 'touchstart' : 'click', started = false, central = observable(), routeFound = false, debouncedEmit, base, current, parser, secondParser, emitStack = [], emitStackLevel = 0 /** * Default parser. You can replace it via router.parser method. * @param {string} path - current path (normalized) * @returns {array} array */ function DEFAULT_PARSER(path) { return path.split(/[/?#]/) } /** * Default parser (second). You can replace it via router.parser method.
export const compile = function (arg, fn, opts) { if (typeof arg === T_STRING) { // 2nd parameter is optional, but can be null if (isObject(fn)) { opts = fn fn = false } // `riot.compile(tag [, callback | true][, options])` if (/^\s*</m.test(arg)) { var js = compiler.compile(arg, opts) if (fn !== true) globalEval(js) if (isFunction(fn)) fn(js, arg, opts) return js } // `riot.compile(url [, callback][, options])` GET(arg, function (str, opts, url) { var js = compiler.compile(str, opts, url) globalEval(js, url) if (fn) fn(js, str, opts) }, opts) } else if (isArray(arg)) { var i = arg.length // `riot.compile([urlsList] [, callback][, options])` arg.forEach(function(str) { GET(str, function (str, opts, url) { var js = compiler.compile(str, opts, url) globalEval(js, url) i -- if (!i && fn) fn(js, str, opts) }, opts) }) } else { // `riot.compile([callback][, options])` if (isFunction(arg)) { opts = fn fn = arg } else { opts = arg fn = undefined } if (ready) { return fn && fn() } if (promise) { if (fn) promise.on('ready', fn) } else { promise = observable() compileScripts(fn, opts) } } }