Getting total View entries including categories - performance issue - xpages

I'm using a custom footer on my View Control; there I have for example:
Displaying 1 - 25 of 34200
My underlying View is Categorized so the total of entries should include categories as well.
So far the only way I'm able to find the total of entries including categories is using NotesViewNavigator; however, performance is not acceptable since it's taking around 27 seconds to compute this piece of code.
I'm sure the issue is with line var nav:NotesViewNavigator = view1.createViewNav(); because I added some debugger info:
start = new Date().getTime();
var viewPanel1:com.ibm.xsp.component.xp.XspViewPanel = getComponent("dataView1");
var nav:NotesViewNavigator = view1.createViewNav();
if (viewScope.VendorSrch == "" || viewScope.VendorSrch == null){
var total = nav.getCount();
}else{
var total = viewPanel1.getRowCount();// View can be filtered by user as well (using categoryFilter property)
}
var from =(viewPanel1.getFirst() < total? (viewPanel1.getFirst() + 1 ) : total);
var tmpTo = viewPanel1.getFirst() + viewPanel1.getRows();
var to = (tmpTo < total? tmpTo : total);
var elapsed = new Date().getTime() - start;
print(elapsed + " ms");
"</b>Displaying <b>"+ from +"</b> - <b>"+ to + "</b> of " + "<b>"+total+"</b>"
Does anyone know how can I improve this piece of code?
Please note documents in this View have Readers fields as well which may be impacting the
performance of this operation.

You are trapped in performance hell. Read access protection is only and only stored inside the document. So when you ask your view navigator to get the count its only option is to open all involved documents - hence the poor performance. Read protection and performance are natural enemies (just imagine: you have an office where every door is locked and to move around you have to check all your keys every time if you have one with the matching lock number).
The way out of reader field introduced performance hell is to read only the entries you actually need (as outlined). It could be a little tricky if a user has access to documents based on name, group-membership and role (that would make one read per access), but it is still very much feasible. In this case you would use a repeat control and a object data source or managed bean, so the multiple passes happen in the background.
Bonus trick: if you add a column with the formula 1 (just the number) and add it up, while categorizing it, then you can just jump from naventry to next sibling (that would be the next category) and add the numbers --> much less reads involved and NO documents opened.
To stress again: nav.count needs to open all documents and is a BAD idea, anything that requires read access to be checked is a bad idea, so using one (or more) viewNav based on access rights that actually only read documents the user can read is the way to go.
Let me know if you need more hints

I tried several approaches and using this loop reduced the time from 27 seconds average to 2.7 seconds:
start = new Date().getTime();
var viewPanel1:com.ibm.xsp.component.xp.XspViewPanel = getComponent("dataView1");
var nav:NotesViewNavigator = view1.createViewNav();
nav.setEntryOptions(NotesViewNavigator.VN_ENTRYOPT_NOCOLUMNVALUES);
// disable autoupdate
view1.setAutoUpdate(false);
if (viewScope.VendorSrch == "" || viewScope.VendorSrch == null){
nav.setBufferMaxEntries(400);
//var total = nav.getCount(); // This works but slow
// peform lookup
var vwentry:NotesViewEntry = nav.getFirst();
var vwentrytmp:NotesViewEntry = null;
count = 0;
while (vwentry != null){
count = count + 1;
// Get entry and go recycle
vwentrytmp = nav.getNext(vwentry);
vwentry.recycle();
vwentry = vwentrytmp;
}
total = count;
}else{
var total = viewPanel1.getRowCount();
}
var from =(viewPanel1.getFirst() < total? (viewPanel1.getFirst() + 1 ) : total);
var tmpTo = viewPanel1.getFirst() + viewPanel1.getRows();
var to = (tmpTo < total? tmpTo : total);
var elapsed = new Date().getTime() - start;
print(elapsed + " ms");
"</b>Displaying <b>"+ from +"</b> - <b>"+ to + "</b> of " + "<b>" + total + "</b>"

