Skip to content

Allow messages to be sent to apps - #259

Merged
jorgebucaran merged 1 commit into
hyperapp:masterfrom
zaceno:return-emit
Jul 3, 2017
Merged

jorgebucaran merged 1 commit into
hyperapp:masterfrom
zaceno:return-emit

Conversation

@zaceno

@zaceno zaceno commented Jun 29, 2017

Copy link
Copy Markdown
Contributor

We let the app(...) call return its emit-function. This allows
external agents to send messages to the app, and the app doesn't
need to know who they are and how they communicate. It only needs
to define its external interface in the events property.

@codecov

codecov Bot commented Jun 29, 2017 •

Copy link
Copy Markdown

Codecov Report

Merging #259 into master will not change coverage.
The diff coverage is 100%.

Impacted file tree graph

@@          Coverage Diff          @@
##           master   #259   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files           3      3           
  Lines         145    146    +1     
  Branches       44     44           
=====================================
+ Hits          145    146    +1
Impacted Files Coverage Δ
src/app.js 100% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 38949d5...ebf3087. Read the comment docs.

@zaceno

zaceno commented Jun 29, 2017

Copy link
Copy Markdown
Contributor Author

There are lots of situations you may find yourself in, where you may want your hyperapp-app to be it's own, self-contained thing, within a larger system of logic. Where moving all the rest of the logic into your hyperapp app is not feasible or desirable.

In such situations, want your app to be able to receive messages from the "outside" -- but you don't want to be dependent on what the outside is (because it might be changing). So you want to set up a "consumer-agnostic" interface for your app.

The great thing is: we don't need to add any new concepts! The events option of the app definition is the perfect place to define such an interface.

This PR makes app() return the emit function, so that external agents are able to consume that interface, once your app has defined it.

@zaceno

zaceno commented Jun 29, 2017

Copy link
Copy Markdown
Contributor Author

Some situations where this would be useful:

  • You want to add a small new hyperapp app to a webpage which is otherwise full jquery-spaghetti (and you're not ready to refactor the whole thing into a single hyperapp app)

  • You want to port a large app written in some other framework to HyperApp. Needs to be done incrementally, by successively replacing parts of the old system with new Hyperapp-parts.

  • You've made a chat-app for example, and you like how it works, but you want to play around with some alternative technologies for the network-communications without messing with the core app. So you break out the websocket-logic into a separate bit of logic, that communicates with your app over the interface. Now you can swap out the underlying technology, while never even having to touch the rest of your app.

  • You want to run your app inside the context of some kind of debugger/profiler.

@jorgebucaran

Copy link
Copy Markdown
Collaborator

@zaceno Thanks!

I'll wait until we've figured out components, since I am still not sure about how to implement those exactly.

@jorgebucaran jorgebucaran added enhancement New feature or request FUTURE labels Jun 29, 2017
@zaceno

zaceno commented Jun 29, 2017

Copy link
Copy Markdown
Contributor Author

@jbucaran No problem. Can't truly focus on more than one thing at a time :)

But just to be clear: This PR doesn't overlap or have anything to with components. Components are about what goes on 'inside' an app. This is about letting the app be aware of stuff going on outside it.

@zaceno

zaceno commented Jun 29, 2017

Copy link
Copy Markdown
Contributor Author

http://tech.noredink.com/post/126978281075/walkthrough-introducing-elm-to-a-js-web-app

Note the use of ports/signals - that's what I'm going for with this.

@zaceno zaceno mentioned this pull request Jun 30, 2017
@selfup

selfup commented Jun 30, 2017 •

Copy link
Copy Markdown
Contributor

This is GREAT! Exactly what I envisioned for cross app communication when sharing a common hyperapp bundle! 🎉

This is also great for socket.io or other things (many of which you mentioned)

@zaceno

zaceno commented Jun 30, 2017

Copy link
Copy Markdown
Contributor Author

I'm a little bummed we have to wait for components before this. That Components thread is on 🔥 ...still...

Comment thread src/app.js Outdated
return element
}

return emit

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 So clean and simple 😛

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zaceno Can you put that return statement before all the functions?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do!

@jorgebucaran

Copy link
Copy Markdown
Collaborator

@zaceno If we return the emit function then we close the door to returning the element. Do you think we could regret that in the future?

