Jörg Bornemann

Qtified JavaScript

Published Thursday May 16th, 2013
14 Comments on Qtified JavaScript
Posted in Qt Quick, Qt Quick 2

When writing JavaScript code, it doesn’t take long until I’m missing some function which is available in Qt’s C++ API. One very simple example is QList::contains. Checking whether an array contains a certain element works like this in JavaScript:

var names = ["Egon", "Peter", "Raymond", "Waldo"];
if (names.indexOf("Waldo") !== -1)
    print("We've found him!");

It would be nice if we could express the condition using a contains method but Array doesn’t provide one.
Luckily, JavaScript enables us to add methods to inbuilt types by modifiying the corresponding prototype object.

Array.prototype.contains = function(e) {
    return this.indexOf(e) !== -1;
}
if (names.contains("Waldo"))
    print("We've found him!");

Yay! Now we can use the contains method for all arrays!
But wait – there’s a surprise lurking right around the corner once you’re trying to iterate over the keys of the array.

for (var i in names)
    print(i);

This will print:

0
1
2
3
contains

I know, iterating over arrays should be done with an index variable…but still…this additional key is unexpected and asking for trouble.

The solution for this problem is to mark the property contains as non-enumerable. We can use the Object.defineProperty function for that which is available since JavaScript 1.8.5.

Object.defineProperty(Array.prototype, "contains", {
    value: function(e) { return this.indexOf(e) !== -1; },
    enumerable: false    // This is the default and can be omitted.
})

We’re passing to Object.defineProperty the object we want to enhance, the name of the property we want to add and a descriptor object that contains the attributes of our new property.
The value of  contains will be the function that we formerly passed directly to the prototype. Setting the enumerable property to false in the descriptor object will hide contains when using a for (... in ...) loop.

This way we can create a nice Qtish API for the inbuilt JavaScript types Array and String. This can be put into a .js file and used it in QML/JS or in qbs project files.

What do you think? Would such an API be helpful for your QML code? Which QList or QString method are you missing most?

Do you like this? Share it
Share on LinkedInGoogle+Share on FacebookTweet about this on Twitter

Posted in Qt Quick, Qt Quick 2

14 comments

d3fault says:

Good work, now there’s only 8999 pieces of functionality left to replicate…

Jörg Bornemann Jörg Bornemann says:

Congrats for noticing that this post does not provide a complete framework.

jan-arve says:

Since I prefer that my JS experience is the same regardless of web or QML development, I would rather take the other approach: If licensing allows, use the shims from for instance Prototype.js/CommonJS (or any other relevant JS framework) and use those in your Qt Quick apps. These usually offers a much better API that takes proper advantage of Javascripts features.

For instance, Prototype.js adds the Enumerable “mixin” to Array. And of course, Enumerable has the API to see if an object is in the enumerable or not. IMO, this is a much better approach, since Array then can be treated like any other Enumerable.

Andre' says:

@Jan-Arve: As someone who uses Qt dayly I’d pretty much prefer that my QML experience gets close to the Qt convenience I am used to. I certainly don’t want to clutter e.g. QBS code with “foo.indexOf(x) != 1” when I actually mean to say “foo.contains(x)”.

I see absolutely no benefit, neither from a theoretical, nor from a practical point of view, of making certain chunks in a file conform 100% to a certain language definition if these chunks are mixed into a non-standard Domain Specific Language to form the full file anyway – especially when some approximation of said language would yield more convenience.

Insofar, I am all for adding whatever feature needed, even on the parser level, that helps to keep user code concise and understandable.

Nikita says:

I heard, that it is really bad idea to modify built-in JavaScript types like Array or Object and could lead problems.
But If some methods is added in Qt, like the arg() to the string and properly documented – it is not a bad idea. I’m using arg() often.

juergen says:

