Passing data from the callCommand "sandbox" to the main plugin code

(I use automatic translation from Russian, the text may look clumsy, sorry)

onlyoffice desktop edition, 8.2.2.

how to pass a message from window.Asc.plugin.callCommand function to the main plugin code ?

I have this code:

(function(window, undefined) {

window.Asc.plugin.init = function() {
    document.getElementById("inputIDstatus").value = "step 1";
    document.getElementById("buttonIDtest").onclick = function() {
        document.getElementById("inputIDstatus").value = "step 2";
        window.Asc.plugin.callCommand(
            function () {
                for (let i = 0; i < 100; i++)
                    {
// here is a long action. we need to pass the "i" value to the plugin to show in the "input" field.
                    }
                return "done";
            }, false, false, function (res1) { document.getElementById("inputIDstatus").value = res1 }
        );
    };
};

})(window, undefined);

how to change it so that in the process of the function inside callCommand - the result goes to the “input” field not only after the function is completed, but also in the process (for example, let only the value of the variable “i” get there), how to implement it ?

This is to pass “progress” when running a long loop (processing a document that can take a few minutes) - I want to do a “feedback” so that the plugin window shows the percentage of progress and the user doesn’t think it’s stuck.

Hello @iteh

Return data from callCommand is possible in several ways, all of them are mentioned here:

Please take a look at this topic.

I might have expressed myself wrong (or misunderstood your answer) - I need to get intermediate data from callCommand, that is, to get intermediate results from callCommand in the main (outside of callCommand) plugin code.

As I understand it, callCommand runs synchronously and until the function launched through callCommand finishes, I won’t see the result.

How can I get intermediate values (in my example - like the value of the variable “i”) while the function inside callCommand is running?

Is there some kind of “postMessage” function to call from inside callCommand (passing the value of “i” for my example), and catch it from the main plugin code without waiting for the block inside callCommand to finish?

My function (inside callCommand) runs for a few minutes and I want to get not just the final value, but also see all the intermediate “i” in real time (i.e., see which iteration of the loop is currently running while callCommand hasn’t finished yet).

Is that possible?

I believe the easiest solution is to put callCommand into a for cycle, it will return values for each step if your condition. Of course it depends on what kind of operation you are performing in callCommand, but it should work.

I am checking the rows of a .docx file in a loop (approximately 2-3 thousand rows with 15 columns each), and depending on the content of the cells, either some cells of each row are merged or the formatting (font, indents, etc.) is changed.

I checked - the difference between a single call to callCommand (inside the function - 3000 iterations of the loop in which a simple action is done, only measure the total time of all iterations) and 100 times to call callCommand (each call has 30 iterations of the loop) - the difference is 10 times (in the first case it took 1ms, in the second - 10ms), but in absolute time - acceptable, I can use multiple callCommand calls, As you said above. Thanks for this idea :slight_smile:

And one more question: could you please advise if there is any way (API function of the text editor) to display information from a loop running inside callCommand, but already in the editor’s interface?

Is there any API function to display information in the text editor’s interface?

For example, in VBA in MS Office (WinWord), we can use the following code:

Application.DisplayStatusBar = True
Application.StatusBar = "Processing " & i & ", completed Percentage: " & pct_complete

and display any information during the macro execution.

Are there any similar methods (to display the text string in the editor interface while executing the function code inside callCommand) in OnlyOffice desktop editors ?

maybe I’m doing something wrong (with a “multiple” callCommand call), but I’m getting weird results. the following code:

    document.getElementById("buttonIDtest2").onclick = function() {
        Asc.scope.i = 0;
        while (Asc.scope.i < 5)
        {
            Asc.scope.i++;
            console.log('[',Date.now(),'] Asc.scope.i (while) = ',Asc.scope.i);
            window.Asc.plugin.callCommand(
                function () {
                    Asc.scope.i++;
                    console.log('[',Date.now(),'] iRetCode(int) = Asc.scope.i (int) = ',Asc.scope.i);
                    return { iRetCode : Asc.scope.i };
                }, false, false);
            window.Asc.plugin.onCommandCallback = function (res1) {
                console.log('[',Date.now(),'] iRetCode(ext) = ',res1.iRetCode) 
                Asc.scope.i++
                console.log('[',Date.now(),'] Asc.scope.i (onCommandCallback) = ',Asc.scope.i);
            };
        }
    };

