Thursday 27 June 2013

QML: Faking an SQLResultSet Object with JavaScript

A few days ago while hunting down an elusive bug in the way the Silica ContextMenu component work I decided to produce a radically stripped down version of LandedSettings. The idea was to prove that the strange behaviour was still present in a pure Sailfish project, and make things much more readable by removing anything from the project not directly related to the issue.

Now LandedSettings is ultimately a front end to an SQLite database. But in my simplified project I only needed data from one table, basically a list which is returned from the Javascript handling SQLite as a JavaScript rs object.

I was keen to remove all the code concerned with database setup and access, yet did not want to change the code deep in my app that processes the resultset. 

So would it be possible to produce a JavaScript object equivalent to that returned from SQLite? This object would return the same data (a list of 6 items), and would have to have the same structure as its SQLite equivalent.

In effect, could I replace the whole database access code with a simple javascript method returning a javascript object?

As we will see, not only is it possible, but it is actually very simple, elegant and easy to achieve. (But as is often the case, working out how to do this was not so easy …)



First I had to find out exactly what is the "rs" object returned from an SQLite select, and how it is structured. After a bit of googling I found this documentation: http://www.w3.org/TR/webdatabase/#sqlresultset

It turns out that 'rs' is officially called an SQLResultSet. This object has 2 properties and a child object 'rows'.

For a SQLResultSet returned from a select the 2 properties insertId and rowsAffected are not interesting, so while we will add these to our object we will set them to zero.

But the child object 'rows' is interesting. This is an object of type "SQLResultSetRowList' and has 2 members that should be familiar to us.

The first is a property length. This will return the number of rows in the result set. We are used to accessing this as rs.rows.length.

The second is the method item(). This is an accessor method used to 'get' one row. e.g. rs.rows.item(0).name would give us the name field of the first item in the result set.

So now we understand the desired end structure, how do we express this in JavaScript?

There are several ways / formats to declare an object in JavaScript, but the one that worked for me and seemed to be most Qt / QML friendly is as described here.http://javascript.info/tutorial/objects

Then I set about building up and testing the required objects, properties and methods step by step.

First I made a simple JavaScript Array object to store the rows. Each row has 2 fields id and name.

var qmlrs = [];
qmlrs.push({id:1, name:'SouthAfrica'});
qmlrs.push({id:1, name:'Greece'});
qmlrs.push({id:1, name:'Schweiz'});
qmlrs.push({id:4, name:'France'});
qmlrs.push({id:5, name:'Italy'});
qmlrs.push({id:6, name:'Austria'});

Next i built a SQLResultSetRowList

var rows = {
 //rows is a SQLResultSetRowListobject
   length: qmlrs.length,
   item: function(index) {
 return qmlrs[index];
   }
}
 
Note that each property / method is separated by a comma, not a semicolon.
 
Then I created the SQLResultset around this.

var rs = {
 //rs is a SQLResultSetobject
   insertId: 0,
   rowsAffected: 0,
   rows: {
 //rows is a SQLResultSetRowListobject
      length: qmlrs.length,
      item: function(index) {
 return qmlrs[index];
      }
   }
}

Here the child object rows is now expressed as 'rows: ' rather than the original 'vars rows ='.

Then all I needed to do was wrap this in a function. Here I used the same name as the function in the original SQLite accessor script.

 
function getTemplateGroups() {
 
 var qmlrs = [];
 qmlrs.push({id:1, name:'SouthAfrica'});
 qmlrs.push({id:1, name:'Greece'});
 qmlrs.push({id:1, name:'Schweiz'});
 qmlrs.push({id:4, name:'France'});
 qmlrs.push({id:5, name:'Italy'});
 qmlrs.push({id:6, name:'Austria'});
 
 

 var rs = {
 
 //rs is a SQLResultSetobject
 
      insertId: 0,
 
      rowsAffected: 0,
 
      rows: {
 
 //rows is a SQLResultSetRowListobject
 
         length: qmlrs.length,
 
         item: function(index) {
 
 return qmlrs[index];
 
         }
 
      }
 
   }
 return rs;
 
}

With this technique I could simulate an SQL select. Of course it is no good for persisting changes to data, but that was not necessary for my stripped down project.
 

1 comment:

  1. Very nice, needed this to mock a service return in angularJS, many thanks!

    ReplyDelete