Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Choo to create reusable widgets? #309

Closed
dannief opened this issue Nov 3, 2016 · 13 comments
Closed

Using Choo to create reusable widgets? #309

dannief opened this issue Nov 3, 2016 · 13 comments

Comments

@dannief
Copy link

dannief commented Nov 3, 2016

I wanted to see if I could use choo to create a widget that I can add to any HTML page. I had the following requirements:

  1. Ability to pass in options to customize the created DOM element
  2. Imperatively call methods on the element to trigger actions
  3. Handle events triggered by the element
  4. Have more than once instance of the element on a page

Was choo built to handle this? I noticed that all examples require the router to configured for example. I would not need this and I am interested to know if choo can be used without it. What I would want to use is the model and the view functionality.

I hacked together a POC of how this could be done. You can look at the jsbin here. It works but, as I said it's kind of hacky. My problem was really that I needed to get access to the send function, to be able to attach it to the element instance, in order to be able to call a function on the rendered element. The only way I could think to do it was to expose the send function outside of the main view.

So, is there a better way?

By the way, great framework. It's really fun!

@yoshuawuyts
Copy link
Member

Thanks! - Yeah, I think this should be possible: probably wanna turn off
the href handler and the history API, and then it should be good I think

On Thu, Nov 3, 2016 at 11:30 PM Debbie Facey [email protected]
wrote:

I wanted to see if I could use choo to create a widget that I can add to
any HTML page. I had the following requirements:

  1. Ability to pass in options to customize the created DOM element
  2. Imperatively call methods on the element to trigger actions
  3. Handle events triggered by the element
  4. Have more than once instance of the element on a page

Was choo built to handle this? I noticed that all examples require the
router to configured for example. I would not need this and I am interested
to know if choo can be used without it. What I would want to use is the
model and the view functionality.

I hacked together a POC of how this could be done. You can look at the
jsbin here http://jsbin.com/najuna/edit?html,js,output. It works but,
as I said it's kind of hacky. My problem was really that I needed to get
access to the send function, to be able to attach it to the element
instance, in order to be able to call a function on the rendered element.
The only way I could think to do it was to expose the send function
outside of the main view.

So, is there a better way?

By the way, great framework. It's really fun!


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#309, or mute the thread
https://github.com/notifications/unsubscribe-auth/ACWlevn4By9f-yS5DDZPUoHsk7WMHP3sks5q6lJ4gaJpZM4Ko8Mc
.

@dannief
Copy link
Author

dannief commented Nov 3, 2016

OK. So you saying it possible that future version of choo would not required the router?
What about the ability to utilize the send function outside of a view? Maybe something like app.send().

@yoshuawuyts
Copy link
Member

Oh you can already disable these things; there's no way to take out the
router though but yeah if you just have one route it's basically the same
thing. As for interacting with the outside world: you can pull down events
from outside through a subscription making something like app.send()
unnecessary :D

On Thu, Nov 3, 2016 at 11:58 PM Debbie Facey [email protected]
wrote:

OK. So you saying it possible that future version of choo would not
required the router?
What about the ability to utilize the send function outside of a view?
Maybe something like app.send().


You are receiving this because you commented.

Reply to this email directly, view it on GitHub
#309 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ACWlepWPI45cXo-atzbA7alJ6UVpFbUhks5q6lkTgaJpZM4Ko8Mc
.

@rollymaduk
Copy link

rollymaduk commented Nov 3, 2016

Hey I did something similar in my app, I have a wrapper utility function that injects child views into a parent view, that way I can separate my components from a parent template view and still use the router. It also allows my setup a pre-route and post route hook. I can also have different parent views for different routes like different layouts if i want.
util.js

export const loadPage= (parent:Function,child:any)=>(...args:any)=>{
//pre-route function here, post route can be added as callbacks in the args
  return parent(...args,child(...args))
}

main.js

export function main (state, prev, send,child)
{
return html '<div>${child}</div>'
}

child.js

export const child=function(state,prev,send){
return html '<div>I am a child</div>'
}

app.js

import {child} from "child.js"
import {main} from "main.js"
import {loaPage} from "util.js"
const app= require("choo")()