I was hoping it would run (and added the time in milliseconds to check) like this:

Asc.scope.i (while) =  1
iRetCode(int) = Asc.scope.i (int) =  2
iRetCode(ext) =  2
Asc.scope.i (onCommandCallback) =  3
Asc.scope.i (while) =  4
iRetCode(int) = Asc.scope.i (int) =  5
iRetCode(ext) =  5
Asc.scope.i (onCommandCallback) =  6
...

But in fact, I see this:

[ 1738663271433 ] Asc.scope.i (while) =  1
[ 1738663271434 ] Asc.scope.i (while) =  2
[ 1738663271434 ] Asc.scope.i (while) =  3
[ 1738663271434 ] Asc.scope.i (while) =  4
[ 1738663271434 ] Asc.scope.i (while) =  5
[ 1738663271437 ] iRetCode(int) = Asc.scope.i (int) =  2
[ 1738663271443 ] iRetCode(int) = Asc.scope.i (int) =  3
[ 1738663271449 ] iRetCode(int) = Asc.scope.i (int) =  4
[ 1738663271453 ] iRetCode(int) = Asc.scope.i (int) =  5
[ 1738663271456 ] iRetCode(int) = Asc.scope.i (int) =  6
[ 1738663271459 ] iRetCode(ext) =  2
[ 1738663271459 ] Asc.scope.i (onCommandCallback) =  6
[ 1738663271459 ] iRetCode(ext) =  3
[ 1738663271459 ] Asc.scope.i (onCommandCallback) =  7
[ 1738663271459 ] iRetCode(ext) =  4
[ 1738663271459 ] Asc.scope.i (onCommandCallback) =  8
[ 1738663271460 ] iRetCode(ext) =  5
[ 1738663271460 ] Asc.scope.i (onCommandCallback) =  9
[ 1738663271460 ] iRetCode(ext) =  6
[ 1738663271460 ] Asc.scope.i (onCommandCallback) =  10

why does this happen ? :frowning:

If the Onlyoffce engine algorithm works only like this (all the steps of the “external” loop of while are executed first, then all callCommand functions at once, then all onCommandCallback functions at once), then how do I implement such an algorithm:

  1. Use callCommand to get the number of rows from the document table
  2. if the number of rows is greater than zero, then use the while loop, where the condition is that the current row (the number of which is returned from onCommandCallback on each iteration of the while loop, and within callCommand to process 20-30 rows at a time) is less than the number of rows in the table, or that the previous callCommand was with an error (problems with row processing), and inside while there will be a callCommand , But after each call, analyze onCommandCallback to see if we need to exit while.

How to implement this if, when executing the test code (see above), we get the “wrong” order of command execution ? :frowning:

I could assume that the code works very fast, and the onCommandCallback functions work in a separate thread and because of this the callCommand functions and onCommandCallback are “mixed”, but I ran it 5 times in a row - and the order is always strictly like this (both the order in the log and the chronology by timestamps)… :frowning:

Personally I don’t think that while is the best option here, at least if I understand the scenario correctly. I think some more info needed for me to get complete understanding.

What kind of processing you are trying to perform? Why won’t you use XLSX documents for that?

In general, is there particular reason for using while loop?


Unfortunately, there is not.


Sorry, I see the scenario, missed previous reply in the first reading. I am trying to understand now how your conditions (cell merge, font change, etc) are supposed to applied. Generally, you are running a loop cycle - for what? It is supposed to “map” data or it applies conditions too in one go?

The document is in .docx because it needs to be “fitted” to the ready template (there are formatting requirements at work).

Data is added to the table: this is done manually, just copying from xlsx, where part of the data is generated by formulas and the operator just does “copy-paste” from xlsx to the header of this .docx document; but, at the moment, they do it in MS Winword (I didn’t find how to implement this in onlyoffice and created a corresponding theme in the section about desktop editors).

The processing is also happening through winword macros right now, but as part of the transition to onlyoffice - I’m trying to make an equivalent through onlyoffice plugins.

Then, for the .docx, such processing is needed (there are several hundred pages, so I wanted to create “feedback” to display at least some “progress bar” while the plugin is working, so it doesn’t seem like everything is frozen):

  1. check if there is a table in the document and how many rows (besides the header) are in the table, then loop through these rows
  2. check that there are no merged cells (count the number of cells for the first row and then in the loop check that for all subsequent rows the number doesn’t differ)
  3. then go through all the rows and look for certain signs in a specific column of each cell, there are 2 checks:
    3.1. if a certain character is at the beginning of the line, then make indentations (cell: .GetContent().GetElement(0).GetParaPr().SetIndLeft(..))
    3.2. If any other (specific) symbol appears anywhere in the text cell, then for that cell - merge the cells from this one to the end of the row (table: .MergeCells([array of cells])) and underline the previous cell (not merged, but from the previous line - .SetCellBorderBottom('single', 4)).

That’s the whole algorithm. Nothing complicated, but there are a lot of pages and it all works for several minutes. At the same time, I need to collect statistics (how many times we made indents, how many cells we merged).

We can just wait while it works, but I wanted to create some kind of progress bar (for example, process no more than “number of rows divided by 100” rows per callCommand and move the progress bar after each callCommand) and I don’t understand how to implement it: the number of rows can be variable, but we can only find out the number of rows through callCommand, so I need to execute callCommand first to find out the number of rows, and then loop through all the rows, but also collect statistics.

If there was a way to guarantee that the while loop (with the condition being either “we reached the last row” or “an error occurred during execution” - since I’m doing some checks inside) would go through all the rows and I could call callCommand inside the while, but onCommandCallback would be triggered strict after callCommand (to move the progress bar) and I could get some flag from onCommandCallback (to pass to the while) to stop the process if there was an error in callCommand - that would be great.

If that’s not possible (can’t create a loop where I call callCommand but also check onCommandCallback and adjust the loop’s exit conditions based on the results returned in onCommandCallback and also moving the progress bar), then I’ll give up on the “feedback” idea and just show a warning “please wait and don’t click anything until it’s done.”

But I would really like the progress bar.

It would be much easier if I could “pull” HTML elements in the plugin (like moving the progress bar) from the code inside callCommand, but I actually started this topic with that and you told me it wasn’t possible, so I decided to break the whole code inside callCommand into several callCommand calls, so that after each call I could move that “stupid” progress bar (for user convenience)…

and now I’m “stuck” with the inability to ensure the order of callCommand execution in a loop, but in a way that after each callCommand I can immediately analyze the results of that specific CallCommand (see the example above - for some reason I can’t process onCommandCallback right after each callcommand !) and based on the results decide whether to continue the loop or not.

I hope I didn’t write it too confusingly (I’m using a translator). :slight_smile:

Thanks for the details. Let me process this scenario a bit more closely, I will share my feedback on the consecutive callCommand and onCommandCallback responses.

in addition to checking the order of execution (so that window.Asc.plugin.onCommandCallback always follows window.Asc.plugin.callCommand, synchronously) -

can I ask to consider the possibility of adding to future versions of OnlyOffice the ability to synchronously call callback function from window.Asc.plugin.callCommand (so that if a lot of operations are performed in window.Asc.plugin.callCommand - periodically, through callback functions, do some actions - for example, move the same progressbar in the plugin, to indicate that everything is fine and we need to wait) ?

callCommand is a synchronous method already. To get result like the one you are describing you need to call it asynchronously instead. For instance, in a way like that:

  async function callCommand(func)
  {
      return new Promise(resolve => (function(){
          Asc.plugin.callCommand(func, false, true, function(returnValue){
              resolve(returnValue);
          });
      })());
  };
  
  // Get result
Asc.scope.i = 0;
while (Asc.scope.i < 5)
{
    Asc.scope.i++;
    let result = await callCommand(...);
}

This is just a demo, you should fit your code accordingly.

When calling a function callCommand (after await), I can specify my function with the necessary actions (which will be executed inside the editor and will have access to the document data), right?

I don’t know JS well (I’m trying to translate my VBA macro to onlyoffice) and to check the syntax - I just pasted your code into the macro editor in onlyoffice. It gave me this:

For the first one (line number 11) - remove “;” right ?

