‘Чистый’ JavaScript: «Начало»

в 11:00, , рубрики: javascript, web-разработка, Веб-разработка, велосипеды, Песочница, метки: , ,

Привет всем.

Как вы уже поняли из названия статьи, я не люблю JS framework'и.
Меня пугает их размер, медлительность и неизвестность…
Да-да, именно 'неизвестность', даже когда есть берешь 'чистые' исходники без какой-либо компрессии и тд., сложно проследить весь путь исполняемой функции. Сколько раз на работе сталкивался с тем что элементарная задача, типа: «Удалить целую строку из html таблицы, при клике на кнопку» — заканчивалась error'ом в IE. Именно поэтому я и задумался над созданием своего 'джентльменского' набора для работы с JS.

Что-то было найдено на просторах javascript.ru, что-то — на просторах Интернета, а что-то и было написано мной.
Большинство функций каждый из вас уже видел, поэтому я буду задерживаться только на интересных моментах (на мой взгляд).

Итак, приступим:

    // простой debug, лень писать каждый раз console.log(), 
    // на момент написания ее, про использование более одного параметра, мыслей не было, так и живем
    /**
     * Debug function
     *
     * @version 2012-07-16
     * @param   mixed    param
     * @return  void
     */
    function _d(param)
    {
        console.log(param);
    }


    // ну тут все понятно
    /**
     * Function return random integer value
     *
     * @version 2012-07-22
     * @param   integer  minV
     * @param   integer  maxV
     * @return  integer
     */
    function getRandomInt(minV, maxV)
    {
        if(minV == undefined)
            minV = 0;
        if(maxV == undefined)
            maxV = 10;

        return Math.floor(Math.random() * (maxV - minV + 1)) + minV;
    }


    // здесь я тоже думаю, проблем не возникнет
    /**
     * Function return element by id
     *
     * @version 2012-07-22
     * @param   string   id    title of id
     * @return  Object
     */
    function $(id)
    {
        return document.getElementById(id);
    }


    // Здесь хочется сказать про то, что если используется 'нормальный' браузет - все ok,
    // а вот если нет, то предется обойти всех по-порядку и спросить его className
    /**
     * Function return element by class name
     *
     * @version 2012-11-07
     * @param   string   classList
     * @param   object   node
     * @return  Object
     */
    function $$(classList, node)
    {
        if(document.getElementsByClassName)
            return (node || document).getElementsByClassName(classList);
        else{
            var node = node || document,
            list = node.getElementsByTagName('*'), 
            length = list.length,  
            classArray = classList.split(/s+/), 
            classes = classArray.length, 
            result = [], i,j;

            for(i = 0; i < length; i++) {
                for(j = 0; j < classes; j++)  {
                    if(list[i].className.search('\b' + classArray[j] + '\b') != -1) {
                        result.push(list[i])
                        break
                    }
                }
            }

            return result;
        }
    }

Здесь заканчивается первая партия функций, и начинается вторая (первые две, я думаю все уже встречали, поэтому они не нуждаются в пояснениях):

    /**
     * Function attach event to element
     *
     * @version 2012-07-22
     * @param   Object   elem    dom element
     * @param   string   type    event type
     * @param   mixed    handler
     * @return  Object
     */
    function addEvent(elem, type, handler)
    {
        if(!elem)
            _d('Error: invalid param in `addEvent()`.');

        if (elem.addEventListener)
            elem.addEventListener(type, handler, false);
        else if (elem.attachEvent)
            elem.attachEvent("on" + type, handler);
    }


    /**
     * Function deattach event to element
     *
     * @version 2012-07-22
     * @param   Object   elem    dom element
     * @param   string   type    event type
     * @param   mixed    handler
     * @return  Object
     */
    function removeEvent(elem, type, handler)
    {
        if(!elem)
            _d('Error: invalid param in `removeEvent()`.');

        if (elem.removeEventListener)
            elem.removeEventListener(type, handler, false)
        else if (elem.detachEvent)
            elem.detachEvent("on" + type, handler)
    }


    // Данная функция вернет Event объект, с некоторыми дополнениями.
    // Главная ее фишка в том что она вернет наиболее популярные свойства и как в IE, и так в остальных браузерах
    /**
     * Function return Cross-browser object Event
     *
     * @version 2012-08-17
     * @param   Object   e    event Object
     * @return  Object
     */
    function getEvent(e)
    {
        // get event element
        event = e || window.event

        // function edit event element one time
        if ( event.isFixed )
            return event

        event.isFixed = true

        // add 'preventDefault/stopPropagation' for IE
        event.preventDefault = event.preventDefault || function(){this.returnValue = false}
        event.stopPropagation = event.stopPropagaton || function(){this.cancelBubble = true}

        // add 'target' for IE
        if (!event.target)
            event.target = event.srcElement

        // add 'relatedTarget' for IE, if need
        if (!event.relatedTarget && event.fromElement)
            event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;

        // add 'pageX/pageY' for IE
        if ( event.pageX == null && event.clientX != null ) {
            var html = document.documentElement, body = document.body;
            event.pageX = event.clientX + (html && html.scrollLeft || body && body.scrollLeft || 0) - (html.clientLeft || 0);
            event.pageY = event.clientY + (html && html.scrollTop || body && body.scrollTop || 0) - (html.clientTop || 0);
        }
        
        // add 'which' for mouse in IE
        // 1 == left; 2 == middle; 3 == right
        if ( !event.which && event.button )
            event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));

        return event;
    }



    // Я думаю, что каждый js web-разработчик сталкивался с тем что,
    // необходимо выполнить какой-либо JS код, после загрузки страницы (но не всех фото и тд.)
    // Данная функция для этого и предназначена.
    // Если никак браузер, ни как не смог этого сделать, функция выполнится по стандартному onLoad()
    /**
     * Function attach one event on dom ready
     *
     * @version 2012-10-27
     * @param   mixed   handler
     * @return  void
     */
    function bindReady(handler)
    {
        var called = false;

        // simple function
        function ready() {
            if (called)
                return;

            called = true;
            handler();
        }

        // if brouser support event "DOMContentLoaded"
        if ( document.addEventListener ) {
            document.addEventListener( "DOMContentLoaded", function(){
                ready()
            }, false );
        }
        else if ( document.attachEvent ) {
            // if IE brousers
            if ( document.documentElement.doScroll && window == window.top ) {
                function tryScroll(){
                    if (called)
                        return;
                    if (!document.body)
                        return;

                    // try scroll page, else try scroll through some time
                    try {
                        document.documentElement.doScroll("left");
                        ready();
                    } catch(e) {
                        setTimeout(tryScroll, 0);
                    }
                }
                tryScroll()
            }

            // if brouser support event "onreadystatechange"
            document.attachEvent("onreadystatechange", function(){
                if ( document.readyState === "complete" )
                    ready()
            });
        }

        // attach to standart onLoad event
        if (window.addEventListener)
            window.addEventListener('load', ready, false)
        else if (window.attachEvent)
            window.attachEvent('onload', ready)

        window.onload=ready
    }


    // Данная переменная, и функция описанная ниже, нужны для того, 
    // чтобы можно было вешать несколько обработчиков, одновременно
    // initialize array for onReady()
    readyList = [];


    /**
     * Function attach some dom ready events 
     *
     * @version 2012-10-27
     * @param   mixed   handler
     * @see bindReady()
     * @return  void
     */
    function onReady(handler)
    {
        if (!readyList.length) {
            bindReady(function() {
                for(var i=0; i<readyList.length; i++)
                    readyList[i]();
            });
        }
        readyList.push(handler)
    }



    // Если кто-то не понял, то пример использования:
    onReady(function () {
        alert('Первый...');
    });
    onReady(function () {
        alert('А может быть и не первый...');
    });

