Back to my page

Developer Blog



Converting a MiniAPI module to a UWA widget (part 4)

Here comes the high point of this series of articles, where all the bricks come together. During the first three parts, I’ve been banging one specific drum: whereas MiniAPI modules could be based on a multitude of inter-linked dynamic files, UWA widgets should really stick to one static file, and rely on Ajax requests to import/export data. The third part even focused entirely on showing how easy it was to do this with one of UWA’s cool Ajax getXXX methods.

“Fine”, you say, “but my module uses a form to send data to my PHP script, how am I supposed to do that with just one file, and with only JavaScript to boot?” That’s where this part comes in, as you might have guessed…

As usual in this series, we’ll see the (deprecated) MiniAPI way to send data, and the (recommended) UWA way. This way, please…

Posting data with the MiniAPI

The not-to-be-used-anymore MiniAPI tutorial has a specific step to create classic forms. It follows an expected path: since MiniAPI has been designed to be as easy to code as classic HTML, so where classic forms in MiniAPI. Hence, a form in MiniAPI wouldn’t be that much different from one you’d code for a website:

<form method="post" action="addBookmark.php">
  <fieldset>
    <legend>Update your data</legend>

    <label for="url">URL</label><br />
    <input name="url" id="url" type="text" value=""/>

    <label for="title">Title</label><br />
    <input name="title" id="title" type="text" value=""/>

    <label for="description">Description</label><br />
    <textarea name="description" id="description"
      cols="30" rows="5"> </textarea>

    <input name="add" type="submit" value="Ok"/>
  </fieldset>
</form>

See, it’s all there, as any HTML code would expect:

  • the method attribute, set to "post"
  • the action attribute, set to a server-side script (here, "addBookmark.php")
  • input elements of various types, the last of which being of type "submit", and a textarea - all with name, id and value attributes
  • additionally: proper fieldset, legend and label elements

Note that the action attribute could just as well be set to <?php echo $PHP_SELF;?> (or any equivalent in your preferred server-side language). It didn’t matter, as long as the targeted script sent back (or redirected to) a MiniAPI module. Just as in classic HTML, the target script determined what the widget would look like after the data submission. The browser’s form handly ability do the rest…

Thus, MiniAPI’s forms worked just like a webpage’s, but in Netvibes, not all the page reloaded itself on submit (thankfully), only the widget itself. Apart from that, the principle remains the same.

Posting data with the UWA

Before we talk on why we changed all that, let’s see how we would adapt the above MiniAPI code to UWA. Here goes:

<form method="post" action="addBookmark.php">
  <fieldset>
    <legend>Update your data</legend>

    <label for="url">URL</label><br />
    <input name="url" id="url" type="text" value=""/>

    <label for="title">Title</label><br />
    <input name="title" id="title" type="text" value=""/>

    <label for="description">Description</label><br />
    <textarea name="description" id="description"
      cols="30" rows="5"> </textarea>

    <input name="add" type="submit" value="Ok"/>
  </fieldset>
</form>

Hey, but it’s exactly the same form code as for MiniAPI, innit? Indeed it is! But we’re going to use some JavaScript code to hijack the form from the browser’s normal behavior. This JavaScript will need to:

  • set the form’s onsubmit event to hijack the form’s submit button’s behavior, in order to have this button not actually submit the form to the action URL, but trigger one of our custom JavaScript method, like WidgetName.submit().
  • have submit()
    1. retrieve the value of the form’s elements and the URL of the form’s action attribute
    2. send these values to the URL through using an Ajax request. The Ajax request features a callback method, let’s say WidgetName.onSubmitOk()
  • have onSubmitOk() update the widget’s content according to the server-side script sent back - that script having being modified to not spit out a UWA widget, but only the data we need in order to update the widget

Seems clear enough? Let’s code it!

var BookmarkWidget = {};

BookmarkWidget.form = UWA.$element(
    widget.body.getElementsByTagName('form')[0] );
BookmarkWidget.formUrl =
  UWA.$element(widget.body.getElementsByTagName('input')[0]);
BookmarkWidget.formTitle =
  UWA.$element(widget.body.getElementsByTagName('input')[1]);
BookmarkWidget.formDesc =
  UWA.$element(widget.body.getElementsByTagName('textarea')[0]);
BookmarkWidget.formButton =
  UWA.$element(widget.body.getElementsByTagName('input')[2]);
BookmarkWidget.form.onsubmit = BookmarkWidget.submit;

What we’re doing here:

  • our sample widget is used to store and display bookmarks, so we’ll store all JavaScript code in the BookmarkWidget namespace.
  • we create the form variable in BookmarkWidget, and turn it into a reference to the form. Because the form is already hand-coded into the body, we need to extend that reference with UWA.$element() so that we can use UWA’s Element extension methods on it - we wouldn’t have to do that had the form been build using DOM methods, such as BookmarkWidget.form = widget.createElement('form');
  • we do the same with formUrl, formTitle and formDesc, as respective references to the first and second input element, and the textarea one.
  • finally, we assign the soon-to-be-coded submit() method to the form’s onsubmit event handler. Notice how we don’t put the final “()” to submit here: that’s because we want to assign a reference to submit(), not assign its result…

We’re all set up, let’s see how submit() and it Ajax request would look like!

