This acrticle at wikidot.com

JavaScript AJAX

Представляю вам набор функций, которые у меня лежат в отдельном файле utils.js — это функции, которые я использую чаще всего. Они стараются быть кроссбраузерными и проверены на IE6/7, FF2 и Safari 2 и на боевой, сложной системе, в XHTML документах. Должны, по идее, работать, и на других, но не очень старых версиях браузеров — проверку браузера я использовал только в исключительных случаях. Некоторая часть из них, конечно же, просто нарыта на просторах интернета (где — обычно указано) и заимствована ввиду открытости, а большая часть — сконструирована из многих ресурсов и своих идей (и советов коллег), дабы работать на ура — поскольку часто в разных скриптах не учитываются разные тонкости, которые, тем не менее — при ближайшем рассмотрении — оказываются общностями :) , ну и быть довольно читабельными.

Фукнции разделены тематически:

  • ООПобеспечение (или, вернее сказать — эмуляция) возможности использовать принципы ООП в JavaScript
  • Объектная модель JSиспользование и расширение встроенных объектов JS
  • Определение браузерачтобы использовать в тех редких случаях, когда это все-таки неизбежно необходимо :)
  • Координаты / Позициионированиевычисление координат и позиционирование объектов — ввиду того, что это часто довольно хитрая штука
  • DOMработа с объектной моделью документа
  • AJAXвспомогательные функции для AJAX — так как это средство часто применимо :)
  • Логгинг — иногда он нужен чтобы везде :)

NB! (советы по оптимизации и исправлениям приветствуются)

NB! (если вам не хватает подстветки кода в данной статье и вы используете браузер Firefox — прошу проследовать по этой ссылке за соответствующим плагином, поскольку в бесплатной версии WordPress обеспечить её внутренними средствами нет возможности)

NB! (при написании статьи примеры были взяты из рабочего кода, но в некоторых навскидку изменены названия функций и даже некоторая функциональность. также к коду функций было применено такое форматирование как переносы строк — на данный момент — без проверки последующей работоспособности. как только код будет полностью проверен на работоспособность — этот комментарий будет отсюда удален)

ООП

1. Первый блок — набор из трех функций (две из которых пустые :) ), позволяющих применять (эмулировать?) все три принципа ООП в JavaScript. Из нескольких предложенных на AJAXPath и на AJAXPatterns вариантов я выбрал именно этот ввиду его одновременной понятности (Upd.: на глаз :) — см. комментарии) и быстрой скорости выполнения и немного его видоизменил, так — чтобы отдельно объявленные свойства воспринимались как статические константы.

function Class() { };

Class.prototype.construct = function() { };

Class.extend = function(def) {

    var classDef = function() {
        if (arguments[0] !== Class) {
            this.construct.apply(this, arguments);
        }
    };

    var proto = new this(Class);
    var superClass = this.prototype;

    for (var n in def) {
        var item = def[n];
        if (item instanceof Function) item.$ = superClass;
                else classDef[n] = item;
        proto[n] = item;
    }

    classDef.prototype = proto;
    classDef.extend = this.extend;

    return classDef;
};

Полные примеры использования относительно велики, поэтому я их вынесу в следующую статью и проследую далее. Пару простых примеров вы можете наблюдать в пунктах 2, 5 и 15.

2. Следующая функция — простая но изящная — полезна в сочетании с предыдущим набором — она создает функцию-ссылку на метод:

function createMethodReference(object, methodName) {
    return function () {
        return object[methodName].apply(object, arguments);
    };
}

Теперь можно, например, сделать так:

