I've reached the point in my first Meteor app where it's time to move all of my update and inserts over to Meteor.methods on the server side. The first thing that I noticed is that I lost the "instant" reactivity once I did this. Here is what I have:
html
<template name="income">
{{#each accounts}}
<tr>
<td class="row1"><input type="text" maxlength="32" value="{{income_acct_name}}" id="income_acct_name{{_id}}"></td>
<td class="row2" align="right"><input type="text" size="13" value="{{income_acct_budget}}" id="income_acct_budget{{_id}}"></td>
</tr>
{{/each}}
</template>
<template name="cashIn">
{{#each accounts}}
<tr>
<td class="row1"><input type="text" size="18" value="{{cashIn_acct_name}}" id="cashIn_acct_name{{_id}}" readonly></td>
<td class="row2_protected" align="right">{{cashIn_acct_budget}}</td>
</tr>
{{/each}}
</template>
client/income.js
Template.income.events ({
'change td.row2': function(theEvent, theTemplate) {
var changedRow = this._id;
var budget = parseFloat(theTemplate.find('#income_acct_budget'+changedRow).value.replace(/[^\/\d.-]/g,''));
var incomeCursor = income.find({"_id": changedRow});
incomeCursor.forEach( function(acct) {
total = acct.income_acct_total;
});
var achieved = 0;
if (budget > 0)
{
achieved = Math.round(total/budget*100);
}
Meteor.call("incomeBudgetChange", changedRow, budget, achieved);
// Update Total Income Budget
var incomeCursor = income.find({"userID": Meteor.userId()});
var budgetTotal = 0;
incomeCursor.forEach( function(acct) {
budgetTotal = budgetTotal + acct.income_acct_budget;
});
var achieved = 0;
if (budgetTotal > 0)
{
achieved = Math.round(incomeTotal/budgetTotal*100);
}
Meteor.call('totalIncomeBudgetChange', totalIncomeID, budgetTotal, achieved);
}
});
server/income.js
Meteor.methods({
'incomeBudgetChange': function(changedRow, budget, achieved){
income.update (
{"_id": changedRow},
{$set: {"income_acct_budget": budget, "income_budget_achieved": achieved}}
)
},
'totalIncomeBudgetChange': function(totalIncomeID, budgetTotal, achieved){
cashIn.update (
{"_id": totalIncomeID},
{$set: {"cashIn_acct_budget": budgetTotal, "cashIn_budget_achieved": achieved}}
)
}
});
The issue here is with totalIncomeBudgetChange. I start out with 10,000 in both {{income_acct_budget}} and {{cashIn_acct_budget}}. I change the value to 15,000 on the income side and the cashIn side is still 10,000. I then change the income side to 20,000 and now the cashIn side is 15,000. cashIn is always one change behind. What happened here when I moved the updating process to the server side? How do I get them to be in sync again?
The issue with your code right now is that your Meteor.calls are asynchronous but you still consider that they are synchronous : see how after calling the first Meteor.call you immediately start trying to fetch the updated income collection by iterating over a cursor and performing a computation on its supposed updated value, but it's not yet updated !
By moving your collection.update code to the server you understand that you have to perform a round-trip to the server before the client can acknowledge that the collection was indeed modified : this is why client-side Meteor.call are asynchronous most of the time (client-side Meteor.call can be synchronous by providing a method stub when it makes sense to achieve latency compensation).
Your problem is that the code executing immediately after your first Meteor.call assumes that the collection (which is a mini-mongo replica subset of the actual MongoDB server persisted collection) is already updated, but it won't be the case until the server modifications are executed and sent back to the client.
I think you should simply refactor your 2 methods calls into one server-side method call that will perform the updates synchronously as the second method call does not depend on the result of the first one : in general, it does not make sense to perform 2 method calls if the user didn't performed 2 separate actions on their own.
Related
I have an input with an #change event:
<f7-searchbar
:clear-button="true"
#focus="showFilters=true"
#blur="showFilters=false"
v-model="searchText"
#change="handleSearch"
>
This is meant to get search results from a database and display them in a table.
The method to get the results:
handleSearch: function(){
var preResults = []
var postResults
var self=this
var vals
db.query('categories/search', {reduce: false}).then(function (res) {
console.log(res['rows'][0]['key'] + ' is the result of searched')
vals = res['rows']
}).then(function(){
self.searchResults = vals.map(row => {
return row['key']['metadata']
})
})
},
Which should be displayed in this table:
<table>
<tr v-for="entry in searchResults" class="popup-trigger" style="text-align: left;">
<span>{{ entry }}</span>
</tr>
</table>
I am expecting these results to update when searchText changes. But that doesn't happen. The results display only after I enter something into the search, and then click where the table should be displayed. I have no idea why clicking should update it. Can anyone help me understand this?
You should use #input event instead of #change event
Also my suggestion is to not send request for every character, for example if you want to search apple, and type fast:
a - send request
p - send request
p - send request
l - send request
e - send request
You can use timer to check if user stoped with typing
<input #input="userStopTyping"
userStopTyping () {
if (this.timer) {
clearTimeout(this.timer)
}
this.timer = setTimeout(this.yourFunction, 200)
}
So if you type fast apple, you will send only one request.
I have the following scenario:
Client side has a button clicking it will execute Meteor.call method on the server-side which will call API and fetch products, During this time I wan't to disable this button + block this method from executing again basically nothing stops you from clicking the button 100x times and server will keep on executing same method again and again.
Few ideas I had in my mind: Use sessions to disable button (Problem: can still using the console Meteor.call and abuse it)
I also looked at Meteor.apply in the docs with wait:true didn't seems to stop from method execution. I honestly not sure how this kind of thing is handled with no hacks.
Client-side:
'click .button-products': function(e){
Meteor.call('getActiveProducts', function(error, results){
if (error)
return Alerts.add(error.reason, 'danger', {autoHide: 5000});
if (results.success)
return Alerts.add('Finished Importing Products Successfully', 'success', {autoHide: 5000});
})
}
Server-side
Meteor.methods({
getActiveProducts: function(){
var user = Meteor.user();
var api = api.forUser(user);
importProducts = function(items){
nextPage = items.pagination.next_page;
items.results.forEach(function(product){
var sameproduct = apiProducts.findOne({listing_id: product.listing_id});
if (sameproduct) {
return;
}
var productExtend = _.extend(product, {userId: Meteor.userId()});
apiProducts.insert(productExtend);
});
};
var products = api.ProductsActive('GET', {includes: 'Images', limit: 1});
importProducts(products);
while (nextPage !== null) {
products = api.ProductsActive('GET', {includes: 'Images', page: nextPage, limit: 1});
importProducts(products);
}
return {success: true};
}
});
From the Meteor docs:
On the server, methods from a given client run one at a time. The N+1th invocation from a client won't start until the Nth invocation returns. However, you can change this by calling this.unblock. This will allow the N+1th invocation to start running in a new fiber.
What this means is that subsequent calls to the method won't actually know that they were made while the first call was still running, because the first call will have already finished running. But you could do something like this:
Meteor.methods({
getActiveProducts: function() {
var currentUser = Meteor.users.findOne(this.userId);
if (currentUser && !currentUser.gettingProducts) {
Meteor.users.update(this.userId, {$set: {gettingProducts: true}});
// let the other calls run, but now they won't get past the if block
this.unblock();
// do your actual method stuff here
Meteor.users.update(this.userId, {$set: {gettingProducts: false}});
}
}
});
Now subsequent calls may run while the first is still running, but they won't run anything inside the if block. Theoretically, if the user sends enough calls, the first call could finish before all of the others have started. But this should at least significantly limit the number of etsy calls that can be initiated by a user. You could adapt this technique to be more robust, such as storing the last time a successful call was initiated and making sure X seconds have passed, or storing the number of times the method has been called in the last hour and limiting that number, etc.
A package I wrote a while back might come in handy for you. Essentially it exposes the Session api on the server side (hence the name), meaning you can do something like ServerSession.set('doingSomethingImportant', true) within the call, and then check this session's value in subsequent calls. The session can only be set on the server, and expires upon connection close (so they could spam calls, but only as fast as they can refresh the page).
In the event of error, you can just reset the session. There shouldn't be any issues related to unexpected errors either because the session will just expire upon connection close. Let me know what you think :)
I am seeing a repeatable issue where a user authenticates ("logs in") with a Meteor server, and then a client subscription that depends on userId is updated (and dependent UI templates reactively update) before Meteor.userId() registers the successful login.
For example, in this code snippet, the assert will throw:
var coll = new Meteor.Collection("test");
if (Meteor.isServer) {
Meteor.publish('mineOrPublic', function () {
// Publish public records and those owned by subscribing user
return coll.find({owner: { $in: [ this.userId, null ]}});
});
}
if (Meteor.isClient) {
var sub = Meteor.subscribe('mineOrPublic');
var cursor = coll.find({});
cursor.observe({
added: function (doc) {
if (doc.owner) {
// This should always be true?!
assert(doc.owner === Meteor.userId());
}
}
});
}
Analogous to the added function above, if I write a template helper that checks Meteor.userId(), it will see a value of null, even when it is invoked with a data context of a document with an owner.
There is apparently a race condition between Meteor collection Pub/Sub and the Account userId update mechanisms. It seems to me that Meteor.userId() should always be updated before any subscriptions update based on a change in this.userId in a server publish function, but for some reason the opposite usually seems to be true (that is, the assert in the code above will usually throw).
The reason I care is because I have packages that depend on obtaining a valid Meteor Authentication token (using Accounts._storedLoginToken()) on the client for use in securing HTTP requests for files stored on the Meteor server. And the authentication token isn't correct until Meteor.userId() is. So the flow of events usually goes something like this:
User logs in
Publish function on server reruns based on the change in this.userId.
Client begins receiving new documents corresponding to the change in userId.
UI Template reactively updates to add DOM elements driven by new documents
Some of the DOM elements are <img> tags with src= values that depend on the data context.
HTTP requests are triggered and ultimately fail with 403 (forbidden) errors because the required authentication cookie hasn't been set yet.
Meteor.userId() finally updates on the client, and code reactively runs to set the authentication cookie
Helpers in the template that depend on a session variable set in the cookie update code are rerun, but the DOM doesn't change, because the URLs in the <img> tags don't change.
Because the DOM doesn't change, the tags don't retry their failed attempts to load the images.
Everything settles down, and the user has to manually reload the page to get their images to appear.
I've come up with two possible approaches to work around this issue:
In the template helper that generates the URL for the <img> tag, always append a dummy query string such as: "?time=" + new Date().getTime(). This causes the DOM to change every time the helper is called and fixes the problem, but it screws-up browser caching and if not coordinated will cause some assets to unnecessarily load multiple times, etc.
In every template helper that touches document data add a test of:
if (this.owner && this.owner !== Meteor.userId()) {
// Perhaps Meteor.loggingIn() could be used above?
// Invalid state, output placeholder
} else {
// Valid state, output proper value for template
}
I really hope someone knows of a less kludgy way to work around this. Alternatively, if consensus arises that this is a bug and Meteor's behavior is incorrect in this respect. I will happily file an issue on Github. I mostly really enjoy working with Meteor, but this is the kind of gritty annoyance that grinds in the gears of "it just works".
Thanks for any and all insights.
After trying lots of things, this variation on the example code in the OP seems to consistently solve the race condition, and I find this an acceptable resolution, unlike my initial attempted workarounds.
I still feel that this kind of logic should be unnecessary and welcome other approaches or opinions on whether Meteor's behavior in the OP sample code is correct or erroneous. If consensus emerges in the comments that Meteor's behavior is wrong, I will create an issue on Github for this.
Thanks for any additional feedback or alternative solutions.
var coll = new Meteor.Collection("test");
if (Meteor.isServer) {
Meteor.publish('mineOrPublic', function (clientUserId) {
if (this.userId === clientUserId) {
// Publish public records and those owned by subscribing user
return coll.find({owner: { $in: [ this.userId, null ]}});
} else {
// Don't return user owned docs unless client sub matches
return coll.find({owner: null});
}
});
}
if (Meteor.isClient) {
Deps.autorun(function () {
// Resubscribe anytime userId changes
var sub = Meteor.subscribe('mineOrPublic', Meteor.userId());
});
var cursor = coll.find({});
cursor.observe({
added: function (doc) {
if (doc.owner) {
// This should always be true?!
assert(doc.owner === Meteor.userId());
}
}
});
}
This code works by giving the server publish function the information it needs to recognize when it is running ahead of the client's own login state, thereby breaking the race condition.
I think this is something that Meteor should do automatically: clients should not see documents based on changes to this.userId in a publish function until after the client Meteor.userId() has been updated.
Do others agree?
I tried with this code that works on server too. In association with FileCollection package.
if (Meteor.isServer) {
CurrentUserId = null;
Meteor.publish(null, function() {
CurrentUserId = this.userId;
});
}
....
OrgFiles.allow({
read: function (userId, file) {
if (CurrentUserId !== file.metadata.owner) {
return false;
} else {
return true;
}
}
...
i have been knocking my head for 2 days now in that .
am creating a search engine, am creating queries dynamically using Meteor Framwork, the queries are working fine and when i search i can rebind the UI (Table in My Case) with the dynamic data query output.
however if an insert/update/delete operation occures the data object
and the UI (html Table) is not updating.
which means that the template is not re-rendered when the data object changes.
Template.search.rendered = function () {
Meteor.autorun(function() {
alarmsData = Alarms.find(getSearchSelector($('#searchTxt').val(), $('#startTimeTxt').val(), $('#endTimeTxt').val())).fetch()
console.log("rendered")
//alarmsData = Alarms.find({},{sort: {timestamp: "desc"} }).fetch();
searchControls(alarmsData)
getConsole(alarmsData, ".console")
$('#badge').html(alarmsData.length)
})
}
the get console function is just reading the array from teh search and creating an html table (this is working fine)
as for the begining i am creating a simple query as the default for my search. and then am changing this query whenever user changes the search criteria. i can notice that only the first instance of teh data object is kept and tracked for changes, so if the second search criteria resides within the first one, it's updating the UI, if not nothing happenes
i have used Meteor.autorun(function(){}) function however i traced it's execution with console.log and i can see it's no excuting when i insert data in the database for the same collection.
One, I believe you are trying to use Deps.autorun. Also, there is nothing in your autorun that seems to be dependent on a reactive source. Since alarmsData is taking a snapshot of data it won't care when Alarms has data changing.
Second, I would probably approach this with a redirect. I would compile my data, and redirect to the same page, allowing the server to handle the querying for me. This easily allows you to jump to this page from anywhere else with a prefilled query in the parameters (because the route would then handle it) and also gives a visual change to the navigation bar when a search has happened (just like every other search engine). You would do something like this on a button click:
var query = {},
path;
query.text = encodeURIComponent($('#searchTxt').val()),
query.start = encodeURIComponent($('#startTimeTxt').val()),
query.end = encodeURIComponent($('#endTimeTxt').val()),
// redirect to current path
path = Router.routes[Router.current().route.name].path({}, {
query: query
});
Router.go( path );
In your router you would just pass the query into your server and route as a data object (assuming you are using iron-router):
this.route( "search", {
path: "/search",
waitOn: function() {
return [
Meteor.subscribe( "searchAlarms", _.omit( this.params, "hash" ) ),
]
},
data: function () {
return { "query": _.omit( this.params, "hash" ) };
}
});
This will not only give you the query data that was used for the search (in your template) but the server can now handle the search for you! Your Alarms data now holds all of the documents needed to display to the user and you no longer need to subscribe to all your Alarms. This is also great because it is automatically reactive. So if a new Alarm matches your query filter it will automatically be passed down to the client and displayed to the user without needing to setup any extra dependencies/autoruns.
Note though, that if you are subscribing to Alarms elsewhere you will still need to do filtering client-side.
What a strange meteor code…
The "rendered" code method code is called once you will be rendering the search template
getSearchSelector($('#searchTxt').val() is not reactive, my advise is to use the session variable to put your search criteria inside and use this same session to inject the find parameters inside.
Are you looking for displaying all the alarms Data ?
function getAlarms()
{
var text = Session.get("text");
var from = Session.get("start");
var to = Session.get("end");
var filter = getSearchSelector(text, from, to);
return Alarms.find(filter);
}
Template.search.alarms = function () {
return getAlarms();
}
Template.search.alarmsCount = function () {
return getAlarms().count();
}
Template.search.events({
'keypress input[name=text]' : function(e,o)
{
var val = $("input[name= text]").val()
Session.set("text", val);
},
'keypress input[name=start]' : function(e,o)
{
var val = $("input[name=start]").val()
Session.set("start", val);
},
'keypress input[name=end]' : function(e,o)
{
var val = $("input[name=end]").val()
Session.set("end", val);
}
});
// And your template will look something like:
<template name="search">
Search alarms
<input type="text" name="text" placeholder="Enter your text here…"/>
<input type="text" name="start" placeholder="start time"/>
<input type="text" name="end" placeholder="end time/>
There is {{alarmsCount}} alarms(s);
{{#each alarms}}
Alarm object: {{.}}
{{/each}}
</template>
I Guess its Solved it by using Session.set & get, and automatically subscribing to the Serevr and send the dynamic Query.
Check the below Code
Template.zConsole.rendered = function () {
Session.set("obj", getSearchSelector($('#searchTxt').val(), $('#startTimeTxt').val(), $('#endTimeTxt').val()))
Deps.autorun(function (){
Meteor.subscribe("dynamicAlarms", Session.get("obj"))
console.log("Count from AutoRun ==> " + Alarms.find(Session.get("obj")).count())
})
}
on the server
Meteor.publish('dynamicAlarms',function (searchObj) {
return Alarms.find(searchObj)
})
& it works perfect with less code.
I have messages collection.
Each message has an userId.
I also defined displayUsername() function, that gets the id of user, and returns fullName.
My question is can I extend it with underscorejs on the server. or what is pratical way to extend an Object
messages = new Meteor.Collection("messages");
Meteor.publish("messages", function () {
var allMessages = messages.find({}).fetch();
return _.each(allMessages, function (msg) {
return _.extend(msg, {
username: displayName(msg.userId)
});
});
so I want
{{#each messages}}
<p><strong>{{username}}:</strong> {{messageBody}}</p>
{{/each}}
I know, that it is possible on the client side, but I am going to use it some more time...
thanks..
check transform on Collection.find
http://docs.meteor.com/#find
chris has a video tut talk about "Transforming Collection Documents"
The transform option on Meteor Collections allows us to transform MongoDB documents before they're returned in a fetch, findOne or find call, and before they are passed to observer callbacks. It lays the foundation for a Model layer. In this episode I'll build a simple transform class that has a formatPrice method for a price that is stored as cents in the database.
http://www.eventedmind.com/posts/meteor-transforming-collection-documents
Unfortunately you can't send down a transformed collection. But you can transform it on the client side.
e.g when you define your collection on the client:
client side js
var messages = new Meteor.Collection("messages", {transform:function(doc) {
doc.username = displayName(doc.userId);
return doc;
}});