But how do I resolve the second warning (line number 18) ?

Please, can you write an example code that will perform such an algorithm:

  1. Asc.scope.i is set to 0;
  2. the while loop is called until Asc.scope.i < 5;
  3. the following actions are performed within the cycle:
    3.1. Asc.scope.i is printed to the console with the prefix "while: "
    3.2. call Asc.plugin.callCommand, which executes a function that does the following:
    3.2.1. prints Asc.scope.i to console with the prefix "in call (i): "
    3.2.2. increases Asc.scope.i by one
    3.2.3. prints Asc.scope.i to console again with the prefix "in call (i++): "
    3.3. after calling Asc.plugin.callCommand (in the callBack function ?) - the following actions are performed:
    3.3.1. the console outputs Asc.scope.i with the prefix "after call (i): "
    3.3.2. Asc.scope.i is increased by one
    3.3.3. the Asc.scope.i is printed to the console again with the prefix "after call (i++): "

But all of this should work consistently - and then we should see this in the console:

while: 0
in call (i): 0
in call (i++): 1
after call (i): 1
after call (i++): 2

while: 2
in call (i): 2
in call (i++): 3
after call (i): 3
after call (i++): 4

while: 4
in call (i): 4
in call (i++): 5
after call (i): 5
after call (i++): 6

is it possible to do this in JS from a plugin ?

If we can make this happen - it will solve the issue of calling callCommand sequentially and then I’ll just add the necessary code, but I need everything to be executed in order, I don’t know how to implement this :frowning:

As I see, the problem like You described here cannot be realized with standard patterns of OnlyOffice programming. It could’ve be done with some sort of message exchange between plugin and document runtime contexts. Maybe there is some way to use shared worker? I’d like to know the developers opinion for this.
Otherwise, your decision should be changed for using an async queue or an async generator function.

Possibly I am missing the point of printing that many parameters. In callCommand you can only return a single value, so other values will be just console.log entries, so to say purely informational, hence plugin will print them, but not access from outside callCommand. Considering this, it is better to return a value as a callback parameter instead of using onCommandCallback:


Hello @malubimcev

Technically, plugin is a basic HTML page, so you can use shared worker too depending on your desired goal. We do not have any examples of that, but if you manage to implement it in your plugin, you can share you example for the community.

  1. No-no, I always return just the value of Asc.scope.i (in my question above about “Please, can you write an example code that will perform such an algorithm”), and everything else is just a text prefix to make it clear from which piece of code the value Asc.scope.i was returned - to make sure the code runs in the order we need (that first the part from the “while” loop runs, then the “inner” code from callCommand, then the onCommandCallback code, and then the next iteration of “while”).

So we only output Asc.scope.i, but we add clarification about which part of our code we got Asc.scope.i from, to ensure the code executes sequentially. That’s what I’m trying to achieve (making it run in that order).

  1. Can we really only return a single value from callCommand ?
    Can’t I do something like: “return { i: 0, s: “text”, k: var1 }” and use “result.i”, “result.s”, “result.k” variables when analyzing the response in onCommandCallback (not for my task, but just in general from callCommand ) ?

Regarding How to call commands - it says:

“If some parameters or any additional data (objects, parameters, variables, etc.) need to be passed to this method, use Asc.scope object.”

Does this mean that parameters can only be passed through Asc.scope within callCommand ?

And can I change the value inside the callCommand function - for example, “Asc.scope.i++”, so that after exiting callCommand, Asc.scope.i will be one unit greater than it was before executing callCommand, or does it not work that way ?

When you call some function in callCommand method, you could pass some parameters by Asc.scope, extending it by new props. However, when you gonna return data from this function back to plugin, Asc.scope doesn’t work. You should just call “return data”. And, yes, only simple types: number, string… So, if you need to return some object, like you mean “return { i: 0, s: “text”, k: var1 }”, you should use JSON.stringify method to make a string:

return JSON.stringify({ i: 0, s: “text”, k: var1 })

And then in plugin callback (or in event handler onCommandCallback) you should transform your string back to object:

window.onCommandCallback(res) {
   try {
        const resultFromDocument = JSON.parse(res);
    } catch (err) {
        ...
    }
}
1 Like