Skip to content
This repository has been archived by the owner on Oct 24, 2021. It is now read-only.

Blaze outline #75

Merged
merged 5 commits into from
Nov 3, 2015
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions outlines/blaze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Blaze

1. Introduction to Spacebars -- the tracker-backed handlebars like templating syntax
1. Example of spacebars syntax, data context + helpers
Copy link
Contributor

Choose a reason for hiding this comment

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

I think in this guide, we should go into detail about data context and how it relates to "Views" - we gloss over this in the intro tutorial completely.

Copy link
Contributor

Choose a reason for hiding this comment

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

However, there is now a world in which you could use Blaze without data context entirely! We could see what that looks like.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see you have this in an explanation below.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

2. Data contexts and access - name lookup (`.`, `this`, `..`, `[0]`), and null values
3. Helpers, arguments, options
4. Inclusion of other templates + arguments, passing data contexts
5. Helpers inside tags -- returning strings and objects, `checked={{isChecked}}`
6. Nested helpers + sub expressions
7. Block helpers (you can create them with templates, see 10. below)
8. Safestrings and `{{{`
9. Builtin block helpers
1. `{{#if/unless}}`
2. `{{#each .. in ..}}`
3. `{{#let}}`
1. NOTE: we need to ensure that issues around lexical scope and event handlers are resolved before we fail to even mention `{{#each}}` and `{{#with}}`. But we should attempt it.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should still mention it, no matter what. People should know about them because they are often documented in old tutorials and Stack Overflow answers. Just ignoring that will make them hard to understand how to migrate to new stuff.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well this isn't the full documentation of spacebars (or is it?).

I think in general, in the guide we mention things we think people should use. Like we aren't going to mention allow and deny really.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stubailo what do you think about this?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think in general, in the guide we mention things we think people should use.

Yea, but I think that with proper use they are still useful. So maybe we should document proper use, instead of not documenting it at all. :-)

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's similar to allow/deny - we should mention it in the same way perhaps.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

10. Comments
11. Strictness
12. Escaping
2. Creating reusable "pure" components with Blaze / best practice (a lot of this is repeating @sanjo's boilerplate)
1. Validating data context fits a schema
Copy link
Contributor

Choose a reason for hiding this comment

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

Also, what about uses models and instances of models for data contexts? This works for us very well. Then you can just do instanceof checks if you really want.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think SS allows you to do instanceof checks in a straightforward way, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

Choose a reason for hiding this comment

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

SS? And easy checking was not the point. The point is to suggest to people to maybe consider using high-level objects as data contexts. So to describe (it is a guide) a pattern where you use transform to transform your document from database, and then you use instanceof to find it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Simple Schema.

We are going to describe that pattern (see collections article + helpers).

Copy link
Contributor

Choose a reason for hiding this comment

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

OK.

BTW, we should consider also something about JSON schema, a standard. Simple Schema is great, but it is not inter-operable with others. Not that we should do something about this now, but in the longer term it would be great if our publish functions/mongo collection would have JSON schema associated with it (then also EJSON would be simpler).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

2. Always set data contexts to `{name: doc}` rather than just `doc`. Always set a d.c on an inclusion.
3. Use `{{#each .. in .. }}` to achieve the above
4. Use the template instance as a component -- adding a `{{instance}}` helper to access it
5. Use a (named / scoped on _id if possible) reactive dict for instance state
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we know if this actually works with hot code push? I'm not sure that just adding the ID will cut it?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think better then attaching reactive dict is to simply attach reactive field or computed field.

Then you can do things like:

onCreated: function () {
  this.foo = new ReactiveField(42);
}

And in the template:

{{instance.foo}}

Copy link
Contributor

Choose a reason for hiding this comment

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

Curious why you chose the name "field" for those? Rather than property, value, etc?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, no particular reason. :-) Field is something which is more used in MongoDB context, over property. Less characters to type. I didn't use value because the primary use case was with components for me.

But yea, it is not necessary the best naming. ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So a reactive field is sugar around ReactiveVar? That's nice -- I can get behind it.

Yes, a reactive-dict works as advertised (exercise for the reader, update the scaffolded app to not use the Session, it's like a million times better and embarrassing that we haven't already done this).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, I'd still recommend a dict as the "default" to start with (hint hint should just be a part of all template instances without any boilerplate), as:

  1. HCR-niceness
  2. Can store more than one thing, which you usually want
  3. You can still write {{instance.state.get 'counter'}}.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stubailo agree?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, I really think we should not make it be a part of template instance just so. We have too many places where people can store then stuff, into state, into template instance, into data context. Too many choices then mean people will be confused where to store it, and some people will use something, other something else so things will be less interoperable.

I really think that {{instance.counter}} is much nicer than {{instance.state.get 'counter'}}. Moreover, it allows you to have multiple stuff attached to instance. Like computer field, or simply a method/function. And you can then call all in the same way, with instance.something. Otherwise you have some stuff in state, some stuff in data context, some stuff as a method, some stuff as a computed field.

I really think this is bad design. There is no need for this. While my other comments are just comments, here I really think strongly that the best thing is just to encourage use of template instances without any need for extra sub-namespacing into state.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The advantage of having a single point to store state is that it allows the system to do things for you.

Auto-migration on HCR is a big advantage that we see already here, but another is extra APIs like the equivalent of shouldComponentUpdate(nextProps, nextState). This becomes a lot trickier if things are adhoc.

To your point on where people should store things, I think it's simple -- in the data if it's an argument (i.e. coming down the component heirarchy), in the state if it's state that's relevant to this component (and it's children, potentially).

Where it doesn't quite make sense is if you want to store something more complex than a serializable scalar -- for instance a computed field or a local collection. I don't have a good answer to this yet...

Copy link
Contributor

Choose a reason for hiding this comment

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

Auto-migration on HCR is a big advantage that we see already here

But that could also be made automatically in the background? It is not so hard to go over all properties of a template instance and see which are reactive fields and have some interface to serialize and deserialize and stuff like that?

6. Attach functions to the template instance (in `onCreated`) to sensibly modify state
Copy link
Contributor

Choose a reason for hiding this comment

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

Using reactive field you get that for free. :-)

template.onCreated(function () {
  this.foo = new ReactiveField(42);
});
template.events({
  'click button': function (event, instance) {
    instance.foo($(event.currentTarget).val());
  }
});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh right, but I mean complicated state changes that shouldn't be in the event handler. Like

Template.X.onCreated(function() {
  this.updateFoo = (input) => {
    const newValue = do * something + complicated / with * foo;
    this.state.set('foo', newValue);
  }
});

Template.X.events({
  'click button': function(event, instance) {
    instance.updateFoo(event.target.value);
  }
});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

Choose a reason for hiding this comment

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

Exactly. That is what we even suggest in Blaze Components. That event maps should map between selectors and methods and then all event handling should be in methods. Then you can extend those methods in child classes. And then it is really cool when:

  this.updateFoo = (input) => {
    const newValue = do * something + complicated / with * this.foo();
    this.foo(newValue);
  }

So that you can access reactive fields as you would access some other method or property on template instance. This is a really powerful pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

7. Place `const template = Template.instance()` at the top of all helpers that care about state
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's do instance = Template.instance() here as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I'm inclined to agree. My only hesitancy would be that in all documentation ever you find on the web you'll see function(event, template) in event handlers. We are now saying you should write function(event, instance) I guess.

@stubailo what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's not a big deal. I'd obviously prefer this, but instance or self would be second best. The word template is over-used, who knows what it refers to.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we just need to be consistent across template access, event handlers and helpers. I think instance works best for those three so we'll run with it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

8. Always scope DOM lookups with `this.$`
9. Use `.js-X` in event maps
10. Pass extra content to components with `Template.contentBlock`, or named template arguments
11. Use the `onRendered` callback to integrate w/ 3rd party libraries
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe document wait for subscription to finish pattern I have to use regularly:

onRendered(function ()
  const instance = this;
  this.autorun(function (computation) {
    if (!instance.subscriptionsReady()) return;
    computation.stop();

    // Use 3rd party stuff to render now something.
  });
});

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, this is an unfortunate pattern, we should add it here. lol

Copy link
Contributor

Choose a reason for hiding this comment

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

Talking about promises, this is one very common pattern I am using and I have not yet made a package for it. This wait for condition and stop autoruns.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am using it a lot for inter-component communication: peerlibrary/meteor-blaze-components#82 But I have not yet found a good name or API for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good one, thanks @mitar

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

Choose a reason for hiding this comment

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

So, should I make a package only for this or what? :-)

BTW, I am starting to think that this could also be combined with promises. I will wrote more into the ticket about promises.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well I think a tracker-extensions package is probably a pretty handy thing if stuff can be built without needing changes to core. Please take anything from my old deps-extension package that is still relevant if you do end up making the package, and let me know if you make the package.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

1. Waiting on subscriptions w/ `autorun` pattern: https://github.com/meteor/guide/pull/75/files#r43545240
3. Writing "smart" components with Blaze
1. All of section 2.
2. Use `this.autorun` and `this.subscribe`, listening to `FlowRouter` and `this.state`
3. Set up cursors in helpers to be passed into pure sub-components, and filtered with `cursor-utils`
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps mention how cursors are better than arrays when working with Blaze

Copy link
Contributor

Choose a reason for hiding this comment

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

What are cursor-utils?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stubailo yup

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

1. Why cursors are preferred to arrays.
4. Access stores directly from helpers.
4. Reusing code between templates
1. Prefer utilities/composition to mixins or inheritance
2. How to write global helpers
5. Understanding Blaze
1. When does a template re-render (when its data context changes)
2. When does a helper-rerun? (when its data context or reactive deps change)
1. So be careful, this can happen a lot! If your helper is expensive, consider something like https://github.com/peerlibrary/meteor-computed-field
3. How does an each tag re-run / decide when new data should appear?
Copy link
Contributor

Choose a reason for hiding this comment

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

And attributes how are they changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mitar I'm not quite sure what you mean here?

Copy link
Contributor

Choose a reason for hiding this comment

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

So DOM element attributes. Things like <div {{attrs}}>. So we should explain how do they reactivelly change as well, no?

BTW, with Blaze Components there is also a nice pattern which emerged for attributes. Namely that you can have multiple sources contribute to the same dict which you then render out. Like a dict of CSS classes, and inline style, and then you render them at once out. Instead of patching them together in templates. With inheritance and mixins you can do this really well.

Copy link
Contributor

Choose a reason for hiding this comment

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

Then you can have a simple helper like:


  # Converts an array of style classes into a class attribute. It doesn't return anything
  # if the array is empty (or null) so that class attribute is not unnecessarily created.
  class: (styleClassesArray) ->
    if styleClassesArray?.length
      class: styleClassesArray.join ' '

And just call <foo {{class myClasses}}> and it works great. :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think they behave reactively like any other helper, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, we should maybe just explain a bit here more, and give some good-practice examples for those attribute-helpers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

4. How do name lookups work?
5. What does the build system do exactly?
6. What is a view?
6. Testing Blaze templates
1. Rendering a template in a unit test
2. Querying the DOM (general but just a pointer here)
3. Triggering reactivity and waiting for re-rendering
4. Simulating events w/ JQ
7. Useful Blaze utilities / other approaches
1. https://github.com/peerlibrary/meteor-blaze-components
2. https://github.com/raix/Meteor-handlebar-helpers
3. Much, much more...