Related

Is there a google-script method like Application.Intersect(Target, Range) in Excel?

Now I understand that the question is deeper, and is connected with the tracking of events.
In Excel, I use this code:
If Not Intersect(Target, Sh.Range("$A$1:$A$300")) Is Nothing sub_do_something()
Here, Target - the address of the selected cell, Intersect determines whether the cell belongs to the specified range.
I use it in the system for filling and calculating the costing of the project.
The user clicks a row in a specific section of the calculation template. The script determines the address of the selected cell and switches the user to a specific sheet of the directory. Next, the user clicks on the desired line of the directory, the script copies a certain range of cells in the line and returns the user back to the calculation. When this happens, the copied data is inserted into a range of cells, starting with the selected one.
Thus, the creating a calculation, in which there can be more than 100 positions, is greatly simplified.
In Excel, everything works fine, but soon I plan to transfer this project to a cloud-based service, and Google Sheets is the best option.
Alas, only some events can be tracked in GAS, for example, using onOpen or onEdit triggers.
Excel has much more tracked events.
After a search on the StackOverflow, I found several similar issues related to tracing events, for example, How to find where user's cursor is in Document-bound script, Can we implement some code that fires upon selecting something in google document?, Google app script monitor spreadsheet selected ranges.
From the answers to these questions, it is clear that in GAS there is no such simple solution as Intersect(Target, Range) in Excel.
The last example uses the side menu, running a script from it that queries the sheet 5 times per second, and displays the address of the active cell in the "data" field.
Unfortunately, this code does not work for me. In the debugger, the getActiveRange() function works fine, but this code does not work:
$(document).ready(() => {
setInterval(()=>{
google.script.run.withSuccessHandler(log).getActiveRange();
},200)
})
log(e) => {
$('#data').val(e)
}
Question.
If someone did something similar, please share your experience.
Or tell me why this example does not work. If he can be reanimated, I will adapt him to solve my task.
I worked on a similar project and here's the solution:
function onSelectionChange(e)
{
var ss = e.source;
var Sh = ss.getActiveSheet();
var range = Sh.getRange("A1:A300");
var target = e.source.getActiveRange();
//check for intersection
if(RangeIntersects(target, range))
{
Logger.log("Changed Row: " + target.getRow() + "\nValue: " + target.getValue());
}
}
//returns true if target intersects with the predefined range
function RangeIntersects(target, range)
{
return (target.getLastRow() >= range.getRow()) && (range.getLastRow() >= target.getRow()) && (target.getLastColumn() >= range.getColumn()) && (range.getLastColumn() >= target.getColumn());
}
Here's an idea. I can't quite get it to work though.
Maybe someone else can give a better answer.
Also, having functions running 24/7 is not possible with GAS, I think, as there are limits to the total run-time. You may wish to add a code-guard that exits the script if the last update time is longer than 10 minutes ago or something.
function checkSelection() {
var spreadsheet = SpreadsheetApp.getActive();
var targetRange = spreadsheet.getRange('activate');
// Change your named ranged name here
var tCol = targetRange.getColumn();
var tLastCol = targetRange.getLastColumn();
var tRow = targetRange.getRow();
var tLastRow = targetRange.getLastRow();
var num = 0;
for (num; num < 115; ++num) {
// Repeats the code below 100 times
var range = spreadsheet.getActiveRange();
var row = range.getRow();
var col = range.getColumn();
if (col >= tCol && col <= tLastCol && row >= tRow && row <= tLastRow) {
range.setBackground('#000000');
// Change the code in this block to your code.
}
SpreadsheetApp.flush();
Utilities.sleep(500);
// Waits half a second before repeating
}
}
115 repetitions * 500ms wait seems to run for almost a minute, then the trigger will fire the whole function again.
Intersection of two Ranges
You can use this to calculate intersection of two ranges. It requires an object in the form of: {rg1:'A1Notation String',rg2:'A1Notation String'}
function calculateIntersection1(rgObj) {
var iObj={};
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var rg1=sh.getRange(rgObj.rg1);
var rg2=sh.getRange(rgObj.rg2);
var iObj={rg1colst:rg1.getColumn(),rg1colen:rg1.getColumn()+rg1.getWidth()-1,rg1rowst:rg1.getRow(),rg1rowen:rg1.getRow()+rg1.getHeight()-1,rg2colst:rg2.getColumn(),rg2colen:rg2.getColumn()+rg2.getWidth()-1,rg2rowst:rg2.getRow(),rg2rowen:rg2.getRow()+rg2.getHeight()-1};
if(iObj.rg1colst>iObj.rg2colen || iObj.rg1colen<iObj.rg2colst || iObj.rg1rowst>iObj.rg2rowen || iObj.rg1rowen<iObj.rg2rowst || iObj.rg2colst>iObj.rg1colen || iObj.rg2colen<iObj.rg1colst || iObj.rg2rowst>iObj.rg1rowen || iObj.rg2rowen<iObj.rg1rowst) {
return '<h1>No intersecting cells</h1>';
}else{
var vA1=rg1.getValues();
var v1=[];
var vA2=rg2.getValues();
var v2=[];
for(var i=0;i<vA1.length;i++){
for(var j=0;j<vA1[i].length;j++){
var s=Utilities.formatString('(%s,%s)', iObj.rg1rowst+i,iObj.rg1colst+j);
v1.push(s);
}
}
for(var i=0;i<vA2.length;i++){
for(var j=0;j<vA2[i].length;j++){
var s=Utilities.formatString('(%s,%s)', iObj.rg2rowst+i,iObj.rg2colst+j);
v2.push(s);
}
}
var oA=[];
for(var i=0;i<v1.length;i++){
var idx=v2.indexOf(v1[i]);
if(idx>-1){
oA.push(v2[idx]);
}
}
return Utilities.formatString('Intersecting Cells: %s', oA.join(', '));
}
}
It either returns the string "No Intersecting Cells" or a string identifying the intersecting cells in (row, column) format.