var ScrollingHandler = Class.extend({

    construct:
        function(elementId) {
            this._elementId = elementId;
            this.assignListener();
        },

    assignListener:
        function() {
            var scrollControlElem =
                        document.getElementById(this._elementId);
            if (scrollControlElem) {
                scrollControlElem.onscroll =
                    createMethodReference(this, "_onElementScroll");
            }
        },

    _onElementScroll:
        function(ev) {
            ev = ev || window.event;
            alert("please stop scrolling,
                        I've already got an event: " + ev);
        }
});

var elmScrollHandler = new ScrollHandler('SomeElmId');

Объект этого класса можно будет ассоциировать с событием скроллинга элемента с указанным ID и совершать что-либо по этому случаю.

Объектная модель JS

3. Нижеприведенная функция клонирует любой объект вместе со всеми его свойствами:

function cloneObj(objToClone) {
    var clone = [];
    for (i in objToClone) {
        clone[i] = objToClone[i];
    }
    return clone;
}

Использование — простейшее до невозможности:

var clonedObj = cloneObj(objToClone);

4. Конвертер объектов, следующая функция, позволяет удобно использовать всяческие условные (и претендующие ими быть :) ) конструкции вида if (tablet.toLowerCase() in oc(['cialis','mevacor','zocor'])) { alert('I will not!') };. Код заимствован отсюда.

function oc(a) {
    var o = {};
    for(var i=0;i<a.length;i++) {
        o[a[i]]='';
    }
    return o;
}

Для примера возьмем ситуацию, когда сначала требуется определить, входит ли объект в какое-либо множество одиночных объектов, а затем — не входит ли он в сочетании с другим объектом в другое множество пар объектов. Допустим, на вечеринку пускают одиночек только с определенными именами, либо пары из списка с позволенными сочетаниями имен:

function isPersonAllowed(maleName, femaleName) {
    var pairsAllowed = new Array([ "John", "Yoko" ],
            [ "Bill",  "Monica" ], [ "Phil",  "Sue" ],
            [ "Jason",  "Harrison" ], [ "Adam",  "Eve" ]);
    var singlesAllowed = new Array("Michael", "Pete", "John",
            "Dave", "Matthew");
    return (femaleName
            ? ([maleName, femaleName] in oc(pairsAllowed))
            : (maleName in oc(singlesAllowed)));
}

alert(isPersonAllowed("Jack")); // false
alert(isPersonAllowed("Adam")); // false
alert(isPersonAllowed("John")); // true
alert(isPersonAllowed("Phil","Marlo")); // false
alert(isPersonAllowed("Jason","Harrison")); // true
alert(isPersonAllowed("Martin","Luther")); // false

5. Функция, позволяющая создавать хэш сначала кажется немного излишней: объекты в JavaScript — те же хеши, но вот иногда в качестве имени проперти/ключа требуется задать значение значение переменной и тогда приходит на помощь функия Hash. (да-да, конечно же есть встроенные возможности, но так возможно просто немного очевиднее :) — можете исключить эту функцию из полезных, если хотите :) )

function Hash()
{
    this.length = 0;
    this.items = new Array();
    for (var i = 0; i < arguments.length; i++) {
        this.items[arguments[i][0]] = arguments[i][1];
    }
}

Доступ к элементам производится засчет свойства items (кстати, следует, может, в более тяжелой версии добавить keys :) ?):

var Game = Class.extend({ 

    STG_STOP: 0,
    STG_START: 1,
    STG_LOADING: 2,
    STG_MENU: 3,
    STG_PROCESS: 4,

    construct:
        function() { this._stage = Game.STG_LOADING; },

    getStage:
        function() { return this._stage; }

});

var stateMap = new Hash(
            [ Game.STG_START,   "start"    ],
            [ Game.STG_LOADING, "loading"  ],
            [ Game.STG_MENU,    "menu"     ],
            [ Game.STG_PROCESS, "process"  ],
            [ Game.STG_STOP,    "stopping" ]);

var someGame = new Game();
alert("You are in "+stateMap.items[someGame.getStage()]+" stage!");

