Clean way to pass events to Titanium from webview? - uiwebview

A common explanation of how to pass events from a Titanium webview involves calling Ti.App.fireEvent() from the HTML in the webview. But, I would like to listen to the webview itself instead of the global Ti.App object, so that duplicate events from different webviews won't trigger inappropriate code for the context.
Eg. if I listen to Ti.App for the "OK_BUTTON" event, it will mean something different depending on where in the flow it is being called. So, I would have to remove and add new listeners for each context.
There is a documented way to pass events directly from the webview using a normal HTML anchor tag with the undefined protocol "xxxx://" and catching the ensuing error event in Titanium. Is there a cleaner way to do this yet? It would be nice to keep the "error" event for errors as intended.

I think you'll need to use Ti.App.fireEvent() from the webview but add data identifying the source webview to the event.
e.g. assign a unique id to each of your webviews and pass that into the webview by doing an 'evalJS()' in the webview's 'load' event handler. (Or by setting the id in your html if you have Titanium generating that)

Here is a basic utility module that assigns an id to a webview and provides a function fireEvent() to webview context that will trigger an event on the webView object in Titanium.
var listenerWebViews = {};
exports.createListenerWebView = function(config) {
var id = createRandomString();
var webView = Ti.UI.createWebView(config);
webView.listenerId = id;
webView.evalJS('function fireEvent(type, e) { var f = { originalType:type, listenerId:"'+id+'"}; Ti.App.fireEvent("WEBVIEW_LISTENER_EVENT", f); }');
listenerWebViews[id] = webView;
var oldRemoveFunction = webView.remove;
webView.remove = function(){
listenerWebViews[id] = null;
oldRemoveFunction();
}
return webView;
}
Ti.App.addEventListener("WEBVIEW_LISTENER_EVENT", function(e){
var webView = listenerWebViews[e.listenerId];
if (webView) {
webView.fireEvent(e.originalType, e);
}
});
With that module included, this works:
var view = module.createListenerWebView({
url: 'myPage.html'
});
view.addEventListener('my_type', function(){
alert('webview event!');
});
view.evalJS("fireEvent('my_type');");

Related

How to communicate between native and native web view running a flutter's web app?

Android allows specifying a Javascript interface to act as a bridge between the webview and Android code. Similarly, iOS provides the UiWebView stringByEvaluatingJavaScriptFromString method that allows communicating with the underlying webview.
But the issue is that once we build the flutter web app and say we include it as part of a web view in android , Then whenever communication needs to take place , there is some amount of code that we should write in both android side and javascript side manually.
Since editing javascript code generated from flutter web app build is impractical , we need a way to establish a communication channel on flutter side which will talk to the native side using the same type of javascript bridge above. This will be something similar to the method channels when using the flutter app on the native side directly.
So how would we inject or add our custom javascript code from inside flutter so that it will be added during compiling the js file ?
You can create a webmessage channel on the android side (iOS has an equivalent).
There is a description of how to do it here:
How Do You Use WebMessagePort As An Alternative to addJavascriptInterface()?
A brief setup on the javascript side looks like this - you would need to implement this in dart html:
const channel = new MessageChannel();
var nativeJsPortOne = channel.port1;
var nativeJsPortTwo = channel.port2;
window.addEventListener('message', function(event) {
if (event.data != 'capturePort') {
nativeJsPortOne.postMessage(event.data)
} else if (event.data == 'capturePort') {
/* The following three lines form Android class 'WebViewCallBackDemo' capture the port and assign it to nativeJsPortTwo
var destPort = arrayOf(nativeToJsPorts[1])
nativeToJsPorts[0].setWebMessageCallback(nativeToJs!!)
WebViewCompat.postWebMessage(webView, WebMessageCompat("capturePort", destPort), Uri.EMPTY) */
if (event.ports[0] != null) {
nativeJsPortTwo = event.ports[0]
}
}
}, false);
nativeJsPortOne.addEventListener('message', function(event) {
alert(event.data);
}, false);
nativeJsPortTwo.addEventListener('message', function(event) {
alert(event.data);
}, false);
nativeJsPortOne.start();
nativeJsPortTwo.start();
A similar solution for listening over using an eventlistener on the window in flutter web is below:
window.onMessage.listen((onData) {
print(onData.toString());
MessageEvent messageEvent = onData;
for (Property property in properties) {
if (messageEvent.data["id"] == property.id) {
});
} else if (messageEvent.data["northEastLng"] != null) {
print(messageEvent.data["northEastLat"]);
if (double.parse(messageEvent.data["northEastLat"]) == bounds.northEast.lat && double.parse(messageEvent.data["northEastLng"]) == bounds.northEast.lng){
return;
}
LatLng southWest = new LatLng(double.parse(messageEvent.data["southWestLat"]), double.parse(messageEvent.data["southWestLng"]));
LatLng northEast = new LatLng(double.parse(messageEvent.data["northEastLat"]), double.parse(messageEvent.data["northEastLng"]));
LatLngBounds incomingBounds = new LatLngBounds(southWest, northEast);
if (incomingBounds == bounds) {
return;
}
bounds = incomingBounds;
_propertyListPresenter.filterBounds(bounds);
}
}
});

display a herocard using luis action binding