app.router("/",(route) => [
  route('/',loadPage(main,child))
]
const tree = app.start()
document.body.appendChild(tree)

@dannief
Copy link
Author

dannief commented Nov 3, 2016

@rollymaduk. That's a nice pattern, but doesn't really answer my question. I want to be able to essentially use a choo created element outside of a choo application. Just basically drop it on any page and be able to communicate with it via both handling events triggered from within the element as well as manually triggering actions. It is the latter that I asking about.

@yoshuawuyts I think I need to clarify a bit more.
You say I can pull events from outside world. That is fine, but what if I want to push data to the widget?

i.e. What if I want to trigger an action programmatically from outside? In the code snippet below, I've exposed an incr function on the element to do just that. It's just not very clean.
As you see in the mainView function, i'm creating a closure around the send parameter by creating a increment function that calls it, and then assigning the increment function to a variable outside of the function. This way I can then add the increment function to the element instance after it is created: const tree = app.start(); tree.incr = increment;

As I said, it works but not very clean. Just wondered if there is a better way.
I guess it's not a popular use case though, so I can just work with the hack for now.

function counterElement(id, initialCount) {
  const app = choo()

  let increment = null;
  let decrement = null;

  app.model({
    namespace: 'counter',
    effects: {
      triggerIncrement: (data, state, send, done) => {  
        document.getElementById(id).dispatchEvent(new Event('incr'));
        send('counter:increment', null, done);
      },
      triggerDecrement: (data, state, send, done) => {       
        document.getElementById(id).dispatchEvent(new Event('decr'));
        send('counter:decrement', null, done);
      }
    },
    state: {
      count: initialCount || 0
    },
    reducers: {
      increment: (data, state) => ({
        count: state.count + 1
      }),
      decrement: (data, state) => ({
        count: state.count - 1
      })
    }
  })

  const mainView = (state, prev, send) => {

    increment = function(e) { send('counter:increment'); }
    decrement = function(e) { send('counter:increment'); }

     return counter({
       count: state.counter.count, 
       incr: (e) => send('counter:triggerIncrement'),
       decr: (e) => send('counter:triggerDecrement')
     })    
  };

  const counter = (opts) => {
    return html`
    <div id="${id}">
         Count: ${opts.count || 0}
        <button onclick=${opts.incr}>+</button>
        <button onclick=${opts.decr}>-</button>
    </div>
  `
  }

  app.router((route) => [
    route('/', mainView)
  ])

  const tree = app.start()

  tree.incr = increment;
  tree.decr = decrement;

  return tree;
}

let el = counterElement("counter1", 5);
document.body.appendChild(el);

let el2 = counterElement("counter2", 20);
document.body.appendChild(el2);

el.incr()
el.incr()

el.addEventListener('incr', function (e) { alert('You incremented! Yay!') }, false);

@rollymaduk
Copy link

I see what you mean, perhaps in order to create a more generic send you could just avoid the closure and expose the send from your main view assigned to a variable on your returned tree element... havent tried it but I suspect it might work.

function counterElement(id, initialCount) {
  const app = choo()

  let send= null;

  app.model({
    namespace: 'counter',
    effects: {
      triggerIncrement: (data, state, send, done) => {  
        document.getElementById(id).dispatchEvent(new Event('incr'));
        send('counter:increment', null, done);
      },
      triggerDecrement: (data, state, send, done) => {       
        document.getElementById(id).dispatchEvent(new Event('decr'));
        send('counter:decrement', null, done);
      }
    },
    state: {
      count: initialCount || 0
    },
    reducers: {
      increment: (data, state) => ({
        count: state.count + 1
      }),
      decrement: (data, state) => ({
        count: state.count - 1
      })
    }
  })

  const mainView = (state, prev, send) => {

    send= send

     return counter({
       count: state.counter.count, 
       incr: (e) => send('counter:triggerIncrement'),
       decr: (e) => send('counter:triggerDecrement')
     })    
  };

  const counter = (opts) => {
    return html`
    <div id="${id}">
         Count: ${opts.count || 0}
        <button onclick=${opts.incr}>+</button>
        <button onclick=${opts.decr}>-</button>
    </div>
  `
  }

  app.router((route) => [
    route('/', mainView)
  ])

  const tree = app.start()

  tree.send= send;

  return tree;
}

let el = counterElement("counter1", 5);
document.body.appendChild(el);

let el2 = counterElement("counter2", 20);
document.body.appendChild(el2);

el.send('counter:increment')
el.send('counter:triggerIncrement')

el.addEventListener('incr', function (e) { alert('You incremented! Yay!') }, false);

@rreusser
Copy link

rreusser commented Dec 18, 2016

@rollymaduk Thanks so much! This fits my needs exactly. At a glance, it appears the API may have changed slightly since you posted this, so in case anyone else is interested I've added an example that works for me with 4.0.1 here

The only other note is that href: false seems necessary to prevent the router from failing. I'll update the example as I clean it up a bit more…

@yoshuawuyts
Copy link
Member

Were you able to resolve this?

@rreusser
Copy link

rreusser commented Jan 8, 2017

@yoshuawuyts was that a question for me or in response to the above? I hate to hijack the issue with my concerns simply because I was the last to comment. 😄

Edit: Rereading, yeah, just checking with the original poster for closability. Describing the below since after trying a couple different approaches, I just deleted what I didn't need from choo and found the remaining part to be concise and useful for a self-contained widget! Probably the way I'd do it if I hadn't run into the other snag.

FWIW though, my goal with choo is to recreate a control widget more modern and feature-complete than dat.gui and perhaps a bit too divergent from control-panel to simply keep building on that. React is too big; plain JS is too ad-hoc. Choo seems perfect.

Taking a slightly different tack, I got as far as distilling choo down to choo-lite.js and built this.

I got hung up on this yo-yo issue in which what seems like a weird browser quirk negates some of the simplicity for which I was aiming. Haven't yet figured out how to work around it, but everything else about the approach works for me!

@yoshuawuyts
Copy link
Member

yoshuawuyts commented Jan 8, 2017 via email

@josephluck
Copy link

Although super early stages, this might be useful.

https://github.com/ohgoodlord/choo-component

Suggestions welcome!

@yoshuawuyts
Copy link
Member

yoshuawuyts commented Jan 21, 2017 via email

@yoshuawuyts
Copy link
Member

We should probably document this a bit further in the handbook, but https://www.npmjs.com/package/nanocomponent is a thing now ✨

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants