tunnckoCore / ideas

:notebook: My centralized place for ideas, thoughts and todos. Raw, PoC implementations and so on... :star:
http://j.mp/1stW47C
6 stars 0 forks source link

gibon@next based on dush (probably) #75

Open tunnckoCore opened 7 years ago

tunnckoCore commented 7 years ago

Not considered yet, because it will got to 1kb, not 500bytes.

But it is more smaller and more stable, based on dush-router and just 2 events for hrefChange and historyChange. A lot more easier to build app on top of it.

/*!
 * gibon <https://github.com/tunnckoCore/gibon>
 *
 * Copyright (c) Charlike Mike Reagent <@tunnckoCore> (https://i.am.charlike.online)
 * Released under the MIT license.
 */

'use strict'

import dush from 'dush'
import router from 'dush-router'

export default () => dush().use(router()).use((app) => {
  onHistory((node, e) => {
    app.emit('historyChange', node, e)
  })
  onHref((node, e) => {
    app.emit('hrefChange', node, e)
  })
})

function onHistory (cb) {
  window.addEventListener('popstate', function onpopstate (e) {
    cb({
      pathname: window.location.pathname,
      search: window.location.search,
      href: window.location.href,
      hash: window.location.hash
    }, e)
  })
}

function onHref (cb) {
  window.addEventListener('click', function onclick (e) {
    if (which(e) !== 1) {
      return
    }
    if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.defaultPrevented) {
      return
    }

    // ensure link
    // use shadow dom when available
    var el = e.path ? e.path[0] : e.target
    while (el && el.nodeName !== 'A') {
      el = el.parentNode
    }

    if (!el || el.nodeName !== 'A') {
      return
    }

    // allow mailto links to work normally
    var link = el.getAttribute('href')
    if (link && link.indexOf('mailto:') > -1) {
      return
    }

    // allow external links to work normally
    var sameHost = el.host === window.location.host
    if (!sameHost) {
      return
    }

    // prevent default behaviour on internal links
    if (sameHost || el.pathname === window.location.pathname) {
      e.preventDefault()
    }

    // allow opt out when custom attribute
    if (!el.hasAttribute('data-no-routing')) {
      cb(el, e)
      return
    }
  })
}

function which (e) {
  e = e || window.event
  return e.which === null ? e.button : e.which
}

sample app built on it, using nanomorph


var html = require('bel')
var gibon = require('../dist/gibon.common')
var nanomorph = require('nanomorph')

function GibonApp (state) {
  var router = gibon()
  var tree = null
  var data = {}

  router.off('route')
  router.on('route', function (view, context) {
    return (tree = nanomorph(tree, view(context.state, context.params)))
  })

  router.start = function () {
    tree = router.navigate(window.location.pathname, state)

    router.on('render', function (node, hist) {
      if (hist) {
        window.history.replaceState(data, '', node.pathname)
      } else {
        window.history.pushState(data, '', node.pathname)
      }
      router.navigate(node.pathname, state)
    })

    router.on('historyChange', function (node) {
      router.emit('render', node, true)
    })
    router.on('hrefChange', function (node) {
      if (node.href === window.location.href) return

      router.emit('render', node)
    })

    return tree
  }

  router.mount = function mount_ (selector) {
    var newTree = router.start()
    var main = document.querySelector(selector)
    tree = nanomorph(main, newTree)
  }

  router.toString = function toString_ (_pathname, _state) {
    return router.navigate(_pathname, _state).toString()
  }

  return router
}

var app = GibonApp({ title: 'Hello World!!' })

var routes = {
  '/': (state) => html`<div><h1>home</h1><h2>${state.title}</h2></div>`,
  '/about': (state) => html`<div><h1>about</h1><h2>${state.title}</h2></div>`,
  '/users/:user': (state, params) => html`<div>
    <h1>user</h1>
    <h2>${params.user}</h2>
    <h3>${state.title}</h3>
  </div>`,
  '/users/:user/edit': (state, params) => html`<div>
    <h1>user edit</h1>
    <h2>${params.user}</h2>
    <h3>${state.title}</h3>
  </div>`,
  '/groups/:group/users/:user/edit': (state, params) => html`<div>
    <h1>edit user from group ${params.group}</h1>
    <h2>${params.user}</h2>
    <h3>${state.title}</h3>
  </div>`
}

Object.keys(routes).forEach(function (route) {
  app.addRoute(route, routes[route])
})

app.mount('#app div')
tunnckoCore commented 7 years ago

basic prototype, but probably something similar would work (declarative routing)

the snippet is from React Router v4, a bit adapted


let router = null
const Router = ({ children }) => {
  router = gibon()
  return <div>{children}</div>
}

const Link = ({ to, children }) => {
  return (
    <a href={to}>
      {children}
    </a>
  )
}

const Route = ({ path, state, exact, component }) => {
  router.addRoute(path, (ctx) =>
    component(
      Object.assign(
        {},
        {
          state,
          match: {
            path: path,
            isExact: exact,
            url: window.location.href,
            params: ctx.params
          }
        }
      )
    )
  )
}

