пятница, 28 марта 2014 г.

Асинхронные циклы for, forEach и асинхронная сортировка массивов sort

define(function(){
    // Асинхронные функции
    var Async = {};
   
    // Асинхронный цикл For
    function asyncFor (parameters) {

        parameters = parameters || {}; // Параметры цикла
       
        parameters.initializationParameters = parameters.initializationParameters || {}; // Начальные значения счетчиков цикла
        parameters.testConditionFunction = parameters.testConditionFunction || function () {return true;}; // Условие выхода из цикла
        parameters.incrementFunction = parameters.incrementFunction || function () {}; // Приращение счетчиков цикла
        parameters.bodyStatementFunction = parameters.bodyStatementFunction || function () {}; // Тело цикла
        parameters.doneFunction = parameters.doneFunction || function () {}; // Функция, выполняющаяся после завершения цикла
       
        parameters.breakFunction = function () {}; // Функция, выполняющаясь при прерывания цикла
        parameters.continueFunction = function () {}; // Функция, выполняющаясь при перепрыгивании на следующий шаг цикла

        parameters.break = function (labelFunction) { // Определение типа перепрыгивания на следующий шаг цикла
            if (labelFunction === undefined) {
                return 'break';
            } else {
                parameters.breakFunction = labelFunction;
                return 'break label';
            }
        };
       
        parameters.continue = function (labelFunction) { // Определение типа прерывания цикла
            if (labelFunction === undefined) {
                return 'continue';
            } else {
                parameters.continueFunction = labelFunction;
                return 'continue label';
            }
        };

        parameters.counter = 0; // Счетчик числа выполненных шагов цикла

        parameters.nextIterationStep = function () { // Выполнить шаг цикла
            if (parameters.counter > 0) {
                parameters.incrementFunction(parameters); // Произвести приращение счетчиков цикла при выполнении следующего шага
            }
            parameters.counter++;
            if (parameters.testConditionFunction(parameters)) { // Проверить тестовое условие цикла
                setTimeout(function(){
                    var breakOrContinueAsyncFor = parameters.bodyStatementFunction(parameters); // Выполнить тело цикла
                    if (breakOrContinueAsyncFor === 'break') { // Завершить цикл
                        parameters.doneFunction(parameters);
                    } else if (breakOrContinueAsyncFor === 'break label') { // Прервать цикл и выполнить функцию, в которой описан переход во внешний цикл
                        parameters.breakFunction(parameters);
                    } else if (breakOrContinueAsyncFor === 'continue') { // Перепрыгнуть на следующий шаг цикла
                        parameters.nextIterationStep();
                    } else if (breakOrContinueAsyncFor === 'continue label') { // Выполнить функцию, в которой описан переход во внешний цикл
                        parameters.continueFunction(parameters);
                    }
                }, 0); // В общем случае для выполнения маленьких задержек рекомендуется использовать значение не менее 25 мс,
                // потому что меньшие задержки оставляют слишком короткие интервалы времени для обновления пользовательского интерфейса.
                // Однако для ускорения выполнения цикла оставлена задержка 0 мс.
            } else {
                parameters.doneFunction(parameters); // Выполнить завершающую функцию в конце работы цикла
            }
        };
       
        parameters.nextIterationStep(); // Выполнить проверку тестового условия цикла и сделать первый шаг
       
    }
   
    /*= Примеры кода асинхронных циклов asyncFor =*\

    // Одиночный асинхронный цикл
    asyncFor ({
          initializationParameters: {i: 0}
        , testConditionFunction: function (asyncForLoop) {
            if (asyncForLoop.initializationParameters.i < 10) {return true;} else {return false;}
          }
        , incrementFunction: function (asyncForLoop) {
            asyncForLoop.initializationParameters.i = asyncForLoop.initializationParameters.i + 1;
          }
        , bodyStatementFunction: function (asyncForLoop) {
            console.log(asyncForLoop.initializationParameters.i);
            asyncForLoop.nextIterationStep(); // Переход на следующий шаг цикла
          }
        , doneFunction: function (asyncForLoop) {
            console.log('The End. Last index: ' + asyncForLoop.initializationParameters.i);
          }
    });
       
    // Вложенные асинхронные циклы
    // Внешний цикл - начало
    asyncFor ({
          initializationParameters: {i: 0}    
        , testConditionFunction: function (outerLoop) {
            if (outerLoop.initializationParameters.i < 2) {return true;} else {return false;}
          }  
        , incrementFunction: function (outerLoop) {
            outerLoop.initializationParameters.i = outerLoop.initializationParameters.i + 1;
          }
        , bodyStatementFunction: function (outerLoop) {
            // Внутренний цикл - начало
            asyncFor ({
                  initializationParameters: {j: 0}    
                , testConditionFunction: function (innerLoop) {
                    if (innerLoop.initializationParameters.j < 2) {return true;} else {return false;}
                  }  
                , incrementFunction: function (innerLoop) {
                    innerLoop.initializationParameters.j = innerLoop.initializationParameters.j + 1;
                  }
                , bodyStatementFunction: function (innerLoop) {
                    console.log(outerLoop.initializationParameters.i + ' ' + innerLoop.initializationParameters.j);
                    innerLoop.nextIterationStep(); // Переход на следующий шаг внутреннего цикла
                  }
                , doneFunction: function (innerLoop) {
                    outerLoop.nextIterationStep(); // После завершения внутреннего цикла осуществляется переход на следующий шаг внешнего цикла
                  }
            });
            // Внутренний цикл - конец
          }
        , doneFunction: function (outerLoop) {
            console.log('All cycles done');
          }
    });
    // Внешний цикл - конец
   
    // Прерывание одиночного асинхронного цикла
    asyncFor ({
          initializationParameters: {i: 0}
        , testConditionFunction: function (asyncForLoop) {
            if (asyncForLoop.initializationParameters.i < 10) {return true;} else {return false;}
          }
        , incrementFunction: function (asyncForLoop) {
            asyncForLoop.initializationParameters.i = asyncForLoop.initializationParameters.i + 1;
          }
        , bodyStatementFunction: function (asyncForLoop) {
            if (asyncForLoop.initializationParameters.i === 3) {
                // Перепрыгивание на следующий шаг цикла
                return asyncForLoop.continue(
                // Если в качестве метки label будет передана функция, то будет выполнен её код
                // function(asyncForLoop) {
                //     console.log('Continue label. Current index: ' + asyncForLoop.initializationParameters.i);
                //     asyncForLoop.doneFunction(asyncForLoop); // В результате перепрыгивания шага можно просто завершить выполнение цикла
                // }
                );
            }
            if (asyncForLoop.initializationParameters.i === 5) {
                // Прерывание цикла
                return asyncForLoop.break(
                // function(asyncForLoop) {
                //    console.log('Break label. Current index: ' + asyncForLoop.initializationParameters.i);
                //    asyncForLoop.doneFunction(asyncForLoop);  // В результате прерывания цикла можно просто завершить выполнение цикла
                // }
                );
            }
            console.log(asyncForLoop.initializationParameters.i);
            asyncForLoop.nextIterationStep(); // Обычный переход на следующий шаг цикла
          }
        , doneFunction: function (asyncForLoop) {
            console.log('The End. Last index: ' + asyncForLoop.initializationParameters.i);
          }
    });
   
    // Прерывание вложенных асинхронных циклов
    // Внешний цикл - начало
    asyncFor ({
          initializationParameters: {i: 0}    
        , testConditionFunction: function (outerLoop) {
            if (outerLoop.initializationParameters.i < 5) {return true;} else {return false;}
          }  
        , incrementFunction: function (outerLoop) {
            outerLoop.initializationParameters.i = outerLoop.initializationParameters.i + 1;
          }
        , bodyStatementFunction: function (outerLoop) {
            // Внутренний цикл - начало
            asyncFor ({
                  initializationParameters: {j: 0}    
                , testConditionFunction: function (innerLoop) {
                    if (innerLoop.initializationParameters.j < 5) {return true;} else {return false;}
                  }  
                , incrementFunction: function (innerLoop) {
                    innerLoop.initializationParameters.j = innerLoop.initializationParameters.j + 1;
                  }
                , bodyStatementFunction: function (innerLoop) {
                    if (innerLoop.initializationParameters.j === 2) {
                       // Перепрыгивание на следующий шаг цикла
                        return innerLoop.continue(
                        // Если в качестве метки label будет передана функция, то будет выполнен её код
                        // function(outerLoop){
                        //    console.log('Continue label. Current index: ' + innerLoop.initializationParameters.j);
                        //    innerLoop.doneFunction(); // В результате перепрыгивания шага можно просто завершить выполнение внутреннего цикла
                        // }
                        );
                    }
                    if (innerLoop.initializationParameters.j === 2) {
                        // Прерывание цикла
                        return innerLoop.break(
                        // function(innerLoop){
                        //     console.log('Break label. Current index: ' + innerLoop.initializationParameters.j);
                        //     outerLoop.doneFunction(outerLoop); // В результате прерывания цикла можно просто завершить выполнение внешнего цикла
                        // }
                        );
                    }
                    console.log(outerLoop.initializationParameters.i + ' ' + innerLoop.initializationParameters.j);
                    innerLoop.nextIterationStep(); // Обычный переход на следующий шаг внутреннего цикла
                  }
                , doneFunction: function (innerLoop) {
                    outerLoop.nextIterationStep(); // После завершения внутреннего цикла осуществляется переход на следующий шаг внешнего цикла
                  }
            });
            // Внутренний цикл - конец
          }
        , doneFunction: function (outerLoop) {
            console.log('All cycles done');
          }
    });
    // Внешний цикл - конец
   
    \*================================ */

    // Асинхронный цикл forEach
    function asyncForEachInArray (array, process, callback) {
        if (array.length === 0) {
            callback(array);
            return;
        }
        var tempArray = array.slice()
            , currentTempArrayIndex = 0
            , breakProcess;
        function startIterationProcess () {
            var startTime = +new Date();
            do {
                breakProcess = process(tempArray.shift(), currentTempArrayIndex);
                if (breakProcess === 'break') {
                    tempArray.length = 0;
                } else {
                    currentTempArrayIndex++;
                }
            } while (
                      tempArray.length > 0
                && ((+new Date() - startTime) < 50) // Установить таймаут, если цикл выполняется дольше 50 мс
            );
            if (tempArray.length > 0) {
                setTimeout(startIterationProcess, 0); // В общем случае для выполнения маленьких задержек рекомендуется использовать значение не менее 25 мс,
                // потому что меньшие задержки оставляют слишком короткие интервалы времени для обновления пользовательского интерфейса.
            } else {
                callback(array);
            }
        }
        startIterationProcess();
    }
   
    /*= Пример кода асинхронного цикла asyncForEachInArray =*\
   
    var array = new Array(5000);
    asyncForEachInArray (
          array
        , function (item, index) {
            array[index] = item + '-' + index;
            if (index === 2000) {
                // Прервать выполнение итераций
                return 'break';
            }
          }
        , function (array) {
            console.log('All done');
            console.log(array);
        }
    );
   
    \*====================================== */
   
    /*= Описание алгоритма сортировки массивов =*\
   
    Алгоритм сортировки массивов merge sort используют браузеры Firefox и Safari в своей реализации Array.prototype.sort()

    Алгоритм merge sort быстр в работе и легок в реализации.
    Он базируется на идее, что легче сравнить и слить два уже отсортированных списка,
    нежели разбираться с одним несортированным списком.
    Реализация алгоритма merge sort начинается с создания некоторого числа N одноэлементных массивов,
    где N - общее число элементов в оригинальном массиве, который требуется отсортировать.
    Затем эти одноэлементные массивы объединяются обратно в единый отсортированный массив.

    Сравнение и слияние двух уже отсортированных списков представляет собой довольной простой алгоритм.
    Представим, что у нас есть два списка: список A и список B.
    Мы начинаем с самого начала каждого списка и сравниваем два их значения.
    Меньшее из двух сравниваемых значений вставляется в итоговый массив.
    Пусть к примеру меньшим окажется значение из списка A, тогда это значение будет помещено в итоговый массив.
    Далее второе значение из списка A сравнивается с первым значением из списка B.
    Снова меньшее из двух значений вставляется в итоговый массив.
    Теперь для примера меньшим пусть будет число из списка B, тогда на следующем шаге будут сравниваться второй элемент из
    списка A и второй элемент из списка B.
    Код такого сравнения будет следующим:

    function merge (leftArray, rightArray) {
        var resultArray = []
            , leftArrayLength = leftArray.length
            , rightArrayLength = rightArray.length
            , indexLeft = 0,
            , indexRight = 0;
        while (
                  indexLeft < leftArrayLength
            && indexRight < rightArrayLength
        ) {
            if (leftArray[indexLeft] < rightArray[indexRight]) {
                result.push(leftArray[indexLeft]);
                indexLeft++;
            } else {
                result.push(rightArray[indexRight]);
                indexRight++;
            }
        }
        return resultArray.concat(leftArray.slice(indexLeft)).concat(rightArray.slice(indexRight));
    }

    В приведенной выше функции сравнивается и сливается два массива: левый и правый.
    Переменная indexLeft хранит в себе индекс сравниваемого элемента из списка leftArray,
    в то время как переменная indexRight хранит в себе индекс сравниваемого элемента из списка rightArray.
    Кажлый раз, когда значение из одного массива добавляется в итоговый массив,
    его соотвествующий индекс элемента увеличивается на единицу.
    Как только один из массивов закончится, то оставшиеся значания будут добавлены в конец итогового массива
    посредством операции concat().

    Функция merge() работает просто, но теперь необходимо объединить два отсортированных списка.
    Как было замечено ранее, это делается путем разделения массива на несколько одноэлементных массивов,
    которые затем объединяются в списки.
    Это можно легко сделать, применив рекурсию:

    function mergeSort (array) {
        var arrayLength = array.length;
        // Условие выхода из функции
        // Если число элементов в сортируемом массиве 0 или 1, то такой массив не требует сортировки
        if (arrayLength < 2) {
            return array;
        }
        var middle = Math.floor(arrayLength / 2) // определяем середину исходного массива
            , leftArray = items.slice(0, middle) // левая половина исходного массива
            , rightArray = items.slice(middle); // правая половина исходного массива
        return merge(mergeSort(leftArray), mergeSort(rightArray)); // Рекурсивно сравниваем и сливаем два массива,
        // периодически разделяя их пополам на более мелкие составляющие
    }

    Сперва стоит отметить, что если в сортируемом массивке 0 или 1 элемент, то такой массив не требует сортировки.
    Если массив содержит в себе 2 или более значений, то он разделяется пополам на левый и правый массивы.
    Каждый из этих массивов затем опять разделяется пополам, помещаясь обратно в функцию mergeSort(), после завершения которой
    результат помещается в функцию merge(), где происходит сравнение и слияние двух маленьких массивов с
    последующим помещением их в итоговый отсортированный массив.
    Таким образом сортируется сначала левая половина массива, а затем сортируется правая часть массива,
    после чего итоговые результаты сравниваются и объединяются.
    Благодаря рекурсии вы в конечно итоге доходите до точки, где два одноэлементных массива сравниваются и сливаются в итоговы массив.

    Имплементация алгоритма merge sort возвращает другой массив, отличающийся от того, который был передан для сортировки,
    то есть это не сортировка массива на месте. Если вам требуется отсортировать исходный массив на месте,
    то в этом случае вы можете очистить оригинальный массив и заполнить его отсортированным содержимым следующим образом:

    function mergeSort (array) {
        var arrayLength = array.length;
        // Условие выхода из функции
        // Если число элементов в сортируемом массиве 0 или 1, то такой массив не требует сортировки
        if (arrayLength < 2) {
            return array;
        }
        var middle = Math.floor(arrayLength / 2) // определяем середину исходного массива
            , leftArray = array.slice(0, middle) // левая половина исходного массива
            , rightArray = array.slice(middle) // правая половина исходного массива
            , params = merge(mergeSort(leftArray), mergeSort(rightArray));
        // Добавляем аргументы для замены всего, начиная с 0 и до последнего элемента в массиве
        params.unshift(0, arrayLength);
        array.splice.apply(array, params);
        return array;
    }

    Эта версия функции mergeSort() хранит результат сортировки в переменной params.
    Самый лучший способ заменить элементы в массиве - это использовать метод splice(), который принимает два и более аргументов.
    Первый аргумент - это индекс первого заменяемого значения, а второй аргумент - это число элементов, которые надо заменить.
    Каждый последующий аргумент - это значение, которое надо вставить на соотвествующщую позицию.
    Посокольку нет способа передать значения в метод splice(), то необходимо использовать метод apply(),
    в который передать первые два аргумента объединенные с отсортированным массивом.
    Таким образом 0 и array.length добавляются в начало массива, используя метод unshift() так,
    что apply() может быть использован совместно со splice().
    После этого исходный массив возвращается из функции.
   
    // Функция arrayMergeSort сортирует массив синхронно
    // По умолчанию сортировка производится по возрастанию
    // Параметр array - массив требующий сортировки
    // Необязательный параметр compareFunction - функция, задающая порядок сравнения элементов массива
    // Функция compareFunction должна возвращать:
    // 1 - если первый сравниваемый элемент массива больше второго сравниваемого элемента
    // -1 - если первый сравниваемый элемент массива меньше второго сравниваемого элемента
    // 0 - если сравниваемые элементы массива равны друг другу
    // Функция возвращает отсортированный массив
    function arrayMergeSort (array, compareFunction) {
        // для примера array = [5, 4, 6]
        var work = []
            , arrayLength = array.length // для примера arrayLength = 3
            , limit
            , i
            , j
            , k;
        // Для массивов с числом элементов 0 или 1 сортировка не требуется
        if (arrayLength < 2) {return array;}
        // Фомируем массив, состоящий из одноэлементных массивов, для примера work = [[5], [4], [6]]
        for (i = 0; i < arrayLength; i++){
            work.push([array[i]]);
        }
        work.push([]); // на случай, если в массиве нечетное число элементов work = [[5], [4], [6], []]
        // для примера изначально limit = 3, на каждом шаге цикла limit = Math.floor( (3 + 1) / 2 )), поскольку мы добавили пустой массив []
        for (limit = arrayLength; limit > 1; limit = Math.floor( (limit + 1) / 2 )) {
            for (j = 0, k = 0; k < limit; j++, k += 2) {
                work[j] = merge(work[k], work[k + 1], compareFunction);
            }
            work[j] = []; // на случай, если в массиве нечетное число элементов
        }
        return work[0];
    }
   
    \*============================== */
   
    // Асинхронная сортировка массивов sort
    function asyncArraySort (array, callback, compareFunction) {
        // для примера array = [5, 4, 6]
        var work = []
            , arrayLength = array.length; // для примера arrayLength = 3
        // Для массивов с числом элементов 0 или 1 сортировка не требуется
        if (arrayLength < 2) {return array;}
        if (callback === undefined) {
            callback =  function(){};
        }
        // Асинхронно фомируем массив, состоящий из одноэлементных массивов, для примера work = [[5], [4], [6]]
        asyncForEachInArray(array, addElementsToWorkArray, startMergeProcess);
        function addElementsToWorkArray (item) {
            work.push([item]);
        }
        function startMergeProcess () {
            work.push([]); // на случай, если в массиве нечетное число элементов work = [[5], [4], [6], []]
            // для примера изначально limit = 3, на каждом шаге цикла limit = Math.floor( (3 + 1) / 2 )), поскольку мы добавили пустой массив []
            var limit = arrayLength;
            asyncFor ({
                  initializationParameters: {limit: limit}
                , testConditionFunction: function (outerLoop) {
                    if (outerLoop.initializationParameters.limit > 1) {return true;} else {return false;}
                  }
                , incrementFunction: function (outerLoop) {
                    outerLoop.initializationParameters.limit = Math.floor( (outerLoop.initializationParameters.limit + 1) / 2 );
                  }
                , bodyStatementFunction: function (outerLoop) {
                    asyncFor ({
                          initializationParameters: {j: 0, k: 0}
                        , testConditionFunction: function (innerLoop) {
                            if (innerLoop.initializationParameters.k < outerLoop.initializationParameters.limit) {return true;} else {return false;}
                          }
                        , incrementFunction: function (innerLoop) {
                            innerLoop.initializationParameters.j = innerLoop.initializationParameters.j + 1;
                            innerLoop.initializationParameters.k = innerLoop.initializationParameters.k + 2;
                          }
                        , bodyStatementFunction: function (innerLoop) {
                            work[innerLoop.initializationParameters.j] = merge(work[innerLoop.initializationParameters.k], work[innerLoop.initializationParameters.k + 1], compareFunction);
                            innerLoop.nextIterationStep(); // Переход на следующий шаг внутреннего цикла
                          }
                        , doneFunction: function (innerLoop) {
                            work[innerLoop.initializationParameters.j] = []; // на случай, если в массиве нечетное число элементов
                            outerLoop.nextIterationStep(); // Переход на следующий шаг внешнего цикла
                          }
                    });
                  }
                , doneFunction: function () { // Завершение внешнего цикла
                    callback(work[0]); // Возвращение отсортированного массива
                  }
            });
        }
    }

    // Функция merge производит сравнение и слияние двух массивов по естесвенному порядку расположения чисел и строк в них
    // Параметр leftArray - первый массив для сравнение и слияния
    // Параметр rightArray - второй массив для сравнение и слияния
    // Необязательный параметр compareFunction - функция, задающая порядок сравнения элементов массива
    // Функция compareFunction должна возвращать:
    // 1 - если первый сравниваемый элемент массива больше второго сравниваемого элемента
    // -1 - если первый сравниваемый элемент массива меньше второго сравниваемого элемента
    // 0 - если сравниваемые элементы массива равны друг другу
    // Функция возвращает слитый массив
    function merge (leftArray, rightArray, compareFunction) {
        var resultArray = [];
        if (compareFunction === undefined) {
            compareFunction = function (a, b) {
                if (a > b) {
                    return 1;
                } else if (a < b) {
                    return -1;
                } else if (a === b) {
                    return 0;
                }
            };
        }
        while (leftArray.length > 0 && rightArray.length > 0) {
            if (compareFunction(leftArray[0], rightArray[0]) === -1) {
                resultArray.push(leftArray.shift());
            } else {
                resultArray.push(rightArray.shift());
            }
        }
        resultArray = resultArray.concat(leftArray).concat(rightArray);
        // Убедимся, что оставшиеся массивы пусты
        leftArray.splice(0, leftArray.length);
        rightArray.splice(0, rightArray.length);
        return resultArray;
    }

    /*= Примеры кода асинхронной сортировки массива asyncArraySort =*\
   
    // Асинхронная сортировка обычного массива
    var array = []
        , len = 999;
    for (;len--;) {
        array.push(len);
    }
    asyncArraySort(
          array
        , function(result){
            array = result;
            console.log(array);
          }
    );
   
    // Асинхронная сортировка массива, содержащего объекты, по убыванию
    var array = [
          {n: 5}
        , {n: 4}
        , {n: 1}
        , {n: 3}
        , {n: 2}
    ];
    asyncArraySort(
          array
        , function(result){
            array = result;
            console.dir(array);
          }
        , function (a, b) {
            if (a.n < b.n) {
                return 1;
            } else if (a.n > b.n) {
                return -1;
            } else if (a.n === b.n) {
                return 0;
            }
        }
    );
   
    \*=========================================== */

    Async.asyncFor = asyncFor;
    Async.asyncForEachInArray = asyncForEachInArray;
    Async.asyncArraySort = asyncArraySort;

    return Async;
});

