Add inheritance helpers to Handlebars.js

This commit is contained in:
Dmitriy Simushev 2014-09-18 15:00:35 +00:00
parent fa615bc2e0
commit cb4bd3acaa
2 changed files with 259 additions and 0 deletions

View File

@ -219,4 +219,127 @@
Handlebars.registerHelper('cutString', function(length, options) { Handlebars.registerHelper('cutString', function(length, options) {
return options.fn(this).substr(0, length); return options.fn(this).substr(0, length);
}); });
/**
* Registers "block" helper.
*
* This helper defines default content of a block. Example of usage:
* <code>
* {{#block "blockName"}}
* Default content for the block
* {{/block}}
* </code>
*/
Handlebars.registerHelper('block', function(name, options) {
if (this._blocksStorage && this._blocksStorage.hasOwnProperty(name)) {
return this._blocksStorage[name];
}
return options.fn(this);
});
/**
* Registers "extends" helper.
*
* This is used for templates inheritance. Example of usage:
* <code>
* {{#extends "parentTemplateName"}}
* {{#override "blockName"}}
* Overridden first block
* {{/override}}
*
* {{#override "anotherBlockName"}}
* Overridden second block
* {{/override}}
* {{/extends}}
* </code>
*/
Handlebars.registerHelper('extends', function(parentTemplate, options) {
// Create a blocks storage. If the current inheritance level is not the
// deepest one, a storage already exists. In this case we do not need
// to override it.
this._blocksStorage = this._blocksStorage || {};
// Render content inside "extends" helper to override blocks
options.fn(this);
// Check if the parent template exists
if (!Handlebars.templates.hasOwnProperty(parentTemplate)) {
throw Error('Parent template "' + parentTemplate + '" is not defined');
}
// Render the parent template. We assume that templates are stored in
// Handlebars.templates property. It is the most common case and take
// place when templates were compiled with node.js Handlebars CLI tool.
return Handlebars.templates[parentTemplate](this);
});
/**
* Registers "override" helper.
*
* This helper overrides content of a block. Example of usage:
* <code>
* {{#extends "parentTemplateName"}}
* {{#override "blockName"}}
* Overridden first block
* {{/override}}
*
* {{#override "anotherBlockName"}}
* Overridden second block
* {{/override}}
* {{/extends}}
* </code>
*/
Handlebars.registerHelper('override', function(name, options) {
// We need to provide unlimited inheritence level. Rendering is started
// from the deepest level template. If the content is in the block
// storage it is related with the deepest level template. Thus we do not
// need to override it.
if (!this._blocksStorage.hasOwnProperty(name)) {
this._blocksStorage[name] = options.fn(this);
}
// An empty string is returned for consistency.
return '';
});
/**
* Registers "ifOverridden" helper.
*
* This helper checks if a block is overridden or not. Example of usage:
* <code>
* {{#ifOverridden "blockName"}}
* The block was overridden
* {{else}}
* The block was not overridden
* {{/ifOverridden}}
* </code>
*/
Handlebars.registerHelper('ifOverridden', function(name, options) {
if (this._blocksStorage && this._blocksStorage.hasOwnProperty(name)) {
return options.fn(this);
} else {
return options.inverse(this);
}
});
/**
* Registers "unlessOverridden" helper.
*
* This helper checks if a block is overridden or not. Example of usage:
* <code>
* {{#unlessOverridden "blockName"}}
* The block was not overridden
* {{else}}
* The block was overridden
* {{/unlessOverridden}}
* </code>
*/
Handlebars.registerHelper('unlessOverridden', function(name, options) {
if (this._blocksStorage && this._blocksStorage.hasOwnProperty(name)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
})(Mibew, Handlebars); })(Mibew, Handlebars);

View File

@ -217,3 +217,139 @@ test('l10n', function() {
'Test string with placeholder' 'Test string with placeholder'
); );
}); });
// Test "block", "extends" and "override" Handlebars helpers.
test('inheritance', function() {
// Test inheritance
Handlebars.templates = {
parent: Handlebars.compile(
'Test {{#block "first"}}1{{/block}} {{#block "second"}}2{{/block}}'
),
child: Handlebars.compile(
'{{#extends "parent"}}{{#override "first"}}0{{/override}}{{/extends}}'
),
grandChild: Handlebars.compile(
'{{#extends "child"}}{{#override "first"}}one{{/override}}{{#override "second"}}two{{/override}}{{/extends}}'
)
};
equal(
Handlebars.templates['parent']({}),
'Test 1 2',
'Test default block content'
);
equal(
Handlebars.templates['child']({}),
'Test 0 2',
'Test inheritance'
);
equal(
Handlebars.templates['grandChild']({}),
'Test one two',
'Test nested inheritance'
);
// Test nested blocks
Handlebars.templates = {
parent: Handlebars.compile(
'Test {{#block "first"}}1 {{#block "second"}}2{{/block}}{{/block}}'
),
childInnerBlock: Handlebars.compile(
'{{#extends "parent"}}{{#override "second"}}two{{/override}}{{/extends}}'
),
childOuterBlock: Handlebars.compile(
'{{#extends "parent"}}{{#override "first"}}foo{{/override}}{{/extends}}'
)
}
equal(
Handlebars.templates['childInnerBlock']({}),
'Test 1 two',
'Test overriding of the inner block'
);
equal(
Handlebars.templates['childOuterBlock']({}),
'Test foo',
'Test overriding of the outer block'
);
// Clean up environment
delete Handlebars.templates;
});
// Test "ifOverridden" Handlebars helper.
test('ifOverridden', function() {
// Test inheritance
Handlebars.templates = {
parent: Handlebars.compile(
'{{#ifOverridden "foo"}}Child{{else}}Parent{{/ifOverridden}}{{#block "foo"}}{{/block}}'
),
child: Handlebars.compile(
'{{#extends "parent"}}{{/extends}}'
),
childOverridden: Handlebars.compile(
'{{#extends "parent"}}{{#override "foo"}}{{/override}}{{/extends}}'
)
};
equal(
Handlebars.templates['childOverridden']({}),
'Child',
'Test overridden block'
);
equal(
Handlebars.templates['child']({}),
'Parent',
'Test not overridden block'
);
equal(
Handlebars.templates['parent']({}),
'Parent',
'Test with no inheritance'
);
// Clean up environment
delete Handlebars.templates;
});
// Test "unlessOverridden" Handlebars helper.
test('unlessOverridden', function() {
// Test inheritance
Handlebars.templates = {
parent: Handlebars.compile(
'{{#unlessOverridden "foo"}}Parent{{else}}Child{{/unlessOverridden}}{{#block "foo"}}{{/block}}'
),
child: Handlebars.compile(
'{{#extends "parent"}}{{/extends}}'
),
childOverridden: Handlebars.compile(
'{{#extends "parent"}}{{#override "foo"}}{{/override}}{{/extends}}'
)
};
equal(
Handlebars.templates['childOverridden']({}),
'Child',
'Test overridden block'
);
equal(
Handlebars.templates['child']({}),
'Parent',
'Test not overridden block'
);
equal(
Handlebars.templates['parent']({}),
'Parent',
'Test with no inheritance'
);
// Clean up environment
delete Handlebars.templates;
});