How to create and display a "HeroCard" within the fulfill() function of LUIS action binding using node.js ? I am following the samples provided by the microsoft(https://github.com/Microsoft/BotBuilder-Samples/tree/master/Node/blog-LUISActionBinding)
Here is that how I tried to do this...
fulfill: function (parameters, callback) {
utilities.FilterFunction(parameters.x, parameters.y).then(function (matches){
utilities.CreateCard(session, matches).then(function(cards){
var reply = new builder.Message(session)
.attachmentLayout(builder.AttachmentLayout.carousel)
.attachments(cards);
callback(util.format(reply));
});
});
}
How can I use session value in the fulfill method?...without session "utilities.CreateCard" won't work...
Since session is not available in the action's fulfill method, we can make call to only to the utilities.FilterFunction and return the result via the callback. Now in our main js file, in the fulfillReplyHandler we get the actionModel which contains the result from utilities.FilterFunction.
Now we can create the "HeroCard" using the "session" that is accessible in the fulfillReplyHandler.

Xamarin: Issues with transitioning between controllers and storyboard views

I realize this might come across as a very basic question, but I just downloaded Xamarin three days ago, and I've been stuck on the same issue for two days without finding a solution.
Here is what I am trying to do:
Get user input, call API, parse JSON and pass data to another controller, and change views.
Here is what I have been able to do so far: I get the user input, I call the API, I parse the response back, write the token to a file, and I want to pass the remaining data to the second controller.
This is the code I am trying:
var verifyCode = Storyboard.InstantiateViewController("verify") as VerifyCodeController;
if (verifyCode != null)
{
this.NavigationController.PushViewController(verifyCode, true);
}
My storyboard setup:
Navigation controller -> routesTo -> FirstController
I have another UI Controller View set up with the following properties set:
storyboardid: verify
restorationid: verify
and I am trying to push that controller view onto the navigation controller.
Right now this line is erroring out:
var verifyCode = Storyboard.InstantiateViewController("verify") as VerifyCodeController;
giving me this error, which I don't know what it means: Could not find an existing managed instance for this object, nor was it possible to create a new managed instance.
Am I way off in my approach?
p.s: I cannot use the ctrl drag thing like the tutorial suggests because I have an asynchronous call. And I cannot under no circumstances make it synchronous. So all the page transition has to be manual.
EDIT
to anyone requesting more code or more info:
partial void registerButton_TouchUpInside (UIButton sender)
{
phone = registrationNumber.Text;
string url = url;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create (url);
request.Method = "GET";
Console.WriteLine("Getting response...");
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
if (response.StatusCode != HttpStatusCode.OK)
{
Console.Out.WriteLine("Error fetching data. Server returned status code: {0}", response.StatusCode);
}
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
string content = reader.ReadToEnd();
if (string.IsNullOrWhiteSpace(content))
{
//Console.WriteLine(text);
Console.Out.WriteLine("Response contained empty body...");
}
else
{
var json = JObject.Parse (content);
var token = json["token"];
var regCode = json["regCode"];
var aURL = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
var filename = Path.Combine (aURL, "app.json");
File.WriteAllText(filename, "{token: '"+token+"'}");
// transition to main view. THIS IS WHERE EVERYTHING GETS MESSED UP
var verifyCode = Storyboard.InstantiateViewController("verify") as VerifyCodeController;
if (verifyCode != null)
{
this.NavigationController.PushViewController(verifyCode, true);
}
}
}
}
}
Here is all the info for every view in my storyboard:
1- Navigation controller:
- App starts there
- The root is the register pager, which is the page we are currently working on.
2- The register view.
- The root page
- class RegisterController
- No storyboard id
- No restoration id
3- The validate view
- Not connected to the navigation controller initially, but I want it to be connected eventually. Do I have to connect it either way? Through a segue?
- class VerifyCodeController
- storyboard id : verify
- restoration id : verify
If you guys need more information I'm willing to post more. I just think I posted everything relevant.

Default Media Receiver limitations

Can I use the Default Media Receiver to display a web page or an HTML5 app? Using Javascript in the Chrome browser, I have no problem sending a single png image (content type image/png) to the Chromecast but it fails if I specify an html link (content type text/html). session.loadMedia will fire the error handler and e.code/e.description reports session_error/LOAD_FAILED. I used Google's home page for my test:
//var currentMediaURL = "https://www.google.com/images/srpr/logo11w.png";
//var currentMediaType = "image/png";
var currentMediaURL = "https://www.google.com";
var currentMediaType = "text/html";
function startApp()
{
var mediaInfo = new chrome.cast.media.MediaInfo(currentMediaURL, currentMediaType);
var request = new chrome.cast.media.LoadRequest(mediaInfo);
session.loadMedia(request, onMediaDiscovered.bind(this, 'loadMedia'), onMediaError);
};
I think you need to have custom receiver, just have it run your code accordingly...

Pusher binding to events regardless of channel

I am attempting to listen to a particular event type regardless of the channel it was triggered in. My understanding of the docs (http://pusher.com/docs/client_api_guide/client_events#bind-events/lang=js) was that I can do so by calling the bind method on the pusher instance rather than on a channel instance. Here is my code:
var pusher = new Pusher('MYSECRETAPPKEY', {'encrypted':true}); // Replace with your app key
var eventName = 'new-comment';
var callback = function(data) {
// add comment into page
console.log(data);
};
pusher.bind(eventName, callback);
I then used the Event Creator tool in my account portal to generate an event. I used a random channel name, set the Event to "new-comment" and just put in some random piece of text into the Event Data. But, I am getting nothing appearing in my Console.
I am using https://d3dy5gmtp8yhk7.cloudfront.net/2.1/pusher.min.js, and performing this test in the latest Chrome.
What am I missing?
Thanks!
Shaheeb R.
Pusher will only send events to the client if that client has subscribed to the channel. So, the first thing you need to do is subscribe the channel. Binding to the event on the client:
pusher.bind('event_name', function( data ) {
// handle update
} );
This is also known as "global event binding".
I've tested this using this code and it does work:
http://jsbin.com/AROvEDO/1/edit
For completeness, here's the code:
var pusher = new Pusher('APP_KEY');
var channel = pusher.subscribe('test_channel');
pusher.bind('my_event', function(data) {
alert(data.message);
});

Resources