JavaScript | Dmytro Shteflyuk's Home https://kpumuk.info In my blog I'll try to describe about interesting technologies, my discovery in IT and some useful things about programming. Tue, 08 Sep 2015 00:40:32 +0000 en-US hourly 1 https://wordpress.org/?v=6.7.1 Synchronous page method call in ASP.NET AJAX library https://kpumuk.info/asp-net/synchronous-page-method-call-in-asp-net-ajax-library/ https://kpumuk.info/asp-net/synchronous-page-method-call-in-asp-net-ajax-library/#comments Mon, 10 Sep 2007 21:15:28 +0000 http://kpumuk.info/asp-net/synchronous-page-method-call-in-asp-net-ajax-library/ Sometimes we need to do some tasks, that libraries developers even have not foreseen. One of such cases is to make synchronous AJAX call (Asynchronous JavaScript And XML). Below you could find quick solution. Please note: my solution is just copy/past of the XmlHttpExecutor code with two small changes: I have passed false as third […]

The post Synchronous page method call in ASP.NET AJAX library first appeared on Dmytro Shteflyuk's Home.]]>
Sometimes we need to do some tasks, that libraries developers even have not foreseen. One of such cases is to make synchronous AJAX call (Asynchronous JavaScript And XML). Below you could find quick solution.

Please note: my solution is just copy/past of the XmlHttpExecutor code with two small changes:

  • I have passed false as third parameter of the XMLHttpRequest.open method to make synchronous call.
  • I have cached request results in XMLHttpSyncExecutor members before freeing XMLHttpRequest in the _onReadyStateChange function.

So let’s get started. First of all we would define XMLHttpSyncExecutor class, descendant of the WebRequestExecutor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
Type.registerNamespace('Sys.Net');

Sys.Net.XMLHttpSyncExecutor = function()
{
    if (arguments.length !== 0) throw Error.parameterCount();
    Sys.Net.XMLHttpSyncExecutor.initializeBase(this);

    var _this = this;
    this._xmlHttpRequest = null;
    this._webRequest = null;
    this._responseAvailable = false;
    this._timedOut = false;
    this._timer = null;
    this._aborted = false;
    this._started = false;

    this._responseData = null;
    this._statusCode = null;
    this._statusText = null;
    this._headers = null;

    this._onReadyStateChange = function () {
        if (_this._xmlHttpRequest.readyState === 4 ) {
            _this._clearTimer();
            _this._responseAvailable = true;

            _this._responseData = _this._xmlHttpRequest.responseText;
            _this._statusCode = _this._xmlHttpRequest.status;
            _this._statusText = _this._xmlHttpRequest.statusText;
            _this._headers = _this._xmlHttpRequest.getAllResponseHeaders();

            _this._webRequest.completed(Sys.EventArgs.Empty);
            if (_this._xmlHttpRequest != null) {
                _this._xmlHttpRequest.onreadystatechange = Function.emptyMethod;
                _this._xmlHttpRequest = null;
            }
        }
    }

    this._clearTimer = function this$_clearTimer() {
        if (_this._timer != null) {
            window.clearTimeout(_this._timer);
            _this._timer = null;
        }
    }

    this._onTimeout = function this$_onTimeout() {
        if (!_this._responseAvailable) {
            _this._clearTimer();
            _this._timedOut = true;
            _this._xmlHttpRequest.onreadystatechange = Function.emptyMethod;
            _this._xmlHttpRequest.abort();
            _this._webRequest.completed(Sys.EventArgs.Empty);
            _this._xmlHttpRequest = null;
        }
    }
}

This is very simple and clean, so I’ll just show you other methods of the XMLHttpSyncExecutor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
function Sys$Net$XMLHttpSyncExecutor$get_timedOut() {
    /// <value type="Boolean"></value>
    if (arguments.length !== 0) throw Error.parameterCount();
    return this._timedOut;
}

function Sys$Net$XMLHttpSyncExecutor$get_started() {
    /// <value type="Boolean"></value>
    if (arguments.length !== 0) throw Error.parameterCount();
    return this._started;
}

function Sys$Net$XMLHttpSyncExecutor$get_responseAvailable() {
    /// <value type="Boolean"></value>
    if (arguments.length !== 0) throw Error.parameterCount();
    return this._responseAvailable;
}

function Sys$Net$XMLHttpSyncExecutor$get_aborted() {
    /// <value type="Boolean"></value>
    if (arguments.length !== 0) throw Error.parameterCount();
    return this._aborted;
}

function Sys$Net$XMLHttpSyncExecutor$executeRequest() {
    if (arguments.length !== 0) throw Error.parameterCount();
    this._webRequest = this.get_webRequest();

    if (this._started) {
        throw Error.invalidOperation(String.format(Sys.Res.cannotCallOnceStarted, 'executeRequest'));
    }
    if (this._webRequest === null) {
        throw Error.invalidOperation(Sys.Res.nullWebRequest);
    }

    var body = this._webRequest.get_body();
    var headers = this._webRequest.get_headers();
    this._xmlHttpRequest = new XMLHttpRequest();
    this._xmlHttpRequest.onreadystatechange = this._onReadyStateChange;
    var verb = this._webRequest.get_httpVerb();
    this._xmlHttpRequest.open(verb, this._webRequest.getResolvedUrl(), false); // False to call Synchronously
    if (headers) {
        for (var header in headers) {
            var val = headers[header];
            if (typeof(val) !== "function")
                this._xmlHttpRequest.setRequestHeader(header, val);
        }
    }

    if (verb.toLowerCase() === "post") {
        if ((headers === null) || !headers['Content-Type']) {
            this._xmlHttpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        }

        if (!body) {
            body = "";
        }
    }

    var timeout = this._webRequest.get_timeout();
    if (timeout > 0) {
        this._timer = window.setTimeout(Function.createDelegate(this, this._onTimeout), timeout);
    }
    this._xmlHttpRequest.send(body);
    this._started = true;
}

 function Sys$Net$XMLHttpSyncExecutor$getAllResponseHeaders() {
    /// <returns type="String"></returns>
    if (arguments.length !== 0) throw Error.parameterCount();
    if (!this._responseAvailable) {
        throw Error.invalidOperation(String.format(Sys.Res.cannotCallBeforeResponse, 'getAllResponseHeaders'));
    }

    return this._headers;
}

function Sys$Net$XMLHttpSyncExecutor$get_responseData() {
    /// <value type="String"></value>
    if (arguments.length !== 0) throw Error.parameterCount();
    if (!this._responseAvailable)
    {
        throw Error.invalidOperation(String.format(Sys.Res.cannotCallBeforeResponse, 'get_responseData'));
    }

    return this._responseData;
}

function Sys$Net$XMLHttpSyncExecutor$get_statusCode() {
    /// <value type="Number"></value>
    if (arguments.length !== 0) throw Error.parameterCount();
    if (!this._responseAvailable)
    {
        throw Error.invalidOperation(String.format(Sys.Res.cannotCallBeforeResponse, 'get_statusCode'));
    }

    return this._statusCode;
}

function Sys$Net$XMLHttpSyncExecutor$get_statusText() {
    /// <value type="String"></value>
    if (arguments.length !== 0) throw Error.parameterCount();
    if (!this._responseAvailable)
    {
        throw Error.invalidOperation(String.format(Sys.Res.cannotCallBeforeResponse, 'get_statusText'));
    }

    return this._statusText;
}

function Sys$Net$XMLHttpSyncExecutor$get_xml() {
    /// <value></value>
    if (arguments.length !== 0) throw Error.parameterCount();
    if (!this._responseAvailable)
    {
        throw Error.invalidOperation(String.format(Sys.Res.cannotCallBeforeResponse, 'get_xml'));
    }

    var xml = this._responseData;
    if ((!xml) || (!xml.documentElement))
    {
        xml = new XMLDOM(this._responseData);
        if ((!xml) || (!xml.documentElement))
        {
            return null;
        }
    }
    else if (navigator.userAgent.indexOf('MSIE') !== -1)
    {
        xml.setProperty('SelectionLanguage', 'XPath');
    }

    if ((xml.documentElement.namespaceURI === "http://www.mozilla.org/newlayout/xml/parsererror.xml") &&
        (xml.documentElement.tagName === "parsererror"))
    {
        return null;
    }

    if (xml.documentElement.firstChild && xml.documentElement.firstChild.tagName === "parsererror")
    {
        return null;
    }

    return xml;
}

function Sys$Net$XMLHttpSyncExecutor$abort() {
    if (arguments.length !== 0) throw Error.parameterCount();
    if (!this._started) {
        throw Error.invalidOperation(Sys.Res.cannotAbortBeforeStart);
    }

    if (this._aborted || this._responseAvailable || this._timedOut)
        return;

    this._aborted = true;

    this._clearTimer();

    if (this._xmlHttpRequest && !this._responseAvailable) {
        this._xmlHttpRequest.onreadystatechange = Function.emptyMethod;
        this._xmlHttpRequest.abort();

        this._xmlHttpRequest = null;
        var handler = this._webRequest._get_eventHandlerList().getHandler("completed");
        if (handler) {
            handler(this, Sys.EventArgs.Empty);
        }
    }
}

Sys.Net.XMLHttpSyncExecutor.prototype = {
    get_timedOut: Sys$Net$XMLHttpSyncExecutor$get_timedOut,
    get_started: Sys$Net$XMLHttpSyncExecutor$get_started,
    get_responseAvailable: Sys$Net$XMLHttpSyncExecutor$get_responseAvailable,
    get_aborted: Sys$Net$XMLHttpSyncExecutor$get_aborted,
    executeRequest: Sys$Net$XMLHttpSyncExecutor$executeRequest,
    getAllResponseHeaders: Sys$Net$XMLHttpSyncExecutor$getAllResponseHeaders,
    get_responseData: Sys$Net$XMLHttpSyncExecutor$get_responseData,
    get_statusCode: Sys$Net$XMLHttpSyncExecutor$get_statusCode,
    get_statusText: Sys$Net$XMLHttpSyncExecutor$get_statusText,
    get_xml: Sys$Net$XMLHttpSyncExecutor$get_xml,
    abort: Sys$Net$XMLHttpSyncExecutor$abort
}
Sys.Net.XMLHttpSyncExecutor.registerClass('Sys.Net.XMLHttpSyncExecutor', Sys.Net.WebRequestExecutor);

And finally we will notify ASP.NET AJAX that script have been loaded.

1
2
3
4
if (typeof(Sys) != 'undefined')
{
    Sys.Application.notifyScriptLoaded();
}

Usage is not so simple as wanted, but it’s still quite easy to understand:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function getServerTime() {
    // Instantiate a WebRequest.
    var wRequest = new Sys.Net.WebRequest();
    // Set the request URL.
    wRequest.set_url(PageMethods.get_path() + "/GetServerTime");
    // Set the request verb.
    wRequest.set_httpVerb('POST');
   
    wRequest.get_headers()['Content-Type'] = 'application/json; charset=utf-8';

    var executor = new Sys.Net.XMLHttpSyncExecutor();
    wRequest.set_executor(executor);
    // Execute the request.
    wRequest.invoke();

    if (executor.get_responseAvailable()) {
        return executor.get_object();
    }
    return false;
}

When you are using ScriptService, you will not see these difficulties, because ASP.NET will generate wrappers by itself. But if you need synchronous AJAX call — sorry, but in this case you should do it manually. Sample solution could be found here.

The post Synchronous page method call in ASP.NET AJAX library first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/asp-net/synchronous-page-method-call-in-asp-net-ajax-library/feed/ 10
Do I need to buy additional memory? https://kpumuk.info/javascript/do-i-need-to-buy-additional-memory/ https://kpumuk.info/javascript/do-i-need-to-buy-additional-memory/#comments Tue, 29 May 2007 14:07:41 +0000 http://kpumuk.info/life/do-i-need-to-buy-additional-memory/ Today I spent almost full day for reflection on to buy or not additional memory for my computer. As a result new script was born, and he saved my brain: If you have questions like this — ask the script, maybe it would help you too :-)

The post Do I need to buy additional memory? first appeared on Dmytro Shteflyuk's Home.]]>
Today I spent almost full day for reflection on to buy or not additional memory for my computer. As a result new script was born, and he saved my brain:

Do I need to buy additional memory?

If you have questions like this — ask the script, maybe it would help you too :-)

The post Do I need to buy additional memory? first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/javascript/do-i-need-to-buy-additional-memory/feed/ 28
JavaScript optimization Part 3: Attaching events https://kpumuk.info/javascript/javascript-optimization-part-3-attaching-events/ https://kpumuk.info/javascript/javascript-optimization-part-3-attaching-events/#comments Wed, 09 May 2007 23:41:55 +0000 http://kpumuk.info/javascript/javascript-optimization-part-3-attaching-events/ This is a third part of the JavaScript optimization tutorial, and today I’m going to talk about events. Sorry for a long delay between posts, I hope remaining parts would not be delayed so much. Scenario: you have some elements and you need to add some actions to them (for example, when user moves mouse […]

The post JavaScript optimization Part 3: Attaching events first appeared on Dmytro Shteflyuk's Home.]]>
This is a third part of the JavaScript optimization tutorial, and today I’m going to talk about events. Sorry for a long delay between posts, I hope remaining parts would not be delayed so much.

Scenario: you have some elements and you need to add some actions to them (for example, when user moves mouse cursor over element, or clicks on elements).

This is pretty usual task in web-development. And the first thing that everybody knows is a different event attaching syntax in Internet Explorer and Firefox: first uses element.attachEvent, second — element.addEventListeners. Therefor you need to add code, which would detect what browser is being used and what method to use in each case. In the most popular client script library Prototype it is already standardized and you can always use Event.observe. Let’s benchmark a little.

I’m going to start attaching events from manual detection of which browser is being used:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Attaching events
for (var i = items.length; i--; ) {
    if (items[i].addEventListener) {
        items[i].addEventListener('click', e_onclick, false);
    } else if (items[i].attachEvent) {
        items[i].attachEvent('onclick', e_onclick);
    }
}
// Detaching events
for (var i = items.length; i--; ) {
    if (items[i].removeEventListener) {
        items[i].removeEventListener('click', e_onclick, false);
    } else if (items[i].detachEvent) {
        items[i].detachEvent('onclick', e_onclick);
    }
}

This approach shown best times: 188 and 203 ms in Internet Explorer 6 and 7, 125 and 141 ms in Firefox 1.5 and 2.0, and 63 ms in Opera 9, but there is one problem with it — memory leaks: Internet Explorer usually forget to clean up memory, used by event listeners, when you navigating to another page. Therefor all JavaScript frameworks which implement event attaching/detaching functions, provide functionality for removing listeners when you navigating to another page. Let’s test them.

Code for testing Prototype library:

1
2
3
4
5
6
7
8
// Attaching events
for (var i = items.length; i--; ) {
    Event.observe(items[i], 'click', e_onclick, false);
}
// Detaching events
for (var i = items.length; i--; ) {
    Event.stopObserving(items[i], 'click', e_onclick, false);
}

This is very slow, 6453 ms in Internet Explorer 6 and looks like some memory leaks are still exists (it became slower and slower with the lapse of time) and 365 — 653 ms in other browsers.

There are tons of problems and solutions with adding events (just look at the Advanced event registration models). Some time ago even PPK’s addEvent() Recoding Contest was over (but do not use the winner’s solution because of it’s inefficiency and memory leaks).

Instead of winner’s solution, I decided to test incredible and impertinent solution from Dean Edwards, which even does not use the addeventListener/attachEvent methods, but it is entirely cross-browser!

Used code:

1
2
3
4
5
6
7
8
// Attaching events
for (var i = items.length; i--; ) {
    addEvent(items[i], 'click', e_onclick);
}
// Detaching events
for (var i = items.length; i--; ) {
    removeEvent(items[i], 'click', e_onclick);
}