const BasicExample = () => (
  <Router>
    <div>
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/about">About</Link></li>
        <li><Link to="/topics">Topics</Link></li>
      </ul>

      <hr />

      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
      <Route path="/topics" component={Topics} />
    </div>
  </Router>
)

const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
)

const About = () => (
  <div>
    <h2>About</h2>
  </div>
)

const Topics = ({ match }) => (
  <div>
    <h2>Topics</h2>
    <ul>
      <li>
        <Link to={`${match.url}/rendering`}>
          Rendering with React
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/components`}>
          Components
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/props-v-state`}>
          Props v. State
        </Link>
      </li>
    </ul>

    <Route path={`${match.url}/:topicId`} component={Topic} />
    <Route
      exact
      path={match.url}
      render={() => <h3>Please select a topic.</h3>}
    />
  </div>
)

const Topic = ({ match }) => (
  <div>
    <h3>{match.params.topicId}</h3>
  </div>
)
tunnckoCore commented 6 years ago

some chika with latest #85 and #84

const html = require('bel')
const dush = require('dush')
const router = require('dush-router')
const nanomorph = require('nanomorph')
const actions = require('./dush-actions-plugin')

// const app = dush().use(
//   actions({
//     state: {
//       text: 'Hello',
//       name: 'Charlike'
//     },
//     effects: {
//       changeBoth: function ({ actions }, data, arg2) {
//         actions.changeText({ welcome: data.text })
//         actions.changeName(data.name, arg2)
//       }
//     },
//     reducers: {
//       changeText: ({ emit, state }, data) => ({ text: data.welcome }),
//       changeName: ({ actions, state }, name, arg2) => ({ name, arg2 })
//     }
//   })
// )

function chika (model) {
  const app = dush().use(router()).use(actions(model))
  let tree = null
  let data = {}

  app.off('route')
  app.on('route', (view, ctx) => {
    const match = {
      pathname: ctx.pathname,
      params: ctx.params,
      route: ctx.route
    }

    // allows:
    // ({ state, actions, emit, params, match })
    // ({ state, actions, emit }, params)
    // ({ state, actions, emit }, params, { route, pathname })
    // ({ state, actions, emit }, { userId, photoId })
    const likeProps = {
      actions: app.emit,
      emit: app.emit,
      state: app.state,
      params: match.params,
      match: match
    }

    // each route will have signature: ({ state, actions }, params, match)
    // so it is easy to destruct `params` and `match`
    return (tree = nanomorph(tree, view(likeProps, ctx.params, match)))
  })

  app.start = function start () {
    tree = app.navigate(window.location.pathname, app.state)

    app.on('render', (node, hist) => {
      if (hist) {
        window.history.replaceState(data, '', node.pathname)
      } else {
        window.history.pushState(data, '', node.pathname)
      }
      app.navigate(node.pathname, app.state)
    })

    app.on('historyChange', (node) => {
      app.emit('render', node, true)
    })
    app.on('hrefChange', (node) => {
      if (node.href === window.location.href) return

      app.emit('render', node)
    })

    return tree
  }

  app.mount = function mount (sel) {
    let main = typeof sel === 'string' ? document.querySelector(sel) : sel
    let newTree = app.start()
    tree = nanomorph(main, newTree)
  }

  return app
}

const app = chika({
  state: {
    title: 'Hello',
    name: 'Charlike'
  },
  effects: {
    changeBoth ({ actions, emit }, data, arg2) {
      actions.changeText({ welcome: data.text })
      actions.changeName(data.name, arg2)
    }
  },
  reducers: {
    changeText: ({ actions, emit, state }, data) => ({ title: data.welcome }),
    changeName: ({ actions, emit, state }, name, arg2) => ({ name, arg2 })
  }
})

// let routes = {
//   '/': ({ actions, emit, state }, params, match) =>
//     html`<div><h1>home</h1><h2>${state.title}</h2></div>`,
//   '/about': ({ actions, emit, state }, params, match) =>
//     html`<div><h1>about</h1><h2>${state.title}</h2></div>`,
//   '/users/:user': ({ actions, emit, state }, params, match) => html`<div>
//     <h1>user</h1>
//     <h2>${params.user}</h2>
//     <h3>${state.title}</h3>
//   </div>`,
//   '/users/:user/edit': ({ actions, emit, state }, params, match) => html`<div>
//     <h1>user edit</h1>
//     <h2>${params.user}</h2>
//     <h3>${state.title}</h3>
//   </div>`,
//   '/groups/:group/users/:user/edit': ({ actions, params, state }) => html`<div>
//     <h1>edit user from group: ${JSON.stringify(params)}</h1>
//     <h2>user: ${params.user}</h2>
//     <h3>title: ${state.title}</h3>
//   </div>`
// }

// Object.keys(routes).forEach((route) => {
//   app.addRoute(route, routes[route])
// })

// app.use((app) => {
//   console.log('state:', app.state)
// })

app.mount('#app')