Access Javascript class object with Qt runJavaScript function - javascript

As I'm new into javascript/HTML and recently I started a project using Qt QWebEngineView. I've been stuck for some time on finding the best way to share data from the C++ program to Javascript. So far the only way I was able to send data to the Javascript program is by using the QWebEnginePage::runJavaScript function. I also have seen that there is the possibility of using QWebChannels described here but I prefer the QWebEnginePage::runJavaScript for its simplicity.
The only issue I've had so far with the runJavaScript method has been that in order to write a variable, this needs to be defined in the HTML file, I'm actually not 100% sure if this is the only way to do it but it has been the only way it worked for me. My current scenario looks something like this:
In the HTML file:
...
<div id="latitude" ></div>
<div id="longitude"></div>
<div id="heading" "></div>
...
In the C++ file:
...
double Latitude = 44.244; Longitude = 10.3; Heading = 90;
QString jsQuery = QObject::tr(
"document.getElementById('latitude').innerHTML =%1; "
"document.getElementById('longitude').innerHTML =%2; "
"document.getElementById('heading').innerHTML =%3;"
).arg(Latitude).arg(Longitude).arg(Heading));
mapWebView->page()->runJavaScript(jsQuery);
...
With this setup, I'm able to write the variables in the Javascript/HTML side from the C++ code. Because with this solution I need to create as many individual variables on the HTML file for each value I want to send, I wanted to ask if it is possible to instead of using individual variables use a class object or a JavaScript Object. I create a class with some methods to write the class members like the one below:
In the js file:
...
export default class PositionState{
setPosition(latitude = 0.0, longitude = 0.0, heading = 0.0){
this.Latitude = latitude;
this.Longitude = longitude;
this.Heading = heading;
}
getLatitude(){
return this.Latitude;
}
getLongitude(){
return this.Longitude;
}
getHeading(){
return this.Heading;
}
}
var obj = new PositionState();
...
With this solution, if I create an object of the PositionState class and call the function obj.setPosition(44,10.45) from the javascript file the object's class members are set correctly, But if I try it from the C++ I get some errors.
double Latitude = 44.244; Longitude = 10.3; Heading = 90;
Qstring jsQuery = QObject::tr(
"obj.setPosition(%1, %2, %3);"
).arg(Latitude).arg(Longitude).arg(Heading));
mapWebView->page()->runJavaScript(jsQuery);
If only define the obj in the Javascript file I get the error js: Uncaught ReferenceError: obj is not defined. And if I define a variable in the HTML file with the Id="obj" and run the same script I get the error js: Uncaught TypeError: obj.setPosition is not a function, the error occurs even if instead of just obj.setPosition i use document.getElementById('obj').setPosition.
So for what I get with my little to none HTML/Javascript knowledge is that the HTML file is not aware of my class definition, thus not recognizing the setPosition method. So my question is if there's a way to from the C++ code write the class object.
I also tried using a JavaScript Object like var Position = {Latitude: 0, Longitude: 0, Heading: 0} and from the C++ code run the a script with the QString Position = {Latitude: 40, Longitude: 9, Heading: 20}; but was also no able to change the Position object properties.
Any help will be really appreciated, thanks.

Honestly, you seem to be overcomplicating things...
Your "simplier" approach is nowhere as simple as using the QWebChannel to share objects between the JS and the C++ world. Additionally, you are losing a lot of functionality by going another way: connecting to Qt signals in JS, overloading methods, reading and setting properties directly from JS...
Just use the QWebChannel to register QObjects into the QWebEnginePage, load QWebChannel into your HTML page, setup the connection, and that's it!
This article has an excelent and simple explanation (with code snippets) on how to do this.
There is no need for you to reinvent the wheel.

Related

How do I call upon my settings (array) listed in html using prototype in my javascript file?