понедельник, 24 марта 2014 г.

JavaScript MVC - Правила написания кода

Контроллеры

- Контроллеры являются связующим звеном между видами и моделями. Поэтому сохраняйте котроллеры независимыми от типа передаваемых данных и внешним представлением информации.
- Большинство логики контроллеров отвечает за управление событиями, возникающими в процессе работы приложения.
- Не передавайте экземпляр контроллера всем остальным контроллерам. Любое взаимодействие между дочерними и родительскими контроллерами должно происходить посредством публикации и передачи событий.
- Контроллер должен знать только о существовании дочерних контроллеров.
- Контроллер должен быть ограничен одним элементов и не должен иметь доступа к получению данных из DOM и изменению или  изменению его частей.
- Контроллер должен иметь уникальное имя класса и весь код CSS, относящийся к этому контроллеру, должен быть заключен в пространство имен с именем этого класса.
- Контроллер должен работать без сбоев даже, если элемент, к которому он привязан не добавлен в дерево DOM.
- Контроллер должен работать при тестовых запусках относящихся к нему событий программным способом.
- Контроллер должен быть способен в любое время перерисовать элементы страницы без всяких побочных эффектов.
- Контроллер не должен использовать DOM для хранения в нем состояний. Для хранения состояния используйте свойства контроллера.