@lukejacksonn

Copy link
Copy Markdown
Contributor

We could return an object { element, emit, ... }

@jorgebucaran

jorgebucaran commented Jul 3, 2017 •

Copy link
Copy Markdown
Collaborator

@lukejacksonn Right. I shouldn't have said "close the door". There's always a way, but it won't be as elegant as having a single return value. 😏

I am tempted to merge this now, but I'd like to hear more about what other people think? /cc @jamen @selfup @SkaterDad @lukejacksonn @matejmazur @traducer

@lukejacksonn

Copy link
Copy Markdown
Contributor

The yo olde elegant vs extensible seesaw.

We choose either element or emit now and in the future we decide we need/want the other so have to push a breaking change and resort to { element, emit, ... } anyway.

OR

We choose either { element } or { emit } or both { element, emit, ... } now and never have to worry about adding anything.. but you have to do app.element and app.emit forever more.

@zaceno

zaceno commented Jul 3, 2017

Copy link
Copy Markdown
Contributor Author

@jbucaran @lukejacksonn

Good point about element. Both emit and element are probably useful/necessary in integration scenarios.

The difference though, is that you can always find out element somehow. Like a top-level id="my-hyperapp-app" and then document.querySelector('#my-hyperapp-app'). Or just go get the element in whatever you set as the root of your app.

But the emit function will forever be hidden in the closure unless we expose it somehow.

Perhaps there are other aspects to integration that I haven't thought about, but I feel pretty confident that emit is the only one that there won't be a workaround for.

So for now, I say we go with just returning emit.

@zaceno

zaceno commented Jul 3, 2017

Copy link
Copy Markdown
Contributor Author

commit 249224b moves the return line to where @jbucaran wanted it.

@jorgebucaran

Copy link
Copy Markdown
Collaborator

@zaceno Could add some explanation in core.md? Or do you think that's not necessary now?

@jacobtipp

Copy link
Copy Markdown

I really like this change. This makes custom events even more useful and makes any outside service or apps communicating with a hyperapp app predictable.

@jorgebucaran

Copy link
Copy Markdown
Collaborator

@zaceno How would this look in practice?

@zaceno

zaceno commented Jul 3, 2017

Copy link
Copy Markdown
Contributor Author

@jbucaran sure I'll add some more explanation in the docs.

as for "how this would look in practice"... well, lets go with the "I'm porting my app written in framework X to hyperapp"-scenario.

Like at work we have this app made with Backbone/Marionette. It's big and complicated so if we were going to port it, we'd do it one bit at a time. Lets say we go for the mediaplayer part of it. We're just converting our media-player with it's controls, to hyperapp.

So the app would need ways to signal out to the rest of the application things like: "user pressed pause" "current timestamp is ..." "I need more media to keep playing". Those are examples of outgoing messages. The app can be told how to send those, by defining callbacks in the events/actions of the app.

And the app would have to be able to take in messages like "here's your new media", "jump to this timestamp", and some other syncronization signals (we have multiple player windows that need to play back in sync, even though their media may come in at slightly different times) These are incoming signals. The "inner" mediaplayer app defines events for these (like events: { 'heres more media': (state, actions, newMedia) => actions.addMedia(newMedia), ... })

And then the outer backbone app can :

tellMediaController = app(mediaControllerAppDef)
...
tellMediaController('heres more media', newMedia)

We let the `app(...)` call return its emit-function. This allows
external agents to send messages to the app, and the app doesn't
need to know who they are and how they communicate. It only needs
to define its external interface in the `events` property.
@zaceno

zaceno commented Jul 3, 2017

Copy link
Copy Markdown
Contributor Author

ebf3087 adds "integration" section to core.md with a very barebones example of how this could be used

@SkaterDad

Copy link
Copy Markdown
Contributor

I really like this idea. 👍

Once this is landed, you could get more hyperapp advertising by writing an example app for FeathersJS for their docs. They have a client lib which emits events, I believe.

Returning an object would certainly be the most flexible solution long-term. I'm not sure I see the use case for returning the element, though?

@jorgebucaran

Copy link
Copy Markdown
Collaborator

@SkaterDad For integrating a third party with hyperapp, I think the best route is not using an emitter, but wrapping the library into a functional component (custom tags) and use the lifecycle events.

@zaceno

