вторник, 19 ноября 2013 г.

Пианино на JavaScript

Напишем Пианино на JavaScript.

Клавиатура классического фортепиано состоит из 88 клавиш, покрывающих диапазон от A0 (Ля суб-контр-октавы, частота звучания 27.5 Гц) до C8 (До пятой октавы, частота 4186 Гц).

Каждая октава на клавиатуре состоит из двенадцати нот:
До, До-диез, Ре, Ре-диез, Ми, Фа, Фа-диез, Соль, Соль-диез, Ля, Ля-диез/Си-бемоль, Си. Жирным выделены клавиши верхнего ряда. Они на клавиатуре обычно бывают черного цвета.

Вот так выглядит одна октава:


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

Nx = N1 × 2x-1, где:
N – название ноты;
x — номер октавы (от 0 до 8);
Nx, соответственно, частота звука, соответствующая ноте N октавы x;

В формуле фигурирует N1 вместо N0 лишь потому, что часть нот суб-контр-октавы (N0) имеет частоту звучания ниже порога слышимости человеческим ухом (< 20 Hz).

Чтобы ноты получались чистыми, нам нужны достаточно точные значения частот нот контроктавы, от которой мы начинаем считать. Вот они:

C: 32.703,
С#: 34.648,
D: 36.708,
D#: 38.891,
E: 41.203,
F: 43.654,
F#: 46.249,
G: 48.999,
G#: 51.913,
A: 55,
A#: 58.27,
B: 61.735

Основываясь на этом, напишем функцию, принимающую в качестве аргумента строку с именем клавиши в виде "A4" или "C5#", и возвращающую частоту её звучания:

function play(key) {
    var controctave = {
            'C': 32.703,
            'С#': 34.648,
            'D': 36.708,
            'D#': 38.891,
            'E': 41.203,
            'F': 43.654,
            'F#': 46.249,
            'G': 48.999,
            'G#': 51.913,
            'A': 55,
            'A#': 58.27,
            'B': 61.735,
        },
        note = key[0].toUpperCase(),
        octave = parseInt(key[1]),
        sharp = key[2] == '#' ? true : false;
    if (sharp) {
        return controctave[note + '#'] * Math.pow(2, octave-1);
    } else {
        return controctave[note] * Math.pow(2, octave-1);
    }
}

Немного подсократим код нашей функции:

function play(key) {
    var controctave = { 'C': 32.703, 'С#': 34.648, 'D': 36.708, 'D#': 38.891, 'E': 41.203, 'F': 43.654, 'F#': 46.249, 'G': 48.999, 'G#': 51.913, 'A': 55, 'A#': 58.27, 'B': 61.735};
    freq = key[2] == '#' ? controctave[key[0].toUpperCase() + '#'] * Math.pow(2, (key[1]|0) - 1) : controctave[key[0].toUpperCase()] * Math.pow(2, (key[1]|0) - 1);
    return freq; }


Теперь нарисуем клавиатуру.

88 клавиш клавиатуры начинаются с ноты Ля (A0).

Соответственно, цикл будет такой: в цикле рисуем по 12 клавиш, и каждую вторую, четвертую, седьмую, девятую и одиннадцатую делаем чёрной. Каждой клавише присвоим id, соответствующий ноте, которую она должна воспроизводить при нажатии.

Получится так:

var width = 1000;
var deck = document.createElement('div'),
    octave = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'],
    id = "",
    keynumber = 0,
    whitekeys = 0,
    keys = [];

deck.style.width = width;

parent:
for (var i = 0; i < 8; i++) {
    for (var j = 0; j < 12; j++) {
        keynumber = (i * 12) + j;
        if (keynumber >= 88) break parent;
        keys[keynumber] = document.createElement('div');
        keys[keynumber].style.border = "1px solid black";
        keys[keynumber].style.position = "absolute";
        id = (octave[j][1] == '#') ? octave[j] + i + 's' : octave[j] + i;
        keys[keynumber].id = id;

        switch(j%12) {
            case 1:
            case 3:
            case 6:
            case 8:
            case 10:
                keys[keynumber].style.backgroundColor = 'black';
                keys[keynumber].style.left = ((width / 50 * whitekeys) - (width / 200)) + 'px';
                keys[keynumber].style.width = width/100 + "px";
                keys[keynumber].style.height = "200px";
                keys[keynumber].style.zIndex = 10;
                break;
            default:
                keys[keynumber].style.backgroundColor = 'white';
                keys[keynumber].style.left = (width / 50 * whitekeys) + 'px';
                keys[keynumber].style.width = width/50 + "px";
                keys[keynumber].style.height = "300px";
                whitekeys++;

        }

        deck.appendChild(keys[keynumber]);

    }
}