Виды

- Виды должны быть независимы от логики контроллеров за исключением управления потоком выполнения и вспомогательных функций.
- В виды должны передавать и быть доступными все данных необходимые для отрисовки страницы.

Модели

- Модели должны быть сконцентрированы только на управление и обработку данных.
- Модели никогда не должны иметь доступа к контроллерам и видам.
- Модели никогда не должны иметь доступа к DOM дереву.
- Модели должны быть абстрагированы от XHR соединений во всем приложении.
- Модели должны содержать в себе все операции по валидации данных.

Маршрутизаторы

- Маршрутизаторы не должны содержать в себе какой-либо логики и вычислений.
- Маршрутизаторы ничего не должны знать о существовании DOM дерева.

Глобальные переменные

- Глобальные переменные должны быть сведены к минимому. Желательно их не использовать совсем. Передачу информации между частями приложения можно осуществлять посредством передачи событий.

Модули

- Весь код в обязательном порядке должен быть разбит на независимые модули, согласно стандартам CommonJS, AMD, ES6.

среда, 12 марта 2014 г.

html2canvas Resize image

html2canvas(document.getElementById('divId'), {
    onrendered: function(canvas) {
        var extra_canvas = document.createElement('canvas');
             extra_canvas.setAttribute('width', 70);
             extra_canvas.setAttribute('height', 70);
        var ctx = extra_canvas.getContext('2d');
             ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, 70, 70);
        var dataURL = extra_canvas.toDataURL();
        var img = $(document.createElement('img'));
              img.setAttribute('src', dataURL);
        document.body.appendChild(img);
});

