SketchUp WebDialog does not execute JS and action_callback - javascript

I have the following index.html:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<script>
function close() {
window.location.href = 'skp:closeDialog#someString';
}
</script>
</head>
<body>
<p id='data'>Text will go here...</p>
<button onclick="close()">Done</button>
</body>
</html>
And I have the following ruby script:
dlg = UI::WebDialog.new( "Hello World", "", false, 500, 300, 433, 234, false )
dlg.set_file( File.dirname(__FILE__) + 'index.html' )
dlg.add_action_callback( "closeDialog" ) {|dialog, params|
data = dlg.get_element_value( "data" )
puts data
dlg.close
}
dlg.show {
dlg.execute_script( "document.getElementById('data').innerHTML = 'Hello World!'" )
}
For some reason neither the script that should change the value of the <p> element nor the callback action does not work. The strange thing is, that if I change the JS to
dlg.execute_script( "alert('Hello World!')" )
then the alert message is shown upon start.
Any help is appreciated, thanks!

For the block in .show - the HTML might not be ready yet. Have a look at this article: https://github.com/thomthom/sketchup-webdialogs-the-lost-manual/wiki/window.onload
Given this script:
module TT_Test
#d = nil
#d = UI::WebDialog.new('On Load Test')
#d.add_action_callback('onload') { |dialog, params|
puts '>> window.onload'
}
#d.set_html('Onload Test')
def self.onload
#d.show {
puts '>> ruby block'
}
end
end
Windows:
load 'test/webdialog.rb'
true
TT_Test.onload
true
>> ruby block
>> window.onload
OSX:
> load 'test/webdialog.rb'
true
>> window.onload
> TT_Test.onload
true
Observe that the block in show isn't reliable in terms of the onLoad event.
What I always do is use jQuery's ready event and send a callback to Ruby - that way I know the dialog's content is ready. And only in that callback will I perform setup etc.
As for dlg.get_element_value( "data" ) - the data element in your code is a <p>: <p id='data'>Text will go here...</p>. get_element_value is working only for form elements.
Note that in your add_action_callback you have the params argument - that will contain the data after # in skp:closeDialog#someString.

Related

Use jquery with ejs template