document.body.appendChild(deck);

И вновь превратим применим небольшую оптимизацию кода, сделав его более компактным:

var width = 1000, octave = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'], id = "", div, whitekeys=0, keys = [];
parent: for (var i = 0; i < 8; i++) {
    for (var j = 0; j < 12; j++) {
        if ((i * 12) + j >= 88) break parent;
        div = document.createElement('div');
        div.id = (octave[j][1] == '#') ? octave[j][0] + ((((i * 12) + j + 9) / 12)|0) + 's' : octave[j] + ((((i * 12) + j + 9) / 12)|0);
        if (j % 12 == 1 || j % 12 == 4 || j % 12 == 6 || j % 12 == 9 || j % 12 == 11) {
            div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:black; left:' + ((width / 50 * whitekeys) - (width / 200)) + 'px; width:' + width/100 + 'px; height: 200px; z-index:1;');}
        else {
            div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:white; left:' + (width / 50 * whitekeys) + 'px; width:' + width/50 + 'px; height:300px;');
            whitekeys++;
        }
        document.body.appendChild(div);}}


Научим пианино издавать звуки.

Для этого нам понадобится Web Audio API, который на сей момент поддерживается только Webkit-based браузерами и Firefox.

Добавим в строку объявления глобальных переменных создание аудиоконтекста:

context = window.AudioContext ? new AudioContext() : new webkitAudioContext();

Добавим обработчик нажатий на клавиши:

document.body.addEventListener('click', play);

Саму функцию play изменим следующим образом:

function play(e) {
    var controctave = { 'C': 32.703, 'Cs': 34.648, 'D': 36.708, 'Ds': 38.891, 'E': 41.203, 'F': 43.654, 'Fs': 46.249, 'G': 48.999, 'Gs': 51.913, 'A': 55, 'As': 58.27, 'B': 61.735}, osc = context.createOscillator();
    osc.frequency.value = e.target.id[2] == 's' ? controctave[e.target.id[0] + 's'] * Math.pow(2, (e.target.id[1]|0) - 1) : controctave[e.target.id[0]] * Math.pow(2, (e.target.id[1]|0) - 1);
    osc.type = "square";
    osc.connect(context.destination);
    osc.start(0);
    setTimeout(function() {
        osc.stop(0);
        osc.disconnect(context.destination);
    }, 1000 / 2);}


Здесь мы создали осциллятор: osc = context.createOscillator();,
установили ему необходимую частоту звучания: osc.frequency.value = e.target.id[2] == 's' ? controctave[e.target.id[0] + 's'] * Math.pow(2, (e.target.id[1]|0) - 1) : controctave[e.target.id[0]] * Math.pow(2, (e.target.id[1]|0) - 1);,
установили форму сигнала: osc.type = "square"; (по умолчанию был синусоидальный) соединили его с устройством вывода звука: osc.connect(context.destination);
и дали команду начать воспроизведение: osc.start(0);.
После этого нам необходимо заставить клавишу замолчать через некоторое время (500мс), а то она так и будет противно пищать. Для этого используем osc.stop(0), завёрнутый в интервал. Обязательный элемент: osc.disconnect(context.destination); - отключаем осциллятор от устройства вывода.

В итоге у нас получился вот такой код пианино на JavaScript:

var width = 1000, octave = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'], id = "", div, whitekeys=0, keys = [],context = window.AudioContext ? new AudioContext() : new webkitAudioContext();
parent: for (var i = 0; i < 8; i++) {
for (var j = 0; j < 12; j++) {
if ((i * 12) + j >= 88) break parent;
div = document.createElement('div');
div.id = (octave[j][1] == '#') ? octave[j][0] + ((((i * 12) + j + 9) / 12)|0) + 's' : octave[j] + ((((i * 12) + j + 9) / 12)|0);
if (j % 12 == 1 || j % 12 == 4 || j % 12 == 6 || j % 12 == 9 || j % 12 == 11) {
div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:black; left:' + ((width / 50 * whitekeys) - (width / 200)) + 'px; width:' + width/100 + 'px; height: 200px; z-index:1;');}
else {
div.setAttribute('style', 'border:1px solid black; position:absolute; background-color:white; left:' + (width / 50 * whitekeys) + 'px; width:' + width/50 + 'px; height:300px;');
whitekeys++;
}
document.body.appendChild(div);}}
document.body.addEventListener('click', play);
function play(e) {
var controctave = { 'C': 32.703, 'Cs': 34.648, 'D': 36.708, 'Ds': 38.891, 'E': 41.203, 'F': 43.654, 'Fs': 46.249, 'G': 48.999, 'Gs': 51.913, 'A': 55, 'As': 58.27, 'B': 61.735}, osc = context.createOscillator();
osc.frequency.value = e.target.id[2] == 's' ? controctave[e.target.id[0] + 's'] * Math.pow(2, (e.target.id[1]|0) - 1) : controctave[e.target.id[0]] * Math.pow(2, (e.target.id[1]|0) - 1);
osc.type = "square";
osc.connect(context.destination);
osc.start(0);
setTimeout(function() {
   osc.stop(0);
   osc.disconnect(context.destination);
}, 1000 / 2);}

Tetris на JavaScript

Напишем полноценный Tetris на JavaScript.

Особенности программы.
Tetris на JavaScript знает все фигуры тетриса.
Управление с клавиатуры:
- кнопка влево - перемещение фигуры влево,
- кнопка вправо - перемещение фигуры вправо,
- кнопка вверх - поворот фигуры по часовой стрелке,
- кнопка вниз - ускорить падение фигуры.
Во время игры скорость падания фигур постепенно увеличивается.
Очки оперативно подсчитываются.



Опишем принцип работы JavaScript-программы.

1. Все фигурки Tetris хранятся в переменной fs="1111:01|01|01|01*011|110:010|011|001*..." в виде строки.
Чтобы получить массив фигур делаем split('*').
Далее в каждой фигуре хранится от одного (для палки) до четырёх (для L, Г и пирамидки) состояния, чтобы их можно было переворачивать. Состояния разделены двоеточием ":". Соответственно, чтобы получить одно состояние, необходимо выполнить операцию split(':'). Допустим мы получили пирамидку ("010|111"). Делаем split('|') и получаем конечный двухмерный массив для одного состояния данной фигуры. "0" - это пустое пространство, его не нужно отрисовывать, а "1" означает, что нужно рисовать.

2. Всё перемещение фигур делается двумя функциями: "стереть фигуру" и "построить фигуру".
При любых перемещениях: вправо, влево, вниз или даже повороте фигуры сначала стираем текущую отображаемую фигуру, затем пытаемся построить фигуру на новом месте. Если при постройке какой-то из квадратиков влезет за край стакана или попадает на место, где уже есть заполненный квадратик от предыдущих фигур, то в этом случае стираем активную фигуру и строим ее на предыдущем месте.

3. Перемещения фигур Tetris делаются с клавиатуры. По таймеру просто вызывается функция, которая обрабатывает нажатия с клавиатуры с кодом кнопки. При каждом вызове таймаута время до вызова уменьшается.

4. Если не удалось переместить фигуру вниз - это значит, что она упёрлась в предыдущие фигуры или дно стакана. В таком случае проверяем нет ли заполненных строк и рисуем новую фигуру вверху стакана. Если вверху стакана нарисовать фигуру не удалось, то наступает Game Over и игра заканчивается.

Файл index.html

<div class="score">score: <span id="result">0</span></div>

  <div id="stack">
    <div data-y="0" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="1" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="2" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="3" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="4" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="5" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="6" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="7" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="8" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="9" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="10" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="11" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="12" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="13" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="14" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="15" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="16" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="17" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="18" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
    <div data-y="19" class="line">
        <div data-x="0" class="brick"></div><div data-x="1" class="brick"></div><div data-x="2" class="brick"></div><div data-x="3" class="brick"></div><div data-x="4" class="brick"></div><div data-x="5" class="brick"></div><div data-x="6" class="brick"></div><div data-x="7" class="brick"></div><div data-x="8" class="brick"></div><div data-x="9" class="brick"></div>
    </div>
</div>

Файл index.css

#stack {
    width: 130px;
    height: 260px;
    border: solid 1px black;
    border-top: 0px;
}

.brick {
    width: 11px;
    height: 11px;
    border: solid 1px white;
    background: white;
    float: left;
}
.brick.on {
    background: black;
}
.brick.now {
    background: green;
}

Файл index.js

var fs = "1111:01|01|01|01*011|110:010|011|001*110|011:001|011|010*111|010:01|11|01:010|111:10|11|10*11|11*010|010|011:111|100:11|01|01:001|111*01|01|11:100|111:11|10|10:111|001", now = [3,0], pos = [4,0];
var gP = function(x,y) { return document.querySelector('[data-y="'+y+'"] [data-x="'+x+'"]'); };
var draw = function(ch, cls) {
    var f = fs.split('*')[now[0]].split(':')[now[1]].split('|').map(function(a){return a.split('')});
    for(var y=0; y<f.length; y++)
        for(var x=0; x<f[y].length; x++)
            if(f[y][x]=='1') {
                if(x+pos[0]+ch[0]>9||x+pos[0]+ch[0]<0||y+pos[1]+ch[1]>19||gP(x+pos[0]+ch[0],y+pos[1]+ch[1]).classList.contains('on')) return false;
                gP(x+pos[0]+ch[0], y+pos[1]+ch[1]).classList.add(cls!==undefined?cls:'now');
            }
    pos = [pos[0]+ch[0], pos[1]+ch[1]];
}
var deDraw = function(){ if(document.querySelectorAll('.now').length>0) deDraw(document.querySelector('.now').classList.remove('now')); }
var check = function(){
for(var i=0; i<20; i++)
if(document.querySelectorAll('[data-y="'+i+'"] .brick.on').length == 10) 
return check(roll(i), document.querySelector('#result').innerHTML=Math.floor(document.querySelector('#result').innerHTML)+10);
};
var roll = function(ln){ if(false !== (document.querySelector('[data-y="'+ln+'"]').innerHTML = document.querySelector('[data-y="'+(ln-1)+'"]').innerHTML) && ln>1) roll(ln-1); };
window.addEventListener('keydown', kdf = function(e){
    if(e.keyCode==38&&false!==(now[1]=((prv=now[1])+1)%fs.split('*')[now[0]].split(':').length) && false===draw([0,0], undefined, deDraw())) draw([0,0],undefined, deDraw(), now=[now[0],prv]);
    if((e.keyCode==39||e.keyCode==37)&&false===draw([e.keyCode==39?1:-1,0],undefined,deDraw())) draw([0,0],undefined,deDraw());
    if(e.keyCode == 40)
        if(false === draw([0,1], undefined, deDraw())) {
            if(draw([0,0], 'on', deDraw())||true) check();
            if(false === draw([0,0], undefined, now = [Math.floor(Math.random()*fs.split('*').length),0], pos = [4,0])) { 
toV=-1; 
alert('Your score: '+document.querySelector('#result').innerHTML); 
}
        }
});
toF = function() {
    kdf({keyCode:40});
    setTimeout(function(){if(toV>=0)toF();}, toV=toV>0?toV-0.5:toV);
}
toF(toV = 500);

среда, 13 ноября 2013 г.

Express JS Basic Authorizaton

Как сделать свою Basic Authorizaton в Express JS?

В Express.JS есть встроенный middleware для авторизации. Если необходимо заблокировать доступ к вашему приложению, то для этого нужно добавить всего лишь одну:

app.use(express.basicAuth('username', 'password'));

Так же Express JS поддерживает альтернативный вариант с вызовом функции-коллбэка:

app.use(express.basicAuth(function(user, pass, next) {
  var result = (user === 'testUser' && pass === 'testPass');
  next(null /* error */, result);
}));

Если нужно ограничить доступ только к определенным url'ам, то middleware basicAuth можно использовать не глобально, а только в рамках роутера:

var auth = express.basicAuth(function(user, pass, next) {
  var result = (user === 'testUser' && pass === 'testPass');
  next(null, result);
});

app.get('/home', auth, function(req, res) {
  res.send('Hello World');
});

basicAuth достался Express JS в наследство от connect. Она устроена достаточно просто.
Когда браузер запрашивает страницу у сервера, то сервер сообщает браузеру о необходимости авторизации с помощью заголовка WWW-Authenticate:
  res.statusCode = 401;
  res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"');
  res.end('Unauthorized');

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

На этот раз запрос уже будет содержать заголовок:
Authorization:Basic dXNlcm5hbWU6cGFzc3dvcmQ=, где dXNlcm5hbWU6cGFzc3dvcmQ= - это строка 'username:password'.

В этом легко можно убедиться, если на сервере выполнить такую команду:

var str = new Buffer('dXNlcm5hbWU6cGFzc3dvcmQ=', 'base64').toString();
console.log(str); // выведет 'username:password'

Теперь браузер будет прикреплять эту строку к каждому запросу, а basicAuth будет проверять верные ли данные пользователя указаны в запросе.

пятница, 8 ноября 2013 г.

JavaScript Drag and Drop Более простой пример

Файл index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <link href="style/default/index.css" rel="stylesheet" type="text/css" />
    <script data-main="js/index.js" src="js/lib/require/require.js" type="text/javascript"></script>
    <title>Drag</title>
</head>
<body>
<div id="grid">
    <div id="one"></div>
    <div class="two"></div>
    <div class="two"></div>
</div>
</body>
</html>

Файл index.css

/*****************************************************************\
  Drag
\*****************************************************************/

div#grid {
    position: relative;
    margin-top: 100px;
    margin-left: 100px;
    border: 1px solid black;
}

