I have a template with some nested templates in Meteor:
<template name="collectingTmpl">
{{> firstTmpl}}
{{> secondTmpl}}
</template>
If I set a reactive var/dict in firstTmpl with
Template.firstTmpl.events({
'click .class-name': function(event, template) {
template.state = new ReactiveDict;
template.state.set('someName', 'someValue');
}
});
I can get this value within the same template with
Template.firstTmpl.helpers({
myValue: function() {
Template.instance().state.get('someName');
}
});
but can I also retrieve the value being set in firstTmpl from secondTmpl?
I mean something like
Template.secondTmpl.helpers({
myValueFromAnotherTmpl: function() {
Template.firstTmpl.state.get('someName');
}
});
You could alternatively set the ReactiveDict on the parent template which is collectingTmpl.
// always initialize your template instance properties
// in an onCreated lifecycle event
Template.collectingTmpl.onCreated(function(){
this.firstTmplState = new ReactiveDict();
});
Then you can obtain references to this template instance property in child templates using this code :
Template.firstTmpl.onCreated(function(){
// warning, this line is depending on how many parent templates
// the current template has before reaching collectingTmpl
var collectingTmplInstance = this.view.parentView.templateInstance();
this.firstTmplState = collectingTmplInstance.firstTmplState;
});
Template.secondTmpl.onCreated(function(){
var collectingTmplInstance = this.view.parentView.templateInstance();
this.firstTmplState = collectingTmplInstance.firstTmplState;
});
Then you can use the standard Template.instance().firstTmplState syntax in any of your 3 templates and they'll always point to the same ReactiveVar instance defined as a property of collectingTmpl.
If you take care on a currently issue of blaze (missing data context), you are able to access other templates vars by Template.parentData().
See this MeteorPad as a demo, the background color will be changed onn each player when pressing the button. the color is defined by their parent template:
http://meteorpad.com/pad/zoiAvwuT3XXE5ruCf/Leaderboard_Template_parentData_Bug
You may also read at GitHub PullRequest about some updates I suggest
https://github.com/meteor/meteor/pull/4797
Cheers
Tom
Related
I am pretty new to Vue Framework. I am trying to propagate the changes from parent to child whenever the attributes are added or removed or, at a later stage, updated outside the component. In the below snippet I am trying to write a component which shows a greeting message based on the name attribute of the node which is passed as property from the parent node.
Everything works fine as expected if the node contains the attribute "name" (in below snippet commented) when initialized. But if the name attribute is added a later stage of execution (here for demonstration purpose i have added a set timeout and applied). The component throws error and the changes are not reflected . I am not sure how I can propagate changes for dynamic attributes in the component which are generated based on other events outside the component.
Basically I wanted to update the component which displays different type of widgets based on server response in dynamic way based on the property passed to it .Whenever the property gets updated I would like the component update itself. Why the two way binding is not working properly in Vuejs?
Vue.component('greeting', {
template: '#treeContainer',
props: {'message':Object},
watch:{
'message': {
handler: function(val) {
console.log('###### changed');
},
deep: true
}
}
});
var data = {
note: 'My Tree',
// name:"Hello World",
children: [
{ name: 'hello' },
{ name: 'wat' }
]
}
function delayedUpdate() {
data.name='Changed World';
console.log(JSON.stringify(data));
}
var vm = new Vue({
el: '#app',
data:{
msg:data
},
method:{ }
});
setTimeout(function(){ delayedUpdate() ;}, 1000)
<script src="https://vuejs.org/js/vue.js"></script>
<div id="app">
<greeting :message="msg"></greeting>
</div>
<script type="text/x-template" id="treeContainer">
<h1>{{message.name}}</h1>
</script>
Edit 1: #Craig's answer helps me to propagate changes based on the attribute name and by calling set on each of the attribute. But what if the data was complex and the greeting was based on many attributes of the node. Here in the example I have gone through a simple use case, but in real world the widget is based on many attributes dynamically sent from the server and each widget attributes differs based on the type of widget. like "Welcome, {{message.name}} . Temperature at {{ message.location }} is {{ message.temp}} . " and so on. Since the attributes of the node differs , is there any way we can update complete tree without traversing through the entire tree in our javascript code and call set on each attribute .Is there anything in VUE framework which can take care of this ?
Vue cannot detect property addition or deletion unless you use the set method (see: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats), so you need to do:
Vue.set(data, 'name', 'changed world')
Here's the JSFiddle: https://jsfiddle.net/f7ae2364/
EDIT
In your case, I think you are going to have to abandon watching the prop and instead go for an event bus if you want to avoid traversing your data. So, first you set up a global bus for your component to listen on:
var bus = new Vue({});
Then when you receive new data you $emit the event onto the bus with the updated data:
bus.$emit('data-updated', data);
And listen for that event inside your component (which can be placed inside the created hook), update the message and force vue to re-render the component (I'm using ES6 here):
created(){
bus.$on('data-updated', (message) => {
this.message = message;
this.$forceUpdate();
})
}
Here's the JSFiddle: https://jsfiddle.net/9trhcjp4/
Is there a way to detect list change in canjs and make the view redraw? I am changing the list but this is not shown on screen.
At the moment i have view model
TodosListViewModel = can.Map.extend({
todoCreated: function(context, element) {
// new todo is created
var Todo = this.Todo;
new Todo({
name: can.trim(element.val())
}).save();
element.val("");
},
tagFiltered: function(context, element) {
// filter todos according to tag
this.todos = this.todos.filter(function(todo) {
return todo.tag === element.val();
});
}
});
And component
can.Component.extend({
// todos-list component
// lists todos
tag: "todos-list",
template: can.view("javascript_view/todos-list"),
scope: function() {
// make the scope for this component
return new TodosListViewModel({
todos: new TodoList({}),
Todo: Todo
});
},
events: {
"{scope.Todo} created": function(Todo, event, newTodo) {
// todo created
this.scope.attr("todos").push(newTodo);
},
"{scope.todos} changed": function(a,b,c,d,e,f,g,h) {
console.log("todo change",d,e);
}
}
});
The markup
<input type="text" name="tagFilter" placeholder="Tag lookup" can-enter="tagFiltered" />
The rest of code http://git.io/vrPCTQ
In the case you're showing in the fiddle, you haven't define "page" in the scope to take a raw string value from the component's tag (using "#" as the value for scope.page). Check out the one-line difference in router's scope here:
http://jsfiddle.net/tkd9Lvtm/3/
EDIT: That didn't address the original question, so here's what else you can do to get this started. I made a new fiddle version for you.
http://jsfiddle.net/tkd9Lvtm/4/
The best way with CanJS 2.1 to accomplish what you want is to use can-value attributes on your form fields to two-way bind your elements to attribute values on your view model. You can see that the input field for the tag search is now using can-value instead of can-change -- this makes it independent of the filter function, which is only used to draw the items farther down.
CanJS will automatically rerun the filter when the attribute changes, because calling this.attr("filterTerm") inside the view model's filter function sets up binding the first time it's run. The live bound view layer is making computes out of these functions "under the hood" and these computes (a) listen to changes on attributes that are read inside the function; and (b) updates the DOM with each change to listened-to attributes. Using the view model to store the value in the filter field then allows that function to fire again on each change.
Problem: Meteor JS app with 2 distinct templates that need to share some data.
They are dependent on one another, since I aim to extract text (Step 1) from one, and then create dynamic buttons (Step 2) in another template. The content of the buttons is dependent on the table.
buttons.html
<template name="buttons">
{{#each dynamicButtons }}
<button id="{{ name }}">{{ name }}</button>
{{/each}}
</template>
My goal is for the name property to come from the content of reactiveTable.html (see above, or their Github page, package meteor add aslagle:reactive-table.
These need to be dynamically linked since table re-renders constantly w/ different group of products, which are linked up through Template.reactiveTable and a specific data context (Pub/Sub pattern).
IF the table is (re)rendered, then parse it's content and extract text. Once the table is parsed, dynamically inject newly created buttons into the UI. Note UI.insert takes two arguments, the Object to insert, and then location (DOM node to render it in).
Template.reactiveTable.rendered = function () {
UI.insert( UI.render( Template.buttons ) , $('.reactive-table-filter').get(0) )
};
(Insert new buttons every time a reactiveTable is rendered.)
This code works, but is flawed since I cannot grab the newly rendered content from reactiveTable. As shown in this related question, using ReactiveDict package:
Template.buttons.helpers({
dynamicButtons: function() {
var words = UI._templateInstance().state.get('words');
return _.map(words, function(word) {
return {name: word};
});
}
});
Template.buttons.rendered = function() {
// won't work w/ $('.reactiveTable) since table not rendered yet, BUT
// using $('h1') grabs content and successfully rendered dynamicButtons!
var words = $('h1').map(function() {
return $(this).text();
});
this.state.set('words', _.uniq(words));
};
Template.buttons.created = function() {
this.state = new ReactiveDict;
};
How can I change my selector to extract content from Template.reactiveTable every time is re-renders to create buttons dynamically? Thanks.
You’re using a lot of undocumented functions in there, and UI.insert and UI.render which are bad practice. The just-released Meteor 0.9.1 eliminates them, in fact.
Create your dynamic buttons the Meteoric way: by making them dependent on a reactive resource. For example, a Session variable. (You could also use a client-side-only collection if you want.)
Template.reactiveTable.rendered = function () {
// Get words from wherever that data comes from
Session.set('buttons', words);
};
Template.buttons.helpers({
dynamicButtons: function() {
if (Session.equals('buttons', null))
return [];
else
return _.map(Session.get('buttons'), function(word) {
return {name: word};
});
}
});
Every time reactiveTable is rendered or rerendered, the buttons Session variable will update. And because your dynamic buttons are depending on it, and since Session variables are a reactive resource, the buttons will rerender automatically to reflect the changes.
The change in behavior for the Template.foo.rendered callback in Meteor 0.8.0 means that we don't get to automatically use the rendered callback as a way to manipulate the DOM whenever the contents of the template change. One way to achieve this is by using reactive helpers as in https://github.com/avital/meteor-ui-new-rendered-callback. The reactive helpers should theoretically help performance by only being triggered when relevant items change.
However, there is now a new problem: the helper no longer has access to the template instance, like the rendered callback used to. This means that anything used to maintain state on the template instance cannot be done by helpers.
Is there a way to access both the template instance's state as well as use reactive helpers to trigger DOM updates in Blaze?
In the latest versions you can use more convenient Template.instance() instead.
Now there's Template.instance() which allows you to access a template's instance in helpers. e.g
Template.myTemplate.helpers({
myvalue: function() {
var tmpl = Template.instance();
...
}
});
Along with reactiveDict, you could use them to pass values down set in the rendered callback.
Template.myTemplate.created = function() {
this.templatedata = new ReactiveDict();
}
Template.myTemplate.rendered = function() {
this.templatedata.set("myname", "value");
};
Template.myTemplate.helpers({
myvalue: function() {
var tmpl = Template.instance();
return tmpl.templatedata.get('myname');
}
});
This is currently being tracked as "one of the first things to be added" in post 0.8.0 Meteor:
https://github.com/meteor/meteor/issues/1529
Another related issue is the ability to access data reactively in the rendered callback, which avoids this issue in the first place:
https://github.com/meteor/meteor/issues/2010
I'm working on a simple app which displays a list of issues of a particular repo on Github. Below is the code of IssueView which generates the html of an issue and insert to the DOM
App.IssueView = Ember.View.extend({
tagName: "li",
classNames: ["sugar", "issue_wrapper"],
templateName: "app/templates/issue",
init: function() {
App.LabelsController.addObserver("label", this, this.labelUpdated);
this._super();
},
click: function(event) {
var target = event.target;
if (target.className == "title") {
// Using bindingContext is a temporary solution to access data of this issue
App.IssuesController.set("issue", this.bindingContext);
App.IssuesController.set("state", "viewIssueDetails").notifyPropertyChange("state");
}
},
labelUpdated: function() {
this.labels = this.labels || this._collectLabels(),
label = App.LabelsController.get("label").name;
this.set("isVisible", this.labels.indexOf(label) != -1);
},
_collectLabels: function() {
var labels = [];
this.bindingContext.labels.forEach(function(label) { labels.push(label.name) });
return labels;
}
})
The way I generate it is
<script type="text/x-handlebars">
{{#view App.IssuesListView}}
{{#each App.IssuesController}}
{{view App.IssueView contentBinding="this"}}
{{/each}}
{{/view}}
</script>
The problem I had is with this line
App.LabelsController.addObserver("label", this, this.labelUpdated);
Everytime a new IssueView is generated and inserted into the DOM, I got an error "You cannot set observed properties on destroyed objects" when the 'label' property of LabelsController is updated. When I look into Firebug I saw that my IssueView's state is "destroy" instead of inDOM. I wonder why that happened and what can I do to get around it?
The #each helper in your template will ensure that IssueViews are created and destroyed as the collection of issues changes. You are manually adding the observer, which means you are responsible for removing the observer, too. I believe that using the observes(...) function prototype extension will handle that for you. (See http://ember-docs.herokuapp.com/symbols/Ember.Observable.html under "Observing Property Changes").
If you want to pursue the manual route, consider moving the addObserver to didInsertElement and adding a corresponding removeObserver in willDestroyElement.
One side note: if I'm understanding what you are trying to do with this code correctly, I would consider binding to an ArrayController that handles presenting the correct set of issues based on the selected label instead of the approach you are taking.