I'm relatively new to javascript development and have tried to construct my own framework. I've been trying to convert my standard javascript functions to a framework. However I've been stuck at a relatively simple (I think), but nowhere explained issue.
In my HTML I call on the settings I want to use in my JavaScript (the user has to be able to edit them in html, not in js as it will be minified).
<script>
$(document).ready(function(){
var settings = {authenticFilter: 'on',
randomizeHeaders: {state: 'on', topHeader: 'h1', bottomHeader: 'h2'},
};
var papery = new Papery(settings);
});
</script>
But I can't seem to figure out how to use the settings I have given to my framework in the javascript file.
I know I can call upon this.settings = settings; in my javascript file, but in an if statement below, I want make use of specific settings in the given settings. However this option and several other ways I've tried to use the settings won't work. Can I even use the settings I give to the prototype from my html in an array?
var Papery = function (settings) {
Papery.authenticFilter = function() {
if (Papery.authenticFilter == 'on'){
$("img").addClass("authenticFilter");
$(".authenticFilter").css({"filter": "sepia(80%) grayscale(1) contrast(1) opacity(0.7)", "-webkit-filter": "sepia(80%) contrast(1) opacity(0.7)"});
}
}
}
You are using Papery.authenticFilter instead of settings.authenticFilter. Keep in mind that in usual libraries, a bunch of default settings are set in initialization in case they are not defined on the settings passed on the constructor.

Accessing prototype function from another Javascript file keeps returning 'undefined'

