Sessions vars not updating DOM fast enough in Meteor - meteor

I noticed this property of Session vars:
test.html
<input name='test' type='text' placeholder='something' value='{{testVal}}'>
test.js (template.test.helpers)
testVal: function(){
return Session.get("testVal");
}
template.test.onRendered
// Sets the initial value of the session var and the form shows "I like cats"
Session.set("testVal", "I like cats");
var displayValue = function(newInput){
Session.set("testVal", newInput);
console.log("session has been set to: ", newInput);
console.log("value of form entry using jQuery: ", $('input[name="test"]').val());
};
When I run displayValue("lalalalal") in Chrome Console I get:
"session has been set to: lalalalal"
"value of form entry using jQuery: I like cats."
The value of the text input does visually change reactively to "lalalalal" after setting the new value for the session variable, but it seems that the DOM update from the session variable isn't quick enough - jQuery still picks up the old value before the reactive change, which implies that reactive updates to the DOM for Session Vars is async.
So this means that if you use reactive variables to auto-update form fields and you're using jQuery to then grab the form values, you need some way to wait until the DOM has been fully updated before you can use jQuery.
Does anyone know any way to do this, ideally in the context of this example?

In this particular example, why not just using the value you're setting the Session var with, ie newInput ?
The problem with your code is that reactive programming on the client is asynchronous by nature : when you set the Session variable to a new value, you're invalidating the reactive computation within the helper responsible for setting the value of the DOM input inside the template markup.
You may use Tracker.afterFlush to let the reactive computation invalidation propagates and rerun the helper, thus really changing the input value that you'll be able to grab back using jQuery.
var displayValue = function(newInput){
Session.set("testVal", newInput);
console.log("session has been set to: ", newInput);
Tracker.afterFlush(function(){
var testValue = $('input[name="test"]').val();
console.log("value of form entry using jQuery: ", testValue);
});
};

Related

Meteor Blaze Data Context becomes undefined