I'm trying to use jquery in an ejs template to make an input auto complete using an array sent by the server to the template.I get the following error :
ReferenceError: /var/www/html/DM/views/formulaire.ejs:8
6| <title>Formulaire </title>
7| </head>
>> 8| <%
9| $( "#depart" ).autocomplete({
10| source: autoComp
11| });
$ is not defined
I made some researches and found out that you can't use client side javascript (jquery) with server side javascript (ejs), but i didn't find any solution.
Here is the code :
<!DOCTYPE html>
<html lang="fr">
<head>
<script src="https://code.jquery.com/jquery-3.1.1.js"></script>
<meta charset="UTF-8">
<title>Formulaire </title>
</head>
<body>
<script>
$( "#depart" ).autocomplete({
source: autoComp
});
</script>
<form action="/result" method="post">
Départ:<input type="text" name="depart" id="depart"><br>
Arrivée: <input type="text" name="arrivee"><br>
<input type="submit" value="Chercher un itinéraire">
</form>
<%
if(erreur){
%> <p>Erreur lors de la saisie des stations</p>
<%
}
%>
</body>
</html>
Thank you for your help
EDIT : No error anymore but auto completion doesn't work.
You need to put client side code in a <script> tag
Change
<%
$( "#depart" ).autocomplete({
source: autoComp
});
%>
To
<script>
$( "#depart" ).autocomplete({
source: autoComp
});
</script>
And put it inside the head or body
I assume you were creating the array in the action and passing it to the view. You then have to go one step further and pass it from the server-side view engine, to the actual browser engine.
<script>
let autoComp = JSON.parse( `<%= JSON.stringify( autoComp ) %>` );
What this does:
tells the EJS engine to render the array as a JSON string
(note the backticks, you need some kind of quotation mark because it's a string)
The browser-side JS engine then runs that string through JSON.parse
Obviously this ability only works one-way; to get a variable from the browser to the EJS you need to commit an action (POST/GET).
You may also need some substitutions to make the string play nice, such as:
function fixForJSON(val) {
return val.replace(/'/g, '&apos;').replace(/\\/g, '\\\\');
}
function fixForDisplay(val) {
return val.replace(/&apos;/g, "'").replace(/\\\\/g, '\\');
}
If you really want to do this:
<%
$(selector).doStuff();
You need to pass the JQuery object itself, into the template variables.
E.g. in your node code:
const jsdom = require('jsdom');
const jquery = require('jquery');
const { JSDOM } = jsdom;
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
const $ = jquery(dom.window);
global.jq = $;
Then in your specific route action (still node code)
var locals = { // the var you are sending to the EJS template
jq: global.jq,
And finally in your EJS you can:
const $ = jq;
var testDiv = $('<div>').html('hello jquery in the template').appendTo('body');
console.log($('body').html());

Evaluate JavaScript code using PhantomJS

I'm trying to use PhantomJS to run some JavaScript from an ad server and parse out the response object for information about the ad that was served. This is readily available from Firefox/Chrome Dev Tools, but I need to access that same information from a server. I can get Phantom to run, but as soon as I try to include external JS page.includeJs("http://www.someadserver.com/config.js?nwid=1909"and access variables that are set via that external JS someadserver.setup({ domain: 'http://www.someadserver.com'}); it fails miserably. Any help would be greatly appreciated.
"use strict";
var page = require('webpage').create();
page.content = `
<html>
<head>
<script>
someadserver.setup({ domain: 'http://www.someadserver.com'});
</script>
<title>The title of the web page.</title>
</head>
<body>
<div class="ads_leaderboard">
<!-- position: leaderboard -->
<script>
someadserver.call( "std" , {
siteId: 100806,
pageId: 656377,
target: ""
});
</script>
</div>
<div id="foo">this is foo</div>
</body>
</html>`;
var title = page.evaluate(function (s) {
page.includeJs(
"http://www.someadserver.com/config.js?nwid=1909",
function() {
return document.querySelector(s).innerText;
}, 'title');
});
console.log(title);
phantom.exit(1);
EDIT 1:
I've simplified my script (below) and I'm clearly missing something. When I run the script below using bin/phantomjs /srv/phantom_test.js the only output I get is end page. Why aren't the rest of the console.log statements executing?
"use strict";
var page = require('webpage').create();
page.content = "<html>" +
"<head>" +
" <title>The title of the web page.</title>" +
"</head>" +
"<body>" +
"<div id=\"foo\">this is foo</div>" +
"</body>" +
"</html>";
page.includeJs("http://www.someadserver.com/config.js?nwid=1909", function() {
console.log('start function');
var title = page.evaluate(function(s){
return document.querySelector(s).innerText;
}, 'title');
console.log(title);
console.log('end function');
});
console.log('end page');
phantom.exit();
The stuff inside page.evaluate is executed in the context of a target page as if that code was inside of that page.
page.includeJS(...) will not be a valid code on a someadserver.com.
The correct way is vice versa:
page.includeJs("http://www.someadserver.com/config.js?nwid=1909", function() {
var title = page.evaluate(function(s){
return document.querySelector(s).innerText;
}, 'title');
});
Your first snippet doesn't work, because assigning a value to page.content immediately executes it. So, someadserver.setup(...) is executed immediately as if the page is actually loaded, but at this time the page.includeJs(...) call hasn't happened yet.
You should be able to actually include script that you want to run inside of the page source:
var content = `
<html>
<head>
<script src="http://www.someadserver.com/config.js?nwid=1909"></script>
<script>
someadserver.setup({ domain: 'http://www.someadserver.com'});
</script>
<title>The title of the web page.</title>
</head>
<body>
<div class="ads_leaderboard">
<!-- position: leaderboard -->
<script>
someadserver.call( "std" , {
siteId: 100806,
pageId: 656377,
target: ""
});
</script>
</div>
<div id="foo">this is foo</div>
</body>
</html>`;
page.setContent(content, "http://www.someadserver.com/");
var title = page.evaluate(function (s) {
return document.querySelector(s).innerText;
}, 'title');
console.log(title);
phantom.exit();
I've also used page.setContent in order to set the domain, so that further script loading is not broken. When a page source is assigned to page.content, the default URL is actually about:blank and you don't want that.
Further problems with your first snippet:
The beginnings and ends of page.evaluate and page.includeJs don't match up!
There is no page inside of page.evaluate, because the page context is sandboxed!
Your second snippet doesn't work, because page.includeJs(...) is a asynchronous function (it has a callback!), so you're exiting the script too early.

How to load different html files in QUnit?

I'm using QUnit for unit testing js and jquery.
My HTML looks like this:
<!DOCTYPE html>
<html>
<head>
<title>QUnit Test Suite</title>
<script src="../lib/jquery.js"></script>
<link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.16.0.css" type="text/css" media="screen">
<script type="text/javascript" src="http://code.jquery.com/qunit/qunit-1.16.0.js"></script>
<!--This is where I may have to add startPage.html--->
<script src="../login.js"></script>
<script src="../test/myTests.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</body>
</html>
Currently, I'm adding login.js as shown and I'm getting references correctly to objects defined in login.js.
However, functions in login.js contains references to some dom elements defined in startPage.html which is located elsewhere.
So, if I say $('#login-btn'), it is throwing an error. Is there any way to fix this?
Can I
(a) refer to startPage.html to my qunit page given above?
(b) refer to or load startPage.html in the file where I'm running tests (myTests.js):
QUnit.test( "a test", function( assert ) {
assert.equal( 1, "1", "String '1' and number 1 have the same value" );//works
assert.equal( login.abc, "abc", "Abc" );//works with attributes
assert.equal(($("#userid").val()),'', 'Userid field is present');//fails
assert.equal( login.ValidUserId(), true, "ValidUserId" );//fails with functions
});
Does QUnit provide any method to load Html/php files so they'll be defined prior to testing. Like 'fixtures' in jasmine?
EDIT: Please also tell what to do in case I have startPage.php
There are a couple of ways you can do this. The simplest is just to use the built-in QUnit "fixtures" element. In your QUnit HTML file, simply add any HTML you want in the div with the id of qunit-fixture. Any HTML you put in there will be reset to what it was on load before each test (automatically).
<html>
...
<body>
<div id='qunit'></div>
<div id='qunit-fixture'>
<!-- everything in here is reset before each test -->
<form>
<input id='userid' type='text'>
<input id='login-btn' type='submit'>
</form>
</div>
</body>
</html>
Note that the HTML in the fixture doesn't really have to match what you have in production, but obviously you can do that. Really, you should just be adding the minimal necessary HTML so that you can minimize any side effects on your tests.
The second option is to actually pull in the HTML from that login page and delay the start of the QUnit tests until the HTML loading is complete:
<html>
<head>
...
<script type="text/javascript" src="http://code.jquery.com/qunit/qunit-1.16.0.js"></script>
<script>
// tell QUnit you're not ready to start right away...
QUnit.config.autostart = false;
$.ajax({
url: '/path/to/startPage.html',
dataType: 'html',
success: function(html) {
// find specific elements you want...
var elem = $(html).find(...);
$('#qunit-fixture').append(elem);
QUnit.start(); // ...tell QUnit you're ready to go
}
});
</script>
...
</head>
...
</html>
Another way to do this without using jquery is as follows
QUnit.config.autostart = false;
window.onload = function() {
var xhr = new XMLHttpRequest();
if (xhr) {
xhr.onloadend = function () {
if(xhr.status == 200) {
var txt = xhr.responseText;
var start = txt.indexOf('<body>')+6;
var end = txt.indexOf('</body>');;
var body_text = txt.substring(start, end);
var qunit_fixture_body = document.getElementById('qunit-fixture');
qunit_fixture_body.innerHTML = body_text;
}
QUnit.start();
}
xhr.open("GET", "index.html");
xhr.send();
} else {
QUnit.start(); //If getting the html file from server fails run tests and fail anyway
}
}

How to call Onload function in the Main page as well as #include file inner.jsp page

I have two pages one is the main page and the another one is the inner page:
Page names: main.jsp , sidebar.jsp
I want to call the onload function on both of these pages. Is that possible. If yes How?
Below is the code for main.jsp:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%# include file="/pages/common/init.jsp"%>
<%# taglib prefix="sx" uri="/struts-dojo-tags"%>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>J.C. Taylor - Broker Website</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<link rel="stylesheet" href="/css/default.css" media="screen" type="text/css" />
</head>
<body onload="prepopulateFields();load(17);">
<s:form name="continue" id="continue_id" action="continue" method="POST" validate="true" enctype="multipart/form-data">
<div id="wrapper">
<div id="main">
<div id="sidebar">
<%# include file="/pages/common/sidebar.jsp"%>
<span class="clearIt"></span>
</div>
The sidebar.jsp is:
<body onload="setSelected();">
//Some static content here
</body>
So Basically I want is to call prepopulateFields() Javascript method which belongs to onload() event of the main .jsp page and setSelected() which belongs to onload() method of the sidebar.jsp symulatneously and separately.
I know I can call the setSelected(); method inside the prepopulateFields() method but that I dont want to do. All I want is when the page is loaded both the onload functions should be called separately.
If you have some suggestions please do let me know!
I know I am being little bit ridiculous here but if I could do that My job will be very easy.
i don't think you can call more than one onload function.
best way is to call the method from already called function
function prepopulateFields(){
if //condition which check the current page where you want other onload function
setSelected();
}
<body onload="prepopulateFields();load(17);">
</body>
You cannot nest HTML <body> elements, it would only malform HTML.
Best is to put it as a <script> at the bottom of sidebar.jsp.
<script type="text/javascript">setSelected()</script>
If you use firebug to inspect the rendered html page of main.jsp. You would see there is only one < body > element. The < body > element in your sidebar.jsp is not rendered since it will malform HTML as html or body not allowed in included jsp.
Be careful that the included file does not contain <html>, </html>, <body>, or </body> tags
The solution is:
either put your setSelected() into main.jsp body onload event if the sidebar.jsp is always loaded;
or do as BalusC suggested.
window.onload = codeAddress; should work. Here's a demo. And the full code:
<script type="text/javascript">
function codeAddress() {
alert('ok');
}
window.onload = codeAddress;
</script>
Put the class definition in the parent jsp, and instantiate as many onloads as you need in the includes.
<SCRIPT>
// this portion was placed above the Class definition for convenience.
// make sure the Class definition gets loaded first in your code.
new OnLoad(function(){
alert("document loaded");
},100);
</SCRIPT>
...
<SCRIPT>
// Class Definition
OnLoad = function(taskFunction,miliseconds) {
var context = this;
context.cnt = 0;
context.id = null;
context.doTask=taskFunction;
context.interval = function() {
if(document.readyState == "complete"){
try{ context.stop();} catch(e){ throw new Error("stop error: " + context.id); }
try{ context.doTask();} catch(e){ throw new Error("load error: " + context.id); }
}
};
context.start = function(timing) {
if(context.id && context.id!=null)
context.stop();
context.cnt=0;
context.id=setInterval(context.interval,timing);
};
context.stop = function() {
var _id = context.id;
clearInterval(context.id);
context.id=null;
};
context.start(miliseconds ? miliseconds : 100);
};
</SCRIPT>

How might I gain write access to outer objects from a jQuery AJAX callback method?

Your help would be very much appreciated. I do not understand why the following jQuery code does not work:
function saveChanges(obj, n, id) {
var t = "content to send to server";
$.post("http://localhost:8080/Content.do?method=update",{
value: t,
key: id
},function(result){
alert(result); //WORKS
alert("INNER"+$(obj).parent().parent().html());//WORKS
$(obj).parent().parent().after('<div>dddddd</div>').remove();//FAILS ALL
alert(id); //FAILS
alert("stop"); //FAILS
});
}
My conclusion so far is: inside this callback method function(result) I do have READ access to the outer object (here $(obj)) but I do NOT have WRITE access. As soon as I am doing a write the function fails from that point on to the rest of the function.
Is this conclusion correct? If someone knows about a good tutorial who explains this concept I would be greatful. (Object access with jQuery and their scopes...)
thank you very much
There's something else wrong. In an isolated test case, I could not duplicate your error
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<title>test</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
//alert = console.log;
$(function(){
function saveChanges(obj, n, id) {
var t = "content to send to server";
$.post("test.php",{
value: t,
key: id
},function(result){
alert(result); //WORKS
alert("INNER"+$(obj).parent().parent().html());
$(obj).parent().parent().after('<div>dddddd</div>').remove();
alert(id);
alert("stop");
});
}
saveChanges( '#test', 1, 'test' );
});
</script>
</head>
<body>
<div>
<div>
<div id="test">
just a test
</div>
</div>
</div>
</body>
</html>
And my test.php
<?php
echo json_encode( $_POST );
?>
I get four alerts
{"value":"content to send to server","key":"test"}
INNER
<div>
<div id="test">
just a test
</div>
</div>
test
stop
It sure has nothing to do with reading or writing "to" a variable. Something else must be going wrong on the first line you marked as "FAIL". You should check this by enabling Firebug in Firefox (if you don't have Firebug installed, drop everything and do that now) and running the sript again.

Categories