zaceno commented Jul 3, 2017

Copy link
Copy Markdown
Contributor Author

@jbucaran but featherjs (after just now learning about them and checking their website) seems like a backend-communication library. Nothing to render. So seems to me that events are the way to hook in (wether external or internal) -- not lifecycle methods

@zaceno

zaceno commented Jul 3, 2017 •

Copy link
Copy Markdown
Contributor Author

@SkaterDad Nah you're probably right. I had a PR before where the app would return the element, because I was trying to make app's work like stateful components, and I needed the element to inject it into the tree of the parent app. But again, you can always find the element anyway, so I closed that PR.

I think just returning emit is a safe bet for the future.

EDIT: typo

@jorgebucaran

Copy link
Copy Markdown
Collaborator

@zaceno What if instead of returning emit, we return render()?

@zaceno

zaceno commented Jul 3, 2017 •

Copy link
Copy Markdown
Contributor Author

@jbucaran I don't think render() is enough. Like in my example of porting a part of a Backbone/Marionette app. There are different types of messages you want the app to recieve, and you want to be able to pass it a payload (like "here's new media for you to play")

Essentially, you want to be able to affect the state of the app, while leaving it up to the app to define what ways and how state is managed and represented.

@jorgebucaran

Copy link
Copy Markdown
Collaborator

What if we return a function that works like this:

var zap = app({ ... })

zap((emit, render) => {
  // Want to render?
  render(/* different view */)
  // Want to emit?
  emit("msg", data)
})

@zaceno

zaceno commented Jul 3, 2017 •

Copy link
Copy Markdown
Contributor Author

As long as we expose emit somehow, I'm happy :) I don't really see the point in your zap function there, though. Seems like if forcing the app to rerender is something you want, you could define an action for that (rerender: state => state), and an event ('force-rerender': (state, actions) => actions.rerender())... and then use the emit to tell the app to rerender:

tellApp = app({...})
...
tellApp('force-rerender')

...sure it's more work than what you're suggesting. But I don't see that asking an app to render itself would be a common use case either.

Maybe that thing you had in your example: "/* different view */" is the key to understanding what you're suggesting? Could you elaborate?

@jorgebucaran

Copy link
Copy Markdown
Collaborator

@zaceno Awesome thanks! Last question, how would you actually use this?

A module that runs hyperapp / calls app() and export the emit function?

export const emit = app({ state, view, actions })

@zaceno

zaceno commented Jul 3, 2017 •

Copy link
Copy Markdown
Contributor Author

Could be in a module, sure. In the example I brought up about porting an app that seems sensible. But I can also imagine it as a way to start bringing some sanity into a web app made up of just jquery-spaghetti. In that case you wouldn't have modules and a build-system (probably) So it depends I guess.

@jorgebucaran
jorgebucaran merged commit f468dc0 into hyperapp:master Jul 3, 2017
@jorgebucaran

Copy link
Copy Markdown
Collaborator

SGTM. What's the worst that could happen anyway.

4450554

@FlorianWendelborn

Copy link
Copy Markdown

@jbucaran I'm calling it now — it will manage to break something for me. I don't know how, yet. 🙂

@jorgebucaran

jorgebucaran commented Jul 3, 2017 •

Copy link
Copy Markdown
Collaborator

@dodekeract Unlikely, app never returned anything. Now, #275 might break stuff. 😏:wink:

@zaceno

zaceno commented Jul 3, 2017

Copy link
Copy Markdown
Contributor Author

🎉 Thanks, everyone involved!

@dodekeract from now on, if anything breaks, just assume it's my fault, ok? ;)

@selfup

selfup commented Jul 3, 2017 •

Copy link
Copy Markdown
Contributor

OMG it is in! This is amazing, now I can easily use hyperapp for a side project (web sockets hehe) I had in mind 👍 🎉

Thank you so much @zaceno 🥇

@zaceno
zaceno deleted the return-emit branch July 3, 2017 19:41
@Swizz Swizz mentioned this pull request Jul 4, 2017
jorgebucaran pushed a commit that referenced this pull request Jan 7, 2018
We let the `app(...)` call return its emit-function. This allows
external agents to send messages to the app, and the app doesn't
need to know who they are and how they communicate. It only needs
to define its external interface in the `events` property. 🐵🙈
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants