Android Question Wait a Javascript value

max123

Well-Known Member
Licensed User
Longtime User
Hi all,

I 've ported this @stevel05 B4J CodeMirror wrapper to B4A
https://www.b4x.com/android/forum/threads/codemirror-wrapper-and-example.125775/#post-785682

Now I need to get values back from Javascript but I've some problems.

Steve in his B4J code implemented itself a Javascript interface on a JavaFX WebView, ExecuteJavaScript command can execute JS and return back an object so it can back with just one call.

B4X:
'Executes a script in the context of the current page.
Public Sub ExecuteScript(Script As String) As Object
    Return TJO.RunMethod("executeScript",Array As Object(Script))
End Sub

B4J CodeMirrorWrapper to get editor code do this:
B4X:
'Get the current code editor text code
Public Sub GetCode() As String
    Return ExecuteScript("editor.getValue();")  ' A single call to inject JS and get back return value to B4J calling module that request it (Main or class module)
End Sub

In my code I use WebViewExtras and ExecuteJavascript method do not return values, so I just created a JS function to get a value and then JS call a B4A sub to pass back it.

The problem here is that this require some time and I do not know how to wait a value.
It always return non last value assigned to a global variable (from previous call).

I've even tried to reset it and wait with while loop (not good option, just for test) while the value change, but as expected it enter in an infinite loop.

Even I do not like that require a global variable.

Here my attemp:
B4X:
'Get the current text on this CodeEditor.
Public Sub GetCode As String
    '    Code = ""  ' Code is a String global variable
    wve.executeJavascript(mWebView, "getValue();")

'    Return wve.executeJavascript(mWebView, "editor.getValue();"  ' This cannot be used because ExecuteJavascript
                                                                ' method return nothing
    Return Code
End Sub

Public Sub codemirrortext(Value As String)  ' Called by JS to return back the value
    Code = Value
    Log(">>> CodeMirror text from JS: " & CRLF & Code)
End Sub

JS Code:
function GetValue() {
  var text = editor.getValue();
  B4A.CallSub('codemirrortext', true, text);
}

I need to do it on a lots of methods (in the wrapper class).

Wait For I think cannot help here because the sub is called by JS and not B4A, right ?

Please can someone help me to know how to do it in an elegant way ?

Thanks
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
This is the @teddybear solution, I changed Sleep(50) to Sleep(0) and seem to work, see the timestamp on functions, get a simple string require 63 milliseconds.

Now I will try your solution and then try both on codemirror wrapper.

Ah Steve I've created a first B4A app using a TabHost with 2 pages, in one page now there is a webview with codemirror and synyax hilights (the old version use an EditText), on the second page a second webview that render fullscreen a threejs 3D scene based on HTML + Javascript code of first page. Load and Save button using FileProvider, theme spinner that permit to change theme.
 

Attachments

  • Screen Shot 07-01-22 at 05.32 PM.PNG
    Screen Shot 07-01-22 at 05.32 PM.PNG
    8.5 KB · Views: 163
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Here a comparison of both methods advices from @teddybear and @stevel05, both works, need to know if there are no better ways to do it.
 

Attachments

  • GET_JS_VALUE.zip
    8.1 KB · Views: 190
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
It is not a problem to avoid using Wait For in the main. you can put waitfor into MyClass.

B4X:
Sub WebView1_PageFinished (Url As String)
    wait For(GetJavascriptString) complete(result As Boolean)
    If SubExists(mCallBack, mEvent & "_Ready") Then CallSubDelayed(mCallBack, mEvent & "_Ready")
End Sub

public Sub GetJsS As String
    Return jsText
End Sub

Public Sub GetJavascriptString () As ResumableSub
    wve.executeJavascript(WebView1, "getString();")
    Do While jsRunning
        Sleep(50)
    Loop
    Return True
End Sub

Main
B4X:
Sub obj_Ready()  'HERE HTML IS LOAD ON A WEBVIEW
    Log("Main:_Ready")
    Dim text As String = obj.GetJsS
    Log("Main: text: " & text)
End Sub
The problem here is that this is static, after page is load it get the text, I already tried this solution but had the same problem.

If the code changes, then you need to get changes, please can you explain me how to get the new code without calling Wait For that is just called a first time when page is load ?

Probably it return a void string that is the current text of code editor when page is load and assigned to global jsText that remain static an never changes.

Thanks
 
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
I don't know how you wrap your class, if you have 50 getters, it is necessary to create 50 variables.they are not global, and they are just members of the class.
User can create an instance of the class by Initialize and get values and properties by getters of the instance

Ditto, In class CodeEditor, you can add members CursorPosition and Selection, I think one jsRunning is enough
Global variables only required because Android cannot inject a Javascript and return value directly, but need a callback.

What I wanted to do was the same that B4J class, so inject JS and directly get a value back in the same sub, so return it to the caller that request it (main).
Unfortunately this seem that cannot be done on Android.

As an example, this is something used in B4J class, this avoid use of Wait For and ResumableSub:
B4X:
' Get a code editor text. Here no need callback and global variable to store value, just get it
' from JS and return back to a calling module that request it (Main on this case, or a Class)
Public Sub GetCode() As String
   Return ExecuteJavascript("editor.getValue();"))
End Sub
 
Last edited:
Upvote 0

teddybear

Well-Known Member
Licensed User
The problem here is that this is static, after page is load it get the text, I already tried this solution but had the same problem.
It's not a static , it doesn't get the text after page is loaded
the text As String = obj.GetJsS you got in Main, it runs after getString() is done, because launch _Ready event is waiting for GetJavascriptString to complete
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
It's not a static , it doesn't get the text after page is loaded
the text As String = obj.GetJsS you got in Main, it runs after getString() is done, because launch _Ready event is waiting for GetJavascriptString to complete
This confuse me a lot. Anyway I will try it.
 
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
It's not a static , it doesn't get the text after page is loaded
B4X:
Sub WebView1_PageFinished (Url As String)
    wait For(GetJavascriptString) complete(result As Boolean)
    If SubExists(mCallBack, mEvent & "_Ready") Then CallSubDelayed(mCallBack, mEvent & "_Ready")
End Sub
How I can get the text a second time after it changes ? Need to reload the page ?

And in your code you return jsText, but where you assign it ?
 
Last edited:
Upvote 0

teddybear

Well-Known Member
Licensed User
B4X:
Sub WebView1_PageFinished (Url As String)
    wait For(GetJavascriptString) complete(result As Boolean)
    If SubExists(mCallBack, mEvent & "_Ready") Then CallSubDelayed(mCallBack, mEvent & "_Ready")
End Sub
How I can get the text a second time after it changes ? Need to reload the page ?

And in your code you return jsText, but where you assign it ?
If you get the text in sub mClass_Ready, then it is necessary to reload the page .
jsText is assigned in Javascriptcallback
 
Upvote 0
Top