I've dug into this for a couple of hours, looking at Javascript prototype accessing another prototype function, Accessing a Javascript prototype function, Trigger one prototype function from another (including itself), Cannot call prototype method from another function and around 3-4 other similar questions, and thought "ok, that doesn't seem so bad" and went to implement a solution (or three) to my particular problem. Or so I had thought!
I have a JS file (compiled from Typescript) that contains an AppComponent class and several methods with it (shortened version focused on my specific trouble follows):
AppComponent = (function () {
function AppComponent() {
var _this = this;
this.gridNo = '1';
//... and so on...
}
AppComponent.prototype.MenuSelect = function (link) {
this.tabCount = 0;
this.tables = [];
utils_1.Logging(' MenuSelect: ' + JSON.stringify(link));
var grids = link.grids;
this.ws.emit('C:GDRDN', { ds: grids });
// build up some HTML to make a table of data and return it to
// the caller
return "grid stuff!";
};
.
.
.
}
The above is loaded up into Angular 2/Node (written by another co-worker) and works just fine in the context it was written: ie it displays tables of data ('grids') when called from other components written by that co-worker in TypeScript.
But when I am generating a menu and try to access the MenuSelect prototype directly from another, 'normal', JS file like so...
function createWHeelNavigation() {
basic.navigateFunction = function () {
var grids_selected = [ 4, 11 ];
var appcomp = new AppComponent();
output = appcomp.MenuSelect(grids_selected);
// minified.js function to add children content to a DOM element
$("grid_container").add(output);
}
// other navigation menu functions...
}
createWHeelNavigation();
...I continue to get "Uncaught ReferenceError: AppComponent is not defined" when I click on that particular 'basic' menu item, even though according to what I've read in SO and elsewhere that creating a 'new' instance of the object is the way to access its prototype methods.
So before I pull my hair out and go back to rocking in the corner of my office, whispering "mommy...", I thought I would pass this around to you fine people to see where I am going wrong. I have a niggling suspicion I should be using 'this' somewhere, but my eyes are crossing, and wish to be pointed in the right direction. Thanks for your time!
I continue to get "Uncaught ReferenceError: AppComponent is not defined" w
A common JavaScript ordering issue. Make sure you are loading your js / ts in the right order.
More
Please use modules if possible. https://basarat.gitbooks.io/typescript/content/docs/tips/outFile.html

Store and retrieve Google Dart objects in JavaScript library containers

Store and retrieve Google Dart objects in JavaScript library containers
In a Dart application I am using an external JavaScript library to do various matrix calculations.
The specific functionality of the library is not important, what it's important is that I need to store and retrieve Dart object that I put in the matrix.
Dart Class - Lets image i have a dart object that which has a parameter called name
MyDartClass mydc = new MyDartClass(something, something);
mydc.name;
// Everything works as planned
Storing
matrix = js.context.matrix
matrix.cell(1,1).store("thing", new MyDartClass(something, something));
Retrieving
matrix.cell(1,1).has_object_of_type("thing");
// true
MyDartClass mydc = matrix.cell(1,1).retrieve("thing");
Do something with the object
mydc.name;
// Exception: The null object does not have a getter 'name'.
// NoSuchMethodError : method not found: 'name'
// Receiver: null
// Arguments: []
Does the library really work?
Yes it does. I have done the exact same thing in pure javascript many times and there are plenty of test to test the behaviour ( in Javascript )
Is Dart Broken?
When I try to use a javascriptified Hash to do the same behavoiur it works like a charm.
var options = js.map({ 'dart' : new MyDartclass(something, something));
var y = options["dart"];
js.context.console.log(y.name);
// Name is printed
What do you get out from the retrieve?
It seems that I get some kind of Dart Proxy
MyDartClass mydc = matrix.cell(1,1). retrieve("thing");
js.context.console.log(mydc);
DartProxy {id: "dart-ref-20", port: DartSendPortSync}
id: "dart-ref-20"
port: DartSendPortSync
__proto__: DartProxy
I belive that the lib stores the objects, deep down, in a hash map. But it seems like when I retrieve the object into the Dart I get something, but not in a way that I can work with it. So i need help since I don't know how to make it work.
Do I need to de-proxify the object?
Perhaps it IS a Dart bug when you try to retrieve objects from hashes inside objects
Perhaps I missunderstod everything that this is not suppose to work.
Passing and retrieving Dart objects inside the same scope is working. There's the following test case in the tests of js-interop to proove it :
test('retrieve same dart Object', () {
final date = new DateTime.now();
js.context.dartDate = date;
expect(js.context.dartDate, equals(date));
});
However there seems to be an issue with multiple scopes (and multiple event loops as well). There is no way to retain a dart object for now. So your dart object reference goes away at the end of scope. Here's a simple test case that fails :
test('retrieve same dart Object', () {
final date = new DateTime.now();
js.scoped(() {
js.context.dartDate = date;
});
js.scoped(() {
expect(js.context.dartDate, equals(date));
});
});
Please file an issue.

Faking/Mocking a Javascript object for Unit Tests

Quite a large portion of my work day to day involves working with Dynamics CRM and writing JS to extend the functionality on the forms.
Most clientside interaction in Dynamics involves using an object provided for you when the form loads, which is just Xrm. So you might have something like:
function OnLoad() {
Xrm.Page.getAttribute('name').setValue('Stackoverflow!');
var x = Xrm.Page.getAttribute('name').getValue();
}
I tend to write a wrapper for the Xrm object, mainly because it is a lot easier than remembering some of the chaining and end up with something like:
function WrappedXrm(realXrm) {
var xrm = realXrm;
this.getValue(name) {
return xrm.getAttribute(name).getValue();
}
}
//and then use it as so
var myXrm = new FakeXrm(Xrm);
var myXrmValue = myXrm.getValue('Name');
I am trying out QUnit and wondering how would I go about unit testing in this scenario?
Obviously the example above is a single line, it might not be worth testing it. But assume there was some business logic there that I wanted to test.
The only way I can see is doing some set up before each test along the lines of
var fakeXrm = {};
fakeXrm.Page = {};
fakeXrm.Page.getAttribute = function(name) {
var tempAttr = {};
tempAttr.getValue = function() {
return 'A fake value';
}
}
And then testing on 'A fake value' being returned, but this doesn't 'feel' right to me at all.
Where am I going wrong?
Using Mocks
So in this case, you want to create an instance of WrappedXrm, and pass it an object that mocks the Xrm from your lib ; you need a mock of Xrm.
A first alternative is to write it like you did (which is perfectly valid, if you know what the interface of Xrm is.)
Some libraries like sinon.js or "spies" in the jasmine framework can help you write code like ;
create a 'mock' Xrm, to configure what it should return
create an instance of WrappedXrm with this mock
call the getValue method of WrappedXrm
check that some method was called on the mock
But in the case of javascript, simply created a object that has just the right properties might be okay.
Note that your tests would break if the structure of the "real" Xrm object changes ; that might be what bother's you, but that's always the risk with mocks...
Using the real implementation
If you don't want to test against a mock (which might make sense in case of a wrapper), then maybe you can write the mimimal code that would create an actual Xrm object in your qunit html page (Maybe hardcoding markup ? I don't know the library, so...)
Hoping this helps.

JS Function Invocation from AS3 with Complex Object in Parameter

I'm building an AIR desktop application. At one point the application loads a popup window (an MXML component based on s:Window), which contains an mx:HTML component which loads a local (in the application directory) html file, blank.html. The relevant elements in blank.html are:
<script src="jw/jwplayer.js"/> <!--JW Player's JS-based Embedder-->
...
<div id="jwtarget" /> <!-- the target that the embedder will use -->
Since the parameters I want to use are determined at runtime, I use the domWindow property to invoke the method which loads the player. Here's an example that works:
private function injectPlayer():void {
var playerVars:Object = {};
playerVars.flashplayer = "jw/player.swf";
playerVars.file = "http://www.archive.org/download/meet_john_doe_ipod/meet_john_doe_512kb.mp4";
playerVars.height = 360;
playerVars.width = 640;
try { // attempt to invoke the js function
htmlComponent.domWindow.jwplayer("jwtarget").setup(playerVars);
} catch(e:Error) {}
}
which is called when the page finishes loading by:
<mx:HTML id="htmlComponent" location="assets/blank.html" complete="injectPlayer()" />
That all works fine.
Now to the question. I need to be able to pass a more complex playerVars Object to the function, but I don't seem to be getting the syntax correct. Here's the simplest example I've been attempting:
private function injectPlayer():void {
var playerVars:Object = {};
//playerVars.flashplayer = "jw/player.swf";
playerVars.file = "http://www.archive.org/download/meet_john_doe_ipod/meet_john_doe_512kb.mp4";
playerVars.height = 360;
playerVars.width = 640;
playerVars.modes = [{"type":"flash","src":"jw/player.swf"}];
try { // attempt to invoke the js function
htmlComponent.domWindow.jwplayer("jwtarget").setup(playerVars);
} catch(e:Error) {}
}
This code should create the exact same thing as the above code, but it fails to execute. I assume I need to change the syntax in some way to allow the array of Objects (modes) to be passed properly as a parameter to the js function.
I've tried various things, like passing the modes as a String, or putting the whole thing through JSON.stringify() first, but to no avail. Anyone know the correct way for constructing a complex object for a parameter?
Other details, if you haven't inferred them by now: Flex 4.5.1 is the SDK I'm building with, including the AIR 3.0 extensions (which means targeting FP11).
Update:
Another configuration I tried, which does work:
playerVars.modes = {"type":"flash", "src":"jw/player.swf"};
However, this still doesn't solve the problem that I should be able to pass an Array of Objects in the modes property. But at least this way loads the video player.
More Update:
So, I found this little section of code from jwplayer.js where I suspected the player loading was failing:
if (typeof parsedConfig.modes == "string") {
_modes = _playerDefaults();
_modes[0].src = parsedConfig.modes;
} else if (parsedConfig.modes instanceof Array) { // I suspect this was eval'd as false
_modes = parsedConfig.modes;
} else if (typeof parsedConfig.modes == "object" && parsedConfig.modes.type) {
_modes = [parsedConfig.modes];
}
And to test my suspicion I added the following function to my blank.html:
<script type="text/javascript">
var instanceOfArrayTest = function(arr) {
return arr instanceof Array;
}
</script>
And in my ActionScript code tried the following:
trace([1,2,3] is Array); // true
trace(htmlComponent.domWindow.instanceOfArrayTest([1,2,3])); // false!!!!
So, it seems that the problem is that ActionScript is not passing AS3 Array objects as JS Array objects!
Try doing this instead:
playerVars.modes = [{type:"flash",src:"jw/player.swf"}];
Unlike the call() method of the ExternalInterface class, the mx:HTML does not automatically convert AS3 classes to corresponding JS classes when they are passed as parameters to a JS function. Instead, the HTML Control maintains an environment where methods and properties native to the AS3 classes are preserved and made accessible to JS directly.
If a JS function requires a JS Array object, one must create the JS Array explicitly using the JavaScript Window object to access the JS Array constructor. The HTML Control provides access to this with it's domWindow property. Otherwise, there is no way to "cast" an AS3 Array to a JS Array.
Here's a basic example:
var JSArray:Function = htmlComponent.domWindow.Array;
htmlComponent.domWindow.instanceOfArrayTest( JSArray(1,2,3) ); // true
And for the more complex example using the config parameter for JW Player:
playerVars.modes = JSArray({"type":"flash","src":"jw/player.swf"},{"type":"html5"});
which creates a JS Array of two Objects.
For more info on the JavaScript environment in the HTML Control, check out the JavaScript in AIR section of Adobe's Developing AIR Applications with Flex.

Categories