html2canvas Error : Index or size is negative or greater than the allowed amount Bug fix

Для исправления ошибки "Error : Index or size is negative or greater than the allowed amoun" в библиотеке html2canvas необходимо заменить строку

ctx.drawImage(canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height);

на строки:

var imgData = canvas.getContext("2d").getImageData(bounds.left, bounds.top, bounds.width, bounds.height);
ctx.putImageData(imgData, 0, 0);

четверг, 6 марта 2014 г.

Sublime Text 3 User settings

{
"bold_folder_labels": true,
"color_scheme": "Packages/User/Mac Classic (SL).tmTheme",
"default_line_ending": "unix",
"draw_white_space": "all",
"fallback_encoding": "UTF-8",
"font_face": "Verdana",
"font_options": "subpixel_antialias",
"font_size": 15.0,
"highlight_line": true,
"highlight_modified_tabs": true,
"ignored_packages":
[
"Vintage"
],
"line_padding_bottom": 1,
"line_padding_top": 1,
"rulers":
[
80
],
"scroll_past_end": true,
"tab_completion": false,
"tab_size": 4,
"translate_tabs_to_spaces": true,
"trim_trailing_white_space_on_save": true,
"word_wrap": false
}

среда, 5 марта 2014 г.

Alertbox - не блокирующие Alert, Comfirm, Prompt

Файл alertbox.js

define(function(){

    // Вывод собственных вариантов Alert, Confirm, Prompt
   
    // Примеры использования:
   
    // Замена alert(text')
   
    // AlertBox.alert(
    //        'Внимание!'
    //      , function(returnedValue){
    //          console.log('Alert всегда возвращает значение: ' + returnedValue + '.');
    //       }
    // );
   
    // Замена confirm('text');
   
    // AlertBox.confirm(
    //        'Вы уверены, что хотите выйти?'
    //      , function(returnedValue){console.log('Вы выбрали "Нет", поскольку получено значение: ' + returnedValue + '.');}
    //      , function(returnedValue){console.log('Вы выбрали "Да", поскольку получено значение: ' + returnedValue + '.');}
    // );
   
    // Замена confirm('text', 'default value');
   
    // AlertBox.prompt(
    //       'Сколько будет равно 2+2?'
    //     , '2'
    //     , function(returnedValue){console.log('Вы решили не отвечать на вопрос, поскольку получено значение: ' + returnedValue + '.');}
    //     , function(returnedValue){
    //          returnedValue = parseInt(returnedValue, 10);
    //          if (returnedValue === 4) {
    //              console.log('Ответ правильный.');
    //          } else {
    //              console.log('Ответ неправильный. 2+2=4');
    //          }
    //       }
    // );
   
    // Во всех случаях функции обратного вызова можно не писать.

    var AlertBox = (function(){

        // Constructor
       
        var AlertBox = {};

        // Private variables
       
        var stack = []
            , currentAlertBoxObject = null;
       
        // Private functions
       
        function correctValue (object, property) {
            if (object[property] === undefined || object[property] === null) {
                return '';
            }
            return object[property];
        }
       
        function addMessageToStack (args) {
            var type = correctValue(args, 'type')
                , text = correctValue(args, 'text')
                , inputDefaultValue = correctValue(args, 'inputDefaultValue')
                , cancelFunction = args.cancelFunction || function () {}
                , okFunction = args.okFunction  || function () {}
                , message = {};
            message.type = '' + type;
            message.text = '' + text;
            if (message.type === 'prompt') {
                message.inputDefaultValue = '' + inputDefaultValue;
            }
            message.cancelFunction = cancelFunction;
            if (message.type !== 'alert') {
                message.okFunction = okFunction;
            }
            stack.push(message);
        }
       
        function showMessageFromStack () {
            var alertBoxBlock = document.getElementById('alertbox');
            if (
                    ( alertBoxBlock === null || (alertBoxBlock.offsetWidth <= 0 && alertBoxBlock.offsetHeight <= 0) )
                && stack.length !== 0
            ) {
                currentAlertBoxObject = stack.shift();
                document.body.appendChild(renderAlertBoxTemplate());
                addOnClickFunctions(currentAlertBoxObject);
            }
        }
       
        function renderAlertBoxTemplate () {
            var alertBoxElement = document.createElement('div');
                  alertBoxElement.id = 'alertbox';
            var alertBoxHTML = '<div id="alertbox-opaque-layer"></div>'
                                        + '<div id="alertbox-' + currentAlertBoxObject.type + '">'
                                        + '<div id="alertbox-' + currentAlertBoxObject.type + '-wrapper">'
                                        + '<p id="alertbox-text">' + currentAlertBoxObject.text + '</p>'
                                        + '</div>';
            if (currentAlertBoxObject.type === 'alert') {
                alertBoxHTML += '<div id="alertbox-alert-buttons">'
                                        + '<div id="alertbox-cancel">Ok</div>';
            } else if (currentAlertBoxObject.type === 'confirm') {
                alertBoxHTML += '<div id="alertbox-confirm-buttons">'
                                        + '<div id="alertbox-cancel">Нет</div>'
                                        + '<div id="alertbox-ok">Да</div>';
            } else if (currentAlertBoxObject.type === 'prompt') {
                alertBoxHTML += '<div id="alertbox-input-wrapper">'
                                        + '<input id="alertbox-input" value="' + currentAlertBoxObject.inputDefaultValue + '" />'
                                        + '</div>'
                                        + '<div id="alertbox-prompt-buttons">'
                                        + '<div id="alertbox-cancel">Отмена</div>'
                                        + '<div id="alertbox-ok">Ok</div>';
            }
                alertBoxHTML += '</div>'
                                        + '</div>'
                                        + '</div>';
            alertBoxElement.innerHTML = alertBoxHTML;
            return alertBoxElement;
        }
       
        function onAlertBoxClick (event) {
            if (event.stopPropagation) {event.stopPropagation();}
            event.cancelBubble = true;
        }
       
        function addOnClickFunctions (currentAlertBoxObject) {
            on('click', document.getElementById('alertbox-opaque-layer'), onAlertBoxClick);
            if (currentAlertBoxObject.type === 'alert') {
                on('click', document.getElementById('alertbox-alert'), onAlertBoxClick);
                on('click', document.getElementById('alertbox-alert-buttons'), onAlertBoxClick);
            }
            if (currentAlertBoxObject.type === 'confirm') {
                on('click', document.getElementById('alertbox-confirm'), onAlertBoxClick);
                on('click', document.getElementById('alertbox-confirm-buttons'), onAlertBoxClick);
            }
            if (currentAlertBoxObject.type === 'prompt') {
                on('click', document.getElementById('alertbox-prompt'), onAlertBoxClick);
                on('click', document.getElementById('alertbox-prompt-buttons'), onAlertBoxClick);
            }
            on('click', document.getElementById('alertbox-cancel'), onCancelClick);
            if (currentAlertBoxObject.type !== 'alert') {
                on('click', document.getElementById('alertbox-ok'), onOkClick);
            }
            on('keydown', document, onKeyDown);
        }
       
        function addOffClickFunctions (currentAlertBoxObject) {
            off('click', document.getElementById('alertbox-opaque-layer'), onAlertBoxClick);
            if (currentAlertBoxObject.type === 'alert') {
                off('click', document.getElementById('alertbox-alert'), onAlertBoxClick);
                off('click', document.getElementById('alertbox-alert-buttons'), onAlertBoxClick);
            }
            if (currentAlertBoxObject.type === 'confirm') {
                off('click', document.getElementById('alertbox-confirm'), onAlertBoxClick);
                off('click', document.getElementById('alertbox-confirm-buttons'), onAlertBoxClick);
            }
            if (currentAlertBoxObject.type === 'prompt') {
                off('click', document.getElementById('alertbox-prompt'), onAlertBoxClick);
                off('click', document.getElementById('alertbox-prompt-buttons'), onAlertBoxClick);
            }
            if (currentAlertBoxObject.type !== 'alert') {
                off('click', document.getElementById('alertbox-ok'), onOkClick);
            }
            off('click', document.getElementById('alertbox-cancel'), onCancelClick);
            off('keydown', document, onKeyDown);
        }

        function onCancelClick (event) {
            onAlertBoxClick(event);
            var alertBoxValue;
                       if (currentAlertBoxObject.type === 'alert') {alertBoxValue = undefined;
            } else if (currentAlertBoxObject.type === 'confirm') {alertBoxValue = false;
            } else if (currentAlertBoxObject.type === 'prompt') {alertBoxValue = null;
            }
            currentAlertBoxObject.cancelFunction(alertBoxValue);
            addOffClickFunctions(currentAlertBoxObject);
            document.body.removeChild(document.getElementById('alertbox'));
            showMessageFromStack();
        }

        function onOkClick (event) {
            onAlertBoxClick(event);
            var alertBoxValue;
                       if (currentAlertBoxObject.type === 'alert') {alertBoxValue = undefined;
            } else if (currentAlertBoxObject.type === 'confirm') {alertBoxValue = true;
            } else if (currentAlertBoxObject.type === 'prompt') {alertBoxValue = document.getElementById('alertbox-input').value;
            }
            currentAlertBoxObject.okFunction(alertBoxValue);
            addOffClickFunctions(currentAlertBoxObject);
            document.body.removeChild(document.getElementById('alertbox'));
            showMessageFromStack();
        }
                   
        function onKeyDown (event) {
            onAlertBoxClick(event);
            var alertBoxValue;
            if (event.keyCode === 13 || event.keyCode === 32) { // Enter or Space
                           if (currentAlertBoxObject.type === 'alert') {alertBoxValue = undefined;
                } else if (currentAlertBoxObject.type === 'confirm') {alertBoxValue = true;
                } else if (currentAlertBoxObject.type === 'prompt') {alertBoxValue = document.getElementById('alertbox-input').value;
                }
                if (currentAlertBoxObject.type === 'alert') {
                    currentAlertBoxObject.cancelFunction(alertBoxValue);
                } else {
                    currentAlertBoxObject.okFunction(alertBoxValue);
                }
                addOffClickFunctions(currentAlertBoxObject);
                document.body.removeChild(document.getElementById('alertbox'));
                showMessageFromStack();
            } else if (event.keyCode === 27) { // Escape
                          if (currentAlertBoxObject.type === 'alert') {alertBoxValue = undefined;
                } else if (currentAlertBoxObject.type === 'confirm') {alertBoxValue = false;
                } else if (currentAlertBoxObject.type === 'prompt') {alertBoxValue = null;
                }
                currentAlertBoxObject.cancelFunction(alertBoxValue);
                addOffClickFunctions(currentAlertBoxObject);
                document.body.removeChild(document.getElementById('alertbox'));
                showMessageFromStack();
            }
        }

        function on (type, element, handler) {
            if (document.addEventListener) {
                element.addEventListener(type, handler, false);
            } else {
                element.attachEvent('on' + type, handler);
            }
        }
       
        function off (type, element, handler) {
            if (document.removeEventListener) {
                element.removeEventListener(type, handler, false);
            } else {
                element.detachEvent('on' + type, handler);
            }
        }
       
        // Static functions
       
        AlertBox.alert = function (text, cancelFunction) {
            addMessageToStack({
                  type: 'alert'
                , text: text
                , cancelFunction: cancelFunction
            });
            showMessageFromStack();
        };
       
        AlertBox.confirm = function (text, cancelFunction, okFunction) {
            addMessageToStack({
                  type: 'confirm'
                , text: text
                , cancelFunction: cancelFunction
                , okFunction: okFunction
            });
            showMessageFromStack();
        };

        AlertBox.prompt = function (text, inputDefaultValue, cancelFunction, okFunction) {
            addMessageToStack({
                  type: 'prompt'
                , text: text
                , inputDefaultValue: inputDefaultValue
                , cancelFunction: cancelFunction
                , okFunction: okFunction
            });
            showMessageFromStack();
        };

        // Return constructor

        return AlertBox;
       
    })();
   
    return AlertBox;

});