6. Три других функции просто упрощают и/или делают очевиднее некоторые операции: getTime на 11 символов сокращает доступ к получению текущего времени, getTimeDelta позволяет найти промежуток в милисекундах между отрезками времени (или указанным моментом и текущим временем, в формате с одним параметром), а последняя функция расширяет свойства объекта Number для того чтобы при его значении NaN можно было чуть быстрее получить 0.

function getTime() {
    return new Date().getTime();
}

function getTimeDelta(timeBegin, timeEnd) {
    timeEnd = timeEnd || getTime();
    return timeEnd - timeBegin;
}

Number.prototype.NaN0=function() { return isNaN(this) ? 0 : this; }

Определение браузера

7. Небольшой объект, поименованные по названиям браузеров свойства которого — суть условия. Этим достигается более читабельное (но не настолько скурпулезное насколько могло бы быть) определение большинства типов браузеров. Этот объект был заимствован мной из проекта, в котором я учавствовал — и как-то прижился, но, думаю, истинные авторы всё-таки где-то в сети, да и код не так уж сложен и громоздок чтобы на него сильно претендовать :). Кроме того, он конечно не идеально надежен (а некоторые говорят что не надежен вообще), но пока на перечисленных браузерах он меня не подвел ни разу :). Если вас не устраивает такое положение дел — вы можете использовать нечто похожее с HowToCreate. И повторюсь: данное определение я стараюсь использовать (как и сказано, например, по ссылке) «только в случае если известен конкретный баг в конкретном браузере и его нужно обойти». Также — несложно пересобрать этот объект в одно длинное условие, для меньшей скорости исполнения (см., опять же, ссылку)

var USER_DATA = {

    Browser: {
        KHTML: /Konqueror|KHTML/.test(navigator.userAgent) &&
                !/Apple/.test(navigator.userAgent),
        Safari: /KHTML/.test(navigator.userAgent) &&
                /Apple/.test(navigator.userAgent),
        Opera: !!window.opera,
        MSIE: !!(window.attachEvent && !window.opera),
        Gecko: /Gecko/.test(navigator.userAgent) &&
                !/Konqueror|KHTML/.test(navigator.userAgent)
    },

    OS: {
        Windows: navigator.platform.indexOf("Win") > -1,
        Mac: navigator.platform.indexOf("Mac") > -1,
        Linux: navigator.platform.indexOf("Linux") > -1
    }
}

Координаты / Позициионирование

8. Набор функций, позволяющих получить координаты элемента на экране пользователя. Если ваш документ статичен относительно окна и не имеет скроллбаров — лучше использовать функцию getPosition — так будет быстрее. В обратном случае используйте getAlignedPosition — она учитывает положения скроллбаров. Только обратите внимание: значение top у элемента может быть орицательным, если элемент верхней частью за пределами окна — для синхронизации с курсором мыши иногда нужно обнулить в этом случае высоту. Основной скрипт позаимствован из одного блога, Aligned-версия — результат поисков по сусекам и совмещения с информацией из двух статей (при обнаружении DOCTYPE IE входит в свой собственный, несколько непредсказуемый, режим). Также этот метод скомбинирован с получением позиций из исходников руководства по Drag’n’Drop. Обратите внимание: здесь используется функция NaN0 из пункта 6, вам нужно будет добавить ее в скрипт чтобы все работало как надо :) (спасибо, Homer).

var IS_IE = USER_DATA['Browser'].MSIE;

function getPosition(e){
    var left = 0;
    var top  = 0;

    while (e.offsetParent) {
        left += e.offsetLeft + (e.currentStyle ?
            (parseInt(e.currentStyle.borderLeftWidth)).NaN0() : 0);
        top  += e.offsetTop  + (e.currentStyle ?
            (parseInt(e.currentStyle.borderTopWidth)).NaN0() : 0);
        e = e.offsetParent;
    }

    left += e.offsetLeft + (e.currentStyle ?
            (parseInt(e.currentStyle.borderLeftWidth)).NaN0() : 0);
    top  += e.offsetTop  + (e.currentStyle ?
            (parseInt(e.currentStyle.borderTopWidth)).NaN0(): 0); 	

    return {x:left, y:top};
}

function getAlignedPosition(e) {
    var left = 0;
    var top  = 0;

    while (e.offsetParent) {
        left += e.offsetLeft + (e.currentStyle ?
            (parseInt(e.currentStyle.borderLeftWidth)).NaN0() : 0);
        top  += e.offsetTop  + (e.currentStyle ?
            (parseInt(e.currentStyle.borderTopWidth)).NaN0() : 0);
        e  = e.offsetParent;
        if (e.scrollLeft) {left -= e.scrollLeft; }
        if (e.scrollTop)  {top  -= e.scrollTop; }
    }

    var docBody = document.documentElement ?
        document.documentElement : document.body;

    left += e.offsetLeft +
        (e.currentStyle ?
                (parseInt(e.currentStyle.borderLeftWidth)).NaN0()
                : 0) +
        (IS_IE ? (parseInt(docBody.scrollLeft)).NaN0() : 0) -
        (parseInt(docBody.clientLeft)).NaN0();
    top  += e.offsetTop  +
        (e.currentStyle ?
                (parseInt(e.currentStyle.borderTopWidth)).NaN0()
                :  0) +
        (IS_IE ? (parseInt(docBody.scrollTop)).NaN0() : 0) -
        (parseInt(docBody.clientTop)).NaN0();

    return {x:left, y:top};
}

9. Определить текущие координаты курсора мыши и смещение элемента относительно курсора легко, если использовать соответствующие функции:

function mouseCoords(ev) {

    if (ev.pageX || ev.pageY) {
        return {x:ev.pageX, y:ev.pageY};
    }

    var docBody = document.documentElement
                        ? document.documentElement
                        : document.body;

    return {
        x: ev.clientX + docBody.scrollLeft - docBody.clientLeft,
        y: ev.clientY + docBody.scrollTop  - docBody.clientTop
    };
}

function getMouseOffset(target, ev, aligned) {
    ev = ev || window.event;
    if (aligned == null) aligned = false;

    var docPos    = aligned
        ? getAlignedPosition(target)
        : getPosition(target);
    var mousePos  = mouseCoords(ev);

    return {
        x: mousePos.x - docPos.x,
        y: mousePos.y - docPos.y
    };
}

Последняя функция также может использоваться в двух режимах засчет атрибута aligned и предназначена для удобного использования в обработчиках событий, например:

function onMouseMove(elm, ev) {
    var mouseOffset = getMouseOffset(elm, ev);
    console.log("x: %d; y: %d", mouseOffset.x, mouseOffset.y);
}
. . .
<div id="someId" onmousemove="onMouseMove(this, event);
        return false;"></div>

NB! (если данные функции (вдруг :) ) не заработают в каком-либо определенном случае — прошу сообщать — хочется добиться максимальной их переносимости)

10. Определение высоты элемента иногда более нелегкая задача чем определение других его параметров, но эти две функции придут на помощь:

function findOffsetHeight(e) {
    var res = 0;
    while ((res == 0) && e.parentNode) {
        e = e.parentNode;
        res = e.offsetHeight;
    }
    return res;
}

function getOffsetHeight(e) {
    return this.element.offsetHeight ||
            this.element.style.pixelHeight ||
            findOffsetHeight(e);
}

DOM

11. Иногда нужно пройти рекурсивно по дереву DOM, начиная с некоторого элемента и выполняя некоторую функцию над каждым из потомков, забираясь в самую глубь. В DOM есть объект TreeWalker, но он не работает в IE и не всегда удобен/прост в использовании. Функция walkTree позволяет выполнить некоторую другую функцию над каждым из элементов и позволяет также передать в нее некоторый пакет данных. Функция searchTree отличается от нее тем, что останавливает проход по дереву при первом удачном результате и возвращает результат в точку вызова:

function walkTree(node, mapFunction, dataPackage) {
    if (node == null) return;
    mapFunction(node, dataPackage);
    for (var i = 0; i < node.childNodes.length; i++) {
        walkTree(node.childNodes[i], mapFunction, dataPackage);
    }
}

function searchTree(node, searchFunction, dataPackage) {
    if (node == null) return;
    var funcResult = searchFunction(node, dataPackage);
    if (funcResult) return funcResult;
    for (var i = 0; i < node.childNodes.length; i++) {
        var searchResult = searchTree(node.childNodes[i],
                            searchFunction, dataPackage);
        if (searchResult) return searchResult;
    }
}

В примере используются функции setElmAttr и getElmAttr, которые будут рассмотрены позже — в пункте 13. По сути они делают то же что и getAttribute и setAttribute. Пояснения к используемой функции oc вы можете посмотреть в пукте 4. В первой части примера корневому элементу атрибут «nodeType» устанавливается в «root», а всем его потомкам — в «child». Во второй части демонстрируется также передача пакета данных — при нахождении первого элемента с атрибутом «class», равным одному из перечисленных в пакете имен, атрибут «isTarget» ему устанавливается в «true».

var rootElement = document.getElementById('rootElm');

setElmAttr(rootElement, "nodeType", "root");
var childNodeFunc = function(node) {
    if (node.nodeName && (node.nodeName !== '#text')
                        && (node.nodeName !== '#comment')) {
        setElmAttr(node, "nodeType", "child");
    }
}
walkTree(rootElement, childNodeFunc);

var findTargetNode = function(node, classList) {
    if ((node.nodeName && (node.nodeName !== '#text')
                    && (node.nodeName !== '#comment')) &&
                    (getElmAttr(node, "class") in oc(classList))) {
        return node;
    }
}
var targetNode = searchTree(rootElement, findTargetNode,
                    ['headingClass', 'footerClass', 'tableClass']);
setElmAttr(targetNode, "isTarget", true);

NB! (будьте осторожны с использованием этих функций и постарайтесь избежать их чересчур частого вызова (более раза в секунду) даже на средней ветвистости дереве — они могут пожрать немало ресурсов. или, по крайней мере, вызывайте их в фоне через setTimeout)

12. Удаление узлов — иногда необходимая задача. Иногда нужно удалить сам узел, а иногда — только его потомков. Функция removeChildrenRecursively рекурсивно удаляет всех потомков указанного узла, не затрагивая, конечно, его самого. Функция removeElementById, как и сказано в названии, удалает узел по его id — при всей простоте задачи способ относительно хитрый:

function removeChildrenRecursively(node)
{
    if (!node) return;
    while (node.hasChildNodes()) {
        removeChildrenRecursively(node.firstChild);
        node.removeChild(node.firstChild);
    }
}

function removeElementById(nodeId) {
    document.getElementById(nodeId).parentNode.removeChild(
                            document.getElementById(nodeId));
}

13. Казалось бы — элементарная задача работы с атрибутами элемента — иногда наталкивает на абсолютно неожиданные проблемы: например, IE бросает исключение при попытке доступа к атрибутам высоты/ширины элемента table, а у Safari отличается способ доступа к атрибутам с пространствами имен. Приведенные ниже функции обходят все встреченные мной проблемы без сильного ущерба к скорости выполнения (конечно же, в стандартных случаях лучше использовать встроенные функции):

var IS_SAFARI = USER_DATA['Browser'].Safari;

function getElmAttr(elm, attrName, ns) {
    // IE6 fails getAttribute when used on table element
    var elmValue = null;
    try {
        elmValue = (elm.getAttribute
                    ? elm.getAttribute((ns ? (ns + NS_SYMB) : '')
                    + attrName) : null);
    } catch (e) { return null; }
    if (!elmValue && IS_SAFARI) {
        elmValue = (elm.getAttributeNS
                    ? elm.getAttributeNS(ns, attrName)
                    : null);
    }
    return elmValue;
}