div#one {
    width: 100px;
    height: 100px;
    background-color: green;
}

div.two {
    width: 300px;
    height: 300px;
    margin-top: 200px;
    margin-left: 200px;
    background-color: blue;
}

.drag {
    z-index: 100;
    position: absolute;
    background-color: red !important;
    cursor: move !important;
}

Файл index.js

require.config({
    paths: {
          jquery: 'lib/jquery/jquery'
    }
});

require(
    [
          'jquery'
    ]
    , function(
          $
    ) {
 
        var globalDraggingElement = null;

        $('div#one').mousedown(function(event){
            globalDraggingElement = $(this);
            startDragElement(event);
            event.preventDefault();
        });
     
        $(document).mousemove(function(event){
            if (globalDraggingElement) {
                moveElement(event);
            }
            event.preventDefault();
        });
     
        $(document).mouseup(function(event){
            if (globalDraggingElement) {
                dropElement(event);
            }
        });
     
        var globalMouseCursorOffset = null;
     
        // Начинаем перетаскивать элемент
        function startDragElement (event) {
            if (event.target.nodeName === 'INPUT' || event.target.nodeName === 'SELECT' || event.target.nodeName === 'TEXTAREA') {
                globalDraggingElement = null;
                return;
            }
            globalMouseCursorOffset = {
                  x: event.pageX - globalDraggingElement.position().left
                , y: event.pageY - globalDraggingElement.position().top
            };
            globalDraggingElement.addClass('drag');
        }
     
        // Передвигаем перетаскиваемый элемент
        function moveElement (event) {
            globalDraggingElement.css({
                  left: event.pageX - globalMouseCursorOffset.x
                , top: event.pageY - globalMouseCursorOffset.y
            });
        }
     
        // Сбрасываем перетаскиваемый элемент в нужное место
        function dropElement (event) {
            // Вставка перетаскиваемого элемента
            var dropTargetElements = $('div.two')
                , dropTargetElementsLength = dropTargetElements.length
                , dropTargetElement
                , i;
            for (i = 0; i < dropTargetElementsLength; i++) {
                dropTargetElement = dropTargetElements.eq(i);
                if (
                           (event.pageX > dropTargetElement.offset().left)        
                     && (event.pageX < dropTargetElement.offset().left + dropTargetElement.width())
                     && (event.pageY > dropTargetElement.offset().top)
                     && (event.pageY < dropTargetElement.offset().top + dropTargetElement.height())
                ) {
                    dropTargetElement.append(globalDraggingElement);
                    // Запуск функции после сброса перетаскиваемого элемента
                    dropResult();
                }
            }    
            // Строку с перетаскиваемым элементом мы уже вставили, поэтому просто убираем класс "drag"
            globalDraggingElement.removeClass('drag');
            globalDraggingElement = null;
        }
     
        function dropResult () {
            console.log('Element dropped.');
        }
 
    }
);

