How to combine Dojo.Deferred + dojo.data.read - asynchronous

I have a chain of asynchronous events that i want to execute, some of them fetch commands. I understand the basics of using dojo.Deferred, but all of my attempts so far to add a fetch callback have failed...
What im trying to do is 1) add a fetch call to my Deferred object 2) have that fetch function be run, possibly with some extra arguments and 3) pass on the 'fetched' values to the next callback in the chain... Here's what i've got:
var myDeferred = new dojo.Deferred();
myDeferred.addCallback(this.myStore.fetch({query:{//some query//},
onComplete: function(items) { return items } })); //add the fetch callback? i think this is wrong...
myDeferred.addCallback(function(items) { console.log(items) }); //log the fetched items
myDeferred.callback(); //to fire

you have to fire your deferred from the callback of the fetch
var myDeferred = new dojo.Deferred();
this.myStore.fetch({
query:{//some query//},
onComplete: function(items) { myDeferred.callback(items); }
})
myDeferred.addCallback(function(items) { console.log(items) }); //log the fetched items
If you have multiple fetches you should look into DeferredList and into returning a Deferred from the deferred-callback. Both can be used to create quite complex behavior.
You should also look into the new dojo 1.5 Deferred which have a simpler usage pattern, which might help you.

Related

Notify browser UI about SignalR requests to the server

I ma using SignalR in the browser. Some request (calling function on the server) are long and I would like to show spinner/loading-bar.
Can I somehow hook for an event when this function is started and when it returns back.
I'm trying to figure out what you mean - I think basically you want some way to hook into the start of a call and the end of a call (to load and unload a spinner)?
I've done this in two different ways - firstly as a one-off (first example), and then more systematically (the second example). Hopefully one of these will be what you need.
$.connection.myHub.server.hubMethod().done(function () {
//called on success
}).fail(function (e) {
//called on failure - I don't recommend reading e
}).always(function() {
//called regardless
spinner.close();
});
spinner.open(); // must be triggerd AFTER call incase exception thrown (due to connection not being up yet)
If you don't like that - perhaps because you call hub methods in hundreds of different sections of codes, then there are other tricks which are a bit more complicated. Lets see:
function SetupSpinnerOnCallToSignalrMethod(hubServer, method, spinnerStartCallback, spinnerEndCallback) {
var prevFunc = hubServer[method];
hubServer[method] = function () {
var ret = prevFunc.apply(this, arguments);
spinnerStartCallback(); // must be triggerd AFTER call incase exception thrown (due to connection not being up yet)
ret.always(function() {
spinnerEndCallback();
});
return ret;
};
}
//then call this for each method
SetupSpinnerOnCallToSignalrMethod($.connection.myHub.server,
"hubMethod",
function() { spinner.open(); },
function() { spinner.close(); }
);
//the server call should then work exactly as before, but the spinner open and close calls are invoked each time.

How to use angular-meteor helpers and Meteor.methods together

I use a bunch of helper methods in my project. Some of them require to load the whole collection into the client due the restriction of the api on client side (the distinct function!). I googled the problem and found Meteor.methods as solution.
Can I use helpers (like this.helpers) into Meteor methods? Or how should I dynamically update my data in the frontend?
Can someone give me an example?
Additional information:
class View2 {
constructor($interval, $scope, $reactive) {
'ngInject';
$reactive(this).attach($scope);
this.helpers({
getOrderNumber(){
this.tempVar = Kafkadata.find().fetch();
this.tempVar2 = _.pluck(this.tempVar, 'orderNumber');
this.tempVar3 = _.uniq(tempVar2, false);
return this.tempVar3;
},
});
}
This is an example for a helpers query. Currently, this code runs client-side. I get ALL orders(tempvar) and then remove ALL data except the ordernumbers(tempvar2). At the end I remove all multiple ordernumbers. ordernumber is not an unique value. Here is an example from one of the collections:
{"orderNumber":"f2a3ed95-fcc3-4da0-9b3f-32cf5ed087f8","value":12480,"booleanValue":false,"intValue":12480,"doubleValue":0,"status":"GOOD","itemName":"MILLING_SPEED","timestamp":1479145734448,"_id":{"_str":"5824f4bc7ff3f0199861f11d"}}
I want to use functions like db.collection.distinct(). But they only work server-side. I think I must use Meteor.methods()to make this thing server-side. But what about this helpers function? How do they work on Meteor.methods()?
EDIT2:
my test:
client-side:
folder:myProject/imports/ui/view1
class View1 {
constructor($interval, $scope, $reactive) {
'ngInject';
$reactive(this).attach($scope);
this.helpers({
// some code
getTestData(){
Meteor.call('allTestData',function(error, result){
if(error){
console.log("error");
}else{
return result;
}
});
}
}); //end of contructor
// this is my testfunction, which is bound to a button!
testFunction(){
Meteor.call('allTestData',function(error, result){
if(error){
alert('Error');
}else{
console.log(result);
}
});
}
on the server-side:
folder:myProject/server/main.js
Meteor.methods({
allTestData:()=>{
var results=Kafkadata.find().count();
console.log(results);
return results;
},
});
and this is my view1.html:
//some code
<md-button ng-click="view1.testFunction()"> It works!</md-button>
<h1>{{view1.getTestData}}</h1>
Why does the button work, but not the helper?
Even though .distinct is supported by Mongo, Meteor does not expose it, even on the server. You just have to use _.uniq as your example shows, but for performance reasons it's better if it runs on the server.
Below is an example of a helper that I use:
aweek: () => {
if (debug)
console.log("Querying weekly appointments for "+this.selectedWeek.format("Do MMMM"));
var weekApts = Appointments.find(
{start: {$gte: new Date(this.getReactively('this.selectedWeek').clone().day(1)),
$lt: new Date(this.getReactively('this.selectedWeek').clone().endOf('week'))},
elderid: Meteor.userId()
}).fetch();
return utils.services.getAWeek(weekApts,utils.data.elderTimeFormat);
},
Note the use of this.getReactively('this.selectedWeek') in the code... basically this tells Meteor to run this helper reactively, so if the value of this.selectedWeek changes, the helper will get re-run. So when I click on a week in the calendar and update the variable, it runs my helper again to get the data.
The utils.services.getAWeek() function does some calculation and formatting on the array of data that makes is easier to display.
If you create a Meteor Method to do processing, I would make it update a collection with its results, and then your helper on the client will update automatically. Best make the technology do the work for you :)

How to get all subscription data before rendering and then just update the dom when new data is received?

I currently havea widget which grabs hundreds of documents from the DB via subscription and then keep listening for new documents, so it can update a stock chart.
There is a problem tough, which is every time the data is updated the chart is updated, which causes a redraw.
This is a problem cause it's calling redraw hundreds of time at the beginning even tough it just need to "fetch all data then draw and wait for updates", the updates will then happen not very often, so then it would be ok to redraw.
my current code:
Template.nwidget.onRendered(function() {
return this.autorun(function() {
var data;
data = {};
data = Data.find({
type: 'my_type'
});
data = data.fetch();
return update(data);
});
});
For doing some after data subscription you can do like this:
Meteor.subscribe( 'collection', {
onStop: function( error /* optional */ ) {
// when the sub terminates for any reason,
// with an error argument if an error triggered the stop
},
onReady: function() {
// when ready
}
});
If you want to render page after the data subcribe then you can add waitOn in your router.
There is one more way to check where subscription is ready or not. If subscription is not ready you can show something else like a loading screen.
var handle = Meteor.subscribe( 'collection');
Tracker.autorun(function() {
if (handle.ready())
//write whatever you want to do here.
});
For the auto update in your view you can store the date in a reactive thing its may reactive var, Session or collection.
Then you can return there values from helper to view. And that will auto update your view.

MeteorJS ReactiveVar with database data

Clearly, I am doing something wrong with ReactiveVar because I cannot get it to work as I expect it should.
I am trying to set the value of an ReactiveVar by calling a Meteor.call method which returns the list of usernames. But it does not update when the usernames get changed in another part of the app.
I tried both:
Template.qastatistics.created = function () {
this.trackUsernames = new ReactiveVar(false);
var instance = Template.instance();
Meteor.call('trackUsernames', function (err, data) {
instance.trackUsernames.set(data);
});
};
and:
Template.qastatistics.helpers({
users: function () {
var usernames,
instance = Template.instance();
if (instance.trackUsernames.get() === false) {
Meteor.call('trackUsernames', function (err, data) {
instance.trackUsernames.set(data);
});
}
usernames = instance.trackUsernames.get();
...
But neither updates the list of usernames when these change in the database.
Is this even possible with ReactiveVars or have I completely misunderstood them?
EDIT: The usernames I mention are not from Meteor.users collection, but rather a distinct call from another collection that has usernames in it.
Fist of all I would use the onCreated function instead of defining created. That's a little more extendable and it's the new API. created is just kept around for backwards compatibility.
About your problem. You are right, you seem to have misunderstood what ReactiveVars do. They are a reactive data source. That means that when you call myReactiveVar.get in some Tracker.autorun (aka. reactive computation), the computation will rerun whenever myReactiveVar.set is called.
You got the first part right. Spacebars helpers always run inside their own computation. What you got wrong is thinking that a method call is a reactive action. That means, that you could call trackUsernames and set the trackUsernames ReativeVar again and the value in your template would update itself. But a method is only run once. It doesn't do anything fancy with reactivity.
A method call only transfers data once. When you publish a set of documents (like all users) on the other hand, they will be updated dynamically. Whenever a change happens inside that set of published documents, it will be synced to the client. So in general, it's a better idea to use publications and subscriptions to sync data reactively. If you'd want to use a method for the same thing you'd need to do some kind of polling (so your back in the stone-age again).
The easiest way to implement what you are trying to do is to use Meteor.users.find().fetch(). As it says in the docs fetch registers dependencies for all the documents you are fetching if it's being called from within a reactive computation.
First you'll need to properly set up your publications, so that users can see other users usernames. I'll leave that to you. Then you need to reimplement your helper
Template.qastatistics.helpers({
users: function () {
var usernames = _.pluck(Meteor.users.find().fetch(), 'username');
...
Thanks to suggestions from #kyll, I managed to get what I wanted by publishing the data I need:
server:
cope.publish.usernamesID = Random.id();
Meteor.publish("itemsusernames", function () {
self = this;
var initializing = true;
var handle = Items.find().observeChanges({
added: function (id) {
!initializing && self.changed(
"itemsusernames",
cope.publish.usernamesID,
Items.distinct("p4User"));
},
changed: function (id) {
!initializing && self.changed(
"itemsusernames",
cope.publish.usernamesID,
Items.distinct("p4User"));
},
removed: function (id) {
!initializing && self.changed(
"itemsusernames",
cope.publish.usernamesID,
Items.distinct("p4User"));
}
});
initializing = false;
self.added("itemsusernames", cope.publish.usernamesID, Items.distinct("p4User"));
self.ready();
self.onStop(function () {
handle.stop();
});
});
client:
users: function () {
var usernames = [],
oUsernames = ItemsUsernames.find().fetch();
if (!oUsernames[0]) return [];
usernames = $.map(oUsernames[0], function (value, index) {
if (!isNaN(index)) {
return [value];
}
});
...
And ofcourse: ItemsUsernames = new Mongo.Collection("itemsusernames");

How to 'transform' data returned via a Meteor.publish?

Meteor Collections have a transform ability that allows behavior to be attached to the objects returned from mongo.
We want to have autopublish turned off so the client does not have access to the database collections, but we still want the transform functionality.
We are sending data to the client with a more explicit Meteor.publish/Meteor.subscribe or the RPC mechanism ( Meteor.call()/Meteor.methods() )
How can we have the Meteor client automatically apply a transform like it will when retrieving data directly with the Meteor.Collection methods?
While you can't directly use transforms, there is a way to transform the result of a database query before publishing it. This is what the "publish the current size of a collection" example describes here.
It took me a while to figure out a really simple application of that, so maybe my code will help you, too:
Meteor.publish("publicationsWithHTML", function (data) {
var self = this;
Publications
.find()
.forEach(function(entry) {
addSomeHTML(entry); // this function changes the content of entry
self.added("publications", entry._id, entry);
});
self.ready();
});
On the client you subscribe to this:
Meteor.subscribe("publicationsWithHTML");
But your model still need to create a collection (on both sides) that is called 'publications':
Publications = new Meteor.Collection('publications');
Mind you, this is not a very good example, as it doesn't maintain the reactivity. But I found the count example a bit confusing at first, so maybe you'll find it helpful.
(Meteor 0.7.0.1) - meteor does allow behavior to be attached to the objects returned via the pub/sub.
This is from a pull request I submitted to the meteor project.
Todos = new Meteor.Collection('todos', {
// transform allows behavior to be attached to the objects returned via the pub/sub communication.
transform : function(todo) {
todo.update = function(change) {
Meteor.call('Todos_update', this._id, change);
},
todo.remove = function() {
Meteor.call('Todos_remove', this._id);
}
return todo;
}
});
todosHandle = Meteor.subscribe('todos');
Any objects returned via the 'todos' topic will have the update() and the remove() function - which is exactly what I want: I now attach behavior to the returned data.
Try:
let transformTodo = (fields) => {
fields._pubType = 'todos';
return fields;
};
Meteor.publish('todos', function() {
let subHandle = Todos
.find()
.observeChanges({
added: (id, fields) => {
fields = transformTodo(fields);
this.added('todos', id, fields);
},
changed: (id, fields) => {
fields = transformTodo(fields);
this.changed('todos', id, fields);
},
removed: (id) => {
this.removed('todos', id);
}
});
this.ready();
this.onStop(() => {
subHandle.stop();
});
});
Currently, you can't apply transforms on the server to published collections. See this question for more details. That leaves you with either transforming the data on the client, or using a meteor method. In a method, you can have the server do whatever you want to the data.
In one of my projects, we perform our most expensive query (it joins several collections, denormalizes the documents, and trims unnecessary fields) via a method call. It isn't reactive, but it greatly simplifies our code because all of the transformation happens on the server.
To extend #Christian Fritz answer, with Reactive Solution using peerlibrary:reactive-publish
Meteor.publish("todos", function() {
const self = this;
return this.autorun(function(computation) {
// Loop over each document in collection
todo.find().forEach(function(entry) {
// Add function to transform / modify each document here
self.added("todos", entry._id, entry);
});
});
});

Resources