Skip to content

Instantly share code, notes, and snippets.

@Gozala
Forked from ZER0/gist:5548355
Last active December 17, 2015 09:08
Show Gist options
  • Save Gozala/5584873 to your computer and use it in GitHub Desktop.
Save Gozala/5584873 to your computer and use it in GitHub Desktop.
// Button is a high level construct that can be used
// to define button types with a specific behaviors
let { Button } = require("sdk/ui");
// Button with specicic behavior can be defined by passing a function
// defining a behaivor of a button on the specific state changes.
var button = Button(function behavior(state, {owner}) {
// First argument represents current `state` snapshot for the button
// in the context of the given `options.owner` window.
// Button behavior may cause other state changes as a recation
// to specific state chnage. For example whenever button is
// pressed it's image and checked status changes:
var checked = state.pressed ? !state.checked : state.checked
return {
checked: checked,
image: checked ? "./coffee.png" : "./beer.png"
};
});
// Buttons can be added to the UI by writing inital state into them:
button({ image: "./beer.png", label: "My Button", checked: false });
// Button state can be updated in the individual contexts, by passing
// a second context argument. This will also let you instantiate buttons
// only on the spicific windows if you want to.
button({ checked: true }, activeWindow); // you could pass tab instead we'll figure window
// Buttons can be removed from the UI by writing a `null` state:
button(null);
// Making buttons with shared state is also non brainer:
let sharedButton = Button(function sharedBehavior(state) {
var checked = state.checked;
var image = state.checked ? "./coffee.png" : "./beer.png ";
sharedButton({ checked: checked, image: image })
})
// Add shared button to the UI
sharedButton({ image: "./beer.png", label: "My Shared Button", checked: false })
// State computation is buisness of the behavior, but any arbitare state
// changes could be feed into it to do that:
const tabs = require("sdk/tabs");
var myButton = Button(function(state) {
// ....
return { image: state.isImageType && state.checked ? "./on.png" : "./off.png" }
})
tabs.on("activate", function (tab) {
let isImageType = tab.contentType.indexOf("image/") === 0;
myButton(isImageType, tab);
});
@Mossop
Copy link

Mossop commented May 15, 2013

I find this horribly confusing to follow, perhaps because I don't understand what the function passed to Button is for, when is it called and what is it passed?

@Gozala
Copy link
Author

Gozala commented May 16, 2013

I find this horribly confusing to follow, perhaps because I don't understand what the function passed to Button is for, when is it called and what is it passed?

@Mossop Button takes a function as an argument, that defines it's behavior to a state changes. Every time state attributes changes (for example button is pressed, it's state.pressed becomes true) behavior function is invoked with a last state snapshot and an options.owner window to specify scope where change happened. Function is supposed to react by returning hash of attributes, as a result button state will transition to a new state for the given options.owner window. In a way Button here creates a class rather than instance, and lest system create and destroy instances as it needs to (create for each window or maybe tab, and destroy for each tab etc...) system becomes very flexible in terms of when and how many times it calls behavior function. This also triggers more idiomatic approach where user does not needs to find state of the button for an individual context (window, tab) instead it just writes some chance in the context of the window button({ checked: true }, window) and then system triggers behavior function that will be given state and it can perform transaction, causing view of the button for the given window to change. It's also interesting that Button(behavior) returns button function that is just a writer for
changes, so initial rendering is logically just a write with initial state button({ pressed: false }), note that if context of change (window, tab)` is not passed as second argument changes are written into all viewports. Side effect of this design is that shared state VS context sensitive state becomes very trivial & just one line change.

This approach is of course not something I've just made up, it's being known in computer science as FRP (Functional Reactive Programing) which is an alternative to more commonly used MVC. Both solve similar problems but have different pros and cons, although I think FRP is better
suited to one to many cases like we have where single identity may have different state in multiple contexts. For exact same reasons popular d3js library went with reactive API since this problem is even more
visible when dealing with huge graphs that MVC is just not good at.

While I personally think FRP would make a lot of problems go away, I can see constraints of non-mainstream APIs. So I'm ok with doing plain MVC you guys find it to be a better option.

@Mossop
Copy link

Mossop commented May 16, 2013

Ok I guess I get it but I think it's just so different to our existing APIs that we're just going to confuse our developers by going this approach. I think we should stick with @ZER0's approaches.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment