Personal tools
You are here: Home Weblog Javascript

Javascript


2008-09-15

Stupid Internet Explorer and stupid IE bugs

Filed Under:

I'm placing the finishing touches on a web application I've been writing on and off for the last year. At last, I've reached Internet Explorer bugs, which are usually the last to be fixed. Among several other stupid but documented bugs, I've found one which I couldn't find documented in a shallow search on Google: it seems IE has an algorithm for comparing strings that is different then the one that is used internally when sorting an array of strings.

Take this code, for example, where I have separated the issue:

<html>
<body>
<script>

var x = [
  "Zwischenwasse",
  "Gurtis",
  "Götzis",
  "Partenen",
  "Raggal",
  "Rietz",
  "Schnifis",
  "Vösendorf",
  "Bludenz",
  "Galtür"
];

var y = [
  [34241, "Zwischenwasse"],
  [11223, "Gurtis"],
  [12321, "Götzis"],
  [12345, "Partenen"],
  [32454, "Raggal"],
  [34355, "Rietz"],
  [43453, "Schnifis"],
  [42321, "Vösendorf"],
  [43435, "Bludenz"],
  [43222, "Galtür"]
];

x.sort();
document.write(x);

document.write("<br/>");

var sorted = y.sort(function(a,b){
	return a[1] > b[1];
});

for (var i=0;i<sorted.length;i++) {
	document.write(sorted[i][1]+ ",");
}
</script>
</html>

The value which will be written in Internet Explorer when running this html document is:

Bludenz,Galtür,Gurtis,Götzis,Partenen,Raggal,Rietz,Schnifis,Vösendorf,Zwischenwasse
Bludenz,Galtür,Zwischenwasse,Gurtis,Götzis,Partenen,Raggal,Rietz,Schnifis,Vösendorf, 

