I simply want to pass in a javascript object that is populated by the template to a helper function. I have looked at conversations regarding passing in helper objects to loops but have not found anything that directly addresses the following example.
<script type="text/javascript">
// an object set by the template
var SomeObject = {};
SomeObject.Id=6;
</script>
<script id = "SomeTemplate" type="text/html">
<div id="somegroup_{{>Id}}" class="main">
<img id="somegroup_img_{{>Id}}" class="mainImg" src="{{: ~fltrOAMnImgs_hlp(Images, SomeObject)}}">
</div>
</script>
The current above code does not pass SomeObject to the helper func.
How could I do this with the new jsrender lib?
If you want to provide access to your SomeObject within the template, you must pass that object in as a helper. There are some different ways to do that, depending on whether you want it available globally to all templates, just to this template, or just for this render() call. See: the $.views.helpers() topic under Register helpers, converters, tags...
For example if you pass it in with the render call, with a name such as "myObject":
myTemplate.render(myData, {myObject: SomeObject});
then you can reference it as in ~myObject:
<img ... src="{{: ~fltrOAMnImgs_hlp(Images, ~myObject)}}">
See also this related answer: Trying to run a function on an array in a, for loop, using jsrender
Related
I'm essentially trying to add a CSS class to my VueJS components based on the component-name it's registered under (to give all those specific types of components the same style).
For example:
Vue.component('dragdropfile', {
// This refers to a unique template element (see HTML below)
template: "#dragdropfile-tmp",
props: ['action']
});
And in the Vue component template:
<template id="dragdropfile-tmp">
<form action="{{action}}" method="post" enctype="multipart/form-data">
<div class="fallback">
<input name="file" type="file" multiple />
</div>
<div class="dz-message" data-dz-message>
<div class="dz-default">
<!--
According to VueJS docs / forums, "slot" gets replaced by any innerHTML
passed from the incoming component usage.
-->
<slot></slot>
</div>
</div>
</form>
</template>
And finally, how it's used in the "index.html" page is like this:
<dragdropfile id="someDragDropFiles" action="/upload-please">
Do you have files to upload?<br/>
<b>Drop it here!</b>
</dragdropfile>
Now, although I could put in the component-name manually for each component HTML templates, I want to automate this.
Are there any special built-in {{binding}} names that Vue uses internally so that I can inject the component-name into the resulting component on the page?
To result something like so:
<form class="dragdropfile" id="someDragDropFiles" action="/upload-please" ... > ...</form>
Or do I simply need to pass it myself as a new component property? As in:
Manually call it like props: ["componentName", ...] and;
Refer to it in the template as <form class='{{componentName}}' ...>
Is this the only feasible way?
Using VueJS version: 1.0.17
(https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.17/vue.js)
Solution #1
After a few minutes of inspecting what the object holds using the create: function() { console.log(this); } in the Vue.component(...) registration call, I found the name in it's this.$options.name property.
In other words:
<form class="{{this.$options.name}}" ...> ... </form>
Or even shorter:
<form class="{{$options.name}}" ...> ... </form>
Come to think of it, it's still a bit of manual work to enter on each component templates, but there's probably a way to auto-append the class via the created method.
Solution #2
This is the automated approach I was looking for!
Here it goes, basically I made a wrapper function to call whenever I need to register new components, which internally calls the usual Vue.component(...) method.
NOTE: This example depends on jQuery to add the class and underscore.js for object merging via _.assign, but could probably be replaced by a direct *.classList.addClass() call instead. These are just the helper methods I'm familiar with, use what you like! :)
makeVueComponent(name, params)
/*
* Creates a Vue Component with a standardized template ID name (ie: #tagname-tmp)
* combined with given params.
*/
function makeVueComponent(name, params) {
//Ensure params is assigned something:
params = params || {};
//Backup a reference of an existing *.created() method, or just an empty function
var onCreated = params.created || function(){};
/*
Delete the original `created` method so the `_.assign()` call near the end
doesn't accidentally merge and overwrite our defaultParams.created() method.
*/
delete params.created;
var defaultParams = {
//Standardized template components to expect a '-tmp' suffix
// (this gets auto-generated by my NodeJS/Express routing)
template: "#"+name+"-tmp",
// This part auto-adds a class name matching the registered component-name
created: function() {
var $el = $(this.$options.el);
$el.addClass(this.$options.name);
//Then forward this to any potential custom 'created' methods we had in 'params':
onCreated.apply(this, arguments);
}
};
//Merge default params with given custom params:
params = _.assign(defaultParams, params);
Vue.component(name, params);
}
And then just use it like so:
//Register your Vue Components:
makeVueComponent("dragdropfile", {props:['action']});
You can then leave out those {{$options.name}} from your actual component templates that I mentioned in Solution 1.
I'm dynamically instanciating template on event / or array change (with observe-like functionality).
To achieve that, I use
//whatever event you want, eg:
$(".foo").on("click", function(){
Blaze.renderWithData(Template.widgetCard, d, $(".cards").get(0));
}
That is working, but obviously, instances aren't bound to any parent's template.
Because I just rendered this template on the div.cards I'm unable to use the Template.parentData(1) to get the parent datacontext, even so this div.cards is include on a template.
The quick fix would be to set the wanted reference (which in my case is an object) variable parent's datacontext on global scope, or even use Session, or directly pass this context through the renderWithData's data.
Do you know any other way,even better the proper one (I mean Meteor fancy one), to achieve that?
Is it a good Blaze.renderWithData use case?
Tell me if i'm unclear or more code is needed.
EDIT:
Complementary context info:
I've a chart (d3) where it's possible to select some parts of it.
It has an array property to stock this selected data part.
Chart = function Chart(clickCb, hoverCb, leaveCb, addSelectionCb, removeSelectionCb){
var chart = this;
chart.selectedParts = [];
//... code
}
From outside of this Chart class (so on the Meteor client side), the chart.selectedParts is modified (add/delete).
The dream would be to "bind" this array chart.selectedParts like:
Template.templateContainingAllThoseCards.helpers({
selectedDataChart: function(){
return Template.instance.chart.selectedParts;
},
//...
});
and on the template being able to do something like that:
<div class="row">
<div class="large-12 columns">
<div class="cards">
{{#each selectedDataChart}}
{{> cardWidget}}
{{/each}}
</div>
</div>
</div>
Like that, if the chart.selectedParts was reactive, Blaze could automatically create or remove cardWidget template instance due to the binding.
I've tried to use manuel:reactivearray package on it (and it's kind of anoying cause I'm doing complex manipulation on this array with Underscore, which obviously don't work with none-native Array type such reactiveArray).
Not working, but I dunno if it should have worked.
What do you think?
At this time, I'm doing things a bit dirty I suppose; I juste instanciate/destroying Blaze View on element added/removed chart.selectedParts as: Blaze.renderWithData(Template.widgetCard, {data: d, chart: this}, $(".cards").get(0));
So here how I manage to do that.
Actually I don't think using Blaze.renderWithData() is a good solution.
Best way I've found is to pass your data on "Reactive mode", to be able to use all Template functionalities, and keep using Spacebars to instanciate templates. (Like parent DataContext link).
Easiest way to have reactive datasource is to always match your data with your Mongo, so I don't have to declare a custom Reactive Data source (which could be tricky with complex from a complex js data structure).
If someone have the same problem, I'm pretty sure it's because you don't follow the "good" way to do (which was my case).
One con with always updating your DB as reactive Data source should be a case where you're doing a lot of UI state change, and after all, saving the change. On this case, it's pretty useless to always pass by the DB, but it's from far the quickest solution.
Ask me if you have any similar issue understanding philosophy/way to do, I'm starting to understand what i'm doing!
As the Meteor docs state,
A template instance object represents an occurrence of a template in the document. It can be used to access the DOM and it can be assigned properties that persist as the template is reactively updated. [...] you can assign additional properties of your choice to the object.
How can these properties be accessed from Spacebar templates?
It's cumbersome to have to define helpers that do nothing more than
return Template.instance().myProperty
Just like #apendua said in comment, you can use global helper, this worked for me:
Template.mainPage.onCreated(function(){
this.test = new ReactiveVar('stackoverflow');
});
Template.registerHelper('instance', function(context, options) {
return Template.instance()[context].get();
});
and in HTML
{{instance 'test'}}
You could probably use Template.currentData() as well.
Edit
According to this article you can do in HTML something like:
{{Template.instance.test.get}}
so there is no need for global helpers, and it is accessible from Spacebars so no helpers required
OP wants to access the data directly without any helpers. You will need to store the data in Template.currentData() or Template.parentData(n). Anything in those can be accessed as {{myData}} or {{../myParentData}}, respectively.
I think you can use {{#with}}{{/with}}
for example
{{#with getObject}}
<h1>{{prop1}}</h1>
<h2>{{prop2}}</h2>
{{/with}}
and in helper
getObject:function(){
return {prop1:'some text prop1',prop2:'some text prop2'}
}
To the handlebars (version 1.0.0-rc.3) template I am passing two variables , one is the json and the other one is the string containing the current language on site.
self.template = template({ data: self.model, lang:self.lang });
Then inside the template file I have the following structure:
{{#each data}}
//this is working
{{../lang}}
{{#if this.title}}
{{this.desc}}
//i've tried this
{{../lang}}
//and this
{{lang}}
{{/if}}
{{/each}}
...but I couldn't access the lang value inside the if statement. What am I doing wrong?
I know you already solved your issue with a workaround but registering a Helper for doing a native way is cumbersome.
The thing is that every Handlebars helper overwrites the context and nest the new one inside the parent one, so you have to go up uone step further, like a UNIX like directory.
So, to access lang inside an each->if you have to use:
{{ ../../lang }}
I've find a solution by creating a handlebars helper function:
Handlebars.registerHelper('language', function() {
return self.lang; });
Then in the template i could use {{language}}
where ever I want.
I have created two templates one for table and another for form inputs. The function which is in one template is not accessible from the other template . I want one global function which is accessible from both the template . I am new to backbone world . So is there any provision to do this ? Or is there any way access the function on one template from the another ?
There are a few ways of achieving this.
1. Add to Backbone directly
The simplest, but perhaps least desirable from a maintenance point of view, would be to add the function as a property of the Backbone object:
Backbone.myFunction = function (...) { ... };
2. Register a templating helper
The second option, depending on your templating engine of choice, you might be able to register helpers. Example:
Handlebars.registerHelper("myHelper", function (...) { ... });
3. Use dependency injection
A third option, if you are using something like require, would be to define the functions in a common dependency, and add it as a dependency to both views.