Conditional Formatting for items created in the last 5 minutes in SharePoint 2010

All,
I've been looking all day and have tried numerous solutions, but just can't get it to work. Our team projects a list that is constantly updated and we want to highlight only newly created items for 5 minutes. After 5 minutes, the row would return to normal. (FYI- the list is projected on a display and updated using AJAX asynchronous update every 15 seconds)
Basically, I want to set conditional formatting on list items created in the last 5 minutes. If the item was created in the last 5 minutes, the row will be highlighted. After the 5 minutes are up, the row would return to normal.
I tried SharePoint Designer conditional formatting by creating a calculated column in Date/Time format called "Created + 5" and tried to set an expression where the formatting is applied (row is highlighted) when "Created + 5" is greater than or equal to current date. So after 5 minutes, the row will no longer be highlighted (because the current date/time will exceed the "Created + 5" value)
Here is the expression from the SPD Advanced Condition Builder:
ddwrt:DateTimeTick(ddwrt:GenDisplayName(string($thisNode/#Created_x0020__x002b__x0020_5_x))) >=
ddwrt:DateTimeTick(ddwrt:GenDisplayName(string($Today)))
I think the problem is that the [Current Date] option ($Today in the expression builder) only accounts for date and not time. It looks like it just ends up highlighting everything that was created today, which is not very useful.
Any thoughts or help!? I have never messed with the advanced conditions because usually the basic stuff works fine for me! If anyone has any other ideas too like JavaScript or anything else that would work, I am open to that too as long as it will continuously update!
Thanks all!!!!
[Today] actually doesn't work properly in 2010, there are some workarounds though, e.g. https://abstractspaces.wordpress.com/2008/05/19/use-today-and-me-in-calculated-column/.
You can also use the approach with calculated column: https://blog.splibrarian.com/2012/06/06/using-calculated-columns-to-add-color-coding-to-your-sharepoint-lists/
Since you want this to update automatically without requiring someone to manually refresh the page, JavaScript is your best bet. You can have a function run repeatedly on a specified interval, checking the current date against the values in a date column.
Something like the following code would work, though you may need to tweak the CSS selectors specified in the calls to document.querySelector and querySelectorAll to match your particular HTML.
<script>
formatCell();
function formatCell(){
var frequencyToCheck = 2 /* num seconds between updates */
var minutes = 5; /* num minutes back to highlight */
var targetColumn = "Display name of the column you want to check";
var formatting = "background-color:darkred;color:red;font-weight:bold;";
var comparisonDate = new Date();
comparisonDate.setHours(comparisonDate.getHours() - minutes);
var tables = document.querySelectorAll("table.ms-listviewtable"); /* should get all list view web parts on the page */
var t_i = 0;
while(t_i < tables.length){
var headings = tables[t_i].rows[0].cells;
var columnIndex = null;
var h_i = 0;
while(h_i < headings.length){
var heading = headings[h_i].querySelector("div:first-child");
if(heading != null){
var displayName = heading.DisplayName ? heading.DisplayName : (heading.innerText ? heading.innerText : heading.textContent);
displayName = displayName.replace(/^\s+|\s+$/g,''); /* removes leading and trailing whitespace */
if(displayName === targetColumn){
columnIndex = h_i;
break;
}
}
h_i += 1;
}
if(columnIndex != null){ /* we found a matching heading */
var rows = tables[t_i].rows;
for(var i = (rows.length > 0 ? 1 : 0); i < rows.length; i++){
var cells = rows[i].children;
if(cells.length <= columnIndex){continue;}
var valueToEval = cells[columnIndex].innerText ? cells[columnIndex].innerText : cells[columnIndex].textContent;
if(typeof valueToEval == "undefined"){valueToEval = "";}
valueToEval = new Date(valueToEval);
if(valueToEval > comparisonDate){
cells[columnIndex].setAttribute("style",formatting);
}else{
cells[columnIndex].setAttribute("style","");
}
}
}
t_i +=1;
}
setTimeout(formatCell,frequencyToCheck * 1000);
}
</script>
One potential pitfall is that while this approach will "age" records appropriately based on their displayed values (causing them to stop being highlighted as they grow stale), it won't automatically pick up new changes to the list; you'd need to refresh the page (or at least refresh the view in the web part) whenever you want to see updated information.

Adding values to multi-value field and displaying them

I have 3 multi-value fields and I have already inserted values in them. All of the fields are Text type, edible. What I'm trying to do is that I want to add functionality in xpages, so that I can add new values to those fields.
Here's what I got so far:
The code that triggers on the save button:
var statuss = document1.getItemValue("statuss");
var stat_vec:java.util.Vector = document1.getItemValue("statuss_update");
stat_vec.add(statuss);
document1.replaceItemValue("statuss_update", stat_vec);
var vards = session.getEffectiveUserName();
var vards_vec:java.util.Vector = document1.getItemValue("name_update");
vards_vec.add(vards);
document1.replaceItemValue("name_update", vards_vec);
var laiks = session.createDateTime("Today");
var laiks_vec:java.util.Vector = document1.getItemValue("time_update");
laiks_vec.add(laiks);
document1.replaceItemValue("time_update", laiks_vec);
document1.save();
The code that I have atteched to the computedField, where the values are displayed from the 3 multi value fields + it refreshes when I insert new values:
var x = document1.getItemValue("statuss_update");
var y = document1.getItemValue("name_update");
var z = document1.getItemValue("time_update");
var html = "<head><link rel=\"stylesheet\" type = \"text/css\" href=\"test.css\"></head><table id=\"tabula\">";
for (i = 0 ; i < x.size()-1; i++){
html= html + "<tr><td>" + x[i] + "</td><td>" + y[i] + "</td><td>" +z[i] + "</td></tr>";
}
html = html + "</table>";
I can insert the values and they get displayed in the HTML table but the problem is with saving the edits. Whenever I try to save the document (I have a save button that has save document event attached to it) I get the error:
Could not save the document 1B06 NotesException: Unknown or
unsupported object type in Vector
As far as I understand I'm trying to savesomething in a field, where the values type is not supported. Can anyone give me a hint what am I doing wrong or where to look for the problem? Been stuck with this for a pretty long time.
Is it this part?
var statuss = document1.getItemValue("statuss");
var stat_vec:java.util.Vector = document1.getItemValue("statuss_update");
stat_vec.add(statuss);
It looks like you're getting the statuss item's values (potentially a Vector??) and adding it to the Vector for statuss_update. If it's definitely just one value, getItemValueString() would work better.
I'm nnot sure if this is right, but you mention all fields are Text type, but it looks like you're passing a DateTime to the third one.
It might be worth analysing the contents of the Vectors before it's doing the save, just to make sure they contain what you expect.

Insert image into a specified location

I have a Google Apps script which replaces placeholders in a copy of a template document with some text by calling body.replaceText('TextA', 'TextB');.
Now I want to extend it to contain images. Does anybody have idea how to do this?
Thank you,
Andrey
EDIT: Just to make it clear what my script does. I have a Google form created in a spreadsheet. I've created a script which runs upon form submission, traverses a sheet corresponding to the form, find unprocessed rows, takes values from corresponding cells and put them into a copy of a Google document.
Some fields in the Google form are multi-line text fields, that's where '\r\r' comes from.
Here's a workaround I've come up with by now, not elegant, but it works so far:
// replace <IMG src="URL"> with the image fetched from URL
function processIMG_(Doc) {
var totalElements = Doc.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var element = Doc.getChild(j);
var type = element.getType();
if (type =='PARAGRAPH'){
var par_text = element.getText();
var start = par_text.search(new RegExp('<IMG'));
var end = par_text.search(new RegExp('>'));
if (start==-1)
continue;
// Retrieve an image from the web.
var url = getURL_(par_text.substring(start,end));
if(url==null)
continue;
// Before image
var substr = par_text.substring(0,start);
var new_par = Doc.insertParagraph(++j, substr);
// Insert image
var resp = UrlFetchApp.fetch(url);
new_par.appendInlineImage(resp.getBlob());
// After image
var substr = par_text.substring(end+1);
Doc.insertParagraph(++j, substr);
element.removeFromParent();
j -= 2; // one - for latter increment; another one - for increment in for-loop
totalElements = Doc.getNumChildren();
}
}
}
Here is a piece of code that does (roughly) what you want.
(there are probably other ways to do that and it surely needs some enhancements but the general idea is there)
I have chosen to use '###" in the doc to mark the place where the image will be inserted, the image must be in your google drive (or more accurately in 'some' google drive ).
The code below uses a document I shared and an image I shared too so you can try it.
here is the link to the doc, don't forget to remove the image and to put a ### somewhere before testing (if ever someone has run the code before you ;-)
function analyze() { // just a name, I used it to analyse docs
var Doc = DocumentApp.openById('1INkRIviwdjMC-PVT9io5LpiiLW8VwwIfgbq2E4xvKEo');
var image = DocsList.getFileById('0B3qSFd3iikE3cF8tSTI4bWxFMGM')
var totalElements = Doc.getNumChildren();
var el=[]
for( var j = 0; j < totalElements; ++j ) {
var element = Doc.getChild(j);
var type = element.getType();
Logger.log(j+" : "+type);// to see doc's content
if (type =='PARAGRAPH'){
el[j]=element.getText()
if(el[j]=='###'){element.removeFromParent();// remove the ###
Doc.insertImage(j, image);// 'image' is the image file as blob
}
}
}
}
EDIT : for this script to work the ### string MUST be alone in its paragraph, no other character before nor after... remember that each time one forces a new line with ENTER the Document creates a new paragraph.

Adobe InDesign hangs indefinitely when I try to duplicate pages using ExtendScript

I have a very simple ExtendScript script which creates a new document out of a subset of the current active document:
var sourceDocument = app.activeDocument;
var i, j;
for(i = 0; i < sourceDocument.layers.length; i++) {
sourceDocument.layers.item(i).locked = false;
}
for(i = 0; i < sourceDocument.spreads.length; i++) {
for(j = 0; j < sourceDocument.spreads.item(i).textFrames.length; j++) {
if(sourceDocument.spreads.item(i).textFrames.item(j).locked) {
sourceDocument.spreads.item(i).textFrames.item(j).locked = false;
}
}
}
var destDocument = app.documents.add();
var firstPageIndex = 0; // In the actual script, this is chosen by the user.
var lastPageIndex = 5; // In the actual script, this is chosen by the user.
destDocument.importStyles(ImportFormat.paragraphStylesFormat, new File(sourceDocument.filePath + "/" + sourceDocument.name), GlobalClashResolutionStrategy.LOAD_ALL_WITH_OVERWRITE);
destDocument.importStyles(ImportFormat.characterStylesFormat, new File(sourceDocument.filePath + "/" + sourceDocument.name), GlobalClashResolutionStrategy.LOAD_ALL_WITH_OVERWRITE);
destDocument.viewPreferences.horizontalMeasurementUnits = sourceDocument.viewPreferences.horizontalMeasurementUnits;
destDocument.viewPreferences.verticalMeasurementUnits = sourceDocument.viewPreferences.verticalMeasurementUnits;
destDocument.documentPreferences.facingPages = sourceDocument.documentPreferences.facingPages;
destDocument.documentPreferences.pageHeight = sourceDocument.documentPreferences.pageHeight;
destDocument.documentPreferences.pageWidth = sourceDocument.documentPreferences.pageWidth;
destDocument.documentPreferences.pageSize = sourceDocument.documentPreferences.pageSize;
destDocument.documentPreferences.allowPageShuffle = true;
var range = sourceDocument.pages.itemByRange(firstPageIndex, lastPageIndex);
range.duplicate(LocationOptions.AFTER, destDocument.pages[destDocument.pages.length - 1]);
destDocument.pages[0].remove(); // An empty spread containing an empty page is added when the new document is created and we cannot remove it before other pages are inserted (Documents must have at least one page)
This script works perfectly on many documents. But when I execute it against one particular document (let's call it foo.indd), InDesign becomes unresponsive when executing the duplication: range.duplicate(LocationOptions.AFTER, destDocument.pages[destDocument.pages.length - 1]);. From then on, the only thing I can do is force InDesign to quit.
Is this an InDesign bug? How can I find which part of this particular document is creating the problem?
I can't really say what's wrong in your example but if indesign hangs, that might caused by the loops ( to infinity and beyond :) )
So you may try to avoid issues by outputting the loop limit to avoid InDesign re-calculation
var limit = …
for ( i = 0; i<limit ; i++)…
Additionally you could try to write info on the console to get info where InDesign is actually being stuck. So write informations on the fly on a report file and you might finally identify the issue area.
Also, you can try to interrogate every key items to see if the file has some issue.
Last but not least, try a manual export to idml of this file, re open and run again the script. Sometimes files become clunky and passing by idml fix most of them.
Give this script a try onto your probleamtic file. If it fails, please have a look at the report it should have generated onto the desktop.
http://www.loicaigon.com/downloads/cloneDocument.jsx
Loic
http://www.loicaigon.com

Resources