How to get dbId of only visible objects in Viewer? - javascript

I can get the dbId of all items in the Viewer via
const tree = viewerApp.myCurrentViewer.model.getData().instanceTree;
const dbIndices = Object.values(tree.nodeAccess.dbIdToIndex).slice(1);
But for models imported from Revit, their number is much larger than the actually visible objects in the Viewer (for example, for a project consisting of only three walls, this number is approximately 3,500). How do I get a dbId of only visible objects?

By default all nodes (assets to render for Viewer) are visible when a model is loaded. Each node can be uniquely identified by an unique dbid in addition to its externalId that corresponds to the UniqueID of a Revit component.
So the extra dbids that you observed are actually parent nodes. To isolate them, see here to traverse all the leaf nodes (that is nodes representing a single visible components):
function getAllLeafComponents(viewer, callback) {
var cbCount = 0; // count pending callbacks
var components = []; // store the results
var tree; // the instance tree
function getLeafComponentsRec(parent) {
cbCount++;
if (tree.getChildCount(parent) != 0) {
tree.enumNodeChildren(parent, function (children) {
getLeafComponentsRec(children);
}, false);
} else {
components.push(parent);
}
if (--cbCount == 0) callback(components);
}
viewer.getObjectTree(function (objectTree) {
tree = objectTree;
var allLeafComponents = getLeafComponentsRec(tree.getRootId());
});
}

Related

How to create an Object that includes three node elements and sets the same value?

For the following code, I do not want to repeat it three times and it is better than ignore the keys as icona,iconb,iconc.
In another post How to get each one of the objects from an array which includes three objects?, I involve three Objects in an array, and here I want just create an object that includes the same three node elements.
var iconArray =
{
icona: document.createElement('div'),
iconb: document.createElement('div'),
iconc: document.createElement('div')
}
By the way, in order to match the index of two Objects to set value, as the following code from that post, so if the 'iconsData' Object includes three Arrays, the 'iconArray' Object includes three node elements, or maybe both include four or five elements.
Object.keys(iconsData).forEach(function(value, indexa) {
iconsData[value].forEach(function (obj,indexb) {
Object.values(iconArray).forEach(function(keyc, indexc) {
var img = document.createElement('img');
if(indexc === indexa){
img.addEventListener('mouseup', function () {
keyword.beforePopup(obj.popup);
});
img.setAttribute('style', '' +
'cursor:pointer!important;' +
'display:inline-block!important;' +
'');
keyc.appendChild(img);
}
});
});
});
var element = document.createElement('div')
var iconArray =
{
icona: element,
iconb: element,
iconc: element
}

How to document merge a parent record and all of its child records

