Dart: Creating a dynamic list with Dart, PHP and JSON

By on

Dart - Basic Ajax Example

Using an ajax request to update the dom with data from a server is a standard use case in todays web programming. With Dart this task is pretty trivial. In this blog post I would like to describe how one can use Dart to connect to a server running a PHP script. In addition, the data returned should be filtered based on what the user types. This is basically what an autocomplete input box is doing, except you cannot choose anything from your list at the moment.

The complete example can be found on GitHub.

A demo can be found on my own webserver.

Lets look at the PHP script first. It is nothing special.

if (!isset($_GET["q"])) {
    return;
}
$q = strtolower($_GET["q"]);

$data = array(
    "Aberdeen", ... "Zoar"
);

$first = true;
$put = "";
echo '[';

foreach($data as $element) {
    if(!$first) {
        $put = ",";
    } 
    if (strpos(strtolower($element), $q) !== false) {
        echo $put.'"'.$element.'"';
        $first = false;
    }
}
echo ']';

I will explain this code only briefly, as our focus is on Dart, not on PHP. In line 1 to 4 the GET-parameter "q" is extracted. If there is none set, the script returns immediately as we don't want to deliver all the data at once. If there is one, "q" will be lower cased and assigned to "$q".

In line 6 - 8 there is an array holding the actual data defined. At GitHub you will see the full list of data. For this post I cut it down to just two terms.

In line 10 - 22 I create an JSON array. JSON arrays have the form ["element1","element2"]. The $q will be searched in every element. If the element contains it (not only starting with it), the element will be added to my returning JSON array.

That's all - pretty simple. In this example we assume it is installed at http://localhost/data.php. If so, http://localhost/data.php?q=ab should return a list of elements containing the two characters "ab".

Let's look into the Dart code then. When creating a new project with the Editor, you'll see DartEditor has made two files for you. In my case, they are called basicajax.html and basicajax.dart.

Opening basicajax.dart, I have added the following two libraries to my file:

#import('dart:html');
#import("dart:json");

dart:html is a library for manipulating the dom in a very easy way. dart:json is being used to work with json data which we will definitely do.

Basic Ajax with Dart - Editor Libraries

Now we are ready to go - time to think about what we would like to happen. My GitHub HTML file does contain several formatting code. But if would strip it down to the absolute minimum, we would end up with only two elements which are needed for this example:

<input type="input" class="search" value="" />
<ul id="data"></ul>

The input element is the input field in which we want to enter some characters. The ul element below is where we want our data to appear.

Things defined - let's look at the Dart code finally. Here is the outline of our little program:

class basicajax {
  basicajax() {}
  void run() {  }
  void resetData() {  }
  void addRow(String message) {  }
}

void main() {
  new basicajax().run();
}

We start everything from the main() method, as usual in Dart. This creates a new basicajax object (please apologize that I missed the styleguide with my naming). I am not a huge fan of putting much stuff in my constructor, so I created the run() method, in which the most important code resides, except one thing: I query for my two html elements here and store the result in private variables. With this I save multiple queries as I had on an earlier version of this script. The constructor looks like:

class basicajax {
  InputElement _search = null;
  Element _data = null;
  
  basicajax() {
    _search = document.query('.search');
    _data = document.query('#data');
  }

As you can see, I query one time for a CSS class namely ".search" and store the result into a field with type InputElement, which shows me some more methods in my code completion as just Element.

Then I query for the HTML id "#data" and store it as Element - I don't need more, so why should I care what specific type it is.

Then we'll need resetData(). In this method we will put the code which removes the list elements from the data node. And of course we need the addRow() method, with which we will add list elements to our data node. More to these two methods later. Now let us start with the run() method:

void run() {
    element.on.keyUp.add( _(Event event) {
      XMLHttpRequest request = new XMLHttpRequest();
      String url = 'http://localhost/data.php?q=${_search.value}';
      request.open("GET", url, true);
      
      request.on.load.add((event) {
        this.resetData();
        List<String> result = JSON.parse(request.responseText);
        result.forEach((element) {
          this.addRow(element);
        });
      });
      request.send();
    });
    this.addRow("Type one or more letters");
  }

This is probably the most interesting method.

In line 2 we add a keyUp listener to the element. This will cause the anonymous function to execute once the user releases a key he pressed. The interesting thing here is, that all the listeners an bundled in the "element.on" getter. You can find many more there, just look at the code completion of your Editor (STRG - SPACE).

element.on.keyUp.add( _(Event event) {
          
});

The anonymous function, which is executed on key release, performs the actual XMLHttpRequest. It is done very easily.

Create a new XMLHttpRequest() object and call "open", where you define if this is a GET/POST call, the target url and if the call is asynchronous or not. I want it to be async and therefore I need to add a new anonymous function serving as EventListener. This time I add it to request.on.load event. You sense a pattern, it is again the "on" getter, where all EventListeners are somehow stored.

XMLHttpRequest request = new XMLHttpRequest();
String url = 'http://localhost/data.php?q=${_search.value}';
request.open("GET", url, true);
request.on.load.add((event) {

});

Once this event actually happens (this is, when the request has been performed and the data has been loaded), we'll resetData on line 8. Then we get our responseText from the request (which is the JSON returned by the PHP script) and parse it to JSON. One must know, what we get from the PHP script is a String and only via JSON.parse it becomes actual JSON.

List<String> result = JSON.parse(request.responseText);

As we have sent back an JSON array, the result of the parsing is a List containing Strings. Very comfortable, because we can easily iterate over it:

result.forEach((element) {
    this.addRow(element);
});

And finally we execute this XMLHttpRequest, with:

request.send();

Once this is done and the result is loaded, the load event will occur and we resetData, transform the result from String to JSON and add a row for each JSON element.

Now lets look at the easier resetData method:

void resetData() {
    _data.nodes.clear();
}

Wow, this is easy, isn't it? We have already queried for _data, so we just need to get the NodeList via the nodes setter and clear it with a single method call: clear().

And finally there is addRow:

void addRow(String message) {
    Element inner = new Element.tag('li');
    inner.innerHTML = message;
    _data.elements.add(inner);
}

Here we create a new li-Element with the named constructor "tag". Then we put our message, which is our city name as plain text actually, to the innerHTML setter and finally we can add it already to the _data list. That's it.

Summary

But actually to the time of this writing there are some problems, which are already known and addressed in near future. First, the DartC outcome has a size of 9 MB. Trying to optimize it via the Editor leads to an OutOfMemoryException. But no problem, you can increase DartEditors heap size

In my first draft I had a query to the #data element each time I added a row. Please be careful with that as it slows down everything pretty much. It seems (and probably does make sense) the query is not cached.

Tags: Dart, Open Source