I am trying to call a function (widget.onTap()) in the following code
GestureDetector(
onTap: () {
widget.onTap()
},
child: Container(
height: 1.9 * widget.height,
width: 1.2 * widget.width,
child: HtmlElementView(viewType: 'liquid-button', key: UniqueKey(),),
),
)
I have registered my "liquid-button" as such
ui.platformViewRegistry.registerViewFactory('liquid-button', (int viewId) {
var element = html.IFrameElement()
..style.border = 'none'
..srcdoc = """
<!DOCTYPE html>
<head>
</head>
<body>
<svg class="liquid-button"
...
"""
return element;
});
The problem is I cannot capture anything from the user no matter what I try. The GestureDetector doesn't work, neither do the buttons. I am able to trigger some code in JS, but I need to handle it in Dart. Is this possible ?
I just found the workaround for this one.
So basically, we can take advantage of window.localStorage where it can be accessed from both flutter and html, and add listener in flutter for any change in window.localStorage
Add listener inside the flutter
final Storage webLocalStorage = window.localStorage;
htmlListener(){
String buttonClicked = webLocalStorage["buttonClicked"];
if(buttonClicked=="yes"){
//do whatever
}else{
Future.delayed(Duration(milliseconds: 100), (){
htmlListener();
});
}
}
Do some change to the webstorage in the HTML
var webLocalStorage = window.localStorage;
webLocalStorage["buttonClicked"] = "yes";
And you can do the same vice versa, that is listen from html, change in flutter.
Related
Using the HTML below, how can I get a list of the functions in the <script> tag that is IN the #yesplease div. I don't want any other functions from other script tags. I don't want any global functions or native functions. What I'd like is an array with "wantthis1" and "wantthis2" only in it. I'm hoping not to have to use regex.
I am going to be emptying and filling the #yesplease div with different strings of html (including new script tags with new functions), and I need to make sure that I delete or "wantthis1 = undefined" each function in the script tag before filling it, programmatically, since I won't know every function name. (I don't want them to remain in memory)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script>
function dontCare() {
// don't care
}
</script>
</head>
<body>
<div id="notthisone">
<p>
Hello
</p>
<script>
function dontwantthis () {
// nope
}
</script>
</div>
<div id="yesplease">
<p>
Hello again
</p>
<script>
function wantthis1 () {
// yes
}
function wantthis2 () {
// yes
}
</script>
</div>
<script>
// this function can be called by whatever, but this will look to
// see if functions exist, then call them, otherwise do something
// else
function onSaveOrWhatever () {
if (wantThis1 !== "undefined") {
wantThis1();
}
else {
// do something else (won't get called with above example)
}
if (wantThis3 !== "undefined") {
wantThis3();
}
else {
// do something else (will get called with above example)
}
}
</script>
</body>
</html>
Take innerHTML of all script tags you need
Create an iframe
Get a list of built-in functions of iframe.contentWindow object
Write the content of the script to the iframe created
Get a new list of the functions of iframe.contentWindow object
Find new functions added to the new list
Somehow it doesn't work in stack snippets but it works in Codepen link
var code = document.querySelector("#target script").innerHTML;
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
var builtInFunctions = getFunctionsOfWindowObject();
var html = `<html><head><script>${code}</script></head><body /></html>`;
iframe.srcdoc = html;
var allFunctions = getFunctionsOfWindowObject();
var result = allFunctions.filter(function(n) {
return builtInFunctions.indexOf(n) < 0;
});
console.log(result);
function getFunctionsOfWindowObject() {
var functions = [];
var targetWindow = iframe.contentWindow;
for (var key in targetWindow) {
if (
targetWindow.hasOwnProperty(key) &&
typeof targetWindow[key] === "function"
) {
functions.push(key);
}
}
return functions;
}
iframe {
display: none;
}
<div id="target">
<script>
function wantthis1() {}
function wantthis2() {}
</script>
</div>
The are a few ways to solve this problem
Architect your application. Use react or vue (or even jquery) to add more control to your code/dom
AST parsing, which would be overkill
Hack it
If you hack it, the problem that you will face is that you are adding functions to global scope. This is shared by everyone, so you can't really monitor it in a nice way.
You can however take advantage of javascript singlethreadedness, and know that things won't happen in the background while you are doing monitoring tasks.
<script>
// take a cache of what is present before your script
window.itemsPresentBeforeScript = {};
foreach (var item in window) {
window.itemsPresentBeforeScript[item] = window[item];
}
</script>
<script> .... your other script </script>
<script>
// use your first cache to see what changed during the script execution
window.whatWasAddedInTheLastScript = {};
foreach (var item in window) {
if (!window.itemsPresentBeforeScript[item]) {
window.whatWasAddedInTheLastScript[item] = window[item];
}
}
delete window.itemsPresentBeforeScript;
// not you have a global list of what was added and you can clear it down when you need to
</script>
I want to click an element (button) several times in a remote website loaded through webview in Electron.
The following works when I enter it in the console (via inspect element):
a = setInterval( function(){
var elem = document.getElementsByClassName("myclass");
elem[0].click()
},1000)
It doesn't when I use it in Electron main script:
window.webContents.executeJavaScript('a = setInterval( function(){ var elem = document.getElementsByClassName("myclass"); elem[0].click() },1000)', true);
I get an error:
Uncaught TypeError: Cannot read property 'click' of undefined at :1:110
I also tried the scipt preloaded in webview tag, but no luck.
What am I missing, or doing wrong?
chromiumVersion: "58.0.3029.110"
electronVersion: "1.7.9"
nodeVersion:"7.9.0"
EDIT
Testing with Google.com and the speech icon in the searchbar:
var element = document.getElementsByClassName('gsst_a');
if (typeof(element) != 'undefined' && element != null) {
console.log('yep, element is found');
console.log(element);
console.log(element[0]);
a = setInterval(
function(){
var elem = document.getElementsByClassName("gsst_a");
elem[0].click()
},1000)
} else {
console.log('nope, element is not found');
}
This clicks the icon every 1 second in Chrome when entered in console.
When I have my webview set to Google.com and have the following line, it still finds the element, but gives the error mentioned earlier again:
window.webContents.executeJavaScript('var element=document.getElementsByClassName("gsst_a");void 0!==element&&null!=element?(console.log("yep, element is found"),console.log(element),console.log(element[0]),a=setInterval(function(){document.getElementsByClassName("gsst_a")[0].click()},1e3)):console.log("nope, element is not found");', true);
console.log(element) gives: HTMLCollection(0)
console.log(element[0]) gives: undefined
Why can I enter the js in my normal browser, but not in Electron webview?
To answer my own question.
The event's referring to the web page in the BrowserWindow, not the webview within that. So the element doesn't exist in the scope I was looking in, and I needed to do something similar within the BrowserWindow.
Code:
<html>
<head>
<style type="text/css">
* { margin: 0; }
#browserGoogle { height: 100%; }
</style>
</head>
<body>
<webview id="browserGoogle" src="https://google.com"></webview>
<script>
const browserView = document.getElementById('browserGoogle')
browserView.addEventListener('dom-ready', () => {
const browser = browserView.getWebContents()
browser.setDevToolsWebContents(devtoolsView.getWebContents())
browser.webContents.executeJavaScript('var element=document.getElementsByClassName("gsst_a");void 0!==element&&null!=element?(console.log("yep, element is found"),console.log(element),console.log(element[0]),a=setInterval(function(){document.getElementsByClassName("gsst_a")[0].click()},1e3)):console.log("nope, element is not found");', true);
})
</script>
</body>
</html>
In recently, Google upgrade its cast receiver to version V3.
There is a simple demo in google demo page as below:
<html>
<head>
</head>
<body>
<cast-media-player id="player"></cast-media-player>
<style>
#player {
--theme-hue: 210;
--splash-image: url("my.png");
}
</style>
<script type="text/javascript" src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js">
</script>
<script>
const context = cast.framework.CastReceiverContext.getInstance();
const playerManager = context.getPlayerManager();
// intercept the LOAD request to be able to read in a contentId and get data
playerManager.setMessageInterceptor(
cast.framework.messages.MessageType.LOAD, loadRequestData => {
if (loadRequestData.media && loadRequestData.media.contentId) {
return thirdparty.getMediaById(loadRequestData.media.contentId)
.then(media => {
if (media) {
loadRequestData.media.contentUrl = media.url;
loadRequestData.media.contentType = media.contentType;
loadRequestData.media.metadata = media.metadata;
}
return loadRequestData;
});
}
return loadRequestData;
});
// listen to all Core Events
playerManager.addEventListener(cast.framework.events.category.CORE,
event => {
console.log(event);
});
const MyCastQueue = class extends cast.framework.QueueBase {
initialize(loadRequestData) {
const media = loadRequestData.media;
const items = [];
items.push(myCreateItem(media)); // your custom function logic
const queueData = new cast.framework.messages.QueueData();
queueData.items = items;
return queueData;
}
nextItems(itemId) {
return [myCreateNextItem()]; // your custom function logic
}
};
const playbackConfig = new cast.framework.PlaybackConfig();
// Sets the player to start playback as soon as there are five seconds of
// media contents buffered. Default is 10.
playbackConfig.autoResumeDuration = 5;
const myCastQueue = new MyCastQueue(); // create instance of queue Object
context.start({queue: myCastQueue, playbackConfig: playbackConfig});
</script>
</body>
</html>
From:
https://developers.google.com/cast/docs/caf_receiver_features#styling-the-player
But when I debugged it, there is an error that 'thirdparty' is undefined.
Does anyone can show me how to create a CAF receiver?
This is example code. You are supposed to provide those extra functions yourself.
The example shows how to override the queueing system to perform receiver based queueing. It isn't a great example IMHO as it doesn't provide an example of asynchronously fetching server based queues.
If you don't need receiver managed queues you can start with the minimal CAF receiver shown on the previous page. This is enough to get the remote debugger working which is at least one good reason to have a custom receiver.
https://developers.google.com/cast/docs/caf_receiver_basic
I've made a more complete example. https://gist.github.com/jcable/45c65a72074763a9ec30ddb1ff217517
I have a number of Scala function calls in my template file, unfortunately they are getting automatically called during template loading for some reason. How can I prevent those from being called?
My intention is they are called only after certain click events. And I would get a huge performance increase during template load (26s vs 3s).
I have a DataLoader Java object, which is being called from template and does the reading of values from database. The relevant parts of DataLoader:
public void SetAllowLoading() {
System.out.println("DataLoader SetAllowLoading > ");
allowLoading = 1;
}
public void SetDisAllowLoading() {
allowLoading = 0;
}
public void debugdebug(String text) {
System.out.println(text);
}
public List<Double> loadAreaLengthData() {
List<Double> areaLengthArray = new ArrayList<Double>();
System.out.println("DataLoader OUT loadAreaLengthData > ");
if (allowLoading > 0) {
System.out.println("DataLoader IN loadAreaLengthData > ");
areaLengthArray.add(Component.getPipeLenghtsAccordingToRelativeFloorAreaMeters(0, 11));
areaLengthArray.add(Component.getPipeLenghtsAccordingToRelativeFloorAreaMeters(11, 21));
areaLengthArray.add(Component.getPipeLenghtsAccordingToRelativeFloorAreaMeters(21, 31));
areaLengthArray.add(Component.getPipeLenghtsAccordingToRelativeFloorAreaMeters(31, 41));
areaLengthArray.add(Component.getPipeLenghtsAccordingToRelativeFloorAreaMeters(41, 51));
}
return areaLengthArray;
}
If loading is not allowed, the loading method doesn't read from database.
Then the necessary parts from template (pipeIndex.scala.html)
$("#info2").on("click", function() {
if($(this).hasClass("selected")) {
$("#info2").html('#Messages("consequence.floor") (m<sup>2</sup>/m)');
#loader.debugdebug("debugFloorAreaDESELECT");
deselectArea();
} else {
$(this).addClass("selected");
$(".chartsarea").slideFadeToggle(function() {
#loader.debugdebug("drawFloorAreaChart()");
var sarea = new Array();
var i = 0;
var number = 0;
#for(s <- loader.loadAreaLengthData) {
number = Math.round(#s);
if (!isNaN(number))
sarea[i] = Math.round(number / 1000);
else
sarea[i] = 0;
i++;
}
var ticksArea = ["0-10", "10-20", "20-30", "30-40", "40-50"];
areaObj = {
// Only animate if we're not using excanvas (not in IE 7 or IE 8)..
animate: !$.jqplot.use_excanvas,
seriesDefaults:{
renderer:$.jqplot.BarRenderer,
pointLabels: { show: true }
},
axes: {
xaxis: {
renderer: $.jqplot.CategoryAxisRenderer,
ticks: ticksArea
}
},
highlighter: { show: false }
}
plot3 = $.jqplot('chartarea', [sarea], areaObj);
$("#info2").focus();
$("#info2").html('#Messages("consequence.hide.floor")');
});
}
return false;
});
function deselectArea() {
$(".chartsarea").slideFadeToggle(function() {
$("#info2").removeClass("selected");
});
}
My question is how that "on" click handler is called automatically at every pageload? How can I prevent the calling during pageload?
I used for showing/hiding functionality the example at jsfiddle: anotherjsfiddle However I used multiple click handlers (here is shown only for info2 element). And I changed that jquery "live" event method to "on", because "live" is deprecated.
If I understand you right you are asking why the debug messages (e.g. #loader.debugdebug("debugFloorAreaDESELECT");) are getting called on each site load and not only on click..?
This is because Play templates are getting rendered on the server side, i.e. as soon as template.render() will get called in the controller. This affects all the the Scala parts in the template (i.e. everything starting with #)
If you would like to debug on the client side you could use JavaScripts console.log()
Although previous answer describes it I'll use a phrase which I repeated several times already:
Play template is ServerSide technology, so it's rendered during Play pass (i.e. if your views will be cached for 1 month you will have 1 month old values in the view), JavaScript is ClientSide. That means - that you can't mix it like this.
I've very nearly finished developing a HTML5 app with Appcelerator, and I have one function left to add which is a function to allow the user to take a photo when sending the client a message through the app. There is a specific div that is displayed which contains the message form, and I'd like the user to be able to take a photo with their phone, and have it automatically attached to the message which is then submitted to our server.
However, after hunting around I'm stumped as to how to get it working. While the API shows the Javascript to make the camera work, I can't seem to access it, and I don't know where the API call should be located. Does it go in the app.js file, or it's own file, or does it not really matter where it's called? Any help/advice would be appreciated on this.
EDIT
Thanks to Dragon, I've made the following changes to my code:
index.html
<div class="col-square">
<i class="fa fa-camera fa-squareBlock"></i><br />Take Photo
</div>
<script type="text/javascript">
Ti.App.addEventListener("app:fromTitanium", function(e) {
alert(e.message);
});
</script>
app.js
Ti.App.addEventListener('app:fromWebView', function(e){
Titanium.Media.showCamera({
success:function(event)
{
var image = event.media;
var file = Titanium.Filesystem.getFile(Titanium.Filesystem.applicationDataDirectory,"userImg.jpg");
file.write(image);
var data = file.nativePath;
Ti.App.fireEvent('app:fromTitanium', {message: "photo taken fine"});
},
cancel:function()
{
},
error:function(error)
{
var a = Titanium.UI.createAlertDialog({title:'Camera'});
if (error.code == Titanium.Media.NO_CAMERA)
{
a.setMessage('Please run this test on device');
}
else
{
a.setMessage('Unexpected error: ' + error.code);
}
a.show();
},
showControls:false, // don't show system controls
mediaTypes:Ti.Media.MEDIA_TYPE_PHOTO,
autohide:false // tell the system not to auto-hide and we'll do it ourself
});
});
However, in this case the the button opens the camera up fine. But, when the photo is taken, and selected, it returns to the screen but nothing happens. It then gives this error in the debug - "Ti is undefined". When I then define Ti, it will return "App is undefined".
The peculiar thing with this is that if I remove the code that will handle data being sent from app.js to the webview, it works fine, even though the code to open the camera from the webview is near enough the same code?
here is what you can do :
Inside your webview call and Event and write the event listener inside the parent of webview.
Something like this will go inside webview:
<button onclick="Ti.App.fireEvent('app:fromWebView', { message: 'event fired from WebView, handled in Titanium' });">fromWebView</button>
Followed by something like this inside the parent js of webview :
Ti.App.addEventListener('app:fromWebView', function(e) {
alert(e.message);
//Here you can call the camera api.
})
for sending the image to the webview follow the reverse process.
Don't forget to check the Docs.
Hope it helps.
I always avoid having Event Listeners in the 'web' world in Titanium. When you call 'fireEvent' from your webview, you are crossing the bridge from the webview sandbox to the native world. That is where Titanium takes the picture and saves it to the file system. For Titanium to tell the webview it is finished, I recommend evalJS. Much more reliable.
Here is an example using the Photo Gallery instead of the Camera. Much easier to test in a simulator. Just replace Titanium.Media.openPhotoGallery with Titanium.Media.showCamera to use the camera instead.
app.js
var win = Ti.UI.createWindow({
background : 'white',
title : 'camera test'
});
var webview = Ti.UI.createWebView({
url : 'test.html'
});
win.add(webview);
win.open();
Ti.App.addEventListener('choosePicture', function(e) {
var filename = e.filename;
Titanium.Media.openPhotoGallery({
success : function(event) {
var image = event.media;
var file = Titanium.Filesystem.getFile(Titanium.Filesystem.applicationDataDirectory, filename);
file.write(image);
var full_filename = file.nativePath;
webview.evalJS('photoDone("' + full_filename + '");');
},
cancel : function() {
},
error : function(error) {
var a = Titanium.UI.createAlertDialog({
title : 'Camera'
});
if (error.code == Titanium.Media.NO_CAMERA) {
a.setMessage('Please run this test on device');
} else {
a.setMessage('Unexpected error: ' + error.code);
}
a.show();
},
showControls : false, // don't show system controls
mediaTypes : Ti.Media.MEDIA_TYPE_PHOTO,
autohide : true
});
});
test.html
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My HTML Page</title>
<style>
body {
padding-top: 20px
}
</style>
<script>
var photoNumber = 0;
function doShowCamera() {
photoNumber++;
Ti.App.fireEvent('choosePicture', {
filename : photoNumber + ".png"
});
}
function photoDone(filename) {
var img = document.getElementById('myPhoto');
img.src = filename;
}
</script>
</head>
<body>
<img id="myPhoto" width="300" height="400"/>
<input type="button" value="Show Pictures" onclick="doShowCamera();" />
</body>
</html>
The Ti.App.addEventListener call can be anywhere in your Titanium code (not in your webviews) as long as it is only run once. app.js is as good a place as any to run it.