Skip to content

Commit

Permalink
Made it work without templating package.
Browse files Browse the repository at this point in the history
We had weak dependency on the templating package but if templating
package was not around things did not work.

Also preliminary support for server side rendering. See #5903
  • Loading branch information
mitar committed Dec 30, 2015
1 parent 9d574b0 commit 41924fe
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 106 deletions.
14 changes: 0 additions & 14 deletions client.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,6 @@ Blaze._DOMRange::attach = (parentElement, nextNode, _isMove, _isReplace) ->

originalDOMRangeAttach.apply @, arguments

# We make Template.dynamic resolve to the component if component name is specified as a template name, and not
# to the non-component template which is probably used only for the content. We simply reuse Blaze._getTemplate.
# TODO: How to pass args?
# Maybe simply by using Spacebars nested expressions (https://github.com/meteor/meteor/pull/4101)?
# Template.dynamic template="..." data=(args ...)? But this exposes the fact that args are passed as data context.
# Maybe we should simply override Template.dynamic and add "args" argument?
# TODO: This can be removed once https://github.com/meteor/meteor/pull/4036 is merged in.
# TODO: Move this to the server side as well.
Template.__dynamicWithDataContext.__helpers.set 'chooseTemplate', (name) ->
Blaze._getTemplate name, =>
Template.instance()

WHITESPACE_REGEX = /^\s+$/

EventHandler = Blaze._AttributeHandler.extend
Expand Down Expand Up @@ -93,8 +81,6 @@ Blaze._toText = (htmljs, parentView, textMode) ->
else
originalToText htmljs, parentView, textMode

share.inExpandAttributes = false

originalExpandAttributes = Blaze._expandAttributes
Blaze._expandAttributes = (attrs, parentView) ->
previousInExpandAttributes = share.inExpandAttributes
Expand Down
1 change: 1 addition & 0 deletions compatibility/attrs.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* This file is needed to backport this pull request: https://github.com/meteor/meteor/pull/5893
It is a copy of attrs.js file with the changes from the above pull request merged in.
TODO: Remove this file eventually.
*/
Expand Down
29 changes: 29 additions & 0 deletions compatibility/dynamic.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!-- This file is needed to backport this pull request: https://github.com/meteor/meteor/pull/5903
It is a copy of dynamic.html with renamed template names.
TODO: Remove this file eventually.
-->

