How to make part of the document not reactive in Meteor - javascript

Most of the Meteor revolves around collections and cursors and fetching new documents when they appear in collection and match the criteria. Yet I am working with bigger documents, that contain multiple fields and has a deep and not predictable structure. On the top level there is a clear schema, but some subdocuments are unpredictable json data.
But let's look at a simpler example:
Reports = new Mongo.collection('reports');
Meteor.publish('reports', function() {
return Reports.find({});
});
and then on a client side, I open a report, put it in on screen using rather complicated not-only-html rendering functionality and then there are free text comment field embedded within report. And when they are changed, I want to automatically save them
Meteor.call("autosaveReport",reportId,comment);
and then there is meteor method that writes in the comment
Meteor.methods({
"autosaveReport": function(reportId,comment) {
Reports.update({_id:reportId},{$set:{comment:comment}});
}
);
Problem is, that every time comment is autosaved, Meteor Tracker reruns all the subscribtions and finds related to this report. And as report is big and has complicated rendering, that reload is visible for the user and destroys the purpose of seamless autosaving.
So, question - is it possible to trigger reactivity only on parts of the mongo document? Currently I have solved it by manually comparing old and new document on rendering, and if there is no difference in core, then stopping the re-rendering. That feels odd and against meteor spirit.

In your helper or route that sets the data context for your template, use {reactive: false} in the find:
return Reports.find(query,{reactive: false});
That way the helper won't update when the underlying object changes.
That flag is all or nothing however, it doesn't let you be selective about what changes to observe and which to ignore.

Related

js / vue app doing CRUD, how can I track what needs updating?

I'm working on a vue app that uses vuex and gets objects from an api. The tables have paging and fetch batches of objects from the api, sometimes including related entities as nested objects. The UI allows some editing via inputs in a table, and adds via modals.
When the user wants to save all changes, I have a problem: how do I know what to patch via the api?
Idea 1: capture every change on every input and mark the object being edited as dirty
Idea 2: make a deep copy of the data after the fetch, and do a deep comparison to find out what's dirty
Idea 3: this is my question: please tell me that idea 3 exists and it's better than 1 or 2!
If the answer isn't idea 3, I'm really hoping it's not idea 1. There are so many inputs to attach change handlers to, and if the user edits something, then re-edits back to its original value, I'll have marked something dirty that really isn't.
The deep copy / deep compare at least isolates the problem to two places in code, but my sense is that there must be a better way. If this is the answer (also hoping not), do I build the deep copy / deep compare myself, or is there a package for it?
It looks like you have the final state on the UI and want to persist it on the server. Instead of sending over the delta - I would just send over the full final state and overwrite whatever there was on server side
So if you have user settings - instead of sending what settings were toggled - just send over the "this is what the new set of settings is"
Heavy stuff needs to be done on the server rather than the client most of the time. So I'll follow the answer given by Asad. You're not supposed to make huge objects diffs, it's 2022 so we need to think about performance.
Of course, it also depends of your app, what this is all about. Maybe your API guy is opposed to it for a specific reason (not only related to performance). Setup a meeting with your team/PO and check what is feasible.
You can always make something on your side too, looping on all inputs should be feasible without manually doing that yourself.
TLDR: this needs to be a discussion in your company with your very specific constrains/limitations. All "reasonable solutions" are already listed and you will probably not be able to go further because those kind of "opinion based" questions are not allowed anyway on SO.

Using mutationobservers to detect changes in the results of a fetch request

I'm working on a narrow cast that displays an amount of tickets (an integer with the total added up to eachother) from a 3rd party API. I want to display a notification when this amount increases. I've read about mutationobservers, and that they are good for doing similar tasks like when something gets added or deleted.
The app has a Vue frontend, and a Laravel backend which does the requesting/authenticating. The index blade loads in a Vue component which contains the other components (and distributes the data from the API to child components).
I'm not quite sure wether mutationobservers are good for this specific job, though. Googling really didn't give me great alternatives.
In conclusion, I want to know if mutationobservers are the right tools for this task and what property would work. Better suited alternatives are also welcome.
Using vue, you can use a watcher function to watch for changes in a particular variable (amount). Mutation Observers only watches for dom updates, it won't give you what you want

New record appears at bottom of table?

I have a meteor application that is displaying records in a table using the code below:
Template.records.helpers({
trackingData: function() {
return Tracking.find({},{$sort: {fullDate: -1}})
}
})
And
<table>
...
{{#each trackingData}}
<tr class="record" id="{{_id._str}}">
...
{{/each}}
...
</table>
And
Meteor.publish('tracking', function(filter, offset) {
var records = Tracking.find(filter,{
sort: {fullDate: -1},
limit:10,
skip: offset*10
});
return records
});
For some reason, when my new record is added it always shows up at the bottom of the table. Based on the sorting I have in place the new record should show on top. What's odd is that when I refresh the page the record stays on the bottom but when I stop my app and restart it - the record shows at the top like it should. What might I be missing that would cause this sort of odd sorting behavior?
You might want to use Tracker.flush() or similar:
Normally, when you make changes (like writing to the database), their impact (like updating the DOM) is delayed until the system is idle. This keeps things predictable — you can know that the DOM won’t go changing out from under your code as it runs. It’s also one of the things that makes Meteor fast.
Tracker.flush forces all of the pending reactive updates to complete. For example, if an event handler changes a Session variable that will cause part of the user interface to rerender, the handler can call flush to perform the rerender immediately and then access the resulting DOM
Basically, the Collection.find operation returns a cursor that is generally only computed once.
The topic was covered by this blog as well:
When you publish documents to the client, they are merged with other
documents from the same collection and rearranged into an in-memory
data store called minimongo. The key word being rearranged.
Many new meteor developers have a mental model of published data as
existing in an ordered list. This leads to questions like: "I
published my data in sorted order, so why doesn't it appear that way
on the client?" That's expected. There's one simple rule to follow:
If you need your documents to be ordered on the client, sort them on
the client. Sorting in a publish function isn't usually necessary
unless the result of the sort changes which documents are sent (e.g.
you are using a limit).
You may, however, want to retain the server-side sort in cases where
the data transmission time is significant. Imagine publishing several
hundred blog posts but initially showing only the most recent ten. In
this case, having the most recent documents arrive on the client first
would help minimize the number of template renderings.

Vue js prototype function to rerender template

I'm trying to create a very basic key-based translation system since our system doesn't have to be as expansive as something like vuei18n. I'm simply loading a json with a key and each key has four translations. About a hundred items in total. Now; I'm using a seperate Translator window component that I link to Vue like this;
Vue.prototype.translate = function(key){
window.addEventListener('switchedLanguage', function(event) {
console.log(event.detail);
return window.translator.getTranslation(key);
});
return window.translator.getTranslation(key);
}
and in order to render them in the templates I do the following:
{{ translate('key') }}
I understand that connecting scripts like a translator directly to the window isn't the best of practices, but is what's working in the application right now.
The thinking behind it is that when the language is changed within the translator, it will try to get back the key. While this technically works, in the application I am not seeing the keys get re-rendered to the different language. I've been deep down the rabbit hole now and don't seem to get a clear answer why except for the fact that it's not bound to the data model. But for some of the components, they can have up to fifteen keys or more depending on input. It's not feasible to store all the keys in the data model of each component since that will, in my view, unnecessarily clutter the data model.
So, what I've tried so far is the following:
Use a filter with the key as an input (this results in Vue freaking the hell out because it can't resolve the filter since it isn't able to find the translator through Window.translator)
Reload the entire window (while working, very ugly solution since it takes the user back to the main screen)

Is there a standard way of change tracking with a Knockout bound page?

I have a rather complex web page with various tabs, forms, radio buttons, drop downs, etc. It's all bound using Knockout.js to a very complex JavaScript object that was loaded via an AJAX call. Of course the user can muck with stuff to their heart's content, at which point they hit a Save button to persist all their changes back to the server.
I'm in the process of coming up with a good design to track exactly what was changed on the page so I can implement saving. So, I've come up with a few possible implementations.
Option 1) Just send everything back and let the server sort it out: With this method, I'd let Knockout just update the data source. The Save button would call .toJS() and send that data back to the server. Pros: It's super easy, and takes very little work on the client. Cons: The server doesn't really know what changed and has to either load the data from the database to compare, or just save all the fields again. These fields come from multiple tables and have complex relations. It also treats the entire document as a single atomic unit. If someone else changed Field A and you changed field B, one user is going to lose their change.
Option 2) Use JavaScript to compare the original data and the current data: Using this technique, when the user clicks on the Save button, I would systematically compare the original data and current data and generate a graph of changes. Pros: This would ideally result in a compact graph of exactly what the user changed, and could even no-op if nothing was changed. Cons: The data I'm binding to is complex. It consists of strings, arrays, objects, arrays of objects, arrays of objects with other objects, etc. Looking for changes would be a rather complex nested loop.
Option 3) Track changes as they are being made in the UI: I would have to observe changes as they happen, and keep a delta as UI elements were changed. The Save button would simply send that change graph to the server if it had any pending changes. Pros: No need to compare two huge JavaScript objects looking for changes, but still has all the benefits of option 2. Cons: Knockout doesn't appear to have a standard way to listen to all changes using a single event handler. I believe I would have to resort to binding to all the UI elements, or creating custom bindingHandlers in Knockout to implement this real-time change tracking.
My Question:
My question is mostly for Knockout.js experts. Is there a standard approach, or recommended guidelines to solving this obviously common scenario? Is sending back all the data, even stuff that hasn't changed, a common design? Or are people implementing custom change trackers? Does Knockout provide any sort of framework that eases this requirement?
Update: Found this thing, not sure if it could be useful or if anyone has any feedback on it.
If it's a question of enabling/disabling the Save button, allowing the user to navigate "from" that page/state, then you can check with the https://github.com/CodeSeven/kolite
check the knockout.dirtyFlag.js
Hope this helps.
Edit: remember that you should "never" trust the data coming from the "UI". The real comparison and validation, ultimately goes in your "controlled" environment within the server.
What I would probably do is take option 2 - the comparison itself can be as simple as stringifying the JS object and comparing it with a cached version of itself.
A few other options are discussed here.
P.S. Maybe ko.mapping can help you manage this monster of a JS object?
I wrote a change tracker extension for knockout that Pete Smith greatly expanded on...
Take a look here:
https://roysvork.wordpress.com/2014/01/12/tracking-changes-to-complex-viewmodels-with-knockout-js/
It works on the principle of extending the observable to track initial state vs. changes the user has made on the client. I think this works really great and can give users real-time feedback to know what they've modified. In practice, we actually implement a save panel that shows all pending changes and even lets them undo individual changes, all by using the change tracker's reusable capability.
ko.extenders.trackChange = function (target, track) {
if (track) {
target.isDirty = ko.observable(false);
target.originalValue = target();
target.subscribe(function (newValue) {
// use != not !== so numbers will equate naturally
target.isDirty(newValue != target.originalValue);
});
}
return target;
};

Categories

Resources