JavaScript Drag and Drop

Файл index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <link href="style/default/index.css" rel="stylesheet" type="text/css" />
    <script data-main="js/index.js" src="js/lib/require/require.js" type="text/javascript"></script>
    <title>Drag</title>
</head>
<body>
<div id="grid">
    <div id="one"></div>
    <div class="two"></div>
    <div class="two"></div>
</div>
</body>
</html>

Файл index.css

/*****************************************************************\
  Drag
\*****************************************************************/

div#grid {
    position: relative;
    margin-top: 100px;
    margin-left: 100px;
    border: 1px solid black;
}

div#one {
    width: 100px;
    height: 100px;
    background-color: green;
}

div.two {
    width: 300px;
    height: 300px;
    margin-top: 200px;
    margin-left: 200px;
    background-color: blue;
}

.drag {
    z-index: 100;
    position: absolute;
    background-color: #50876b;
    color: #ffffff;
    cursor: move !important;
}

Файл index.js

require.config({
    paths: {
          jquery: 'lib/jquery/jquery'
    }
});

require(
    [
          'jquery'
    ]
    , function(
          $
    ) {
   
        var globalDraggingElement = null;

        $('div#one').mousedown(function(event){
            globalDraggingElement = $(this);
            startDragElement(event);
            event.preventDefault();
        });
       
        $(document).mousemove(function(event){
            if (globalDraggingElement) {
                moveElement(event);
            }
            event.preventDefault();
        });
       
        $(document).mouseup(function(){
            if (globalDraggingElement) {
                dropElement();
            }
        });
       
        var globalDraggingElementOriginalPosition = null
            , globalStartDragMouseCursorPosition = null;
       
        // Начинаем перетаскивать элемент
        function startDragElement (event) {
            if (event.target.nodeName === 'INPUT' || event.target.nodeName === 'SELECT' || event.target.nodeName === 'TEXTAREA') {
                globalDraggingElement = null;
                return;
            }
            globalDraggingElementOriginalPosition = globalDraggingElement.position();
            globalStartDragMouseCursorPosition = {
                  left: event.pageX
                , top: event.pageY
            };
            console.log('Original Position X: ' + globalDraggingElementOriginalPosition.left
                       + ' | Original Position Y: ' + globalDraggingElementOriginalPosition.top);
            console.log('Cursor Start Position X: ' + globalStartDragMouseCursorPosition.left
                       + ' | Cursor Start Position Y: ' + globalStartDragMouseCursorPosition.top);
            globalDraggingElement.addClass('drag');
        }
       
        var globalCurrentPositionOfDraggingElement = null
            , globalResultPositionOfDraggingElement = null
            , globalDropTargetElement = null;
       
        // Передвигаем перетаскиваемый элемент
        function moveElement (event) {
            globalCurrentPositionOfDraggingElement = {
                  left: event.pageX - globalStartDragMouseCursorPosition.left
                , top: event.pageY - globalStartDragMouseCursorPosition.top
            };
            console.log('CurrentPositionOfDraggingElement X: ' + globalCurrentPositionOfDraggingElement.left
                       + ' | CurrentPositionOfDraggingElement Y: ' + globalCurrentPositionOfDraggingElement.top);
            globalResultPositionOfDraggingElement = {
                  left: globalDraggingElementOriginalPosition.left + globalCurrentPositionOfDraggingElement.left
                , top: globalDraggingElementOriginalPosition.top + globalCurrentPositionOfDraggingElement.top
            };
            console.log('ResultPositionOfDraggingElement X: ' + globalResultPositionOfDraggingElement.left
                       + ' | ResultPositionOfDraggingElement Y: ' + globalResultPositionOfDraggingElement.top);
            globalDraggingElement.css({
                  left: globalResultPositionOfDraggingElement.left
                , top: globalResultPositionOfDraggingElement.top
            });

            globalDropTargetElement = findDropTargetElement (globalResultPositionOfDraggingElement.left, globalResultPositionOfDraggingElement.top);
        }
       
        // Определяем над каким элементом находится перетаскиваемый элемент
        function findDropTargetElement (x, y) {
            var dropTargetElements = $('div.two')
                , dropTargetElementsLength = dropTargetElements.length
                , dropTargetElement
                , dropTargetElementX
                , dropTargetElementY
                , dropTargetElementWidth
                , dropTargetElementHeight
                , i;
            for (i = 0; i < dropTargetElementsLength; i++) {
                dropTargetElement = dropTargetElements.eq(i);
                dropTargetElementX = dropTargetElement.offset().left;
                dropTargetElementY = dropTargetElement.offset().top;
                dropTargetElementWidth = dropTargetElement.width() / 2;
                dropTargetElementHeight = dropTargetElement.height() / 2;
                if ( (x > (dropTargetElementX - dropTargetElementWidth)) && (y < (dropTargetElementX + dropTargetElementWidth))
                  || (y > (dropTargetElementY - dropTargetElementHeight)) && (y < (dropTargetElementY + dropTargetElementHeight))
                ) {
                    return dropTargetElement;
                }
            }
            return null;
        }

        // Сбрасываем перетаскиваемый элемент в нужное место
        function dropElement () {
            // Вставка перетаскиваемого элемента
            if (globalDropTargetElement) {
                globalDropTargetElement.append(globalDraggingElement);
            }
            // Строку с перетаскиваемым элементом мы уже вставили, поэтому просто убираем класс "drag"
            globalDraggingElement.removeClass('drag');
            globalDraggingElement = null;
            // Запуск функции после сброса перетаскиваемого элемента
            dropResult();
        }
       
        function dropResult () {
            console.log('Element dropped.');
        }

    }
);