function setElmAttr(elm, attrName, value, ns) {
    if (!IS_SAFARI || !ns) {
        return (elm.setAttribute
                    ? elm.setAttribute((ns ? (ns + NS_SYMB) : '')
                    + attrName, value) : null);
    } else {
        return (elm.setAttributeNS
                    ? elm.setAttributeNS(ns, attrName, value)
                    : null);
    }
}

function remElmAttr(elm, attrName, ns) {
    if (!IS_SAFARI || !ns) {
        return (elm.removeAttribute
                    ? elm.removeAttribute((ns ? (ns + NS_SYMB) : '')
                    + attrName) : null);
    } else {
        return (elm.removeAttributeNS
                    ? elm.removeAttributeNS(ns, attrName)
                    : null);
    }
}

Засчет универсальности появляется некоторая неудобочитаемость ввиду того, что необязательный атрибут пространства имен — последний. Решения приветствуются.

AJAX

14. Если вам не нужно ничего большего, чем просто выполнить асинхронный запрос и на основе полученных данных сделать нечто — для вас эта функция. Способ получения объекта XMLHttpRequest безусловно может быть заменен. Комментарии намеренно оставлены, дабы показать некоторые идеи по расширению:

/* AJAX call */

/* locationURL - URL to use */
/* parameters - url parameters, null if not required
    (format: "parameter1=value1&parameter2=value2[...]") */
/* onComplete - listener: function (http_request) or
    (http_request, package) */
/* doPost - (optional) specifies if POST (true) or GET (false/null)
    request call required
/* package - (optional) some variable or array to tranfer in complete
    listener, may be not specified */

function makeRequest(locationURL, parameters, onComplete, doPost,
    dataPackage) {

    var http_request = false;
    try {
        http_request = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e1) {
        try {
            http_request= new ActiveXObject("Microsoft.XMLHTTP");
        } catch (e2) {
            http_request = new XMLHttpRequest();
        }
    }

    //if (http_request.overrideMimeType) { // optional
    //  http_request.overrideMimeType('text/xml');
    //}

    if (!http_request) {
      alert('Cannot create XMLHTTP instance');
      return false;
    }

    completeListener = function() {
        if (http_request.readyState == 4) {
            if (http_request.status == 200) {
                onComplete(http_request, dataPackage)
            }
        }
    };

    //var salt = hex_md5(new Date().toString());
    http_request.onreadystatechange = completeListener;
    if (doPost) {
		http_request.open('POST', locationURL, true);
		http_request.setRequestHeader("Content-type",
                    "application/x-www-form-urlencoded");
		http_request.setRequestHeader("Content-length",
                    parameters.length);
		http_request.setRequestHeader("Connection", "close");
		http_request.send(parameters);
    } else {
    	http_request.open('GET', locationURL + (parameters ? ("?" +
                parameters) : ""), true);
    	//http_request.open('GET', './proxy.php?' + parameters +
                    // "&salt=" + salt, true);
    	http_request.send(null);
    } 

}

Пример использования — из одного моего рабочего тестового задания, которое занималось поиском в базе музыки и/или фильмов по введенной в элемент (с id «searchStr») строке, используя SQL’ный LIKE:

function gotSearchResults(http_request, dataPackage) {
    request_result = http_request.responseText;
    var divElement = document.getElementById(dataPackage["divId"]);
    divElement.innerHTML = request_result;
}

function insertMusicSearchResults(divId) {
    var searchStrElement = document.getElementById("searchStr");
    var dataPackage = new Array();
    dataPackage["divId"] = divId;
    makeRequest("getAlbums.php", "searchStr="
            + searchStrElement.value, gotSearchResults, false, 
            dataPackage);
}

