Open Source & stuff 

Salix.gr

User login

What I have learned so far Ajax-ing with prototype library

After 3 mounths (at the time updating this it is longer) ajaxing with prototype library and javascript a little more that usual,
I learned a couple of lessons. Unfortunately the hard way...
Will try to synopsize here.

Cross domain javascripts

Cant have cross domain calls. Ajax javascripts cant
make calls to php scripts located on other domains or subdomains. Security issues.

GET and IE cache

IE has an amazing cache mechanism! For example I was making an Ajax call
to retrieve a list using GET method will produce strange things. IE cachess GET
results and even if list elements changed I was getting the list I got the first time.
So, (if you can) use always POST method to get data.

Clear select box on IE

A typical case using ajax is to populate a select box. The non typical is that
IE crashes doing it like:

    while( oStates.options.length>0) {
	    oSelect.remove(0);
    }

Amazinly when last option is removed select box is also removed from the page!!
What worked was:

    for( i=oStates.options.length-1; i>=0; i--) {
		oSelect.options[i]=null;
    }

Traversing XML

Note that I am a common guy, just a php coder, not a Javascript-XML expert.

Back to the usual example. When you want to populate a select box you make a call to
Ajax.Request method, and execute a php script that returns a list with the new elements
in XML, something like:

    <?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
    <response>
        <record>
            <id>1</id>
            <item>Item 1</item>
        </record>
        <record>
            <id>2</id>
            <item>Item 2</item>
        </record>
        <record>
            <id>3</id>
            <item>Item 3</item>
        </record>
    </response>

PHP part to make that is simple. Now its javascript time. We get server's response in XML
with originalRequest.responseXML method. What we have to do now is get all 'record' elements
and read id and item values.

var tagRecords = originalRequest.responseXML.getElementsByTagName("record");

Returns a collection of all 'record' elements in our XML. Now we need a loop to
traverse that collection and for each item get id and item. Something like:

    for(i=0; i<tagRecords.length; i++) {
        var record = tagRecords[i];
        var sValue = record.getElementsByTagName("id")[0].textContent;
        var sText = record.getElementsByTagName("item")[0].textContent;
    }

OK? Firefox is ok with that but IE not! Get a javascript error? No.
Returns undefined? No. Thank god Safari returns undefined. That's how
I found the solution.

    for(i=0; i<tagRecords.length; i++) {
        var record = tagRecords[i];
        var sValue = record.getElementsByTagName("id")[0].firstChild.nodeValue;
        var sText = record.getElementsByTagName("item")[0].firstChild.nodeValue;
    }

Have to .firstChild.nodeValue not .textContent.

How about empty XML tags?

On one case I used XML to return info after doing an Ajax record addition to a db table.
The message was constructed by the PHP code like this:

    print '<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>';
    print '<response>';
    print '<msg>'.$msg.'</msg>';
    print '<success>'.$success.'</success>';
    print '</response>';

If operation fails $success=0 and $msg has mysql_error() message, otherwise $success=1 and
$msg =''. So in case of success the message is:

    <?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
    <response>
        <msg></msg>
        <success>1</success>
    </response>

Note the empty msg tag. Will previous paragraph's javascript work? No. Again the mysterious
no error way. Also javascript function will exit without executing the rest of the code.
Simple workaround? Check first success value and if is 0 then get msg value. Not proper way to
do it but till I find something better....

Now i have found something better. Lets get in hands the traversing code we saw earlier:

    for(i=0; i<tagRecords.length; i++) {
        var record = tagRecords[i];
        var sValue = record.getElementsByTagName("id")[0].firstChild.nodeValue;
        var sText = record.getElementsByTagName("item")[0].firstChild.nodeValue;
    }

This loop fails if id tag is empty, because record.getElementsByTagName("id")[0] has no childs, so all we have to do, is add a check if record.getElementsByTagName("id")[0] has any childs. In case of empty tag it doesnt have! So adding an if statement will fix the problem:

    for(i=0; i<tagRecords.length; i++) {
        var record = tagRecords[i];
        var sValue;
        if( record.getElementsByTagName("id")[0].firstChild==null )
          sValue = '';
        else
           sValue = record.getElementsByTagName("id")[0].firstChild.nodeValue;
        var sText = record.getElementsByTagName("item")[0].firstChild.nodeValue;
    }

Same thing should be done for sText also. But thats the idea.

NOWRAP attribute of a TD

Passing html throu Ajax (xml) nowrap attribute is converted to nowrap=""!!
Ofcourse no the desired effect. The question that comes up is
"Is it possible to duplicate the NOWRAP attribute of a TD via CSS?"
And the answer:
The 'white-space' attribute should do what you want.

<td style="white-space: no-wrap;">blah blah blah</td>

should do the same thing as

<td nowrap>blah blah blah</td>

Note that the white-space property is NOT supported in NN4.

Ajax updater returns Javascript along with HTML

Now the case that Susana put up is what if ajax call returns javascript along with HTML.
According to the very nice "Developer Notes for prototype.js" document the solution is
to add evalScripts: true parameter to Updater call.

    var myAjax = new Ajax.Updater(
                    'container',
                    url,
                    {
                    asynchronous:true,
                    method: 'get',
                    parameters: pars,
                    evalScripts: true,
                    onFailure: reportError
                    }
                 );

But there is also one more point that needs to take care.

"... But there's a caveat. Those script blocks
will not be added to the page's script. As the option name evalScripts suggests, the
scripts will be evaluated. What's the difference, you may ask? Lets assume the
requested URL returns something like this: "

    <script language="javascript" type="text/javascript">
    function sayHi(){
    alert('Hi');
    }
    </script>
    <input type=button value="Click Me" onclick="sayHi()">


In case you've tried it before, you know it doesn't work. The reason is that the script
block will be evaluated, and evaluating a script like the above will not create a function
named sayHi. It will do nothing. To create this function we need to change our script
to create the function. See below.

<script language=\"javascript\" type=\"text/javascript\">
    sayHi = function(){
                alert('Hi');
            };
</script>


Note that in the previous example we did not use the var keyword to declare the
variable. Doing so would have created a function object that would be local to the script
block (at least in IE). Without the var keyword the function object is scoped to the
window, which is our intent. ...

See a working demo.
Download example's full code.

But what I prefer to do? Load all javascript code to the initial page (the one with the container div).
Why? I want have to modify existing javascripts and coding style and major reason is to reduse response size, user may wait a couple of seconds more to load the page but reponses from the server will be smaller and come throu faster. In most cases this works. Having my javascript functions written in a more generic way (more parameters to control them) is the workaround for not having the need to load javascript functions for just a case. For example if updater returns a list of records and each of them has a "delete" option, we need to confirm that action, right? We can have the action_confirm function something like:

    function action_confirm(msg, recid) {
        if( confirm(msg) )
            ....
    }

And on record specific delete link something like onclick="action_confirm('Delete row?,343);".

Last update on 30 Oct 2008.


All Rights Reserved 2006-2010 Salix.gr | Hosting by e-emporio