четверг, 7 ноября 2013 г.

Micro Event Library

/**
 * MicroEvent.js debug
 *
 * # it is the same as MicroEvent.js but it adds checks to help you debug
*/

var MicroEvent = function(){}
MicroEvent.prototype = {
bind : function(event, fct){
this._events = this._events || {};
this._events[event] = this._events[event] || [];
this._events[event].push(fct);
},
unbind : function(event, fct){
console.assert(typeof fct === 'function');
this._events = this._events || {};
if( event in this._events === false  ) return;
console.assert(this._events[event].indexOf(fct) !== -1);
this._events[event].splice(this._events[event].indexOf(fct), 1);
},
trigger : function(event /* , args... */){
this._events = this._events || {};
if( event in this._events === false  ) return;
for(var i = 0; i < this._events[event].length; i++){
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1))
}
}
};

/**
 * mixin will delegate all MicroEvent.js function in the destination object
 *
 * - require('MicroEvent').mixin(Foobar) will make Foobar able to use MicroEvent
 *
 * @param {Object} the object which will support MicroEvent
*/

MicroEvent.mixin = function(destObject){
var props = ['bind', 'unbind', 'trigger'];
for(var i = 0; i < props.length; i ++){
if( typeof destObject === 'function' ){
destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
}else{
destObject[props[i]] = MicroEvent.prototype[props[i]];
}
}
}

