On Github, many Handlebars helpers are presented. You can find them here.
I'd like to use them, but have no Idea, how to include them. The code is looking as it's javascript (files are ending with .js ...), but words like 'import','export','default' are confusing me.
As I understand (guess), I have to include the if-condition.js at first. Later, the other (included) files refer to this file.
But when I do this, the console throws an Error:
Uncaught SyntaxError: Unexpected reserved word .
Has anyone an idea, how to get these codes working?
import and export are keywords for the upcoming module syntax in the next version of Javascript. You can use them today by using a transpiler to convert it to normal ES5 syntax.
However, if you're only using a few helpers, it's very easy to 'transpile' them by hand. Instead of exporting the function, just pass it to a Ember.Hanldebars.registerBoundHelper call. Here's the if-condition helper:
Ember.Handlebars.registerBoundHelper('if-condition', function() {
var args = [].slice.call(arguments);
var options = args.pop();
var context = (options.contexts && options.contexts[0]) || this;
if (!options.conditional) {
throw new Error("A conditional callback must be specified when using the if-condition helper");
}
// Gather all bound property names to pass in order to observe them
var properties = options.types.reduce(function(results, type, index) {
if (type === 'ID') {
results.push(args[index]);
}
return results;
}, []);
// Resolve actual values for all params to pass to the conditional callback
var normalizer = function() {
return Ember.Handlebars.resolveParams(context, args, options);
};
// This effectively makes the helper a bound helper
// NOTE: 'content' path is used so that multiple properties can be bound to using the `childProperties` argument,
// however this means that it can only be used with a controller that proxies values to the 'content' property
return Ember.Handlebars.bind.call(context, 'content', options, true, options.conditional, normalizer, properties);
});
Related
I am very new to testing and I'm struggling my way through all this new stuff I am learning. Today I want to write a test for a vuetify <v-text-field> component like this:
<v-text-field
v-model="user.caption"
label="Name"
:disabled="!checkPermissionFor('users.write')"
required
/>
my test should handle the following case:
an active, logged in user has a array in vuex store which has his permissions as a array of strings. exactly like this
userRights: ['dashboard', 'imprint', 'dataPrivacy']
the checkPermissionFor() function is doing nothing else then checking the array above with a arr.includes('x')
after it came out the right is not included it gives me a negotiated return which handles the disabled state on that input field.
I want to test this exact scenario.
my test at the moment looks like this:
it('user has no rights to edit other user overview data', () => {
const store = new Vuex.Store({
state: {
ActiveUser: {
userData: {
isLoggedIn: true,
isAdmin: false,
userRights: ['dashboard', 'imprint', 'dataPrivacy']
}
}
}
})
const wrapper = shallowMount(Overview, {
store,
localVue
})
const addUserPermission = wrapper.vm.checkPermissionFor('users.write')
const inputName = wrapper.find(
'HOW TO SELECT A INPUT LIKE THIS? DO I HAVE TO ADD A CLASS FOR IT?'
)
expect(addUserPermission).toBe(false)
expect(inputName.props('disabled')).toBe(false)
})
big questions now:
how can I select a input from vuetify which has no class like in my case
how can I test for "is the input disabled?"
wrapper.find method accepts a query string. You can pass a query string like this :
input[label='Name'] or if you know the exact index you can use this CSS query too : input:nth-of-type(2).
Then find method will return you another wrapper. Wrapper has a property named element which returns the underlying native element.
So you can check if input disabled like this :
const buttonWrapper = wrapper.find("input[label='Name']");
const isDisabled = buttonWrapper.element.disabled === true;
expect(isDisabled ).toBe(true)
For question 1 it's a good idea to put extra datasets into your component template that are used just for testing so you can extract that element - the most common convention is data-testid="test-id".
The reason you should do this instead of relying on the classes and ids and positional selectors or anything like that is because those selectors are likely to change in a way that shouldn't break your test - if in the future you change css frameworks or change an id for some reason, your tests will break even though your component is still working.
If you're (understandably) worried about polluting your markup with all these data-testid attributes, you can use a webpack plugin like https://github.com/emensch/vue-remove-attributes to strip them out of your dev builds. Here's how I use that with laravel mix:
const createAttributeRemover = require('vue-remove-attributes');
if (mix.inProduction()) {
mix.options({
vue: {
compilerOptions: {
modules: [
createAttributeRemover('data-testid')
]
}
}
})
}
as for your second question I don't know I was googling the same thing and I landed here!
So let's say I'm storing <div>{{name}}</div> and <div>{{age}}</div> in my database.
Then I want to take the first HTML string and render it in a template - {{> template1}} which just renders the first string with the {{name}} handlebar in it. Then I want to give that newly generated template/html data, so that it can fill in the handlebar with the actual name from the database, so that we would get <div>John</div>. I've tried doing
<template name="firstTemplate">
{{#with dataGetter}}
{{> template1}}
{{/with}}
</template>
Where template1 is defined as
<template name="template1">
{{{templateInfo}}}
</template>
And templateInfo is the helper that returns the aforementioned html string with the handlebar in it from the database.
dataGetter is just this (just an example, I'm working with differently named collections)
Template.firstTemplate.dataGetter = function() {
return Users.findOne({_id: Session.get("userID")});
}
I can't get the {{name}} to populate. I've tried it a couple of different ways, but it seems like Meteor doesn't understand that the handlebars in the string need to be evaluated with the data. I'm on 0.7.0 so no Blaze, I can't upgrade at the moment due to the other packages I'm using, they just don't have 0.8+ version support as of yet. Any ideas on how I can get this to work are much appreciated.
In 1.0 none of the methods described above work. I got this to work with the function below defined in the client code. The key was to pass the options { isTemplate: true} to the compile function.
var compileTemplate = function(name, html_text) {
try {
var compiled = SpacebarsCompiler.compile(html_text, { isTemplate:true });
var renderer = eval(compiled);
console.log('redered:',renderer);
//Template[name] = new Template(name,renderer);
UI.Template.__define__(name, renderer);
} catch (err){
console.log('Error compiling template:' + html_text);
console.log(err.message);
}
};
The you can call with something like this on the client:
compileTemplate('faceplate', '<span>Hello!!!!!!{{_id}}</span>');
This will render with a UI dynamic in your html
{{> Template.dynamic template='faceplate'}}
You can actually compile strings to templates yourself using the spacebars compiler.. You just have to use meteor add spacebars-compiler to add it to your project.
In projects using 0.8.x
var compiled = Spacebars.compile("<div>{{name}}</div> and <div>{{age}}</div>");
var rendered = eval(compiled);
Template["dynamicTemplate"] = UI.Component.extend({
kind: "dynamicTemplate",
render: rendered
});
In projects using 0.9.x
var compiled = SpacebarsCompiler.compile("<div>{{name}}</div> and <div>{{age}}</div>");
var renderer = eval(compiled);
Template["dynamicTemplate"] = Template.__create__("Template.dynamicTemplate", rendered);
Following #user3354036's answer :
var compileTemplate = function(name, html_text) {
try {
var compiled = SpacebarsCompiler.compile(html_text, { isTemplate:true }),
renderer = eval(compiled);
console.log('redered:',renderer);
//Template[name] = new Template(name,renderer);
UI.Template.__define__(name, renderer);
} catch (err) {
console.log('Error compiling template:' + html_text);
console.log(err.message);
}
};
1) Add this in your HTML
{{> Template.dynamic template=template}}
2) Call the compileTemplate method.
compileTemplate('faceplate', '<span>Hello!!!!!!{{_id}}</span>');
Session.set('templateName','faceplate');
Save the template name in a Session variable. The importance of this is explained in the next point.
3) Write a helper function to return the template name. I have used Session variable to do so. This is important if you are adding the dynamic content on a click event or if the parent template has already been rendered. Otherwise you will never see the dynamic template getting rendered.
'template' : function() {
return Session.get('templateName');
}
4) Write this is the rendered method of the parent template. This is to reset the Session variable.
Session.set('templateName','');
This worked for me. Hope it helps someone.
If you need to dynamically compile complex templates, I would suggest Kelly's answer.
Otherwise, you have two options:
Create every template variation, then dynamically choose the right template:
eg, create your templates
<template name="displayName">{{name}}</template>
<template name="displayAge">{{age}}</template>
And then include them dynamically with
{{> Template.dynamic template=templateName}}
Where templateName is a helper that returns "age" or "name"
If your templates are simple, just perform the substitution yourself. You can use Spacebars.SafeString to return HTML.
function simpleTemplate(template, values){
return template.replace(/{{\w+}}/g, function(sub) {
var p = sub.substr(2,sub.length-4);
if(values[p] != null) { return _.escape(values[p]); }
else { return ""; }
})
}
Template.template1.helpers({
templateInfo: function(){
// In this context this/self refers to the "user" data
var templateText = getTemplateString();
return Spacebars.SafeString(
simpleTemplate(templateText, this)
);
}
Luckily, the solution to this entire problem and any other problems like it has been provided to the Meteor API in the form of the Blaze package, which is the core Meteor package that makes reactive templates possible. If you take a look at the linked documentation, the Blaze package provides a long list of functions that allow for a wide range of solutions for programmatically creating, rendering, and removing both reactive and non-reactive content.
In order to solve the above described problem, you would need to do the following things:
First, anticipate the different HTML chunks that would need to be dynamically rendered for the application. In this case, these chunks would be <div>{{name}}</div> and <div>{{age}}</div>, but they could really be anything that is valid HTML (although it is not yet part of the public API, in the future developers will have more options for defining this content in a more dynamic way, as mentioned here in the documentation). You would put these into small template definitions like so:
<template name="nameDiv">
<div>{{name}}</div>
</template>
and
<template name="ageDiv">
<div>{{age}}</div>
</template>
Second, the definition for the firstTemplate template would need to be altered to contain an HTML node that can be referenced programmatically, like so:
<template name="firstTemplate">
<div></div>
</template>
You would then need to have logic defined for your firstTemplate template that takes advantage of some of the functions provided by the Blaze package, namely Blaze.With, Blaze.render, and Blaze.remove (although you could alter the following logic and take advantage of the Blaze.renderWithData function instead; it is all based on your personal preference for how you want to define your logic - I only provide one possible solution below for the sake of explanation).
Template.firstTemplate.onRendered(function() {
var dataContext = Template.currentData();
var unrenderedView = Blaze.With(dataContext, function() {
// Define some logic to determine if name/age template should be rendered
// Return either Template.nameDiv or Template.ageDiv
});
var currentTemplate = Template.instance();
var renderedView = Blaze.render(unrenderedView, currentTemplate.firstNode);
currentTemplate.renderedView = renderedView;
});
Template.firstTemplate.onDestroyed(function() {
var renderedView = Template.instance().renderedView;
Blaze.remove(renderedView);
});
So what we are doing here in the onRendered function for your firstTemplate template is dynamically determining which of the pieces of data that we want to render onto the page (either name or age in your case) and using the Blaze.With() function to create an unrendered view of that template using the data context of the firstTemplate template. Then, we select the firstTemplate template element node that we want the dynamically generated content to be contained in and pass both objects into the Meteor.render() function, which renders the unrendered view onto the page with the specified element node as the parent node of the rendered content.
If you read the details for the Blaze.render() function, you will see that this rendered content will remain reactive until the rendered view is removed using the Blaze.remove() function, or the specified parent node is removed from the DOM. In my example above, I am taking the reference to the rendered view that I received from the call to Blaze.render() and saving it directly on the template object. I do this so that when the template itself is destroyed, I can manually remove the rendered view in the onDestroyed() callback function and be assured that it is truly destroyed.
A very simple way is to include in the onRendered event a call to the global Blaze object.
Blaze.renderWithData(Template[template_name], data ,document.getElementById(template_id))
I was looking for a way to create a path helper in handlebars which generates a url to a specific page. I need to be able to pass a route name and the params needed to generate the route. SO first i was looking to pass a hash to the helper ... but this isn't possible because you can't create this inside the handler template. Now the syntax is like this:
{{{path 'some_path_name' 'foo=bar' }}}
and this seems to work but now i have an issue with that i can't concatenate strings inside the template. Any idea what's the best way to do this? The only option i see now is that i create the params inside my javascript code ... but i don't really like this, i wan't to be able to specify it in the template.
I have a mapping somewhere which maps some_path_name to /path-name/:foo ... so i want the path helper to create /path-name/bar.
kind regards,
Daan
This code should work, but you have to add some extra validation.
var routes = {
'some_path_name': '/path-name/:foo'
};
Handlebars.registerHelper('path', function (routeName, options) {
var route,
params;
if (!routes.hasOwnProperty(routeName)) return;
route = routes[routeName];
params = options.hash;
for (var param in params) {
var value = params[param];
route = route.replace(':'+ param, value);
}
return route;
});
I want to place some helper functions in another file, since they will be overly reused. I took the Computer-Databse sample's listing file:
https://github.com/playframework/Play20/blob/master/samples/scala/computer-database/app/views/list.scala.html
I created a new file, called "listing.scala.html" under the app/views package, and moved the #link function from the original file to it. This new file looks like this:
#(currentSortBy: String, currentOrder: String, currentFilter: String)
#****************************************
* Helper generating navigation links *
****************************************#
#link(newPage:Int, newSortBy:String) = #{
var sortBy = currentSortBy
var order = currentOrder
if(newSortBy != null) {
sortBy = newSortBy
if(currentSortBy == newSortBy) {
if(currentOrder == "asc") {
order = "desc"
} else {
order = "asc"
}
} else {
order = "asc"
}
}
// Generate the link
routes.Application.listPerfil(newPage, sortBy, order, currentFilter)
}
So, on my original file, I replaced the #link call, with this one:
#title
And the problem is, when I try to compile I get this error:
value link is not a member of play.api.templates.Html
But according to the documentation (http://www.playframework.org/documentation/2.0.4/ScalaTemplateUseCases) it seems to be ok.
Any guess?
Play's templates aren't the best place for placing advanced conditions, most probably you'll get better flexibility by processing it in some controller (or other method) which will return you only required link
ie.:
#title
In your case proposed link(...) function of Application controller can also return a reverse-route.
Keep in mind that including other templates is best option for repeating blocks of HTML but sometimes it's hard to get specified string (mainly because of not trimmed spaces). As you can see there is also problem with calling nested functions. Most probably you can generate whole A tag in the listing.scala.html however using it isn't comfortable enough (IMHO).
In a handlebars template in Ember.js, I have blocks like the following:
{{content.some_attribute}}
{{content.some_other_attr}}
{{content.more_attr}}
Some of these attributes don't exist and I'm implementing them slowly.
Is there a way to get these templates to compile and either ignore the blocks that don't evaluate or better yet, replace them with a html element so they're easier to spot in the browser?
(the template is pretty large and it's being converted from ERB slowly,
Is there a way to get these templates to compile and either ignore the blocks that don't evaluate
Properties that don't exist are undefined, and don't get rendered at all. In other words {{thisDoesNotExist}} will simply be invisible -- it will compile just fine.
or better yet, replace them with a html element so they're easier to spot in the browser
As Cory said, you could use a helper for this that checks for undefined, using Ember.Handlebars.registerBoundHelper.
This seems like a perfect case for a handlebars helper. The helper could validate the value and return the value or the html that you desire.
The following code should be used very carefully, since it has not been tested within an application.
A possible solution to replace the possible undefined value in a template is to overwrite Ember.getPath, which is used to lookup the value of a path in a template, see http://jsfiddle.net/pangratz666/hKK8p/:
var getPath = Ember.getPath;
Ember.getPath = function(obj, path) {
var value = getPath(obj, path);
return (Ember.none(value) ? 'OMG %# is not defined!!'.fmt(path) : value);
};
If this code would be used temporarily in an application, I would also restrict the check for undefined values to a specific obj. So something along those lines:
App.objectWhichHasUndefinedProps = Ember.Object.create({
...
});
Ember.View.create({
templateName: 'templateWithAttributes',
objBinding: 'App.objectWhichHasUndefinedProps'
}).append();
var getPath = Ember.getPath;
Ember.getPath = function(obj, path) {
var value = getPath(obj, path);
if (obj === App.objectWhichHasUndefinedProps) {
return (Ember.none(value) ? 'OMG %# is not defined!!'.fmt(path) : value);
}
return value;
};