BookmarkWidget.submit = function() {
  var formUrl = encodeURIComponent(
    BookmarkWidget.formUrl.value);
  var formTitle = encodeURIComponent(
    BookmarkWidget.formTitle.value);
  var formDesc = encodeURIComponent(
    BookmarkWidget.formDesc.value);

  BookmarkWidget.formUrl.setAttribute('disabled', 'disabled');
  BookmarkWidget.formTitle.setAttribute('disabled', 'disabled');
  BookmarkWidget.formDesc.setAttribute('disabled', 'disabled');
  BookmarkWidget.formButton.setAttribute('disabled', 'disabled');

  UWA.Data.request(
    form.action, {
      method: BookmarkWidget.form.method,
      proxy: 'ajax', type: 'text',
      parameters: 'url=' + formUrl + '&title='
        + formTitle + '&description=' + formDesc,
      onComplete: BookmarkWidget.onSubmitOk)
      }
    );
  }

What this does:

  • set the submit function - in fact, we assign an anonymous function to the submit property/variable of the BookmarkWidget object, but it works basically the same. Yeah, JavaScript is cool like that…
  • The submit method itself then…
    1. retrieves the values of the formUrl, formTitle and formDesc references - therefore, the content of each element of the form - in three variables locale to the method named the same. Values are URL-encoded with encodeURIComponent
    2. disables each element of the form so that the user cannot add content while the data is being sent. This is done by adding a disabled attribute to each element, with a value of disabled - as required by XHTML
    3. builds the Ajax request itself

Now, the Ajax request needs a bit of explaining. As said earlier, the action URL remains a server-side script (here, addBookmark.php). But while MiniAPI needed this script to return another MiniAPI module - or the same module, updated with new data -, UWA uses Ajax, which means that the server-side script will only need to return the data needed to update the widget.

Let’s say that our addBookmark.php script used to return a XHTML file which displayed the original MiniAPI module, only with an added line, like “Your bookmark has been correctly saved”. Send over a whole XHTML file just to add such a tiny line is a waste of bandwidth.
Thanks to Ajax, no more! We update the server-side script that it returns, let’s say, a simple text data that said either “true” or “false“, depending on the result. Then, the widget would use the DOM to update its own content with the correct message!

But let’s first see how that Ajax request is built.

  • we are sending data to the server, therefore we need to make a HTTP POST request (it can also be done with HTTP GET, but that’s ugly, stop doing that). Logically, all of UWA’s getXXX Ajax methods use HTTP GET, so we’re going to use UWA.Data.request, the only one where you can set the HTTP method you wish to use.
  • since we never how the form might change, we do not hardcode the target URL in the request, but instead use the content of the form’s action attribute: form.action
  • the method’s second parameter is JavaScript object, which is set this way:
    • same thinking as above for the method: form.method
    • proxy: 'ajax': you most probably don’t need anything else
    • type is where you, obviously, set the type of your request - in this case, ‘text’. There are a handful of types available, depending on which format you are expecting to be sent back from your server-side script: text, xml, json and the UWA-specific feed
    • a HTTP POST request needs parameters, and parameters is where you set them, in a correctly URL-encoded way - which is exactly what we did when retrieving the element’s data, remember? So we created a line ‘&‘-separated parameters, with ‘=‘-separated key/value pairs, in the way your server-side has been coded to expect them
    • finally, we need a callback method, which means a method that will be call upon your request receiving an answer from the server. That method is set with the option onSuccess setting, and as we’ve seen already, the assigned method, onSumbitOk, doesn’t feature it’s final “()

There we go, all set up, your request is ready to go. Congrats!

But what happens with the answer, and that “callback method” thing?

The assign callback method, onSubmitOk() (or, in its full glory, BookmarkWidget.onSubmitOk()), receives the Ajax response in its required first argument. With that answer in hand, it can update the widget to display a useful notice to the user. Let’s see that.

BookmarkWidget.onSubmitOk = function(response) {
  if (response === true)
    widget.addBody('<div>URL submitted correctly</div>');
  else
    widget.addBody('<div>There has been a problem</div>');

  BookmarkWidget.formUrl.removeAttribute('disabled');
  BookmarkWidget.formTitle.removeAttribute('disabled';
  BookmarkWidget.formDesc.removeAttribute('disabled';
  BookmarkWidget.formButton.removeAttribute('disabled');
  }

onSubmitOk() has response as its first parameter. In that method, we’ll use response to add information to the widget’s content, depending on the value of response: true or false. Of course, this sample code only applies to this case, you can do a lot of things with your answer: display it directly if it’s text or HTML, parsing it if it’s XML or JSON, etc.

Once all is done, we re-enable all of the form’s elements, ready to submit another piece of data…

Why did we make it harder?

So, we’ve seen the “how”, let’s tackle the “why”. Indeed, it seems harder to add all this fancy JavaScript code than relying on the browser’s ability to submit a form, right?

Wrong. I’ll just skip over the fact that forms, by design, are not able to submit using Ajax request, which makes it a requirement for us to hijack the onsubmit event and that follows. This, I’m sure, you understood.

Instead, I’ll remind you that UWA has been designed to be highly portable, and to work on as many platforms as possible. All these platforms do not have the same characteristics, do not support the same format, do not allow the same freedoms - this is doubly true for desktop platforms.

This is why we insist on a few things: one file, if possible static, with Ajax request to get/set external data.

You never how one platform is going to handle a .php script (or .rb, or .aspx, anything). Will it load it correctly? Will it understand it’s syntax? Will it blend?

By relying on good ol’ XHTML, with regular Ajax combined to the UWA JavaScript framework, you are giving your widget all the chances it can have to work on all the supported platforms.

Simply put, if you code it any other, we cannot promise you it’ll work everywhere - and wouldn’t it be bad to not take advantage of UWA’s huge portability?

So, there you have it. Keep your code clean and separated, work inside the few UWA constraints, and you should be fine. And remember: if you’re having a hard time converting your MiniAPI module into a UWA widget, our UWA support forum is wide open!

Tags: , , , , , ,

Leave a Reply