<!-- Expects the data context to have a `template` property (the name of
the template to render) and an optional `data` property. If the `data`
property is not specified, then the parent data context will be used
instead. Uses the __dynamicWithDataContext template below to actually
render the template. -->
<template name="__dynamicBackport">
{{checkContext}}
{{#if dataContextPresent}}
{{# __dynamicWithDataContext}}{{> Template.contentBlock}}{{/__dynamicWithDataContext}}
{{else}}
{{! if there was no explicit 'data' argument, use the parent context}}
{{# __dynamicWithDataContext template=template data=..}}{{> Template.contentBlock}}{{/__dynamicWithDataContext}}
{{/if}}
</template>

<!-- Expects the data context to have a `template` property (the name of
the template to render) and a `data` property, which can be falsey. -->
<template name="__dynamicWithDataContextBackport">
{{#with chooseTemplate template}}
{{!-- The .. is evaluated inside {{#with ../data}} --}}
{{# .. ../data}}{{> Template.contentBlock}}{{/ ..}}
{{/with}}
</template>
50 changes: 50 additions & 0 deletions compatibility/dynamic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* This file is needed to backport this pull request: https://github.com/meteor/meteor/pull/5903
If it is a copy of dynamic.js file wrapped into a condition with renaming of backported templates.
TODO: Remove this file eventually.
*/

if (!Blaze.Template.__dynamicWithDataContext) {
Blaze.Template.__dynamicWithDataContext = Blaze.Template.__dynamicWithDataContextBackport;
Blaze.Template.__dynamic = Blaze.Template.__dynamicBackport;

var Template = Blaze.Template;

/**
* @isTemplate true
* @memberOf Template
* @function dynamic
* @summary Choose a template to include dynamically, by name.
* @locus Templates
* @param {String} template The name of the template to include.
* @param {Object} [data] Optional. The data context in which to include the
* template.
*/

Template.__dynamicWithDataContext.helpers({
chooseTemplate: function (name) {
return Blaze._getTemplate(name, function () {
return Template.instance();
});
}
});

Template.__dynamic.helpers({
dataContextPresent: function () {
return _.has(this, "data");
},
checkContext: function () {
if (!_.has(this, "template")) {
throw new Error("Must specify name in the 'template' argument " +
"to {{> Template.dynamic}}.");
}

_.each(this, function (v, k) {
if (k !== "template" && k !== "data") {
throw new Error("Invalid argument to {{> Template.dynamic}}: " +
k);
}
});
}
});
}
6 changes: 3 additions & 3 deletions compatibility/lookup.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* This file backports Blaze lookup.js from Meteor 1.2 so that required
Blaze features to support Blaze Components are available also in
older Meteor versions.
/* This file backports Blaze lookup.js from Meteor 1.2 so that required Blaze features to support Blaze
Components are available also in older Meteor versions.
It is a copy of lookup.js file from Meteor 1.2 with lexical scope lookup commented out.
TODO: Remove this file eventually.
*/
Expand Down
1 change: 1 addition & 0 deletions compatibility/materializer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* This file is needed to backport this pull request: https://github.com/meteor/meteor/pull/5893
It is a copy of the materializer.js file and is needed because it references symbols from attrs.js.
TODO: Remove this file eventually.
*/
Expand Down
85 changes: 85 additions & 0 deletions compatibility/templating.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* This file is needed to backport this pull request: https://github.com/meteor/meteor/pull/5903
If it is a copy of templating.js file wrapped into a condition.
TODO: Remove this file eventually.
*/

if (!Blaze.Template.__checkName) {
// Packages and apps add templates on to this object.

/**
* @summary The class for defining templates
* @class
* @instanceName Template.myTemplate
*/
Template = Blaze.Template;

var RESERVED_TEMPLATE_NAMES = "__proto__ name".split(" ");

// Check for duplicate template names and illegal names that won't work.
Template.__checkName = function (name) {
// Some names can't be used for Templates. These include:
// - Properties Blaze sets on the Template object.
// - Properties that some browsers don't let the code to set.
// These are specified in RESERVED_TEMPLATE_NAMES.
if (name in Template || _.contains(RESERVED_TEMPLATE_NAMES, name)) {
if ((Template[name] instanceof Template) && name !== "body")
throw new Error("There are multiple templates named '" + name + "'. Each template needs a unique name.");
throw new Error("This template name is reserved: " + name);
}
};

// XXX COMPAT WITH 0.8.3
Template.__define__ = function (name, renderFunc) {
Template.__checkName(name);
Template[name] = new Template("Template." + name, renderFunc);
// Exempt packages built pre-0.9.0 from warnings about using old
// helper syntax, because we can. It's not very useful to get a
// warning about someone else's code (like a package on Atmosphere),
// and this should at least put a bit of a dent in number of warnings
// that come from packages that haven't been updated lately.
Template[name]._NOWARN_OLDSTYLE_HELPERS = true;
};

// Define a template `Template.body` that renders its
// `contentRenderFuncs`. `<body>` tags (of which there may be
// multiple) will have their contents added to it.

/**
* @summary The [template object](#templates_api) representing your `<body>`
* tag.
* @locus Client
*/
Template.body = new Template('body', function () {
var view = this;
return _.map(Template.body.contentRenderFuncs, function (func) {
return func.apply(view);
});
});
Template.body.contentRenderFuncs = []; // array of Blaze.Views
Template.body.view = null;

Template.body.addContent = function (renderFunc) {
Template.body.contentRenderFuncs.push(renderFunc);
};

// This function does not use `this` and so it may be called
// as `Meteor.startup(Template.body.renderIntoDocument)`.
Template.body.renderToDocument = function () {
// Only do it once.
if (Template.body.view)
return;

var view = Blaze.render(Template.body, document.body);
Template.body.view = view;
};

// XXX COMPAT WITH 0.9.0
UI.body = Template.body;

// XXX COMPAT WITH 0.9.0
// (<body> tags in packages built with 0.9.0)
Template.__body__ = Template.body;
Template.__body__.__contentParts = Template.body.contentViews;
Template.__body__.__instantiate = Template.body.renderToDocument;
}
2 changes: 0 additions & 2 deletions compile-templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

Plugin.registerCompiler({
extensions: ['html'],
// TODO: Remove web arch only once server side support is in.
archMatching: 'web',
isTemplate: true
}, () => new CachingHtmlCompiler(
"blaze-components-templating",
Expand Down
91 changes: 85 additions & 6 deletions lib.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ Blaze._getTemplateHelper = (template, name, templateInstance) ->

null

share.inExpandAttributes = false

bindComponent = (component, helper) ->
if _.isFunction helper
(args...) ->
Expand Down Expand Up @@ -217,7 +219,7 @@ callTemplateBaseHooks = (component, hookName) ->

return

share.wrapViewAndTemplate = (currentView, f) ->
wrapViewAndTemplate = (currentView, f) ->
# For template content wrapped inside the block helper, we want to skip the block
# helper when searching for corresponding template. This means that Template.instance()
# will return the component's template, while BlazeComponent.currentComponent() will
Expand Down Expand Up @@ -251,7 +253,7 @@ addEvents = (view, component) ->
event = args[0]

currentView = Blaze.getView event.currentTarget
share.wrapViewAndTemplate currentView, ->
wrapViewAndTemplate currentView, ->
handler.apply component, args

# Make sure CoffeeScript does not return anything.
Expand Down Expand Up @@ -299,6 +301,83 @@ registerFirstCreatedHook = (template, onCreated) ->
onCreated.call @
oldCreated?.call @

# We make Template.dynamic resolve to the component if component name is specified as a template name, and not
# to the non-component template which is probably used only for the content. We simply reuse Blaze._getTemplate.
# TODO: How to pass args?
# Maybe simply by using Spacebars nested expressions (https://github.com/meteor/meteor/pull/4101)?
# Template.dynamic template="..." data=(args ...)? But this exposes the fact that args are passed as data context.
# Maybe we should simply override Template.dynamic and add "args" argument?
# TODO: This can be removed once https://github.com/meteor/meteor/pull/4036 is merged in.
Template.__dynamicWithDataContext.__helpers.set 'chooseTemplate', (name) ->
Blaze._getTemplate name, =>
Template.instance()

argumentsConstructor = ->
# This class should never really be created.
assert false

# TODO: Find a way to pass arguments to the component without having to introduce one intermediary data context into the data context hierarchy.
# (In fact two data contexts, because we add one more when restoring the original one.)
Template.registerHelper 'args', ->
obj = {}
# We use custom constructor to know that it is not a real data context.
obj.constructor = argumentsConstructor
obj._arguments = arguments
obj

share.EVENT_HANDLER_REGEX = /^on[A-Z]/

share.isEventHandler = (fun) ->
_.isFunction(fun) and fun.eventHandler

# When event handlers are provided directly as args they are not passed through
# Spacebars.event by the template compiler, so we have to do it ourselves.
originalFlattenAttributes = HTML.flattenAttributes
HTML.flattenAttributes = (attrs) ->
if attrs = originalFlattenAttributes attrs
for name, value of attrs when share.EVENT_HANDLER_REGEX.test name
# Already processed by Spacebars.event.
continue if share.isEventHandler value
continue if _.isArray(value) and _.some value, share.isEventHandler

# When event handlers are provided directly as args,
# we require them to be just event handlers.
if _.isArray value
attrs[name] = _.map value, Spacebars.event
else
attrs[name] = Spacebars.event value

attrs

Spacebars.event = (eventHandler, args...) ->
throw new Error "Event handler not a function: #{eventHandler}" unless _.isFunction eventHandler

# Execute all arguments.
args = Spacebars.mustacheImpl ((xs...) -> xs), args...

fun = (event, eventArgs...) ->
currentView = Blaze.getView event.currentTarget
wrapViewAndTemplate currentView, ->
# We do not have to bind "this" because event handlers are resolved
# as template helpers and are already bound. We bind event handlers
# in dynamic attributes already as well.
eventHandler.apply null, [event].concat args, eventArgs

fun.eventHandler = true

fun

# When converting the component to the static HTML, remove all event handlers.
originalVisitTag = HTML.ToHTMLVisitor::visitTag
HTML.ToHTMLVisitor::visitTag = (tag) ->
if attrs = tag.attrs
attrs = HTML.flattenAttributes attrs
for name of attrs when share.EVENT_HANDLER_REGEX.test name
delete attrs[name]
tag.attrs = attrs

originalVisitTag.call @, tag

class BlazeComponent extends BaseComponent
# TODO: Figure out how to do at the BaseComponent level?
@getComponentForElement: (domElement) ->
Expand Down Expand Up @@ -473,10 +552,10 @@ class BlazeComponent extends BaseComponent
# were provided through "args" template helper, so we just continue normally.
data = null

if data?.constructor isnt share.argumentsConstructor
if data?.constructor isnt argumentsConstructor
# So that currentComponent in the constructor can return the component
# inside which this component has been constructed.
return share.wrapViewAndTemplate Blaze.currentView, =>
return wrapViewAndTemplate Blaze.currentView, =>
component = new componentClass()

return component.renderComponent parentComponent
Expand All @@ -500,7 +579,7 @@ class BlazeComponent extends BaseComponent
# See https://github.com/meteor/meteor/issues/4073
reactiveArguments = new ComputedField ->
data = currentWith.dataVar.get()
assert.equal data?.constructor, share.argumentsConstructor
assert.equal data?.constructor, argumentsConstructor
data._arguments
,
EJSON.equals
Expand All @@ -514,7 +593,7 @@ class BlazeComponent extends BaseComponent
template = Blaze._withCurrentView Blaze.currentView.parentView.parentView, =>
# So that currentComponent in the constructor can return the component
# inside which this component has been constructed.
return share.wrapViewAndTemplate Blaze.currentView, =>
return wrapViewAndTemplate Blaze.currentView, =>
# Use arguments for the constructor.
component = new componentClass nonreactiveArguments...

Expand Down
Loading

0 comments on commit 41924fe

Please sign in to comment.