There is the underscrore JS library which contains many of utility functions for arrays, objects, function objects, etc. porting the library should be possible, as it mostly focuses on JS core language features. (http://underscorejs.org/).

Jörg Bornemann Jörg Bornemann says:

@jan-arve @juergen
For developers who already use JS in other environments I’d agree to use some existing lib.
I thought of people who already know Qt’s C++ API, then get started with QML/JS and want to use a similar API for strings and arrays.

@Nikita: do you have some resource that explains how extending the prototypes can lead to problems?

Kai Koehne says:

Thanks for teaching a bit of JS magic here 🙂 Anyhow, I personally don’t think we should start adding such functionality to QML/JS. First, as jan-arve pointed out we’re then creating something that is different from the JS you can use e.g. on the web. Second, we shouldn’t lure users into staying in JS land. It’s cool for prototyping and one liners etc, but I really think people are better off in the long run if they do their logic in C++, and use only the declarative parts of QML for the UI.

fonzi337 says:

That’s what I was thinking too. I suppose some small common additions could be made (like the contains() example shown here), but for the most part, it seems beneficial to keep the Javascript in QML to one-liners/simple expressions.

terry says:

+1.Sadly most of the examples of QML are focusing on javascript, it is reasonably for us to believe Qt is pushing the developers into the JS land.

Dean says:

IMO it was a huge mistake to go for JS. You should have made your own “VM” for declarative right from the start. This way people could keep to the APIs they are used to, to the language features they are used to like operator overloads and templates. JS will continue to backpedal and waste developer efforts, first there was the effort to integrate V8, now the switch to V4, unify APIs…

Will says:

As a matter of personal taste, JavaScript never really seemed that great to me so it is hard to argue against anything that takes some of the stink off of it. However, this looks looks a lot like the old school C guys who used to try and pretend to write ALGOL by doing #define BEGIN { and #define END }. I understand that the result is “cozier” for people transitioning, but it also means that you never actually finish transitioning to the new language. If the Qt-ish JS becomes standard in QML but nowhere else, then you just have a weird dialect that isn’t useful anywhere else. As a person trying to learn a little JavaScript to keep up with this brave new declarative world, I’ll probably have trouble keeping straight what functionality is from which source. I’d rather just learn the thing once than have to relearn JavaScript all over again the next time I need to use it.

trusktr says:

It’s not necessary to include, but I’m sure a nice js library could be linked to from somewhere for those who want it.

Chris says:

Having been a long-time Qt developer, and an almost-as-long-time web developer, I figured I could throw my $.02 into the ring with this.

When I’m working in the web, or in node.js, I use lodash to provide the bits I’m missing from whatever main framework I’m using. (These days, either AngularJS, or node itself.) I find that works well enough.

However, I’m only just getting my feet wet with QML, and the first thing I looked around for was a library I could include that gave me a more Qt-styled api. Yes, I could just pull in lodash… but I’m working with Qt. I want to not have to switch mental gears when I go from the C++ side of the code to the QML/Javascript side. In this regard, I’d say an optional library that gives array and string a Qt-ish api would be amazing. I strongly support the development of this.

As for modifying the prototypes, the reason you shouldn’t modify the built-in prototypes is pretty simple. Modifying them may cause bugs/unexpected behavior in other javascript libraries that aren’t expecting those changes. And since modifying a prototype changes it for all instances, everywhere, instantly… it’s better to leave well enough alone when you’re worried about interoperability with external libraries.

I think that in the QML case, modifying the prototype’s harmless. Just make it an explicit lib you can include, with the warning, “We modify the string/array prototypes, which might break external libraries.” Chances are, if you’re pulling in external libraries, you’ve solved the problem this lib would be solving anyway.

Frankly, I think it might reduce papercuts when introducing people new to Javascript to QML. (Already had the conversation with a coworker about how ‘crappy’ Javascript’s array is, compared to C#’s list… can’t deny that. But it shouldn’t be a reason to dismiss QML.)

Commenting closed.

Get started today with Qt Download now