Файл alertbox.css

/*****************************************************************\
  AlertBox CSS
\*****************************************************************/

#alertbox {
    font-size: 1.4em;
}

#alertbox-opaque-layer {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background: #000000;
    -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=70)";
    -khtml-opacity: 0.7;
    -moz-opacity: 0.7;
    opacity: 0.7;
    *filter:progid:DXImageTransform.Microsoft.Alpha(opacity = 70);
    z-index: 100000;
}

#alertbox-alert,
#alertbox-confirm,
#alertbox-prompt {
    width: 500px;
    height: 90px;
    position: fixed;
    margin: auto;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 10px;
    background-color: #ffffff;
    border: 1px solid #bbbbbb;
    z-index: 100001;
}

#alertbox-prompt {
    height: 130px;
}

#alertbox-alert-wrapper,
#alertbox-confirm-wrapper,
#alertbox-prompt-wrapper {
    height: 60px;
    overflow-y: auto;
}

#alertbox-prompt-wrapper {
    height: 40px;
}

#alertbox-input-wrapper {
    margin-top: 10px;
    padding: 2px;
    border: 1px solid #bbbbbb;
}

#alertbox-input {
    width: 100%;
    height: 20px;
    line-height: 20px;
    padding: 0;
    border: none;
}

#alertbox-alert-buttons,
#alertbox-confirm-buttons,
#alertbox-prompt-buttons {
    position: absolute;
    width: 186px;
    margin: auto;
    left: 0;
    right: 0;
    bottom: 10px;
}