Ну и наконец, последняя порция JS на сегодня:

    // Эта функция вернет объект вида:
    // {top: xxx, left: xxx, right: xxx, bottom: xxx, width: xxx, height: xxx}, где xxx - количество пикселей
    // Одно но, как-то на днях, нужно было получить положение вспарывающего дива, и все было нормально, пока не трогали scroll
    // Так и был рожден фикс насчет, obj.style.position ='absolute'
    /**
     * Function return all coordinates for element
     *
     * @version 2012-10-26
     * @param   Object   obj    dom element
     * @param   boolean  absolute   if  position = `absolute` or `fixed`
     * @return  Object
     */
    function getCoordinates(obj, absolute)
    {
        if(!obj)
            obj = window;

        // get block box
        if(obj != window && obj != document){
            var box = obj.getBoundingClientRect();
            var width = obj.offsetWidth;
            var height = obj.offsetHeight;
        }
        else{
            var box = new Object({'top':0, 'left':0});
            var width = window.innerWidth || document.documentElement.clientWidth;
            var height = window.innerHeight || document.documentElement.clientHeight;
        }

        // get body
        var body = document.body;
        var docEl = document.documentElement;

        // get scroll params
        if((absolute != undefined) && absolute){
            var scrollTop = 0;
            var scrollLeft = 0;
        }
        else{
            var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
            var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
        }

        // get shift in IE
        var clientTop = docEl.clientTop || body.clientTop || 0;
        var clientLeft = docEl.clientLeft || body.clientLeft || 0;

        var top  = Math.round(box.top +  scrollTop - clientTop);
        var left = Math.round(box.left + scrollLeft - clientLeft);

        return { 'top': top, 'left': left, 'right': left+width, 'bottom': top+height, 'width':width, 'height': height };
    }



    // Стандартная проверка на содержимое класса
    /**
     * Function return TRUE if element has class
     *
     * @version 2012-07-22
     * @param   Object   elem    dom element
     * @param   string   classname   class name
     * @return  boolean
     */
    function hasClass(elem, classname)
    {
        return (' '+elem.className+' ').indexOf(' '+classname+' ') > -1;
    }


    // Добавляем новый класс, если такого нету
    /**
     * Function add new class for element
     *
     * @version 2012-07-27
     * @param   Object   elem    dom element
     * @param   string   classname   class name
     * @return  boolean
     */
    function addClass(elem, classname)
    {
        if(!hasClass(elem, classname))
            if(elem.className != '')
                elem.className += ' '+classname;
            else
                elem.className = classname;
    }


    // ... удаляем - если есть
    /**
     * Function remove class from element
     *
     * @version 2012-07-27
     * @param   Object   elem    dom element
     * @param   string   classname   class name
     * @return  boolean
     */
    function removeClass(elem, classname)
    {
        if(hasClass(elem, classname))
            elem.className = trim(elem.className.replace(new RegExp('(^|\s)' + classname + '(?:\s|$)'), '$1'));
    }


    // Последняя функция на сегодня - аналог php trim()
    // Если не изменяет память, позаимствовал у Mootools :)
    /**
     * Function remove spaces in begin and end of string
     *
     * @version 2012-11-05
     * @param   string  str
     * @return  string
     */
    function trim(str)
    {
        return String(str).replace(/^s+|s+$/g, '');
    }

Вот пожалуй и все.
Если кому-то данные записки сумасшедшего пригодятся — напишу продолжение…

PS. Исходники будут доступны после второй части статьи.

Автор: mamchyts

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js