I have a blaze template like this:
{{>jobsheetsTable companyId=companyId}}
In the JS for the template I have the onCreated function like this ...
Template.jobsheetsTable.onCreated(function(){
const instance = this;
instance.companyId = new ReactiveVar();
console.log(instance, instance.data, instance.data.companyId);
if(instance.data.companyId){
instance.companyId.set(instance.data.companyId);
}
}
The issue is that in the console.log statement I notice something odd ...
instance is correctly outputting the instance with the data object and a companyId
instance.data however returns {companyId: undefined}.
I am not changing instance.data anywhere and the function being passed into this template does not change the companyId.
Update: Using meteor 1.6.1.
The onCreated callback is only run once per template creation, so the data you get is the one that is provided to the initial creation (likely with you attribute set to undefined).
It is likely that the data context is changed after the initial rendering, and this does not trigger the function. as the template is not re-created.
If you are certain that you want to track the data context in the onCreated callback, you need to set a reactive dependency on it, using the Template.currentData() reactive data source. Since it needs to be inside a reactive context in order to be re-run when the data changes, you will need to create one, and a convenient method of doing so is via this.autorun(), which stops the computation for you when the template is destroyed.
Template.jobsheetsTable.onCreated(function(){
this.companyId = new ReactiveVar();
this.autorun(() => { // creates a reactive computation
const data = Template.currentData(); // creates a reactive dependency
console.log('data context', data);
if(data.companyId){
this.companyId.set(data.companyId);
}
})
});
The code above contains an autorun block that will re-run whenever the data context changes.

meteor and reactJS - how to make child components reactive?

I have a child component that depends on another widget to set what data it should subscribe to. So effectively a dynamic subscription.
getMeteorData() {
var topicName = this.context.topicName;
var handle = Meteor.subscribe('RebotTopicDetail', {name: topicName});
...
If topicName changes I want to re-subscribe to new data.
What is the best way to do this?
The topicName is being set by another component, and I am using context to pass it between objects.
https://facebook.github.io/react/docs/context.html
I can see the context.topicName is changing as I am displaying it.
But it's not triggering the reactive calculation inside meteor data to rerun.
Is there a way to declare a Deps on a variable within getMeteorData?
My understanding was that was a reactive block so changes would cause the getMeteorData block to re-run.
https://react-in-meteor.readthedocs.org/en/latest/meteor-data/
I also tried passing the topicName in via props, but that also doesn't trigger a reactive update.
for others coming here, related threads
https://github.com/meteor/react-packages/issues/19
https://github.com/meteor/react-packages/issues/66
https://github.com/facebook/react/issues/2517
edit: props seems to be reactive
```
getMeteorData() {
var topicName = this.props.topicName;
var handle = Meteor.subscribe('RebotTopicDetail', {name: topicName});
/// and in the container passing in a prop
<TriggerList topicName={this.state.topicName} />
```
works. not sure if using state of the container item had something to do with it.

checkbox update to mongo is sometime work and sometime won't work

My code sometime work and some time won't work
Mongo didn't update sometimes...
But his code is work perfact
What am i do wrong?
Here is my code
http://checkboxploblem.meteor.com
https://github.com/codepawn/practice_makes_perfect/tree/master/checkbox_mongo
origin is
https://github.com/EventedMind/class-build-a-multi-page-app-with-iron-meteor-6737880d
Your problem lies in your event within the 'home.coffee' file:
Template.Home.events
'click [name=isDone]': (e, tmpl) ->
id = #_id
isDone = tmpl.find('input[name=isDone]').checked
Todos.update {_id: id},
$set:
isDone: isDone
You are assigning the first checkbox value to the variable isDone. Thus, if you check the first input box, that is what gets applied to every subsequent task.
You can get around this by retrieving the value of the checkbox from the event object. ie. e.currentTarget.checked
Another alternative is use a unique id for each checkbox and retrieve the value using that id

What's the Difference between Template.Instance() vs template.data?

When creating a Meteor event handler, what's the difference between...
'click .something': function(e,t){
var data = t.data
}
vs
'click .something': function(e,t){
var data = Template.instance().data
}
They both seem to bring up the same data. Is there a reason why I should one or the other?
Similar question here:
Difference between Template.instance() and this
The thing to realize is that:
In the template life-cycle functions (onCreated, onRendered...) this is equal to Template.instance() so this.data is the same as Template.instance().data AT THAT TIME!
In a helper or event, this is the current data context.
So, note an important thing here: the Data context can change over time if your data changes upstream:
If you pass data to a template, the template will be re-rendered with the new data. New data = new data context.
So if you do something like:
Template.example.onCreated(function() {
this.data.myKey = "my example data set on template creation"; //WRONG!
// or equivalently:
Template.instance().data.myOtherKey = "another key"; //WRONG!
})
well, this data may be under this (i.e. the data context) in your helper (this.myKey) but only as long as the upstream data does not change.
As soon as the upstream data changes, this will be the new data context, and will NOT contain your added data.
So, in summary:
If you need to add context to your template in onCreated or onRendered, make sure you do NOT bind it to the current data context, but to the Template.instance()
you should do:
Template.example.onCreated(function() {
this.myKey = "my example data set on template creation";
// or equivalently:
Template.instance().myOtherKey = "another key";
})
and you can access this data in helper and events via
Template.instance().myKey
It's actually Template.instance() (with a lower i), and as this function returns the current template instance in scope (the one where the event originated), there's no difference with the second parameter of an event handler, which also holds the current template instance, this is why you can access the template data indifferently using Template.instance().data or t.data in an event handler.
There is however a simpler way to access the current data context inside an event handler : the this object is bound to the data context where the event was triggered.

Writing user entered text (input) back into it's document (in mongo)

I have a meteor template rendering a document comming out of mongo db.
Parts of the document are editable (they render as html input elements).
Now I need the data to flow back into the document (and into mongo),
What is the best way to do this ?
The answer is easy if I want to write back the value of doc.a :
doc = {a: "hello"}
it is less easy with : doc.a[0].z
doc = {a: [{z: "hello"}]}
because in order to do the update, the path must be remembered
in order to write the update statement.
Updating the whole document whenever a field changes looks simple,
but inefficient...
It is an extremely common use case, some frameworks (EmberJs) have
magical bindings that modifies the model whenever the widget's value
changes.
How is this done in meteor ?
As you point out, it would probably be inefficient to run a db update command whenever the input changes. This is especially true for draggable elements like sliders.
One thing you can do is separate the db query into a function, and then debounce it using underscore.js, like so (untested):
var debouncedUpdate = _.debounce(function(newObject) {
CollectionName.update({_id: newObject._id},newObject);
},300,true);
Template.objectInput.events({
'keydown #text-input': function(event) {
var newValue = $(this.find('#text-input')).val();
var self = this;
self.value = newValue;
debouncedUpdate(self);
},
});

Resources