Results are as much impressive as when we used manual events attaching, and this approach is extremely fast to use it in real-world applications. Of course, you would say: “Stop, what you are talking about? I don’t want to use additional methods while Prototype is already used in my code!” Calm down, you don’t need to do it, just download Low Pro library (current version is 0.4, stay tuned). Ruby on Rails developers could use UJS Rails Plugin in their applications to improve performance — it uses Low Pro inside.

No Method IE 6 IE 7 FF 1.5 FF 2.0 Opera 9
1 manually 203 188 125 141 63
2 prototype.js 6453 653 547 469 365
3 addEvent 783 344 94 141 62

Benchmark: Attaching events

You can review benchmark and get your own results here.

Conclusions

  • Always use Low Pro along with Prototype.js (of course, if your application uses rich client logic).
  • Use manual events attaching when you need to get as much as possible speed from your application (for example, when you handling hundreds of elements). But don't forget about memory leaks!
  • Avoid events attaching whenever it's possible (for example, use :hover CSS selector instead of onmouseover event).
  • Keep your eyes opened, maybe tomorrow somebody would post another great performance optimization tip.

Links to other parts

The post JavaScript optimization Part 3: Attaching events first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/javascript/javascript-optimization-part-3-attaching-events/feed/ 10
JavaScript optimization Part 2: Applying styles to elements https://kpumuk.info/javascript/javascript-optimization-part-2-applying-styles-to-elements/ https://kpumuk.info/javascript/javascript-optimization-part-2-applying-styles-to-elements/#comments Tue, 27 Mar 2007 20:02:41 +0000 http://kpumuk.info/javascript/javascript-optimization-part-2-applying-styles-to-elements/ This is second part of articles cycle devoted to JavaScript optimization. In this post I’ll cover dynamic elements styling and explore a little HTML-rendering process. Also you will find here some tricks on how to make your applications faster. Scenario: you have elements in your document and you need to change their color, background, or […]

The post JavaScript optimization Part 2: Applying styles to elements first appeared on Dmytro Shteflyuk's Home.]]>
This is second part of articles cycle devoted to JavaScript optimization. In this post I’ll cover dynamic elements styling and explore a little HTML-rendering process. Also you will find here some tricks on how to make your applications faster.

Scenario: you have elements in your document and you need to change their color, background, or something else related to theirs style. For example, highlight table rows on mouse over or mark them when corresponding checkbox is checked.

And again I know two ways: using styles or set foreground(or background) color directly from JavaScript. Let’s test them before discussion:

1
2
3
4
var items = el.getElementsByTagName('li');
for (var i = 0; i < 1000; i++) {
    items[i].className = 'selected';
}

The average result is 187 – 291 ms, for the InternetExplorer 6 it is 512 ms, and in Opera 9 it is 47 ms.

1
2
3
4
5
var items = el.getElementsByTagName('li');
for (var i = 0; i < 1000; i++) {
    items[i].style.backgroundColor = '#007f00';
    items[i].style.color = '#ff0000';
}

I got results starting from 282 ms in Opera and ending with 1709 ms in Internet Explorer 6.

Result are clean and easy to understand:

No Method IE 6 IE 7 FF 1.5 FF 2.0 Opera 9
1 element.className 512 187 291 203 47
2 element.style.color 1709 422 725 547 282

Benchmark: Applying styles to elements

You can view benchmark test and get your own results here.

Looks like this is simplest optimization tip, but... there is one issue with Internet Explorer -- page update. Are you remember scenario described in the beginning of chapter, about onmouseover? When you change element's class name, your code is much faster, but the page would not be updated immediately. Look at this example. Try to click "Generate elements" when "element.className" radio button is selected. Then move your mouse pointer over the items, scroll list to the bottom, move mouse again (for slow machines items number would be less than default, for fast ones - greater). Have you noticed, that background changes not so fast as the mouse pointer moves? Now switch selected radio button to "element.style.color". Background changing smoothly, right?

At the bottom of page you see the number of onmouseover events triggered and average time spent to dispatch them. As you noticed, first radio button works two times faster! Why it looks slower? I think it is because when you changed className property Internet Explorer isn't actually updates UI, but placed event in update queue. If you have other ideas, please post comments.

Anybody, who read article up to this place, would probably say: "Hey, guy, you are cheating. Where is the :hover?". I'm hurry up to fix this shortcoming and describing this case right now. First, this thing is not working in the Internet Explorer 6 (and don't ask me "who need it?"). But this is not most difficult problem, performance of this issue is much worse. Even don't want to comment it, just select third radiobutton on previously given page and move your mouse in Internet Explorer 7 and Opera 9 (Firefox works great).

Conclusions

  • Use className when it's possible. It grants you more flexibility and control over site look.
  • If you have many items in your container element, and you need really fast UI, set styles directly through element's style property.

Links to other parts

  • Part 1: Adding DOM elements to document
  • Part 2: Applying styles to elements
  • Part 3: Attaching events
  • Part 4: Multiple anonymous functions (will be published soon)
  • Part 5: Attaching events on demand (will be published soon)
  • Part 6: Element hide and show (will be published soon)
  • Part 7: Elements collection enumeration (will be published soon)