#alertbox-alert-buttons {
    width: 50px;
}

#alertbox-cancel,
#alertbox-ok {
    display: inline-block;
    width: 50px;
    padding: 3px 9px;
    background-color: #5a8cd3;
    text-align: center;
    color: #ffffff;
    white-space: nowrap;
    cursor: pointer;
}

#alertbox-ok {
    margin-left: 50px;
}

#alertbox-alert-buttons #alertbox-ok {
    margin-left: 0px;
}

#alertbox-cancel:hover,
#alertbox-ok:hover {
    background-color: #29a2fd;
}

Замена typeof на правильную функциию

Object.toType = (function toType(global) {
  return function(obj) {
    if (obj === global) {
      return 'global';
    }
    return ({}).toString.call(obj).match(/\s([a-z|A-Z]+)/)[1].toLowerCase();
  }
})(this);

Object.toType(window); // "global" (all browsers)
Object.toType([1,2,3]); // "array" (all browsers)
Object.toType(/a-z/); // "regexp" (all browsers)
Object.toType(JSON); // "json" (all browsers)


Или можно просто проверять тип так:

Object.prototype.toString.call([1,2,3]); // "[object Array]"

Асинхронная обработка массивов и асинхронные вызовы функций из очереди

Асинхронная обработка массивов:

function processArray(items, process, callback){
    var todo = items.concat(); // создать копию массива оригинала
    setTimeout(function(){
        process(todo.shift());
        if (todo.length > 0){
            setTimeout(arguments.callee, 25);
        } else {
            callback(items);
        }
    }, 25);
}

processArray([1,2,3], function(element){console.log(element);}, function(){console.log('End');});

Полуасинхронно-пакетная обработка массивов:

function timedProcessArray(items, process, callback){
    var todo = items.concat(); // создать копию массива оригинала
    setTimeout(function(){
        var start = +new Date();
        do {
            process(todo.shift());
        } while (todo.length > 0 && (+new Date() - start < 50));
        if (todo.length > 0){
            setTimeout(arguments.callee, 25);
        } else {
            callback(items);
        }
    }, 25);
}

timedProcessArray([1,2,3], function(element){console.log(element);}, function(){console.log('End');});

Асинхронный вызов функций из очереди:

function multistep(steps, args, callback){
    var tasks = steps.concat(); // скопировать массив
    setTimeout(function(){
        // выполнить следующее задание
        var task = tasks.shift();
        task.apply(null, args || []);
        // проверить наличие других заданий
        if (tasks.length > 0){
            setTimeout(arguments.callee, 25);
        } else {
            callback();
        }
    }, 25);
}

multistep([function(a){console.log(a);}, function(b){console.log(b);}, function(c){console.log(c);}], [1], function(){console.log('End');});

воскресенье, 2 марта 2014 г.

Вывод console.log в любом браузере

Данный код позволяет сделать вывод console.log() даже в устаревших браузерах.

<div id="consolelog" style="font-family: 'Courier New', Courier, monospace; font-size: 12px; margin: 40px 30px 0px; background-color: white; border: 2px solid black; padding: 10px;"></div>

<input type="text" id="consoleinput" style="margin: 0px 30px; width: 400px;" onkeypress="return evalConsoleInput(event, this.value);" />

<script type="text/javascript">
var appendConsole = function(message, type) {
  var color = "black";
  if (type === "error") {
    color = "red";
  } else if (type === "debug") {
    color = "blue";
  }

  var div = document.createElement('div');
  div.style.color = color;
  div.style.marginBottom = "10px";

  div.innerHTML = message;

  document.getElementById("consolelog").appendChild(div);
}

var originalConsole = null;

if (window.console != null) {
  originalConsole = window.console;
}

window.console = {
  log: function(message) {
    appendConsole(message, "info");
    originalConsole.log(message);
  },
  info: function(message) {
    appendConsole(message, "info");
    originalConsole.info(message);
  },
  debug: function(message) {
    appendConsole(message, "debug");
    originalConsole.debug(message);
  },
  error: function(message) {
    appendConsole(message, "error");
    originalConsole.error(message);
  }
};

function evalConsoleInput(e, message)
{
  if (e.keyCode == 13) { // 13 is the keycode for the enter key
    var inputField = document.getElementById("consoleinput");
    var evalString = inputField.value;

    console.log("> " + evalString);

    try {
      var returnValue = eval(evalString);
      console.log(returnValue);
    } catch (e) {
      console.error(e.message);
    } finally {
      inputField.value = "";
    }
  }
}
</script>

Сюда еще можно добавить поддержку Stacktrace.js.