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

Confused by all the output options #87

Closed
stevekrouse opened this issue Dec 12, 2018 · 6 comments
Closed

Confused by all the output options #87

stevekrouse opened this issue Dec 12, 2018 · 6 comments

Comments

@stevekrouse
Copy link
Contributor

stevekrouse commented Dec 12, 2018

I'm very confused by the various styles of output:

// 1
const counterView = ({ count }) =>
  div([
  "Counter ",
  count,
    button("+").output({ incrementClick: "click" }),
]);

// 2
const counterView = ({ count }) =>
  div([
    h2("Counter"),
    count,
    button({ output: { incrementClick: "click" } }, "Count")
  ]);

 // 3
const counterView = go(function*({ count }) {
  const {click: incrementClick} = yield button("Count")
  yield text(count)
  return { incrementClick }
})

Are some older styles that still work or are some fully depreciated? It'd probably go a long way towards my sanity if I knew which was the preferred way. (Unless they are not fully equivalent in which case I'd be curious to know the trade-offs of each style.)

@paldepind
Copy link
Member

Option 1 and 2 does exactly the same. In the latest version of Turbine option 2 was removed. Essentially all the inbuilt HTML element functions in Turbine accepted an output property on their input object with the same behavior as using the output method on components. We removed it to

  • Make things simpler
  • Increase consistency between the "inbuilt" components and the components a user creates himself which , unless the user explicitly does the work, does not handle an output property.

As for option 3. Using generator function corresponds to invocations to the chain method (similarly to how do-notation in Haskell corresponds to invocations of the >>= operator). We've tried to explain it in the documentation. If that is unclear please let me know. With that in mind I think the question is: what is the difference between the output method and the chain method? I think that will take a slightly longer explanation. I will get back to you with that 😄

@stevekrouse
Copy link
Contributor Author

stevekrouse commented Dec 14, 2018

Ok, so version 2 is gone and dead. Are 1 and 3 equivalent?

I understand that chain is bind (>>=) in Haskell or then in JS. So how does output correspond to Haskell?

I guess this relates to my question #93 on go and fgo

@paldepind
Copy link
Member

Option 1 and 3 are not equivalent.

Option 3 is very similarly to what Reflex does, it allows for creating views using do-notation (or in our case the go function) and it supports combining model and view.

Option 1 is unique to Turbine and works well when separating model and view. Option 3 can also be also be used when separating model and view, and we did that in the beginning, but option 1 is ideally suited for this use case. My own opinion is that combining model and view and using option 3 is in most cases a bad idea. But before we can properly have a conversation about that I should explain option 1 properly.

In order to understand the output method it's important to understand the difference between selected output and available output. This is currently not documented at all, thus I've created the PR #99 to document it. Here's a link to the updated section in the PR that hopefully does an adequate job at explaining it.

The takeway of the added documentation in #99 is this: The output method moves output from the available output into the selected output. When a view is build all the selected output is merged an it becomes the final output.

This makes it very easy to build views with nesting with nested elements. It doesn't matter if a button is heavily nested in some other HTML. It's selected output will "bubble up" as the button is combined with its siblings and parents.

I think the following example would be very annoying to implement using Reflex or option 3 since the a elements are nested so deep.

const counterModel = fgo(function* ({ incrementClick, decrementClick }) {
  const changes = combine(incrementClick.mapTo(1), decrementClick.mapTo(-1));
  const count = yield sample(scan((n, m) => n + m, 0, changes));
  return { count };
});

const counterView = ({ count }) =>
  div([
  "Counter ", count,
  div([
    text("Here is the first button"),
    div({class: "button"}, [
      a({ class: "btn btn-default" }, "+").output({ incrementClick: "click" }),
    ]),
  ]),
  div([
    text("Here is the second button"),
    div({class: "button"}, [
      a({ class: "btn btn-default" }, "+").output({ incrementClick: "click" }),
    ]),
  ]),
]);

const counter = modelView(counterModel, counterView);

Maybe Reflex has some way of handling this, but as far as I'm aware doing the above is quite boiler plate heavy as one has to write code at every level to get the output to "bubble up". Option 1 in Turbine does this seamlessly.

Let me know if the added documentation in #99 makes it clear how output works an what it achieves. Otherwise I'll try to explain it more deeply.

@paldepind
Copy link
Member

Did the explanation above help you @stevekrouse?

@stevekrouse
Copy link
Contributor Author

This all makes sense. I sometimes have trouble getting Option 3 working correctly, but I guess that's less recommended/supported....

@paldepind
Copy link
Member

It seems that this issue can be closed now. Option 2 has since been removed in favor of always using option 1 instead. So I guess that improves the situation. Option 3 still works but it's probably more advanced usage and probably not something that a new user would have to even know about.

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

2 participants