James Gardner


YUI Autocomplete AJAX Select Drowdown with ID

Posted in Pylons, Mako, JavaScript by thejimmyg on the October 19th, 2007

The YUI toolkit comes with a very flexible autocomplete control but a common requirement is for an autocomplete control that submits the ID associated with a text value rather than the text value itself, much like a select field submits the option value, not the contents the user selects from a drop down list.

Luckily this is fairly easy to achieve using a forceSelection option, a hidden field, and a custom itemSelectEvent handler.

First setup the imports as described on the YUI AutoComplete page:

<!--CSS file (default YUI Sam Skin) -->
<link type="text/css" rel="stylesheet"
    href="http://yui.yahooapis.com/2.3.0/build/autocomplete/assets/skins/sam/autocomplete.css">

<!-- Dependencies -->
<script type="text/javascript"
    src="http://yui.yahooapis.com/2.3.0/build/yahoo-dom-event/yahoo-dom-event.js"></script>

<!-- OPTIONAL: Connection (required only if using XHR DataSource) -->
<script type="text/javascript"
    src="http://yui.yahooapis.com/2.3.0/build/connection/connection-min.js"></script>

<!-- OPTIONAL: Animation (required only if enabling animation) -->
<script type="text/javascript"
    src="http://yui.yahooapis.com/2.3.0/build/animation/animation-min.js"></script>

<!-- OPTIONAL: External JSON parser from http://www.json.org/ (enables JSON validation) -->
<script type="text/javascript" src="http://www.json.org/json.js"></script>

<!-- Source file -->
<script type="text/javascript"
    src="http://yui.yahooapis.com/2.3.0/build/autocomplete/autocomplete-min.js"></script>

Now let’s set up the data structure in a Pylons controller action which the YUI component will access to populate the find as you type select dropdown. You could write similar code for Rails or PHP, it doesn’t have to be Pylons. Notice that the @jsonify decorator converts the Python data structure we return to valid JSON:

@jsonify
def get_data(self):
    return {
        "ResultSet": {
            "totalResultsAvailable":"100",
            "totalResultsReturned":2,
            "firstResultPosition":1,
            "Result": [
                {
                    "ID": "897",
                    "Title":"foo",
                    "Summary":"... When foo' is used in connection with bar' it has generally traced...",
                    "Url":"http:\/\/www.catb.org\/~esr\/jargon\/html\/F\/foo.html",
                    "ModificationDate":1072684800,
                    "MimeType":"text\/html"
                },
                {
                    "ID": "492",
                    "Title":"Foo Fighters",
                    "Summary":"Official site with news, tour dates, discography, store, community, and more.",
                    "Url":"http:\/\/www.foofighters.com\/",
                    "ModificationDate":1138521600,
                    "MimeType":"text\/html"
                }
            ]
        }
    }

Now we need to write the code to generate the autocomplete control. YUI uses an existing text field as the autocomplete field and this will contain whatever text is looked up. Our application requires the corresponding ID so we need a hidden field to hold that value. The hidden field should have the name you want the ID to be submitted as, the text field can have any name because it doesn’t contain the data the application needs. YUI also needs a container <div> which it populates with the results. Ours is called myContainer.

Here’s some HTML to achive this:

<form action="/fayt/submit">

<p><label for="myInput">Sponsor Name<br /></label></p>

<div id="dashboard_autocomplete" style="clear:both; padding-bottom: 20px; width: 400px;">
    <input id="myInput_id" type="hidden" name="myInput_id" />
    <input id="myInput" type="text" name="item">
    <div id="myContainer"></div>
</div>

<input type="submit" name="submit" value="submit" />
</form>

Now let’s add some JavaScript. This can go after the HTML above:

<script type="text/javascript">
    var mySchema = ["ResultSet.Result","Title","ID","Url","ModificationDate"];
    var myDataSource = new YAHOO.widget.DS_XHR("/fayt/get_data", mySchema);
    myDataSource.responseType = YAHOO.widget.DS_XHR.TYPE_JSON;
    var myAutoComp = new YAHOO.widget.AutoComplete("myInput","myContainer", myDataSource);