The post JavaScript optimization Part 2: Applying styles to elements first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/javascript/javascript-optimization-part-2-applying-styles-to-elements/feed/ 4
JavaScript optimization Part 1: Adding DOM elements to document https://kpumuk.info/javascript/javascript-optimization-part-1-adding-dom-elements-to-document/ https://kpumuk.info/javascript/javascript-optimization-part-1-adding-dom-elements-to-document/#comments Sun, 25 Mar 2007 01:40:49 +0000 http://kpumuk.info/javascript/javascript-optimization-part-1-adding-dom-elements-to-document/ Most web-developers writing tons of JavaScript, especially in our Web 2.0 century. It’s powerful technology, but most browsers has very slow implementation of engine, and everyone at some instant decide to review code and make it faster. In this post I’ll share my experience and explain several tricks how to make your JavaScript as fast […]

The post JavaScript optimization Part 1: Adding DOM elements to document first appeared on Dmytro Shteflyuk's Home.]]>
Most web-developers writing tons of JavaScript, especially in our Web 2.0 century. It’s powerful technology, but most browsers has very slow implementation of engine, and everyone at some instant decide to review code and make it faster. In this post I’ll share my experience and explain several tricks how to make your JavaScript as fast as possible.

This is first article in 7 parts tutorial, stay tuned.

Scenario: you’re developing rich Internet application and you need to load dynamic elements using AJAX and then add them to current document. For some reason you don’t want (or can’t) use fully generated HTML, and decided to fetch items in JavaScript array.

I know two classic ways to do so: create elements using document.createElement() method and concatenate HTML string and assign it to parentElement.innerHTML property. Of course, you can combine both ways. Let’s examine this ways in details.

Classic way (and in ideal world the best way) is to use DOM for element manipulations:

1
2
3
4
5
for (var i = 1; i <= 1000; i++) {
    var li = document.createElement('li')
    li.appendChild(document.createTextNode('Element ' + i));
    el.appendChild(li);
}

Not so bad performance. Internet Explorer 6 is slowest – 1403 ms (but it is a slower browser in the world, right?), and other browser displayed results quickly (63 – 328 ms). Ok, but what about creating DOM element from HTML code?

1
2
3
4
for (var i = 1; i <= 1000; i++) {
    var li = document.createElement('<li>Element ' + i + '</li>');
    el.appendChild(li);
}

It works better in Internet Explorer 6 (1134 ms), but does not work in other browsers at all. Weird! Of course, you can add try/catch block and create elements using first approach in catch block for the other browsers. But I have better solution.

Every DOM node has attribute innerHTML which holds all child nodes as HTML string.

1
2
3
4
el.innerHTML = '';
for (var i = 1; i <= 1000; i++) {
    el.innerHTML += '<li>Element ' + i + '</li>';
}

Wow, I’m highly impressed how slow could be adding elements procedure (11391 – 307938 ms)! Cool result, right? It’s because browser tried to render list while we updating and it’s take so long time. Little optimization:

1
2
3
4
5
var html = '';
for (var i = 1; i <= 1000; i++) {
    html += '<li>Element ' + i + '</li>';
}
el.innerHTML = html;

All browsers shows great perfomance (31 – 109 ms), but Internet Explorer is still slow – 10994 ms. I found solution which works very fast in all browsers: to create array of HTML chunks, and the join them using empty string:

1
2
3
4
5
6
7
var html = [];
for (var i = 1; i <= 1000; i++) {
    html.push('<li>Element ');
    html.push(i);
    html.push('</li>');
}
el.innerHTML = html.join('');

It’s fastest approach for Internet Explorer 6 – 400 ms, and very fast in other browsers. Why I’m not saying fastest in case of Firefox? I added another test to make in cleaner:

1
2
3
4
5
6
7
var html = '';
for (var i = 1; i <= 1000; i++) {
    html += '<li style="padding-left: ' + (i % 50) +
    '" id="item-' + i + '">Element ' + i + ' Column ' +
    (i % 50) + '</li>';
}
el.innerHTML = html;

And second example:

1
2
3
4
5
6
7
8
9
10
11
12
13
var html = [];
for (var i = 1; i <= 1000; i++) {
    html.push('<li style="padding-left: ');
    html.push(i % 50);
    html.push('" id="item-');
    html.push(i);
    html.push('">Element ');
    html.push(i);
    html.push(' Column ');
    html.push(i % 50);
    html.push('</li>');
}
el.innerHTML = html.join('');

Here are the results in table and diagram formats.

No Method IE 6 IE 7 FF 1.5 FF 2.0 Opera 9
1 createElement() 1403 219 166 328 63
2 createElement() full 1134 - - - -
3 innerHTML 39757 20781 41058 307938 11391
4 innerHTML optimized 10994 46 50 109 31
5 innerHTML/join 400 31 47 125 31
 
6 innerHTML/optimized+ 28934 109 84 172 62
7 innerHTML/join+ 950 78 110 189 62

Benchmark: Adding DOM elements to document

You can view benchmark test and get your own results here.