// export in common js

if( typeof module !== "undefined" && ('exports' in module)){
module.exports = MicroEvent
}


Example

var Ticker = function() {
  var self = this;
  setInterval(function() {
    // 'trigger' is provided by MicroEvent
    self.trigger('tick', new Date());
  }, 1000);
};

// You have to use this method to make your class MicroEvent-enabled:

MicroEvent.mixin(Ticker);

var ticker = new Ticker();

// Now we can observe when the event fires

ticker.bind('tick', function(date) {
  console.log('notified date', date);
});

First we define the class which gonna use MicroEvent.js. This is a ticker, it is triggering 'tick' event every second, and add the current date as parameter

var Ticker = function(){
    var self = this;
    setInterval(function(){
        self.trigger('tick', new Date());
    }, 1000);
};

We mixin MicroEvent into Ticker and we are all set.

MicroEvent.mixin(Ticker);

Now lets actually use the Ticker Class. First, create the object.

var ticker = new Ticker();

and bind our tick event with its data parameter

ticker.bind('tick', function(date) {
    console.log('notified date', date);
});

And you will see this output:

notified date Tue, 22 Mar 2011 14:43:41 GMT
notified date Tue, 22 Mar 2011 14:43:42 GMT

пятница, 1 ноября 2013 г.

Grunt Рабочий пример

