I'd like to build a small MeteorJS application, which contains a bar chart, which needs to be updated any time that the underlying MongoDB data changes.
Usually, most of the graphic libraries rely on JSON array as their "local" data sources. If I have to use such an array, how can I associate it with a Mongo collection, and make the array values update any time that collection changes?
While looking at https://forums.meteor.com, I have found a complete working example, which illustrates the solution to my problem. Here is the specific thread: https://forums.meteor.com/t/solved-how-to-draw-bar-chart-using-highcharts-and-mongodb-data-reactively/5156/17 , in which user jhuenges pints to the following meteorpad example: http://meteorpad.com/pad/iNfXQwMW3RwrokTe8/Chart
The key elements of this solution:
1) The 'data' element for the Chart is defined as a variable, who's values come from Mongo cursor:
series: [{
name: 'No. of Patients',
data: data
}]
Template.agecolumn.onRendered(function() {
Tracker.autorun(function () {
var data = Data.find({symptoms: {$in: ["Vomiting"]}}).fetch();
This combination insures, that, at any time, when the server-based MongoDB is updated, the values within 'data' are refreshed, and the Chart is re-rendered.
I have tested this by connecting my RoboMango utility to the server-side instance of MongoDB, and changing/removing values directly from the database.
As soon as any such change was applied to the server-side Mongo, the front-end Chart was refreshed automatically to reflect the changes, which is exactly what I was looking for.
As Christian Fritz suggested in his reply to my initial question, Tracker.autorun was used to facilitate the desired result.
Related
I will first say that I am a firmware developer trying to make a web interface for a personal project. My question might be basic as this is my first web interface with a database. I have searched quite a bit on how to achieve what I am trying to do without success, so I suspect I am not approaching this the right way, but here it is:
Basically I have a device pushing data to a firebase realtime database :
the push function generates an unique Id for me automatically which is nice...
Now In my flutter interface, I want to display the latest entry on a widget so I am using the onChildAdded functionnality of flutterfire:
ref.onChildAdded.listen((DatabaseEvent event) {
print(event.snapshot.key);
print(event.snapshot.value);
});
My first issue is that this function is triggered for all the child at first before waiting for new ones which is unnecessary and could be a problem when I begin to have a lot of data. Is there a way to simply get the latest sample and still get an event when one is added? (I still want to keep the old data to be able to make charts with them)
My second problem is the format of the received data:
{
-Mx2PCeptLf2REP1YFH0: {
"picture_url": "",
"time_stamp": "2022-02-28 21:56:58.502005",
"temperature": 27.71,
"relative_humidity": 42.77,
"eco2": 691,
"tvoc": 198,
"luminosity": 4193,
"vpd": 1.71
}
}
before using the put method, I didn't have the automatically generated ID so I was able to cast this as a Map<String, dynamic> and use the data like this in my widget:
Text(snapshot.data!["temperature"].toString())
but now the added nesting with the generated id is giving me a headache as I can't seem to figure out how to simply get the data.
So if anyone could help me to always get the single latest data when subscribing to a collection and how to access that data within my State of my StatefulWidget that would be much appreciated!
My first issue is that this function is triggered for all the child at first before waiting for new ones which is unnecessary and could be a problem when I begin to have a lot of data.
The Firebase Realtime Database synchronizes the state of the path/query that you listen to. So it doesn't just fire an event on onChildAdded for new data, but also for any existing data that you request. There is (intentionally) no way to change that behavior.
So your options here are limited:
You can remove the data that your application has processed, so that it doesn't get passed to the clients again. That means you essentially implement a message-passing mechanism on top of Firebase's data-synchronization semantics.
You can use a query to only request a certain subset of the child nodes at a path. For example, if you remember the latest key that you've received, you can get data from there on with ref.orderByKey().startAt("-Mx2LastKeyYouAlreadySaw").
Since this comes up regularly, I recommend also checking:
How can I setup Firebase to observe only new data?
How to get only updated data Firebase
How to fetch only latest specific data from Firebase?
and more from these search results
My second problem is the format of the received data:
The screenshot of your database shows two keys, each of which has a single string value. The contents of that value may be JSON, but the way they are stored is just a single string.
If you want to get a single property from the JSON, you either have to:
Decode the string back into JSON with jsonDecode.
Fix the problem at the source by storing the data as proper JSON, rather than as a string.
I have a situation in which I need to subscribe to the same collection twice. The two publish methods in my server-side code are as follows:
Meteor.publish("selected_full_mycollection", function (important_id_list) {
check(important_id_list, Match.Any); // should do better check
// this will return the full doc, including a very long array it contains
return MyCollection.find({
important_id: {$in: important_id_list}
});
});
Meteor.publish("all_brief_mycollection", function() {
// this will return all documents, but only the id and first item in the array
return MyCollection.find({}, {fields: {
important_id: 1,
very_long_array: {$slice: 1}
}});
});
My problem is that I am not seeing the full documents on the client end after I subscribe to them. I think this is because they are being over-written by the method that publishes only the brief versions.
I don't want to clog up my client memory with long arrays when I don't need them, but I do want them available when I do need them.
The brief version is subscribed to on startup. The full version is subscribed to when the user visits a template that drills down for more insight.
How can I properly manage this situation?
TL/DR - skip to the third paragraph.
I'd speculate that this is because the publish function thinks that the very_long_array field has already been sent to the client, so it doesn't send it again. You'd have to fiddle around a bit to confirm this, but sending different data on the same field is bound to cause some problems.
In terms of subscribing on two collections, you're not supposed to be able to do this as the unique mongo collection name needs to be provided to the client and server-side collections object. In practice, you might be able to do something really hacky by making one client subscription a fake remote subscription via DDP and having it populate a totally separate Javascript object. However, this cannot be the best option.
This situation would be resolved by publishing your summary on something other than the same field. Unfortunately, you can't use transforms when returning cursors from a publish function (which would be the easiest way), but you have two options:
Use the low-level publications API as detailed in this answer.
Use collection hooks to populate another field (like very_long_array_summary) with the first item in the array whenever very_long_array changes and publish just the summary field in the former publication.
A third option might be publishing the long version to a different collection that exists for this purpose on the client only. You might want to check the "Advanced Pub/Sub" Chapter of Discover Meteor (last sub chapter).
I'm trying to wrap my head around Meteor's way of dealing with reactivity and I want to make sure I've got some concepts right.
Take the follow reactivity example:
A user types something into a form field. The thing that he is typing is instantly displayed somewhere else on the page, as the user is typing, letter by letter. An instantaneous duplication.
From what I know about Angular, this is a very common example of reactivity. Angular binds the input directly to the output on the client side. There's nothing in between.
Correct me since I could be wrong, but Meteor can do this, but the input would first need to be captured and stored into a Mongo + MiniMongo DB (perhaps only as a collection in local storage), there would need to be a subscribe step, and those values would then be read and displayed on the page.
Is there a way to directly bind an event on the front end to another thing on the front end like Angular does?
Is this right? For Meteor to have the front-end-only reactivity of Angular it must first go through the intermediary of a collection, meaning extra code would be necessary to accomplish this compared to Angular?
The example in the Meteor Docs:
Deps.autorun(function () {
Meteor.subscribe("messages", Session.get("currentRoomId"));
});
So here, when the data of currentRoomId changes, the function is reactive to that data change and the function runs (in this case Meteor subscribes to messages).
Using Session variables is the only way I see of possibly binding two parts of a view together directly. Are there other ways?
Meteor's client-side reactivity system (Deps) is not coupled with its live MongoDB syncing. You can use it with any reactive data source which implements the right interface, including data sources which are entirely client-side. For example, you can use the built-in Session object. This is just a client-side key-value store with support for Meteor's reactivity, and you don't have to do any publish or subscribe to use it.
This standard way to do this sort of thing looks something like this:
<input id="field" value="{{fieldValue}}">
Template.form.fieldValue = function () {
return Session.get("fieldValue");
};
Template.form.events({
"input #field": function (evt) {
Session.set("fieldValue", $(evt.currentTarget).val());
}
});
Now the Session variable fieldValue is synced up to the form field. You can call Session.get("fieldValue") in some helper and that template will re-render when the user types in the form field. And if you call Session.set("fieldValue", "blah") then the form field will update itself.
As for your edit: You can make your own reactive data sources using Deps.Dependency, or you could meteor add reactive-dict although that's not documented. There may be packages on Atmosphere.
On my meteor project users can post events and they have to choose (via an autocomplete) in which city it will take place. I have a full list of french cities and it will never be updated.
I want to use a collection and publish-subscribes based on the input of the autocomplete because I don't want the client to download the full database (5MB). Is there a way, for performance, to tell meteor that this collection is "static"? Or does it make no difference?
Could anyone suggest a different approach?
When you "want to tell the server that a collection is static", I am aware of two potential optimizations:
Don't observe the database using a live query because the data will never change
Don't store the results of this query in the merge box because it doesn't need to be tracked and compared with other data (saving memory and CPU)
(1) is something you can do rather easily by constructing your own publish cursor. However, if any client is observing the same query, I believe Meteor will (at least in the future) optimize for that so it's still just one live query for any number of clients. As for (2), I am not aware of any straightforward way to do this because it could potentially mess up the data merging over multiple publications and subscriptions.
To avoid using a live query, you can manually add data to the publish function instead of returning a cursor, which causes the .observe() function to be called to hook up data to the subscription. Here's a simple example:
Meteor.publish(function() {
var sub = this;
var args = {}; // what you're find()ing
Foo.find(args).forEach(function(document) {
sub.added("client_collection_name", document._id, document);
});
sub.ready();
});
This will cause the data to be added to client_collection_name on the client side, which could have the same name as the collection referenced by Foo, or something different. Be aware that you can do many other things with publications (also, see the link above.)
UPDATE: To resolve issues from (2), which can be potentially very problematic depending on the size of the collection, it's necessary to bypass Meteor altogether. See https://stackoverflow.com/a/21835534/586086 for one way to do it. Another way is to just return the collection fetch()ed as a method call, although this doesn't have the benefits of compression.
From Meteor doc :
"Any change to the collection that changes the documents in a cursor will trigger a recomputation. To disable this behavior, pass {reactive: false} as an option to find."
I think this simple option is the best answer
You don't need to publish your whole collection.
1.Show autocomplete options only after user has inputted first 3 letters - this will narrow your search significantly.
2.Provide no more than 5-10 cities as options - this will keep your recordset really small - thus no need to push 5mb of data to each user.
Your publication should look like this:
Meteor.publish('pub-name', function(userInput){
var firstLetters = new RegExp('^' + userInput);
return Cities.find({name:firstLetters},{limit:10,sort:{name:1}});
});
I want to take a list of item names from a collection as a simple array to use for things like autocompleting user input and checking for duplicates. I would like this list to be reactive so that changes in the data will be reflected in the array. I have tried the following based on the Meteor documentation:
setReactiveArray = (objName, Collection, field) ->
update = ->
context = new Meteor.deps.Context()
context.on_invalidate update
context.run ->
list = Collection.find({},{field: 1}).fetch()
myapp[objName] = _(list).pluck field
update()
Meteor.startup ->
if not app.items?
setReactiveArray('items', Items, 'name')
#set autocomplete using the array
Template.myForm.set_typeahead = ->
Meteor.defer ->
$('[name="item"]').typeahead {source: app.items}
This code seems to work, but it kills my app's load time (takes 5-10 seconds to load on dev/localhost vs. ~1 second without this code). Am I doing something wrong? Is there a better way to accomplish this?
You should be able to use Items.find({},{name: 1}).fetch(), which will return an array of items and is reactive, so it will re-run its enclosing function whenever the query results change, as long as it's called in a reactive context.
For the Template.myForm.set_typeahead helper, you might want to call that query inside the helper itself, store the result in a local variable, and then call Meteor.defer with a function that references that variable. Otherwise I'm not sure that the query will be inside a reactive context when it's called.
Edit: I have updated the code below both because it was fragile, and to put it in context so it's easier to test. I have also added a caution - in most cases, you will want to use #zorlak's or #englandpost's methods (see below).
First of all, kudos to #zorlak for digging up my old question that nobody answered. I have since solved this with a couple of insights gleaned from #David Wihl and will post my own solution. I will hold off on selecting the correct answer until others have a chance to weigh in.
#zorlak's answer solves the autocomplete issue for a single field, but as stated in the question, I was looking for an array that would update reactively, and the autocomplete was just one example of what it would be used for. The advantage of having this array is that it can be used anywhere (not just in template helpers) and that it can be used multiple times in the code without having to re-execute the query (and the _.pluck() that reduces the query to an array). In my case, this array ends up in multiple autocomplete fields as well as validation and other places. It's possible that the advantages I'm putting forth are not significant in most Meteor apps (please leave comments), but this seems like an advantage to me.
To make the array reactive, simply build it inside a Meteor.autorun() callback - it will re-execute any time the target collection changes (but only then, avoiding repetitive queries). This was the key insight I was looking for. Also, using the Template.rendered() callback is cleaner and less of a hack than the set_typeahead template helper I used in the question. The code below uses underscore.js's _.pluck() to extract the array from the collection and uses Twitter bootstrap's $.typeahead() to create the autocomplete.
Updated code: I have edited the code so you can try this with a stock meteor created test environment. Your html will need a line <input id="typeahead" /> in the 'hello' template. #Items has the # sign to make Items available as a global on the console (Meteor 0.6.0 added file-level variable scoping). That way you can enter new items in the console, such as Items.insert({name: "joe"}), but the # is not necessary for the code to work. The other necessary change for standalone use is that the typeahead function now sets the source to a function (->) so that it will query items when activated instead of being set at rendering, which allows it to take advantage of changes to items.
#Items = new Meteor.Collection("items")
items = {}
if Meteor.isClient
Meteor.startup ->
Meteor.autorun ->
items = _(Items.find().fetch()).pluck "name"
console.log items #first result will be empty - see caution below
Template.hello.rendered = ->
$('#typeahead').typeahead {source: -> _(Items.find().fetch()).pluck "name"}
Caution! The array we created is not itself a reactive data source. The reason that the typeahead source: needed to be set to a function -> that returned items is that when Meteor first starts, the code runs before Minimongo has gotten its data from the server, and items is set to an empty array. Minimongo then receives its data, and items is updated You can see this process if you run the above code with the console open: console.log items will log twice if you have any data stored.
Template.x.rendered() calls don't don't set a reactivity context and so won't retrigger due to changes in reactive elements (to check this, pause your code in the debugger and examine Deps.currentComputation -- if it's null, you are not in a reactive context and changes to reactive elements will be ignored). But you might be surprised to learn that your templates and helpers will also not react to items changing -- a template using #each to iterate over items will render empty and never rerender. You could make it act as a reactive source (the simplest way being to store the result with Session.set(), or you can do it yourself), but unless you are doing a very expensive calculation that should be run as seldom as possible, you are better off using #zorlak's or #englandpost's methods. It may seem expensive to have your app querying the database repetitively, but Minimongo is caching the data locally, avoiding the network, so it will be quite fast. Thus in most situations, it's better just to use
Template.hello.rendered = ->
$('#typeahead').typeahead {source: -> _(Items.find().fetch()).pluck "name"}
unless you find that your app is really bogging down.
here is my quick solution for bootstrap typeahead
On client side:
Template.items.rendered = ->
$("input#item").typeahead
source: (query, process) ->
subscription = Meteor.subscribe "autocompleteItems", query, ->
process _(Items.find().fetch()).pluck("name")
subscription.stop() # here may be a bit different logic,
# such as keeping all opened subsriptions until autocomplete
# will be successfully completed and so on
items: 5
On server side:
Meteor.publish "autocompleteItems", (query) ->
Items.find
name: new RegExp(query, "i"),
fields: { name: 1 },
limit: 5
I actually ended up approaching the autocompletion problem completely differently, using client-side code instead of querying servers. I think this is superior because Meteor's data model allows for fast multi-rule searching with custom rendered lists.
https://github.com/mizzao/meteor-autocomplete
Autocompleting users with #, where online users are shown in green:
In the same line, autocompleting something else with metadata and bootstrap icons:
Please fork, pull, and improve!