function insertVideoSearchResults(divId) {
    var searchStrElement = document.getElementById("searchStr");
    var dataPackage = new Array();
    dataPackage["divId"] = divId;
    makeRequest("getMovies.php", "searchStr="
            + searchStrElement.value, gotSearchResults, false, 
            dataPackage);
}

Логгинг

15. Представленная ниже функция для помощи в ведении логов очень проста, добавьте в нужное место в документе элемент <div id="LOG_DIV"></div>, задайте ему необходимую высоту, и в него будет сбрасываться информация + обеспечиваться ее скроллинг:

function LOG(informerName, text) {
    var logElement = document.getElementById('LOG_DIV');
    if (logElement) {
        logElement.appendChild(document.createTextNode(
                        informerName + ': ' + text));
        logElement.appendChild(document.createElement('br'));
        logElement.scrollTop += 50;
    }
}

16. В замечательном плагине Firebug для браузера Firefox есть замечательная консоль, в которую с широкими возможностями можно производить логгинг. Однако, если вы отлаживаете параллельно код в других браузерах — обращения к ней могут вызывать ошибки. Для того чтобы не очищать каждый раз код от логов, можно использовать такую заглушку:

var Console = Class.extend({
    // the stub class to allow using console when browser have it,
    // if not - just pass all calls
    construct: function() {},
    log: function() { },
    info: function() { },
    warn: function() { },
    error: function() { }
});

if (!window.console) {
    console = new Console();
}

Сочетание этого и предыдущего пункта + CSS может вдохновить вас на написание собственной консоли с функциональностью консоли Firebug, но для других браузеров ;). Если вы ее напишете — поделитесь, пожалуйста, со мной :).

Бонус

В качестве бонуса (чтобы не портить приятно отдающее двоичностью число в заголовке :) ) рассажу о проблеме двойного клика — бился над ней не я, а мои коллеги, решение также сетевое — но в некоторой обработке. Проблема состоит в том, что при регистрации события ondblclick все равно вызывается событие onclick. Поэтому, если уж очень это событие (неочевидное, стоит заметить, для пользователя сети) необходимо — лучше всего иметь в скриптах следующий код (с необходимым вам количеством миллисекунд и сохраняя, если необходимо, элемент, на котором был совершен клик):

var dblClicked = false;
var dblClickedNode = null; 

function dblClick(clickedNode) {
    dblClicked = true;
    dblClickedNode = clickedNode || dblClickedNode;
}

function releaseDblClick() {
    setTimeout('dblClicked=false;', 300);
}

Его использование накладывает относительно сложные условия. Теперь в обработчике ondblclick нужно вызывать сначала первую функцию, затем — закончив собственно обработку — вторую, а в обработчике onclick проверять, не совершен ли двойной клик:

<div id="someId" onclick="if (!dblClicked) alert('click');"
ondblick="dblClick(this); alert('dblclick'); releaseDblClick();";></div>

Также, к пункту 1 можно добавить небольшую функцию получения инстанса (на ваше усмотрение вы можете изменить ее так, чтобы она предавала аргументы в конструктор):

function getInstanceOf(className) {
    return eval('new ' + className + '()');
}

К пункту 6 подойдет функция паузы (именно паузы, а не выполнения в отдельном поптоке, как делает setTimeout):

function pause(millis)
{
    var time = new Date();
    var curTime = null;
    do { curTime = new Date(); }
        while (curTime - time < millis);
}

Заключение

Ну вот — кажется, пока всё. Статья — в состоянии готовности к исправлениям (если понадобятся :) ), можно переходить к следующим :). В следующей статье я намереваюсь рассказать поподробнее про ООП в JavaScript и привести в пример пару простых, но полезных классов. Надеюсь, эта статья вам помогла и хоть немного сократила имеющие потенциальную возможность быть потраченными на решение всяких причуд браузеров рабочие человекочасы.