</script>

The first line sets up a schema, the first item of which specifies where the actual results are in the data structrue returned (in this case they are in the Result list of the ResultSet dictionary), the subsequent entries specify values within each result which might be displayed in the control.

Test this example by entering foo into the field. It works as expected returning two options and allowing you to select one but the hidden myInput_id field doesn’t get populated.

YUI autocomplete FAYT

To populate the hidden field we need to create a callback function and subscribe it to the autocomplete control’s itemSelectEvent. Add the following code at the end of the JavaScript above, just before the </script> tag:

function fnCallback(e, args) {
    YAHOO.util.Dom.get("myInput_id").value = args[2][1];
 }
myAutoComp.itemSelectEvent.subscribe(fnCallback);

Now, when an item is selected from the autocomplete control, fnCallback gets called with two arguments. The first is an event object e and the second is a list of arguments described here. These are:

oSelf
<YAHOO.widget.AutoComplete> The AutoComplete instance.
elItem
<HTMLElement> The selected <li> element item.
oData
<Object> The data returned for the item, either as an object, or mapped from the schema into an array.

In this case we want to access the data returned for the item, which we can access as args[2]. Because of the way we set up mySchema earlier the ID field is the second item in the list (you don’t count the first, "ResultSet.Result" because it isn’t one of the data items). We can therefore access the ID of the selected item as args[2][1] (JavaScript arrays are counted from 0 so the second item is numbered 1). All that remains is to assign this ID to the field which is what the YAHOO.util.Dom.get("myInput_id").value = args[2][1]; line does.

Note

If you are adding this HTML and JavaScript to a Mako template in a Pylons application you can replace the URL "/fayt/get_data" with "${h.url_for(controller=’fayt’, action=’get_data’)}" and Pylons will generate the correct URL for you.

Now that the control is working properly let’s write the code to get the ID in a Pylons controller action:

def submit(self):
    id = request.params.get('myInput_id')
    item = request.params.get('item')
    return "The submitted ID was: %s, the selected item was %s" % (id, item)

There are various options you can use to spice up the control. Try adding some of these at the end of the JavaScript, before the </script> tag:

myAutoComp.useShadow = true;
myAutoComp.forceSelection = true;
myAutoComp.useIFrame = true;

The second of these options ensures that a user selects one of the options from the list rather than entering any old value. This is essential if you want the autocomplete control to be able to replace an actual select control. The third puts the content in an iFrame so that on IE, any form elements beneath the div generated during the autocomplete do not show through.

You can also specify a function to change how the dropdown container formats the infomation. You could use this to display images, make certain parts of the text bold etc. Here’s a simple example:

// This function puts the title in bold if the modification date is
// after 1100000000
myAutoComp.formatResult = function(aResultItem, sQuery) {
    var sKey = aResultItem[0]; // the entire result key

    // We aren't using these two in this example but it is useful
    // to know how to get them.
    var sKeyQuery = sKey.substr(0, sQuery.length); // the query itself
    var sKeyRemainder = sKey.substr(sQuery.length); // the rest of the result

    if (aResultItem[3] > 1100000000) {
        var aMarkup = [
           "<div id='ysearchresult'>",
           "<span style='font-weight:bold'>",
           sKey[0],
           "</span>",
           "</div>"
        ];
     } else {
       var aMarkup = [
           "<div id='ysearchresult'>",
           "<span style='font-weight:normal'>",
           sKey[0],
           "</span>",
           "</div>"
       ]
     }
     return (aMarkup.join(""));
};

That’s it. You now have a fully customisable autocomplete control which can be used to replace ordinary HTML <select> fields in cases where there are too many items to easily list in a select alone.

If you spot any mistakes or have suggestions for improvements please feel free to leave a comment.

Re-Using Fields in a Pylons, Mako and FormEncode Workflow

Posted in Pylons, Python, Web, Mako by thejimmyg on the September 19th, 2007

In most of my Pylons apps nowadays I make use of a few tricks form handling tricks that allow me to re-use the same fields template for creating new objects and updating existing ones. Here’s how it works.

First of all I write a Mako template with the fields fragments called fields_fragment.mako:

## -*- coding: utf-8 -*-
<p>
    <label for="approvalgranted">Date Approval Granted<br /></label>
    <input type="text" name="approvalgranted" />
</p>
<p>
    <label for="note">Note<br /></label>
    <input type="text" name="note" />
</p>
<p>
    <label for="enteredby">Last edited by<br /></label>
    <input type=unknown name="enteredby" />
</p>

Then I create two templates to use the fields, one for adding a record, the other for updating it. Here is add.mako:

## -*- coding: utf-8 -*-
<%inherit file="/base/index.mako" />
<%namespace file="fields_fragment.mako" name="fields" import="*"/>
<!% import formencode.htmlfill %>

<h2>Add Requirement</h2>

${h.form(h.url(controller='studyapprovals', action='add', study_id=c.study_id), method='post')}
    ${formencode.htmlfill.render(capture(fields.body), c.values, c.errors)}
    <p><input type="submit" value="Add &raquo;" name="go" class="button" /></p>
${h.end_form()}

and update.mako:

## -*- coding: utf-8 -*-
<%inherit file="/base/index.mako" />
<%namespace file="fields_fragment.mako" name="fields" import="*"/>
<!% import formencode.htmlfill %>

<h2>Update Requirement</h2>

${h.form(h.url(controller='studyapprovals', action='edit', id=c.id,  study_id=c.study_id), method='post')}
     ${formencode.htmlfill.render(capture(fields.body), c.values, c.errors)}
    <p><input type="submit" value="Save" name="go" class="button" /></p>
${h.end_form()}

Notice the lines with ## -*- coding: utf-8 -*- specify the files are encoded with the UTF-8 chracterset which means I can use any Unicode characters I like in the templates. Also notice that both templates inherit from a file /base/index.mako which provides the bulk of the HTML for the page.

The important parts to draw your attention to are the lines:

<%namespace file="fields_fragment.mako" name="fields" import="*"/>

and:

${formencode.htmlfill.render(capture(fields.body), c.values, c.errors)}

The first is effectively an import of the shared fields_fragment.mako file as the namespace fields. I can then output the contents of that file by writing ${fields.body()} in the template to render the body of the fields_fragment.mako template but most of the time that’s not what I want to do.

Working with forms is mainly about handling the validation and then repopulating the form with the entered values and error messages. My controllers handle the validation and set c.values and c.errors so I can use formencode.htmlfill to populate the forms. The only complication is that in order to pass the value of calling fields.body() capture the render() function I have to capture it, calling it simply renders the output directly which isn’t what I’m after. This is why the call to capture is made.

ToscaWidgets twForms Tutorials

Posted in Pylons, Python, Web, Mako, ToscaWidgets by thejimmyg on the April 26th, 2007

I had some discussions with Alberto Valverde a while back about WSGI and Pylons integration and the separation of ToscaWidgets and twForms but I hadn’t really used the finished article very much up until now. It is a very powerful system but there isn’t really very much documentation yet so I’ve put together a few tutorials to get people started:

If you are interested in developing widgets you should also check out Wyatt’s discussion of creating a Google Maps widget.

Pylons 0.9.5 Released

Posted in Pylons, Mako, SQLAlchemy, Software Releases by thejimmyg on the April 13th, 2007

I’m pleased to announce the release of Pylons 0.9.5. This version has seen a lot of work go into it including formal support for Mako and even better internationalization. I’ve also completely re-written the QuickWiki tutorial to bring it bang up to date with the latest features of SQLAlchemy. Ben has the full announcement here.