WebView Javascript

warwound

Expert
Licensed User
Longtime User
Great!

I was just working on a single class file for the library and had moved the actual javascript interface from a class file of it's own to an inner class of the JSInterface class.

It still works but now i had a problem accessing the javascript interface's public methods from B4A as the javascript interface was now a child of the parent JSInterface - another level of property access required.

I'll take you sample code and see what progress i can make .

Thanks.

Martin.
 

warwound

Expert
Licensed User
Longtime User
Hi again.

Can you tell me a bit more about the raiseEvent method?

I have this in my interface:

B4X:
public String CallSub0(String sub) {
   sub = sub.toLowerCase();
   return (String) ba.raiseEvent(this, sub, new Object[0]);
}

My javascript interface name is now 'B4A', so in my web page javascript i do:

B4X:
try{
   var a=B4A.CallSub0('_logme');
   document.getElementById('div2').innerHTML=a;
} catch(error){
   document.getElementById('div2').innerHTML=error;
}

Now what B4A code is expected?

B4X:
Sub logme(str As String)
   Log(str)
   Return "logme executed"
End Sub

That doesn't work, in the javascript the value of 'a' is undefined and in B4A no log output is created.

I tried 'logme' in the javascript and that didn't work either.

Can the interface class generate a custom event on the library instance?

B4X:
ba.raiseEvent(this, 'customeventname', new Object[0]);

And then i can listen for the event in B4A:

B4X:
Sub myLibraryInstance_customeventname(str As String)
 ' code here
End Sub

Martin.
 

agraham

Expert
Licensed User
Longtime User
You don't need the underscore prefix for raiseEvent, it's only needed if you are doing a direct method call so just use the name of the Sub. It probably didn't work as you have a method signature mismatch but I guess you didn't get an error.

Javascript
var a=B4A.CallSub0('logme');

would call B4A
Sub logme
...
End Sub

and

