Is it better to reuse or recreate a reactive source in Meteor - node.js

I have a template called 'contacts'. Inside is an #each which renders the template 'contact'. The user can press the 'edit' button which sets a session variable with the mongo id of the edited row. The row then reactively re-renders into "edit" mode.
Template.contact.viewEditing = function() {
return Session.get("contactViewEditingId") === this._id;
}
The html uses the viewEditing helper a few times, for instance:
{{#if viewEditing}}
<div class="panel-heading">EDITING!</div>
{{/if}}
I need to bind some javascript in the .rendered(). I would like to check again if we are editing. I can think of 2 options:
Should I call Template.content.viewEditing() inside my template.rendered() ? Does this save on reactivity calculations?
Or should I just copy pasta the if statement. This option seems to violate DRY.
Option 1:
Template.contact.rendered = function() {
if( Template.contact.viewEditing.call(this.data) ) {
// Bind some fancy jQuery
bindEditInPlace(this.data);
}
}
Option 2:
Template.contact.rendered = function() {
if( Session.get("contactViewEditingId") === this._id ) {
// Bind some fancy jQuery
bindEditInPlace(this.data);
}
}
I think that putting {{viewEditing}} multiple times in your template doesn't "cost" anything extra. So logically I would think that using this helper elsewhere is better. Maybe I need more help understanding reactivity calculations. Thanks!

Helpers are run inside a Deps.Computation, which means that every time a reactive variable is referenced and modified in a helper, it will re-run.
Template.rendered is a callback that runs each time the template is re-rendered (which usually happens when a helper in the template is re-run reactively), but it is not itself a reactive computation.
So it doesn't matter using either the template helper or copy-pasting its code inside your rendered callback : both ways won't trigger a reactive computation invalidation because we are not inside one.
As far as DRY is concerned, you could refactor your code like this :
function isContactViewEditing(contactView){
return Session.equals("contactViewEditingId",contactView._id);
}
Template.contact.helpers({
viewEditing:isContactViewEditing
});
Template.contact.rendered=function(){
if(isContactViewEditing(this.data)){
//
}
};

I think saimeunt's answer is correct, especially if you have more complex logic in the function which you don't want to replicate.
Create a local function which you can re-use in both the helper and the .rendered callback.
If you had a case where you wanted to use a reactive source minus the reactivity you could make it non-reactive by wrapping it in a Deps.nonreactive function likes so:
Deps.nonreactive(function(){
//Reactive stuff here
});
Regarding reactivity concerns, pay attention to his change from using Session.get to Session.equals. Session.get will cause any reactive computation it is used in to re-calculate on every change of the session variable. So if you use this helper in multiple places with different ids, and you change the session variable, every single one will re-calculate and re-render the templates they are used in. Session.equals only invalidates a computation when the equality changes. So changing the session variable from one non-equal id to another non-equal id will not cause the computation/template to re-run when you use Session.equals.
For your specific example where the helper is only returning the result of a Session.equals you might consider creating a global handlebars helper that can do this for you with any session variable and any value. Like the following.
Handlebars.registerHelper('sessionEquals', function (key, value) {
return Session.equals(key, value);
});
Then in the template use it like so:
{{#if sessionEquals 'contactViewEditingId' _id}}
<div class="panel-heading">EDITING!</div>
{{/if}}
In the template when rendering an item that is editable add a unique class name to mark the item as editable. Then in your Template.rendered callback when binding the javascript use a selector which looks for that class and only binds to elements with that special class.

Related

How do I prevent a re-render of a large list?

I have a 60x30 grid for a game editor and as cells are updated, a new array is created to hold the state.
The problem is that when I update that grid array, this changes the property and it causes render() to recreate the grid. This seems almost obvious but then what do my options become?
If this is overly specific, imagine just a huge list of items and you have an immutable array in which one of the items properties must change.
render() {
return html`
${this.data?.cells.map((row) => {
return row.map((cell) => {
return html`<editor-cell .data="${cell}"></editor-cell>`;
});
})}
`;
}
Coincidentally, I had the same problem on Angular with a for loop only it had trackBy which used the index or item.id to prevent the recreation of a list of items. I just accepted the unicorns for that but here it is the same issue.
Question:
What am I missing about immutable states here? I totally understand why this is happening in that, its a new array and so lit element just renders what it deems a new array. I want that, but once the grid has been rendered, I don't understand the separation between rendering and data updates. I'm either missing a key lifecycle understanding, or my approach to state is just totally whack.
If you update the complete array this change the memory reference and then lit-html has to re-render the whole array because it doesn't know what item changes.
In the lit-html documentation you have a section about Repeating templates that explain that very well.
In your case you should use repeat directive, that performs efficient updates of lists based on user-supplied keys:
render() {
return html`
${repeat(this.data?.cells, row => row.id,
row => html`${repeat(row, cell => cell.id,
cell => html`<editor-cell .data="${cell}"></editor-cell>`
)}`
)}
`;
}
Notice the importance of the second argument, that it's the guaranteed unique key for each item.

JSON-RPC and handle to current document

Good day folks!
I have a call (CSJS) to a JSON-RPC service in my XPage to get the value of a viewScope variable. I use this call to cycle through my field names (dynamically) and validate them.
I'm having trouble figuring out how to get a handle to the datasource (document1 in my case) to which these fields are bound, from the RPC service.
Here's the call:
// Use the JSON-RPC Service to get the number of asset item rows
// from the viewScope variable
var deferred = myRPCService.getScopeVar();
deferred.addCallback(function(result){
alert(result); // <-- viewScope variable value
// get the dynamic field names for the asset items based on row #
var itemname = '';
for (var i = 1; i < result; i++) {
var itemname = 'replace'+(i < 10? '0':'')+ i
if (document1.getItemValueString(itemname) == ""){
// do this
} else{
// do that
}
}
});
I do get back the value of the viewScope variable from the RPC call but I can't get beyond that. Any pointers/examples would be appreciated.
Thanks,
Dan
PS: See my comment below ...
You don't say what calls the JSON-RPC event, so I will guess and say it is a button. You will have to have an additional serverside event set the viewScope variable to the value of document1.getItemValueString(itemname) which you can then retrieve in the RPC.
Your SSJS event code will look something like:
viewScope.result = document1.getItemValueString(itemname);
The additional event can be onmouseover (not recommended) or onkeypress. I assume you are using onclick clientside for this code, you can't use the onclick serverside because this code would need to run prior to your clientside. You will want the viewScope to contain fresh data, hence this suggestion. If the data doesn't need to be fresh, then set it on page load.
Another idea would be to call two RPC's. The first one can set the viewScope variable and the second can use it. The first one won't need a callback, and just calls a java or SSJS function that sets the viewScope. Personally I like this better than my first suggestion.

Docuemt postopen event not operating on profile document

I need to save serial number of the document in a profile document and here is a code of action Execute Script:
if (document1.isNewNote()){
var pdoc:NotesDocument=database.getProfileDocument("LastNumber","")
var lnm=pdoc.getItemValue("lastNumber")[0];
var inputText6:com.ibm.xsp.component.xp.XspInputText = getComponent("inputText6");
inputText6.setValue(lnm);
pdoc.replaceItemValue("lastNumber",lnm);
pdoc.save();
}
This code is not opening profile document at all. Any thing wrong in the code?
"LastNumber" is the name of the form used to create Profile Document ?
this profile document already exist ?
there are no reader fields in this profile document ?
you have an error on this line : var pdoc:NotesDocument=database.getProfileDocument("LastNumber","") ?
or you have debug it and see that pdoc is null ?
instead of pdoc.getItemValue("lastNumber")[0] you can use pdoc.getItemValueInteger("lastNumber") to get a typed result
I supposed that this field contains a number and you want to increment it
instead of using inputText field you can set value directly with document1.setValue("NumberField", lnm);
I second the caution Per is suggesting. Profile documents can be a beast. You should abstract access to the "next number" into a SSJS function call. Btw. in your code snippet you don't actually increment the last number. Also: if your input text control is bound, go after the data source, not the UI.
A crude way (I would use a managed application bean for better isolation) for a better function could be this:
if(document1.isNewNote() {
document1.setValue("DocumentNumber",applicationTools.getNextNumber());
}
Then in a SSJS library you would have:
var applicationTools = {
"getNextNumber" : function() {
synchronized(applicationScope){
var pdoc:NotesDocument=database.getProfileDocument("LastNumber","");
if (!applicationScope.lastNumber) {
applicationScope.lastNumber = pdoc.getItemValueInteger("lastNumber");
}
applicationScope.lastNumber++;
pdoc.replaceItemValue("lastNumber",applicationScope.lastNumber);
pdoc.save(); //Make sure pdoc is writeable by ALL!!!!
pdoc.recycle();
return applicationScope.lastNumber;
}
},
"someOtherUtility" : function(nameToLookup, departments) {
// more stuff here
}
}
Which, in some way has been asked before, but not for a profile field. Someone still could simply go after the applicationScope.lastNumber variable, which is one of the reasons why I rather use a bean. The other: you could do the saving asynchronously, so it would be faster.
Note: in any case the number generation only works when you have a non-replicating database. But abstracting the function opens the possibility to replace fetching the number from the profile with a call to a central number generator ... or any other mechanism ... without changing your form again.

MongoDB Database Semaphores and Node.js Process.NextTick()

This may be a vary bad idea, or a possible solution that we have to a database concurrency problem.
We have a method that is called to do an update of a mongo record. We are seeing some concurrency problems - process A reads the record, process B reads the record, process A makes mods and saves the record, process makes B mods and saves the record. Because B reads after A, before A writes, it doesn't know about the changes A made, and we lose the data from A.
I'm wondering if we could not use a database semaphore, basically a field on the collection, that is a boolean. If we read the record at the start of the method, and the field is true, it's being edited. At that point, re-call the method using process.nexttick(), with the same data. Otherwise, set the semaphore, and carry on.
There would still be a bit of time between the read and the save, but it should be/could be faster than what we are doing now.
Be something like this. Any thoughts, anyone done anything like this? Will it even work?
function remove_source(service_id,session, next)
{
var User = Mongoose.model("User");
/* get the user, based on the session user id */
User.findById(session.me,function(err,user_info)
{
if (user_info.semaphore === true)
{
process.nextTick(remove_source(service_id,session,next));
}
else
{
user_info.semaphore = true;
user_info.save(function(err,user_new)
{
if (err) next(err,user_new);
else continue_on(null,user_new);
});
}
function continue_on(user_new)
{
etc.......
}
Edit: New Code:
The function now looks as follows. I'm doing individual updates to the arrays. This of course means that I now have the possibility, if the transaction fails between the first and second transactions, of having data out of sync. I'm thinking that I could simply resave the user object that I retrieved on entry into the function, overwriting my changes. I don't know if Mongoose/Mongo will not do the save if I have not changed that object, will have to try and see. Any more thoughts?
var User = Mongoose.model("User");
/* get the user, based on the session user id */
User.findById(session.me,function(err,user_info)
{
if (err)
{
next(err,user_info,null);
return;
}
if (!user_info || user_info.length === 0)
{
next(_e("ACCOUNT_NOT_FOUND"),"user_id: " + session.me);
return;
}
var source_service_info = _.where(user_info.credentials, {"source_service_id": service_id});
var source_service = source_service_info.source_service;
User.findByIdAndUpdate(session.me,{$pull: {"credentials": {"source_service_id": service_id}}},{},function(err,user_credential_removed)
{
if (err)
{
next(err,user_info,null);
return;
}
User.findByIdAndUpdate(session.me,{$pull: {"criteria": {"source_service": source_service}}},{},function(err,user_criteria_removed)
{
if (err)
{
next(err,user_info,null);
return;
}
else
{
next(null,user_criteria_removed);
}
});
});
});
};
The problem with your approach is that it just shortens the time during which the data could be read by a second process, it doesn't eliminate the problem.
The solution to this would be to set your semaphore in the same action as the read. I haven't used Mongoose, but in MongoDB you can use findAndModify to only return a User record if the semaphore is false, and if it is false, in one atomic operation, set the semaphore to true.
If you don't want to use findAndModify, you could first do an update that sets the semaphore true (or to some specific ID value so you know that it is YOUR semaphore) only if the semaphore is not set. Then, if that process succeeds, you could do the find (perhaps passing your semaphore ID as a criterion in the find). However, findAndModify, if it is available in Mongoose, would do that in one step.
A variation of that is described here: http://docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations/ where you do a form of optimistic locking that checks that the old values are unchanged before changing them to the new values.
There is a variation on this that uses a separate table to simulate a two-phase commit: http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/
Edited: Upon interchange below, this seems to be a schema and updating issue. Question may become something like: I have some entries in an array, and the ordinal index to those entries relates to some other arrays as well. How do I perform deletes without having mismatches?
Three off the top possibilities occur, depending on frequency in the real world vs QA test scenarios.
Consider adding a deleted flag but keeping the records in the same order. If someone toggles, reuse the same record, but fix however you want.
Use an associative array (JS object) for each element (not a feature from relational world.) If you need an order, add an array that lists the keys in order. Both have syntax to update without touching anything other that what has changed, and will not overwrite changes to different fields.
Use an associative array where the keys are numbers. Actual deletion won't hurt retrieval.
stuff = {}
stuff[1] = {some:'details'}
stuff[2] = {some:'details2'}
Was
1) Are you making changes to the same field? Make that into an array, and push changes, and pop the latest to read the current value.
2) Are you changing different fields, but data is getting trounced? Then there is better syntax to use for the updating. you can update field by field.
$set: { 'fielda': 'valuea' }
won't lose edits on previous fields
3) change your schema
4) change the timing on the processes so they don't overlap. Or so they do so in smaller subsets, that you can manage to prevent from overlapping.
I'd like to know, just out of interest, what multiple processes are needed to make updates on the same record? I don't work with anything that looks like that.

dijit.Tree search and refresh

I can't seem to figure out how to search in a dijit.Tree, using a ItemFileWriteStore and a TreeStoreModel. Everything is declarative, I am using Dojo 1.7.1, here is what I have so far :
<input type="text" dojoType="dijit.form.TextBox" name="search_fruit" id="search_fruit" onclick="search_fruit();">
<!-- store -->
<div data-dojo-id="fruitsStore" data-dojo-type="dojo.data.ItemFileWriteStore" clearOnClose="true" urlPreventCache="true" data-dojo-props='url:"fruits_store.php"'></div>
<!-- model -->
<div data-dojo-id="fruitsModel" data-dojo-type="dijit.tree.TreeStoreModel" data-dojo-props="store:fruitsStore, query:{}"></div>
<!-- tree -->
<div id="fruitsTree" data-dojo-type="dijit.Tree"
data-dojo-props='"class":"container",
model:fruitsModel,
dndController:"dijit.tree.dndSource",
betweenThreshold:5,
persist:true'>
</div>
The json returned by fruits_store.php is like this :
{"identifier":"id",
"label":"name",
"items":[{"id":"OYAHQIBVbeORMfBNZXFGOHPdaRMNUdWEDRPASHSVDBSKALKIcBZQ","name":"Fruits","children":[{"id":"bSKSVDdRMRfEFNccfTZbWHSACWbLJZMTNHDVVcYGcTBDcIdKIfYQ","name":"Banana"},{"id":"JYDeLNIGPDBRMcfSTMeERZZEUUIOMNEYYcNCaCQbCMIWOMQdMEZA","name":"Citrus","children":[{"id":"KdDUfEDaKOQMFNJaYbSbAcAPFBBdLALFMIPTFaYSeCaDOFaEPbJQ","name":"Orange"},{"id":"SDWbXWbTWKNJDIfdAdJbbbRWcLZFJHdEWASYDCeFOZYdcZUXJEUQ","name":"Lemon"}]},{"id":"fUdQTEZaIeBIWCHMeBZbPdEWWIQBFbVDbNFfJXNILYeBLbWUFYeQ","name":"Common ","children":[{"id":"MBeIUKReBHbFWPDFACFGWPePcNANPVdQLBBXYaTPRXXcTYRTJLDQ","name":"Apple"}]}]}]}
Using a grid instead of a tree, my search_fruit() function would look like this :
function search_fruit() {
var grid = dijit.byId('grid_fruits');
grid.query.search_txt = dijit.byId('search_fruit').get('value');
grid.selection.clear();
grid.store.close();
grid._refresh();
}
How to achieve the same using the tree ? Thanks !
The refreshing of a dijit.Tree becomes a little more complicated, since there is a model involved (which in grid afaik is inbuilt, the grid component implements query functionality)
Performing search via store
But how to search, thats incredibly easy whilst using the ItemFileReadStore. Syntax is as such:
myTree.model.store.fetch({
query: {
name: 'Oranges'
},
onComplete: function(items) {
dojo.forEach(items, function(item) {
console.log(myTree.model.store.getValue(item, "ID"));
});
}
});
Displaying search results only
As shown above, the store will fetch, the full payload is put into its _allItemsArray and the store queryengine then filters out what its told by query argument to the fetch method. At any time, we could call fetch on store, even without sending an XHR for json contents - fetch with query argument can be considered as a simple filter.
It becomes slightly more interesting to let the Model know about this query.. If you do so, it will only create treeNodes to fill the tree, based on the returned results from store.fetch({query:model.query});
So, instead of sending store.fetch with a callback, lets _try to set model query and update the tree.
// seing as we are working with a multi-parent tree model (ForestTree), the query Must match a toplevel item or else nothing is shown
myTree.model.query = { name:'Fruits' };
// below method must be implemented to do so runtime
// and note, that the DnD might become invalid
myTree.update();
Refreshing tree with new xhr-request from store
You need to do exactly as you do with regards to the store. Close it but then rebuild the model. Model contains all the TreeNodes (beneath its root-node) and the Tree itself maps an itemarray which needs to be cleared to avoid memory leakage.
So, performing following steps will rebuild the tree - however this sample does not take in account, if you have DnD activated, the dndSource/dndContainer will still reference the old DOM and thereby 'keep-alive' the previous DOMNode hierachy (hidden ofc).
By telling the model that its rootNode is UNCHECKED, the children of it will be checked for changes. This in turn will produce the subhierachy once the tree has done its _load()
Close the store (So that the store will do a new fetch()).
this.model.store.clearOnClose = true;
this.model.store.close();
Completely delete every node from the dijit.Tree
delete this._itemNodesMap;
this._itemNodesMap = {};
this.rootNode.state = "UNCHECKED";
delete this.model.root.children;
this.model.root.children = null;
Destroy the widget
this.rootNode.destroyRecursive();
Recreate the model, (with the model again)
this.model.constructor(this.model)
Rebuild the tree
this.postMixInProperties();
this._load();
Creds; All together as such, scoped onto the dijit.Tree:
new dijit.Tree({
// arguments
...
// And additional functionality
update : function() {
this.model.store.clearOnClose = true;
this.model.store.close();
delete this._itemNodesMap;
this._itemNodesMap = {};
this.rootNode.state = "UNCHECKED";
delete this.model.root.children;
this.model.root.children = null;
this.rootNode.destroyRecursive();
this.model.constructor(this.model)
this.postMixInProperties();
this._load();
}
});

Resources