Conclusions

  • Always use DOM node functions to keep your code standards-compliant. This approach has satisfactory performance and works in all browsers.
  • If you need extremely high performance, use join+innerHTML method, which has best time in benchmark.
  • Never use appending HTML strings to the innerHTML (yeah, if you need append one small element).
  • Opera is fastest browser in the world, but Internet Explorer 7 is fast too, Firefox 2.0 surprised me with his low performance.
  • Never believe fanatics like me and benchmark different approaches by yourself (but don't worry, Microsoft does not paid me for their browser advertisement).

Links to other parts

  • Part 1: Adding DOM elements to document
  • Part 2: Applying styles to elements
  • Part 3: Attaching events
  • Part 4: Multiple anonymous functions (will be published soon)
  • Part 5: Attaching events on demand (will be published soon)
  • Part 6: Element hide and show (will be published soon)
  • Part 7: Elements collection enumeration (will be published soon)
The post JavaScript optimization Part 1: Adding DOM elements to document first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/javascript/javascript-optimization-part-1-adding-dom-elements-to-document/feed/ 18
Ruby on Rails related cheat sheets https://kpumuk.info/ruby-on-rails/ruby-on-rails-related-cheat-sheets/ https://kpumuk.info/ruby-on-rails/ruby-on-rails-related-cheat-sheets/#comments Fri, 17 Nov 2006 05:32:47 +0000 http://kpumuk.info/ajax/ruby-on-rails-related-cheat-sheets/ There are couple of cheat sheets about Ruby on Rails and related technologies can be found in the web. I decided to collect all of them (almost all) in one post just to keep them in mind. All of them are in full color PDFs or PNGs. Ruby on Rails Development Cheat Sheets Ruby Cheatsheet […]

The post Ruby on Rails related cheat sheets first appeared on Dmytro Shteflyuk's Home.]]>
There are couple of cheat sheets about Ruby on Rails and related technologies can be found in the web. I decided to collect all of them (almost all) in one post just to keep them in mind. All of them are in full color PDFs or PNGs.

Ruby on Rails Development Cheat Sheets

JavaScript Development Cheat Sheets

Databases / SQL Cheat Sheets

Web Development Cheat Sheets

Other Cheat Sheets

The post Ruby on Rails related cheat sheets first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/ruby-on-rails-related-cheat-sheets/feed/ 13
In-place file upload with Ruby on Rails https://kpumuk.info/ruby-on-rails/in-place-file-upload-with-ruby-on-rails/ https://kpumuk.info/ruby-on-rails/in-place-file-upload-with-ruby-on-rails/#comments Sat, 28 Oct 2006 13:10:32 +0000 http://kpumuk.info/ruby-on-rails/in-place-file-upload-with-ruby-on-rails/ My friends often asked me how to upload file using AJAX, and usually they got answer “in no way”. Correct answer, but what if I need to upload file without full page reloading? And, of course, I want to use RJS in this case. Here I’ll explain what to do to get effect very similar […]

The post In-place file upload with Ruby on Rails first appeared on Dmytro Shteflyuk's Home.]]>
My friends often asked me how to upload file using AJAX, and usually they got answer “in no way”. Correct answer, but what if I need to upload file without full page reloading? And, of course, I want to use RJS in this case. Here I’ll explain what to do to get effect very similar to AJAX file upload (btw, Gmail uses this technique).

First of all, do you know, that form element has attribute target? If you specify it your form will be submitted into frame with name entered in target attribute. Of course, this frame can be iframe, and it can be hidden! Look at the following chunk of HTML code:

1
2
3
4
5
<form target="upload_frame" action="/test/upload_action" id="upload_form" method="post" enctype="multipart/form-data">
  <input type="file" name="uploaded_file" /><br />
  <input type="submit" />
</form>
<iframe id="upload_frame" name="upload_frame" style="display: none"></iframe>

When you click the “Submit” button, form will be submitted to hidden iframe and controller’s action will be called. But resulting RJS will be returned into the hidden frame! We need to get full respond and execute it in context of whole page (parent window for our frame). There is one interesting technique exists: http://developer.apple.com/internet/webcontent/iframe.html, and it’s implementation as Ruby on Rails plugin here. Example of using:

1
2
3
4
5
6
7
8
9
10
11
class TestController < ActionController::Base
  def upload_action
    # Do stuff with params[:uploaded_file]

    responds_to_parent do
      render :update do |page|
        page.replace_html 'upload_form', :partial => 'upload_form'
      end
    end
  end
end

It’s simple, right?

The post In-place file upload with Ruby on Rails first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/ruby-on-rails/in-place-file-upload-with-ruby-on-rails/feed/ 32
Extending moo.fx with custom effect (fx.Flash) https://kpumuk.info/javascript/extending-moo-fx-with-custom-effect-fx-flash/ https://kpumuk.info/javascript/extending-moo-fx-with-custom-effect-fx-flash/#comments Sat, 29 Apr 2006 08:54:43 +0000 http://kpumuk.info/php/extending-moo-fx-with-custom-effect-fx-flash/ Several days ago my best friend Alexey Kovyrin asked me to help him to create flashing effect on HTML page. I gave him advice to create this effect using moo.fx library. I wanted to show how to extend it, but I couldn’t keep from creating this effect myself. moo.fx is a superlightweight, ultratiny, megasmall javascript […]

The post Extending moo.fx with custom effect (fx.Flash) first appeared on Dmytro Shteflyuk's Home.]]>
Several days ago my best friend Alexey Kovyrin asked me to help him to create flashing effect on HTML page. I gave him advice to create this effect using moo.fx library. I wanted to show how to extend it, but I couldn’t keep from creating this effect myself.

moo.fx is a superlightweight, ultratiny, megasmall javascript effects library, written with prototype.js. It’s easily to extend it with custom effect and in this post I will show you how you can do this. In this article I’ll show how to create flashing effect: element’s background will smoothly change from one color to another and go back to first color (maybe several times).

To extend fx.Base you can use following construction:

1
2
3
fx.Flash = Class.create();
Object.extend(Object.extend(fx.Flash.prototype, fx.Base.prototype), {  
// ...

Now we need to decide in which format initial colors will be passed. I think the most simple to user is to pass color values in #RRGGBB format, the most popular format in web-design. We need to parse this format to get separate color’s parts (reg, green and blue), therefor it’s necessary to create function that makes hexadecimal => decimal conversion. To assign background color to the element while flashing reverse function is required.

1
2
3
4
5
6
7
8
9
10
11
12
hD: "0123456789ABCDEF",

d2h: function(d) {
    var h = this.hD.substr(d & 15, 1);
    while (d > 15) { d >>= 4; h = this.hD.substr(d & 15, 1) + h; }
    if (h.length == 1) h = "0" + h;
    return h;
},
   
h2d: function(h) {
    return parseInt(h, 16);
}

Now we are ready to develop constructor (method initialize()):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
initialize: function(el, options) {
    this.el = $(el);

    var color_from = (options && options.color_from) || "#ffffff";
    var color_to = (options && options.color_to) || "#ff0000";
    var color_f = this.h2d(color_from.substr(1));
    var color_t = this.h2d(color_to.substr(1));
   
    var _options = {
        red: [color_f >> 16, color_t >> 16],
        green: [(color_f >> 8) & 255, (color_t >> 8) & 255],
        blue: [color_f & 255, color_t & 255],
        count: 1
    };
    Object.extend(_options, options || {});
    if (_options.onComplete) _options.flashOnComplete = _options.onComplete;
    this.setOptions(_options);
}

This function can accept following options:

  • duration – duration in milliseconds of changing color from first to second or vice versa. For example, if you pass count = 3, complete effect execution time will be 3 * 2 * duration (default is 500).
  • onComplete – a function that will get called upon effect completion.
  • transition – transition type. (default is fx.sinoidal)
  • color_from – beginning color (default is #ffffff)
  • color_to – ending color (default is #ff0000)
  • count – flashes count (default is 1)

Effect dependents on onComplete function therefor this parameters is saved to another options variable flashOnComplete.

Function increase() is called during effect. In this function we can calculate current color based on internal variable this.now:

1
2
3
4
5
6
increase: function() {
    var r = this.d2h(this.now * (this.options.red[0] - this.options.red[1]) / 255 + this.options.red[1]);
    var g = this.d2h(this.now * (this.options.green[0] - this.options.green[1]) / 255 + this.options.green[1]);
    var b = this.d2h(this.now * (this.options.blue[0] - this.options.blue[1]) / 255 + this.options.blue[1]);
    this.el.style.backgroundColor = "#" + r + g + b;
}

To call effect function toggle() used. OnComplete is used to repeat effect from ending to beginnig color. It also decrements current count value to make it possible to repeat effect count times.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
toggle: function() {
    if (this.flashCount == undefined) this.flashCount = this.options.count;
    this.options.onComplete = this.onComplete.bind(this);
    this.custom(255, 0);
},

onComplete: function() {
    this.flashCount--;
    if (this.flashCount == 0)
    {
        this.flashCount = undefined;
        this.options.onComplete = this.options.flashOnComplete;
    } else
        this.options.onComplete = this.toggle.bind(this);
    this.custom(0, 255);
}

Thats all. You can download source file here. Samples are shown below this.

This is sample text. Click the link below to view fx.Flash effect in action.
1
var sample1 = new fx.Flash("sample-area", {color_from:"#ffffe3", color_to:"#007f00", count:3, transition:fx.circ, duration:300});

Show effect #1

1
var sample2 = new fx.Flash("sample-area", {color_from:"#ffffe3", color_to:"#7f0000", count:1, duration:600});

Show effect #2

The post Extending moo.fx with custom effect (fx.Flash) first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/javascript/extending-moo-fx-with-custom-effect-fx-flash/feed/ 5
AJAX-enabled Smarty plugins Part 2: ajax_form https://kpumuk.info/php/ajax-enabled-smarty-plugins-part-2-ajax_form/ https://kpumuk.info/php/ajax-enabled-smarty-plugins-part-2-ajax_form/#comments Tue, 21 Feb 2006 07:47:28 +0000 http://kpumuk.info/ajax/ajax-enabled-smarty-plugins-part-2-ajax_formajax-%d0%bf%d0%bb%d0%b0%d0%b3%d0%b8%d0%bd%d1%8b-%d0%b4%d0%bb%d1%8f-smarty-%d1%87%d0%b0%d1%81%d1%82%d1%8c-2-ajax_form/ In my previous post I’ve described several simple AJAX plugins. Now I’ll show how to use one of them — ajax_form — in real applications. I think this is the most powerful plugin therefor I’ll dwell on it more elaborately. Most of Web-forms has variuos validation mechanisms. Simplest way is to process form on the […]

The post AJAX-enabled Smarty plugins Part 2: ajax_form first appeared on Dmytro Shteflyuk's Home.]]>
In my previous post I’ve described several simple AJAX plugins. Now I’ll show how to use one of them — ajax_form — in real applications. I think this is the most powerful plugin therefor I’ll dwell on it more elaborately.

Most of Web-forms has variuos validation mechanisms. Simplest way is to process form on the server and parse result on client, where return data can be in following format:

1
2
true;User was registered successfully;http://example.com/login/
false;Please enter your name;Entered passwords doesn't match

Result string can be splitted on client and results can be shown in some part of page. Process function may looks like following (it’s part of smarty_ajax):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var SmartyAjax = {
  onSubmit: function(originalRequest) {
    var results = originalRequest.responseText.split(";");

    if (results[0] == "true") {
      SmartyAjax.Messages.set(results[1],
        SmartyAjax.Messages.MT_WARNING)
    } else {
      SmartyAjax.Messages.clear();
      SmartyAjax.Messages.setType(SmartyAjax.Messages.MT_ERROR);
      for (var i = 1; i < results.length; i++) {
        SmartyAjax.Messages.add(results[i]);
      }
    }
  }
}

This function uses several functions from SmartyAjax.Messages object which depends on following HTML elements:

1
2
3
4
<div id="messages">
  <p id="messages-title"></p>
  <ul id="messages-list"></ul>
</div>

To manage messages I’ve created Smarty template parts/warnings.tpl. You can use it in your PHP file in simple way:

1
2
3
4
// is messages contains warning (or error)
$t->assign('messages_warning', true);
// array of messages
$t->assign('messages', array('Please provide your account information'));

Another thing we need to examine is message which informs user about processing AJAX request. There is SmartyAjax.Process object in the sample which has two methods: hide() and show() which can be used to hide and display message. They are depends on HTML-element with id=”ajax-process”. It’s necessary to add “position: absolute” style because of this messages will be shown in top-right corner of screen independing on scroll position.

ajax_form is simple to use:

1
2
3
{ajax_form method="post" id="form_register"}
Any form-element can be placed here
{/ajax_form}

Possible parameters:

  • url – URL for AJAX-query (when no URL was specified current one will be used)
  • method – query method (by default get, can be get or post)
  • params – URL-encoded parameters
  • id – form ID
  • callback – JavaScript function which will be called when query will be completed

Example can be found here, and full sources can be downloaded here.

The post AJAX-enabled Smarty plugins Part 2: ajax_form first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/php/ajax-enabled-smarty-plugins-part-2-ajax_form/feed/ 52
AJAX-enabled Smarty plugins https://kpumuk.info/php/ajax-enabled-smarty-plugins/ https://kpumuk.info/php/ajax-enabled-smarty-plugins/#comments Sat, 18 Feb 2006 23:49:25 +0000 http://kpumuk.info/ajax/ajax-enabled-smarty-pluginsajax-%d0%bf%d0%bb%d0%b0%d0%b3%d0%b8%d0%bd%d1%8b-%d0%b4%d0%bb%d1%8f-smarty/ Today I’ve created simple AJAX-enabled plugins for Smarty. I don’t try to develop powerful reach-applications framework. I can give you only idea how to integrate AJAX-technology into Smarty. But if you have any offers how to improve anything I’ve described or if you just want to leave feedback please post you comments on my site. […]

The post AJAX-enabled Smarty plugins first appeared on Dmytro Shteflyuk's Home.]]>
Today I’ve created simple AJAX-enabled plugins for Smarty. I don’t try to develop powerful reach-applications framework. I can give you only idea how to integrate AJAX-technology into Smarty. But if you have any offers how to improve anything I’ve described or if you just want to leave feedback please post you comments on my site.

In my practice I need several things from AJAX: to update some nodes in DOM, to send forms without post-back, to retrieve some values or to perform server-side calculation (maybe using database or other server-side resources). It’s necessary to write tons of JavaScript to implement my requirements in spite of using cool JavaScript library Prototype.

I decided to integrate Smarty with AJAX. There are three Smarty plugins has been created: ajax_update, ajax_call, ajax_form. Below all this plugins will be described.

ajax_update

This Smarty function can be used for update some parts of web-page.

Sample usage:

1
2
<a href="#" onclick="{ajax_update update_id='intro_content'
  function='update_intro' params='page=about'}"
>About</a>

Possible parameters:

  • url – URL for AJAX-query (when no URL was specified current one will be used)
  • method – query method (by default get, can be get or post)
  • update_id – ID of HTML element for update
  • function – function which will be called
  • params – URL-encoded parameters

ajax_call

This Smarty function can be used for call PHP function on server side and retrieve its output.

Sample usage:

1
2
<a href="#" onclick="{ajax_call function='calculate'
  params_func='calc_params' callback='calc_cb'}"
>Calculate</a>

Possible parameters:

  • url – URL for AJAX-query (when no URL was specified current one will be used)
  • method – query method (by default get, can be get or post)
  • function – function which will be called
  • params – URL-encoded parameters
  • callback – JavaScript function which will be called when query will be completed
  • params_func – JavaScript function used when you need custom parameters calculated on client side

ajax_form

This Smarty block can be used for submit Web-forms without post-back.

Sample usage:

1
2
3
{ajax_form method="post" id="form_register"}
Any form-element can be placed here
{/ajax_form}

Possible parameters:

  • url – URL for AJAX-query (when no URL was specified current one will be used)
  • method – query method (by default get, can be get or post)
  • params – URL-encoded parameters
  • id – form ID
  • callback – JavaScript function which will be called when query will be completed

Samples

These plugins are quite simple and I think everyone can create even better than mine. I’ve just wanted to show you idea how integration can be done. Working examples can be found here. Also you can download full sources.

The post AJAX-enabled Smarty plugins first appeared on Dmytro Shteflyuk's Home.]]>
https://kpumuk.info/php/ajax-enabled-smarty-plugins/feed/ 71