Javascript
var a=B4A.CallSub1('logme', 'My log message");

would call B4A
Sub logme(str As String)
...
End Sub
 

agraham

Expert
Licensed User
Longtime User
Can the interface class generate a custom event on the library instance?
Yes, events are raised from a library using "raiseEvent" but you need to synthesise the whole event Sub name yourself in the library. There is also a library attribute that you can add to your class to inform the IDE that it raises events so that Intellisense can be used to add the event with the Tab key.

You can have events with whatever parameters you want, or no parameters at all. Just match the @Events declarations with the method signature. You can have more than one event, just comma separate them as individual strings in "values =".

B4X:
@ShortName("SomeLib")
[COLOR="Red"]@Events(values={"Ended(endedOK As Boolean, error As String) Something has terminated. If endedOK is False error holds the reason for failure" }) 
[/COLOR]@Author("Andrew Graham")
@Version(1.0f)
public class SomeLib 
{
   private BA ba;
   [COLOR="Red"]private String event;[/COLOR]

   public void Initialise(BA processBA, String eventname)
   {
      ba = processBA;
      setObject(new Thread());
      [COLOR="Red"]event = (eventname.toLowerCase() + "_ended");
[/COLOR]
   }


   ....
      [COLOR="Red"]ba.raiseEvent(this, event, new Object[] { someflag, somestring);[/COLOR]
   ....

}
 

warwound

Expert
Licensed User
Longtime User
Hi agraham and thanks for your time - i'm nearly there!

I have the new javascript interface as a single class and am trying to get the CallSub0 method to work.

In my JSInterfaceMod code module i have a sub:

B4X:
Sub logme
   '   never gets called
   Log("logme called")
End Sub

Nothing worked until i tried adding a new sub to the Main module:

B4X:
Sub debugtest
   '   works
   Log("Debug message")
End Sub

and in my javascript:

B4X:
B4A.CallSub0('logme');
B4A.CallSub0('debugtest');

I can successfully call the sub in the Main module but not in my code module.
I'm sure i read somewhere that the context of BA depended upon in which module it's associated library constructor was called?
(Can't find where i read that now).

http://www.b4x.com/forum/libraries-developers-questions/6810-creating-libraries-basic4android.html
I have read about the processBA and activityBA but don't really understand it.
It does say:

If your object is an "Activity object" then you will get the activity BA. Otherwise you will get the process BA.
Activity objects cannot be declared in Sub Process_Globals (as this would cause a memory leak).

Is that related to my problem?

Martin.
 

agraham

Expert
Licensed User
Longtime User
To call Subs in your code module your library needs the BA instance belonging to the code module. It sounds like you are initialising the library in your Main activity so that is the main module BA instance that the library is receiving. You need to initialise it in the code module so it gets the code module BA.

Put a

B4X:
Sub Initialise(wv As WebView, EventName as String)
  JSInterfaceModName.Initialise(wv, EventName)
End Sub

in your code module and call it from your main activity.
 

agraham

Expert
Licensed User
Longtime User
Ah! thanks for pointing that out Erel. Sloppy thinking and wrong assumptions on my part I'm afraid.

@warwound - looks like CallSub can only call activity and service Subs.

I guess we could call code module Subs directly by reflection on the code module class. I'll have a play with that.
 

warwound

Expert
Licensed User
Longtime User
I rethought the whole thing when the code module wouldn't work then decided that there's no actual reason why a code module would contain anything to do with a WebView in an activity module.

Any activity module that contains a WebView can simply create an instance of the interface libary class and use that instance to enable the interface on the WebView - all works as desired.

Maybe if an app had more than one activity that contained a WebView and the javascript interface was required on more than one WebView and the same new javascript methods were also required on more than one WebView?

That sounds like an ideal use of a code module but it looks to be a non-starter.

Busy trying to fix last two compile errors on agrahams code snippet posted earlier here:

B4X:
   public String CallSub1(String sub, String param1){
      sub = sub.toLowerCase();
      return (String) ba.raiseEvent(this, sub, new Object[] = { param1 });
   }

   public String CallSub2(String sub, String param1, String param2){
      sub = sub.toLowerCase();
      return (String) ba.raiseEvent(this, sub, new Object[] = { param1, param2 });
   }

I get a Variable must provide either dimension expressions or an array initializer error on both lines that call ba.raiseEvent().
An error with array shorthand maybe - i'll google a bit and see what i can learn.

Martin.
 

agraham

Expert
Licensed User
Longtime User
simply create an instance of the interface libary class and use that instance to enable the interface on the WebView - all works as desired.
Of course!

Busy trying to fix last two compile errors on agrahams code snippet posted earlier here
Sorry, the new is superfluous when creating initialised arrays.
 

warwound

Expert
Licensed User
Longtime User
Hi again.

I have fixed all the bugs etc and it's working perfectly!
I've spent the morning implementing it in an app where i need to save data from a WebView so on orientation change i can restore the WebView exactly.

As a library the class adds four new methods to your application:

Methods that can be called from javascript: 'CallSub0', 'CallSub1' and 'CallSub2'.

And a single method that can be called from your B4A activity 'execJS'.
execJS accepts a single string as an argument - the string is a javascript statement to be executed.
Example:

B4X:
myJSInterfaceInstance.execJS("location.href='http://google.com'")

That doesn't look very useful and can be done without the interface, but more obvious uses of execJS would be to call a javascript function in your webpage.
Lets say your app has data for your WebView to process, but the WebView has no idea that new data is available

Javascript function:

B4X:
function updateData(){
 var newData=B4A.CallSub0('getData');
 // process newData
}

B4A sub:

B4X:
Sub getData As String
 ' return a JSON string to the WebView
 Dim myStr As String
 ' process myStr so it contains the JSON data
 Return myString
End Sub

B4A code to call when your app has data ready for your WebView:

B4X:
myJSInterfaceInstance.execJS("updateData()")

No need to use events as B4A can execute any javascript within the WebView and the WebView can execute any B4A sub.

Shall i update this thread with a final post containing the library or shall i start a new thread on the Additional libraries forum?

A new thread sounds best and will give me a chance to properly document the library?

Martin.
 
Cookies are required to use this site. You must accept them to continue using the site. Learn more…