Needless to say, in Firefox both lines will be the same, displayed in the proper order. On a side note, while I'm bashing Internet Explorer, let me just say that Internet Explorer 8 is a broken piece of software, at least on the side that I'm concerned. The developer tools are really buggy and tend to block the browser for even trivial operations (that is, when it doesn't crash it completely).

Update: apparently my mind is clouded with too much Python (I admit that Javascript is not a language that I use often). The problem is that the inline function used as sorting discriminator should return numeric values of -1, 0, 1, like this:

var sorted = y.sort(function(a,b){
	if (a[1] > b[1]){
		return 1;
	}
	if (a[1] < b[1]){
		return -1
	}
	return 0;
});

2007-05-08

Software is lame

I may sound like a fanboy or something, but I'm starting to think that it's impossible to have a perfect piece of software, especially with larger systems. Two things that happened to me lately that made me think this:

  • How lame is that Windows doesn't automount USB sticks when they're present at boot time? Why would I have to remove+reinsert the stick just so Windows will see the bloody thing? My Kubuntu, of course, handles perfectly everything in this department (at least in my case).
  • How lame is that Prototype handles in a really stupid way forms that have multiple submit buttons and are submitted through Ajax? Not even Form.request() will do the proper thing, which is to only leave one of the submit inputs in the stream, the one that has been clicked on. The problem is with zope.formlib, which gets the action and the validation from the submit button that was pressed. More then one submit input in the request and things become uncontrollable.
    To fix this I have added the following onsubmit handler to my Javascript code:
my_form.onsubmit = function(event){
    button = document.activeElement || event.explicitOriginalTarget;    // IE, Mozilla, Opera
    this.getInputs('submit').each(function(el){
          if (el.name != button.name) {
                el.disable();
          }
}
}

This disables all the submit input controls before serializing the form and doing an ajax request with Form.request(), as disabled inputs are ignored by serialize().

UPDATE: I am told that Prototype 1.6 will support multiple submit buttons.

2007-03-25

One issue using Prototype's Ajax form submission with Zope 3

Filed Under:

I've found a weird issue while trying to debug why on of my Ajax loaded forms wasn't, apparently, properly validated. Even though that the schema specified that the fields are required, the form action would get executed as if the validation wouldn't have been done or the request was properly validated. I thought at first that there's an issue with the form class/handling itself, but after some testing I came to the conclusion that it has to be the way I'm doing the AJAX request.

What I have discovered is that I was using the parameters option of the Ajax.Request to make a form submit with POST method, which apparently causes Zope to validate the fields as having a value, even though they were empty. The right way to do this request is using the postBody parameter, as fixing that made my forms work again. I'm not sure what causes this behaviour in Zope: the visible difference is that postBody includes all the form inputs, even the empty ones, while the parameters version includes just those with a value (in my case, the submit button). And I have tried leaving out a field from the Zope request by saving the html page, deleting one of the inputs and doing a form submission, but Zope does the right thing in this case.

I'm not sure how popular is using the parameters option with POST and Ajax.Request, but I'm adding this note here, just in case.

postBody version

POST /++skin++course/site/new_design.html HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20061024 Firefox/2.0 (Swiftfox)
Accept: text/javascript, text/html, application/xml, text/xml, */*
Accept-Language: en-us,en;q=0.7,ro;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
X-Requested-With: XMLHttpRequest
X-Prototype-Version: 1.5.1_rc1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost:8080/++skin++course/site/
Content-Length: 94
Cookie: top_http___localhost_9080_t2_sticky_view=354px; left_http___localhost_9080_t2_sticky_view=646px; state_http___localhost_9080_t2_sticky_view=hide; zindex_http___localhost_9080_t2_sticky_view=5; WT_FPC=id=82.79.74.153-1913305056.29841814:lv=1172488789513:ss=1172484509013
Authorization: Basic dGliaTp2aWNlcm95
Pragma: no-cache
Cache-Control: no-cache
add_design.title=&add_design.description=&add_design.actions.4164642064657369676e=Add%20design
HTTP/1.x 200 OK
Content-Length: 4408
X-Powered-By: Zope (www.zope.org), Python (www.python.org)
Accept-Ranges: bytes
Server: Twisted/2.5.0+rUnknown TwistedWeb/[twisted.web2, version 0.2.0 (SVN rUnknown)]
Date: Sun, 25 Mar 2007 11:56:52 GMT
Content-Type: text/html;charset=utf-8
----------------------------------------------------------

parameters version


POST /++skin++course/site/new_design.html HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20061024 Firefox/2.0 (Swiftfox)
Accept: text/javascript, text/html, application/xml, text/xml, */*
Accept-Language: en-us,en;q=0.7,ro;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
X-Requested-With: XMLHttpRequest
X-Prototype-Version: 1.5.1_rc1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost:8080/++skin++course/site/
Content-Length: 52
Cookie: top_http___localhost_9080_t2_sticky_view=354px; left_http___localhost_9080_t2_sticky_view=646px; state_http___localhost_9080_t2_sticky_view=hide; zindex_http___localhost_9080_t2_sticky_view=5; WT_FPC=id=82.79.74.153-1913305056.29841814:lv=1172488789513:ss=1172484509013
Authorization: Basic dGliaTp2aWNlcm95
Pragma: no-cache
Cache-Control: no-cache
add_design.actions.4164642064657369676e=Add%20design
HTTP/1.x 200 OK
Content-Length: 3491
X-Powered-By: Zope (www.zope.org), Python (www.python.org)
Accept-Ranges: bytes
Server: Twisted/2.5.0+rUnknown TwistedWeb/[twisted.web2, version 0.2.0 (SVN rUnknown)]
Date: Sun, 25 Mar 2007 11:56:15 GMT
Content-Type: text/html;charset=utf-8
----------------------------------------------------------

2007-02-27

I hate brackets! (or rants of a JavaScript novice programmer)

Filed Under:

Javascript is nice, I'm having fun building this Zope3/Ajax based application that I'm working on right now. What I don't enjoy is the forest of non-alphanumeric characters that has started to clog my code. I'm really a novice when it comes to Javascript, most of my professional programming has been done with other languages, but look for example at this fragment of code:

    for (i=0; i< parent.childNodes.length; i++) {
        el = parent.childNodes.item(i)
        if(el.nodeType == Node.ELEMENT_NODE) {
            if (el.classNames().find(function(s){return s == 'ajax_response'}))
            {
                parent.removeChild(parent.childNodes.item(i));
            }
        }
    }

It's a simple snippet that checks if there's already an "ajax_response" container in the parent container.  On line 4 I've stretched the code as much as I could, already reaching 80 characters (I'm using Prototype, the reasons there's an anonymous function as parameter to the find(), applied on an Enumerator object). The code is ugly and hard to read and comprehend. I'm really beginning to appreciate Python, a python version would have been something like this (trying not to differ too much from the JS version):

for i in range(parent.childNodes.length()):
    el = parent.childNodes[i]
    if el.nodeType is Node.ELEMENT_NODE) and \
         'ajax_response' in el.classNames():
                parent.removeChild(parent.childNodes.item(i))

The block of code have been reduced to almost half the number of lines and I dare say that now it is really possible to read an understand this snippet. Probably the JS code could be improved as well, but I'll keep such optimizations for later on, when my Js-Foo gets better.

Updated (again)

I wrote a Prototype extension to create a "scrollable area". I've modeled the code after the Control.Tabs extension. The control is almost ready. I feel good after understanding JS's OOP system and the Prototype extensions, its bind/bindAsEventListener quirks, but the code remains the same unreadable mess, when compared to Python code.

sizeToNumber = function(size){
//converts a style size (ex: 10px) to a number. Hackish method
return size.substring(0, size.length -2) * 1
}

if(typeof(Control) == "undefined")
var Control = {};
Control.Scroller = Class.create();
Object.extend(Control.Scroller.prototype, {
container:false,
initialize:function(area, options){

// set the options
this.options = $H({
timeslice:0.1,
amount:5,
type:'vertical',
width:'500px',
height:'400px'
}).merge(options || {});
this.area = $(area);
this.sid = 'scroller' + this.area.id;
// insert the control structures
new Insertion.After(this.area,
"<div class='scroller " + this.options.type + "' id='" + this.sid + "'>" +
"<div class='sb b_A' id='" + this.sid + "sb'></div>" +
"<div class='s_outer'>" +
"<div class='s_inner'></div>" +
"</div>" +
"<div class='sb b_B' id='" + this.sid + "sr'></div>" +
"</div>"
);

// div scroller <- container for the entire control
// div sb b_A <- scroll button. b_A = button A, b_B = button B
// div s_outer <- The restricted visible portion of the scrolled content. Has overflow:hidden
// div s_inner <- Container which holds the actual content

inner = $(this.sid).down('.s_inner');
outer = $(this.sid).down('.s_outer');

inner.appendChild(this.area);
outer.style.overflow = 'hidden';
// TODO: only apply one of height/weight

switch (this.options.type) {
case 'vertical':
this.direction = 'marginTop';
outer.style.height = this.options.height;
inner.style.height = this.area.clientHeight + 'px';
break
case 'horizontal':
this.direction = 'marginLeft';
outer.style.width = this.options.width;
inner.style.width = this.area.clientWidth + 'px';
break
default:
throw ("Unsported orientation/style type")
}

//event handlers LEFT (or TOP) scroll button
$(this.sid).down('.b_A').observe('mousedown', function(event){
this.flag=true;
new PeriodicalExecuter(function(pe){
if (this.flag) {
this.doScroll(this.options.amount);
} else {
pe.stop();
}
}.bind(this), this.options.timeslice);
}.bindAsEventListener(this));
$(this.sid).down('.b_A').observe('mouseup', function(event){
this.flag = false;
}.bindAsEventListener(this));

//event handlers RIGHT (or BOTTOM) scroll button
$(this.sid).down('.b_B').observe('mousedown', function(event){
this.flag=true;
new PeriodicalExecuter(function(pe){
if (this.flag) {
inner = $(this.sid).down('.s_inner');
cl_height = sizeToNumber(this.options.height); // the scroller control area
delta = sizeToNumber(inner.style[this.direction]); // how much the inner div was scrolled
if (this.options.type == 'horizontal') {
direction = 'clientWidth'
} else if (this.options.type == 'vertical'){
direction = 'clientHeight'
}
re_size = this.area[direction]; // the real content size (the ideal size)
if (!(((delta * -1) + cl_height) >= re_size)) {
this.doScroll(this.options.amount * -1);
}
} else {
pe.stop();
}
}.bind(this), this.options.timeslice);
}.bindAsEventListener(this));
$(this.sid).down('.b_B').observe('mouseup', function(event){
this.flag = false;
}.bindAsEventListener(this));
},

doScroll:function(amount){
// do a scroll in the direction specified
delta = $(this.sid).down('.s_inner').style[this.direction];
if (!delta) {
delta = "0px";
}
numeric_delta = sizeToNumber(delta) + amount;
if ( numeric_delta > 0) {return}
delta = numeric_delta + "px";
$(this.sid).down('.s_inner').style[this.direction] = delta;
},

scrollToElement:function(target_id){
// scroll to a specified id element inside the area

switch (this.options.type) {
case 'vertical':
offsetType = 'offsetTop';
break;
case 'horizontal':
offsetType = 'offsetLeft';
break;
}
target_offset = $(target_id)[offsetType]; //distance to top of page for the target container
outer_offset = $(this.sid).down('.s_outer')[offsetType];
offset_from_top = target_offset - outer_offset;

this._to_scroll = offset_from_top;
this._incr = Math.ceil(Math.abs(offset_from_top) / 2);
new PeriodicalExecuter(
function(pe){
if (this._to_scroll <= this._incr) {
multiplier = 1
} else {
multiplier = -1
}
this.doScroll(multiplier * this._incr);
this._to_scroll += multiplier * this._incr;
this._incr = Math.ceil(this._incr / 2);
window.console.log(this._incr);
if (this._to_scroll * multiplier * -1 <= this._incr) {
this.doScroll(-1 * this._to_scroll); //the last scroll
pe.stop()
}
}.bind(this),
this.options.timeslice
);
return false;
}
})

2007-02-16

Fresh meat for programmers

I've just discovered (or rediscovered) some resources that I think are important enough to highlight here.

  • The Zope Corporation has several eggs that are not published in the svn.zope.org subversion repository, eggs located at download.zope.org. There are several very interesting packages there, including some to build an intranet. I'll definately have a look at them.
  • I've rediscovered OpenJSAN, a Javascript repository full of goodies.
  • And its really nice Planet Javascript

2007-02-12

Using FCKEditor in Ajax views on Zope 3

Filed Under:

I'm working on a new, AJAX based application for a friend, which will run under Zope 3. Being a rather "CMS-ish" type of application, I need to provide an easy way to edit some rich text fields. I've settled on the FCKEditor, for which there is an already packaged library as zope.html (also depends on zope.file). I would have used TinyMCE, but I hit on a problem: all these visual editors have difficulties when loaded in "dynamic loaded views".

  • When using FCKEditor, everything seemed be fine for the first time, but the second time the editor was loaded, after the form was reloaded, there would be an error about a missing JavaScript object and the editor would fail to load. Form submission is handled by a function that serializes the form and makes an Ajax call, but the form object only contains the old values, not the new ones, as modified by the visual editor, so this had to be solved as well.
  • I love TinyMCE for being able to scale down in terms of interface very easily, but I couldn't make it work in my scenario just as easily, so I gave up. The editor would load just fine, but when submitting the form, the entire web page would be replaced by a white page and would continue to keep loading, without any results. I've found some mentioning of this problem on the web, and even in the TinyMCE wiki, but I couldn't work out what needs to be done in the short time that I had.

To solve the FCKEditor problems I had to do the following (blessed be the other bloggers of the Internet which already had to deal with this problem):

  • For the first problem, I've inserted the following snippet in the form header:
<script type="text/javascript">
FCKeditorAPI = null;
__FCKeditorNS = null;
FCKTools = null;
</script>
  • For the second problem, the form submit handler, I have the following code:
if (FCKeditorAPI) {
       for (instance in FCKeditorAPI.__Instances) {
           field_name = instance.toString();
           field_value = FCKeditorAPI.GetInstance(field_name).GetXHTML();
           sub_form[field_name].value = field_value;
       }
   }

While searching the net for other editors that might not have this problem, I've found this page that contains a big listing of all types of HTML visual editors. To tell the truth, in my use case, I'd be happy with something like Epoz (and I even have checked it on the web), but the project seems dead and I think I would have had to strip the zope/plone integration out of it.

After more then 6 or 7 years of not having to deal with JavaScript I'm very very rusty. Even for the most simple questions - like: how do you get the properties of an object? how do you check if an object has a property? how do you check if an object exists - I had to look at references. But it's all part of the learning experience, which fortunately, is the part that I enjoy most.

Weblog
Atom
RDF
RSS 2.0
Powered by Quills
Technorati
Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.
 

Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: