Thanks @alawi. Here’s some additional info in case yourself or anyone else is interested:
To summarize a few of the primary problems that I’ve faced and the solutions that I’ve arrived at…
Ephemeral v Long-Lived State. The usefulness of ephemeral/template-level state is limited. At the same time, representing all long-lived state using a single object, e.g. Session, is cumbersome and difficult to manage. Session is just a ReactiveDict. An app can create as many ReactiveDicts as it needs to faithfully represent and isolate its’ long-lived state. Easy enough except that some means of organizing and storing the various “state stores” is needed - which is where larger structures come into play.
Additionally, I wanted a consistent means of wiring these up via config data and various other “enhancements”, and so I created a thin wrapper around ReactiveDict - see createStateStore
. Enhancements include:
- simple type checking
- input transformation
- default/initial state
- reset methods
- dirty checking methods
Static v Dynamic Resource Access. The import/export system in Meteor is indispensable but, at some point, explicit imports become difficult to manage. For instance, apps representing tens of domain models each having multiple variations of search, form, and other views. Instead, I wanted to be able to access resources* dynamically/reactively, e.g. SomeApp.getModule('someModule').getView('someView').doSomething()
, and to not be restricted by a template or any other construct in accessing those resources.
The solution I arrived at is just a simple, in-memory key/value registry component - see createRegistry
. Various registries are used throughout the system. For example, each instance of the createApp
component has its’ own module and shell registries. Registering a resource is just a matter of calling a method, e.g. SomeApp.addModule('moduleId', moduleObject)
. Any object can be registered but the intent is to store sub-component instances in some structured manner so that those resources, and their methods, can be accessed later.
Intrinsic v Extrinsic Routing. I wanted a system that would work independent of, or even without, routing. In my view, while the URL is unlikely to die anytime soon, routing shouldn’t be a de-facto requirement. The meteor-composite-* packages have no concept of routing. Instead, any routing is implemented at the app-level. The example app demonstrates a very minimalistic solution using msavin:parrot
.
Page v Nested Region Model. I wanted the ability to compose very complex UIs. For instance, displaying many search views (with complex filtering and pagination) on the same screen. I realize that there are existing “layout” solutions that support the notion of “regions” but I didn’t want to run into any walls and so developed my own solution. I was able to develop a flexible, powerful solution in a little over 100 lines. The example app demonstrates the power and flexibility of these simple solutions. Additionally, since template helpers, UI controls, etc are “region aware” large swaths of functionality can be reused across regions. Take a look at the “tools” regions in the example app and the associated “search_tools” template.
Additionally, these solutions demonstrate how to:
- handle complex publication scenarios
- take control of complex ready and loading state
- pub/sub relational data efficiently and atomically
- implement customizable search and pagination over large collections
- share state and behavior across components, e.g. two or more search views that share filter state or even sub/query component instances
- create reusable, higher-order components, generic components (such as
createSearchView
) but also Blaze components and global template helpers
- use unmanaged collections on the client to create advanced features such as notifying users when a record changes or when new search results are available