Rather than try to introduce you to Meteor myself, I'll simply defer to the meteor web site www.meteor.com and let them do the talking. The site includes a high-level description of the framework, detailed documentation, example code and links to videos. The quality of the description and documentation alone merit your attention - and the capability of Meteor deserves your respect. This stuff is brilliant.
Of all the things Meteor brings to the table, Reactivity is the game changer for me. Updates to data sources cause dependent computations to be re-run, and the application shows updates immediately. The burden of carefully choreographing a dance between the server and the client to display pleasing changes in a timely way has been lifted. The dance is already beautiful and the performers are well-rehearsed. I can focus on building great user experience rather than on things like scratching my head over problems with data structures or working the weird bugs out of ajax callbacks.
Recently, when developing a web app I found myself wanting to use this strategy locally as well, building reactivity to be used only within a client. Though Meteor gives you session-level reactivity that propagates changes made to session variables locally, this wasn't quite what I wanted. Session variables are global to the client; I wanted variables that were at least scoped so I could keep them more closely associated with the functions and objects that use them. I've been conditioned to believe that global variables smell bad and I'm not ready to give up that sense yet.
Instead, I opted to adapt a notion that was mentioned in the Meteor documentation. I associated a variable and a dependency in an object and called it an injective. The idea is that I can inject client values into the reactive flow. In Coffeescript,
Deps.injective = (init, options) ->
_value: init ? 0
_dep: new Deps.Dependency
_force: !!(options && options.force) ? false
set: (value) ->
if (@_value != value) || @_force
@_value = value
@changed()
@
get: ->
@depend()
@_value
depend: ->
@_dep.depend()
@
changed: ->
@_dep.changed()
@
force: (f) ->
@_force = !!f
@
The internal
_value
and _dep
are managed through the get
and set
functions.
Calling get
returns the value while making the function asking for it dependent on the injective.
Updating a value using set
re-computes all functions that depend upon it.A quick example perhaps, again in Coffeescript.
App.innerWidth = Deps.injective window.innerWidth
$(window).resize -> App.innerWidth.set window.innerWidth
This code creates an injective that tracks the inner width of the browser window.
When the window resizes, the injective's value is set to the new inner width.
While this might not sound like much, anything that has been computed based on the getting the value of innerwidth
will be automatically recomputed when it changes.
In the context of this example, that means that you don't have to know about the 23 layout decisions you made based on this value and how they'll need to propagate, and then manage it all yourself.
Instead, you just grab the corner of the window and resize it, and watch the magic happen.The
depend
and changed
functions provide access to the dependency mechanism and are used by get
and set
.
When depend
is called, the injective adds the calling function to its list of dependents.
An update to the injective using set
will re-run these dependent functions.
The changed
function is what triggers the dependents to be re-run.Finally,
force
is a way to force changes to objects and arrays values to be propagated without having to write any special comparitors.
When the value being set is a scalar, ==
works: if the value is different it is copied and changed
is called.
If the value is an object (or an array) and the change is made within the object itself, ==
won't report the object has changed; a more complex comparison must be written to do the test.
But that really isn't needed if you know about the complexity already.
The force
function (or constructor argument) provides a simple shortcut: if _force
has been set to true
then the set
function trusts that the incoming value is different; set
just goes ahead and updates the value and propagates the change.
If something more complex is needed, values in the object can be adjusted and the injective's changed
function can be called directly.I've submitted injective (a Javascript version) to the meteor github repository. Maybe they'll integrate it, maybe not; I wasn't fixing a bug, just adding an enhancement so it doesn't have any sort of priority. However, it's a clean way to inject values into the reactivity stream that's useful for me. If it looks like it may be useful for you too, then enjoy!
No comments:
Post a Comment