Файл package.json для установки модулей в Node JS:

{
      "name": "my-project"
    , "author": "Ivan Borisov"
    , "version": "1.0.0"
    , "description": "My first project."
    , "keywords": ["my", "project"]
    , "dependencies": {
          "connect": "2.9.0"
        , "express": "3.4.0"
        , "grunt": "~0.4.1"
        , "grunt-contrib-watch": "~0.5.3"
        , "grunt-contrib-clean": "~0.5.0"
        , "grunt-contrib-concat": "~0.3.0"
        , "grunt-contrib-requirejs": "~0.4.1"
        , "grunt-contrib-less": "~0.8.1"
        , "grunt-contrib-jshint": "~0.7.0"
        , "grunt-contrib-uglify": "~0.2.5"
        , "grunt-contrib-yuidoc": "~0.5.0"
      }
    , "scripts": {
          "start": "node server.js"
      }
    , "repository": {
          "type": "hg"
        , "url": "http://127.0.0.1:8080/repository/hg/my-project"
      }
    , "engines": {
          "node": ">=0.10.20"
      }
}

Файл Gruntfile.js для запуска задач Grunt JS:

module.exports = function (grunt) {

    grunt.initConfig({

        // Удалить ранее созданные файлы и папки
          clean: {
              files: [
                'build/test.min.js'
              ]
          }
       
        // Объединить содержимое файлов в один итоговый общий файл
        , concat: {
              scripts: {
                  src: [
                          'test-file1.js'
                        , 'test-file2.js'
                  ]
                , dest: 'test.js'
              }
            , styles: {
                  src: [
                          'css-file1.less'
                        , 'css-file2.less'
                  ]
                , dest: 'css-file.less'
              }
          }
       
        // Скомпилировать итоговый LESS-файл в итоговый CSS-файл      
        , less: {
              development: {
                files: {
                      'main-dev.css': 'css-file.less'
                }
              }
            , production: {
                  options: {
                      yuicompress: true
                  }
                , files: {
                      'main-pro.css': 'css-file.less'
                  }
              }
          }

        // Проверить код в JavaScript-файлах
        , jshint: {
              all: [
                  'test.js'
              ]
            , options: {
                  devel: false
                , camelcase: true
                , curly: true
                , eqeqeq: false
                , immed: true
                , indent: 4
                , latedef: true
                , newcap: true
                , noarg: true
                , quotmark: 'single'
                , undef: true
                , unused: true
                , strict: false
                , trailing: true
                , eqnull: true
                , laxcomma: true
                , browser: true
                , jquery: true
                , node: true
                , nonstandard: true
                , worker: true
                , white: false
                , maxlen: 130
              }
            , globals: {
                  require: true
                , define: true
                , underscore: true
                , '_': true
              }
          }
       
        // Создать единый файл из модулей Require JS
        , requirejs: {
            compile: {
                options: {
                      baseUrl: './'
                    , name: 'main'
                    , out: 'main.js'
                    , paths: {
                        jquery: 'empty:'
                      }
                }
            }
          }
       
        // Сжать код в JavaScript-файлах
        , uglify: {
              options: {
                  banner: '/*! Файл создан: <%= grunt.template.today("yyyy-mm-dd") %> */\n'
              }
            , build: {
                  src: 'test.js'
                , dest: 'build/test.min.js'
              }
          }
       
        // Создать папку с документацией кода для JavaScript-файлов
        , yuidoc: {
              compile: {
                options: {
                    paths: [
                          './'
                    ]
                  , outdir: 'doc'
                }
              }
          }
       
        // Начать остлеживание изменений кода файлов
        , watch: {
              options: {
                livereload: true // Разрешить живую перезагрузку страницы
                // Для живой перезагрузки в HTML-файл надо добавить строку
                // <script src="http://localhost:35729/livereload.js" type="text/javascript"></script>
              }
            , scripts: {
                  files: ['test-file1.js']
                , tasks: ['clean', 'concat', 'jshint']
              }
            , styles: {
                  files: ['css-file1.less']
                , tasks: ['less']
              }
          }

    });

    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-requirejs');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-yuidoc');
    grunt.loadNpmTasks('grunt-contrib-watch');

    grunt.registerTask('default', [
          'clean'
        , 'concat'
        , 'less'
        , 'jshint'
        , 'requirejs'
        , 'uglify'
        , 'yuidoc'
        , 'watch'
    ]);

};

Файл rungrunt.js для запуска Grunt JS из консоли:

require('grunt').cli();