I'm creating a document merge (mail merge) from Google App Maker to a Google Document template. I can do so successfully when merging one single record, but how do you merge several records into the one document?
I have an purchase_orders parent record which has several purchase_order_line_items child records but I can't seem to get all of these records into a single document merge.
A similar question (Document Merge with Google App Maker) was asked by by Johan W with a comprehensive answer by Markus Malessa and Pavel Shkleinik (thank you!). However, it only caters for cases when you are merging one single record.
I have tried to build on their answer by using a second for loop to get the data of all associated child records. The script runs but only seems to merge the first child record; not all of them.
Here is an example of the server-side code I've tried to use:
function Export(key, key2) {
// Get the parent record by its key, which was passed by the first parameter above
var record = app.models.Purchase_Orders.getRecord(key);
// Get the first child record by its key, which was passed by the second parameter above
var childRecord = app.models.Purchase_Order_Line_Items.getRecord(key2);
// Get the Google Document which will be used as a template for this merge
var templateId = '1Xbt8camqHJYrhBnx0a6G2-RvTvybqU0PclHifcdiLLA';
//Set the filename of the new merge document to be created
var filename = 'Document for Customer ' + new Date();
//Make a copy of the template to use as the merge document
var copyFile = DriveApp.getFileById(templateId).makeCopy(filename);
//Get the Google Docs ID of the newly created merge document
var copyDoc = DocumentApp.openById(copyFile.getId());
var copyBody = copyDoc.getBody();
// Replace the field names in the template with the field data from the parent record
var fields = app.metadata.models.Purchase_Orders.fields;
for (var i in fields) {
console.log(i);
var text = '<<' + fields[i].name + '>>';
var data = record[fields[i].name];
if (data !== null) {
copyBody.replaceText(text, data);
} else {
// do nothing
}
}
// Replace the field names in the template with the field data from the child records
childFields = app.metadata.models.Purchase_Order_Line_Items.fields;
for (i in childFields) {
console.log(i);
var childtext = '<<' + childFields[i].name + '>>';
var childdata = childRecord[childFields[i].name];
if (childdata !== null) {
copyBody.replaceText(childtext, childdata);
} else {
// do nothing
}
}
}
How can I improve my code so that all associated child records are merged into a single document?
How can I set up my Google Document template to cater for any number of child records?
Rather than passing in the child record key via a second parameter, I would suggest just passing in the parent key and then changing your function as follows:
function Export(key) {
// Get the parent record by its key, which was passed by the first parameter above
var record = app.models.Purchase_Orders.getRecord(key);
// Get the first child record by its key, which was passed by the second parameter above
var childRecords = record.Purchase_Order_Line_Items;
// Get the Google Document which will be used as a template for this merge
var templateId = '1Xbt8camqHJYrhBnx0a6G2-RvTvybqU0PclHifcdiLLA';
//Set the filename of the new merge document to be created
var filename = 'Document for Customer ' + new Date();
//Make a copy of the template to use as the merge document
var copyFile = DriveApp.getFileById(templateId).makeCopy(filename);
//Get the Google Docs ID of the newly created merge document
var copyDoc = DocumentApp.openById(copyFile.getId());
var copyBody = copyDoc.getBody();
// Replace the field names in the template with the field data from the parent record
var fields = app.metadata.models.Purchase_Orders.fields;
for (var i in fields) {
console.log(i);
var text = '<<' + fields[i].name + '>>';
var data = record[fields[i].name];
if (data !== null) {
copyBody.replaceText(text, data);
} else {
// do nothing
}
}
// Replace the field names in the template with the field data from the child records
var childFields = app.metadata.models.Purchase_Order_Line_Items.fields;
var table = [];
var tableheader = [];
for (i in childFields) {
console.log(i);
tableheader.push(childFields[i].displayName);
}
table.push(tableheader);
for (i in childRecords) {
var data = [];
for (var j in childFields) {
data.push(childRecords[i][childFields[j].name]);
}
table.push(data);
}
copyBody.appendTable(table);
The table building is based on a 2D array and the documentation is here https://developers.google.com/apps-script/reference/document/table. But you will also need to remove your prebuilt table in favor of just appending a table instead. This way you are not dependent on the quantity of child records being fixed like they currently are in your document template. Also, the variable for childRecords may or may not work, I have not tested this since I am unsure if prefetch works in conjunction with .getRecord(key). This may require some additional testing but hopefully this will provide enough guidance.
Thought I would add this as an alternative. Lets say you keep your table but remove all the rows with exception for the header row then you could still use DocumentApp service to add your rows to the table like so:
var tableheaderfieldnames = ['Quantity_for_PO', 'Inventory_Item.id', 'Unit_Price']; //set a fixed table header with the field names, uncertain if the table header for the related inventory item will work or not
var table = copyBody.getTables()[0];
for (i in childRecords) {
var row = table.appendRow();
for (var j in tableheaderfieldnames) {
row.appendTableCell(childRecords[i][tableheaderfieldnames[j]]);
}
}
Keep in mind that AM does not allow you to use FK references, so for your inventory item that appears to use a fk field you may need to tinker around with setting the proper name reference for when you are trying to fill in the item in your table.

Cytoscape.js not setting compound node properties when exported as json

I am currently trying to export my Cytoscape.js graph as json to later import it into the Cytoscape application. For some nodes I need to add a compound node after the layout process, so they are not included in the layout process and do not affect the node positions.
After the layout I add the parent nodes via cy.add() and the child nodes are added to the parent nodes via ele.move().
When I debug the nodes the properties autoHeight and autoWidth are set but when I read them it results in undefinded and the positions are not set according to their child nodes.
var json = cy.json();
var jsonNodes = json.elements.nodes;
var graphNodes = cy.nodes(':parent');
jsonNodes.forEach(jsonNode => {
graphNodes.forEach(graphNode => {
if (jsonNode.data['id'] == graphNode._private.data['id']){
if(graphNode._private['autoHeight'] != undefined) {
let height = graphNode._private['autoHeight'];
let width = graphNode._private['autoWidth'];
jsonNode.data['height'] = height;
jsonNode.data['width'] = width;
jsonNode.data['opacity'] = 70;
}
}
}
)
});
I want the json to include the parents width, height and position (according to the position of their child node).

Get all items in NotesXSPDocument

In my Notes Database, I perform an audit when the document is saved. Pretty easy in LotusScript. I grab the original document (oDoc) from the server, then in the document I modified (mDoc), I do a Forall loop that gets the names of each item; forall item in mDoc.items. Grab the same item from oDoc, execute a function with the new item as an argument that will run down a case statement that will see if its a field we care about. if so, I update a set of list values in the document with "When", "Who", "What field", and the "New Value".
I'm doing this in a server side script. In trying this, I discovered a couple of interesting things;
currentDocument is the NotesXSPDocument that contains everything that was just changed.
currentDocument.getDocument() contains the pre-change values. It also returns a NotesDocument which has the "items" field that I can run through.
Thing is, I need something similar in the NotesXSPDocument. Is there a way in an iterative loop to grab the names and values of all items from there?
Here's the broken code. (Currently it's walking through the NotesDocument items, but those are the old values. I'd rather walk down the XSP document items)
function FInvoice_beginAudit() {
var original_doc:NotesDocument = currentDocument.getDocument();
var oItem:NotesItem;
var oItems:java.util.Vector = original_doc.getItems();
var iterator = oItems.iterator();
while (iterator.hasNext()) {
var oItem:NotesItem = iterator.next();
item = currentDocument.getItemValue(oItem.getName());
if (oItem == undefined) {
var MasterItem = ScreenAudit(doc,item,True)
if (MasterItem) { return true }
} else {
if (item.getValueString() != oItem.getValueString()) {
var MasterItem = ScreenAudit(doc,Item,True);
if (MasterItem) { return true }
}
}
}
}
You can get both versions of a document after submit - the original and the one with changed/new values:
original: var original_doc:NotesDocument = currentDocument.getDocument();
changed: var changed_doc:NotesDocument = currentDocument.getDocument(true);
This way you can compare the items for changes.
But, there is a pitfall: after assigning "changed_doc" to currentDocument.getDocument(true) the "original_doc" has the changed values too because both variables point to the same document. That's why we have to copy all items from currentDocument.getDocument() to a new temporary document first and only after get the changed values with currentDocument.getDocument(true). As an alternative you could read the original document from server like you do in LotusScript.
This is a code for detecting changed items as a starting point:
var original_doc:NotesDocument = database.createDocument();
currentDocument.getDocument().copyAllItems(original_doc, true);
var changed_doc:NotesDocument = currentDocument.getDocument(true);
var oItems:java.util.Vector = original_doc.getItems();
var iterator = oItems.iterator();
while (iterator.hasNext()) {
var oItem:NotesItem = iterator.next();
var itemName = oItem.getName();
var cItem:NotesItem = changed_doc.getFirstItem(itemName);
if (cItem.getText() !== oItem.getText()) {
print("changed: " + itemName);
}
oItem.recycle();
cItem.recycle();
}
original_doc.remove(true);
original_doc.recycle();

comparing two JavaScript objects

So, let's say i have object A and object B. Object A has a number of children where object B has a number of children that are the same. How do I find out what are the differences the ones missing in object B and the ones added in object A and then put them into their own object or two-dimensional array.
For example, the first array being those that are added to second, being subracted:
var changes = [["google.com", "yahoo.com"],["facebook.com", "bing.com"]]
I am trying compare a snapshot of stored bookmarks and the current bookmarks list using crossrider.
I believe this is a follow on from the following questions, and so I will combine them all into a single code example that runs in the background scope (background.js):
realtime with non-event programming,
crossrider: store snapshot of bookmarks in local database and compare to current bookmarks list
So for the getChanges function, I prefer to convert the bookmark trees into hash lists and then compare the lists for changes. In the following example, I use createHash to create the hash lists using cloneNode to create a shallow clone of the node objects, and then in getChanges I compare the hash lists for additions, modifications, and deletions:
appAPI.ready(function() {
// Poll every 30 seconds
setInterval(function() {
appAPI.db.async.get('prevBookmarks', function(value) {
// Load or initialize the previous bookmarks list
var prevBookmarks = (value) ? value : {};
// Get current bookmarks
appAPI.bookmarks.getTree(function(nodes) {
// Save bookmark hash for comparison in next interval
appAPI.db.async.set('prevBookmarks', createHash(nodes[0]));
// Get hash list of curent bookmarks
var currBookmarks = createHash(nodes[0]);
// Get changes between the lists
var changes = getChanges(prevBookmarks, currBookmarks);
// Post changes to your API server
appAPI.request.post({
url: http://yourAPIserver.com,
postData: changes,
contentType: 'application/json'
});
});
});
}, 30 * 1000);
// Function to create a hash list from a bookmark tree
function createHash(node) {
var hash = {};
if (typeof node === 'object') hash[node._id] = cloneNode(node);
if (node.isFolder && typeof node.children !== 'undefined' && node.children.length > 0) {
node.children.forEach(function(child) {
var childHash = createHash(child);
for (var key in childHash) {
if (!hash[key]) hash[key] = cloneNode(childHash[key]);
}
});
}
return hash;
}
// Function to create shallow clones of bookmark nodes
function cloneNode(node) {
var clone = appAPI.JSON.parse(appAPI.JSON.stringify(node));
delete clone.children;
delete clone.dateAdded;
return clone;
}
// Get changes between current and previous bookmark hash lists
function getChanges(prev, curr) {
// Initialize return object
var changes = {added:{}, modified:{}, removed:{}};
// Search for added or modified nodes
for (var key in curr) {
if (!prev[key])
changes.added[key] = curr[key];
else if (appAPI.JSON.stringify(prev[key]) !== appAPI.JSON.stringify(curr[key]))
changes.modified[key] = curr[key];
}
// Search for removed nodes
for (var key in prev) {
if (!curr[key])
changes.removed[key] = prev[key];
}
return changes;
}
});
Disclaimer: I am a Crossrider employee
If the two objects to be compared are both one-dimensional arrays, then simply use the set arithmetic functions in Underscore.js, such as _.difference and _.intersection.
Or, use the same logic, which for intersect (unoptimized) is as simple as:
array1.filter(function(v){return array2.indexOf(v)!==-1);});
If you're looking for a generalized way to find the diff between two arbitrary objects of any depth and complexity, this is not a well-defined problem.

Categories