вторник, 31 декабря 2013 г.

JavaScript Router в 70 строк

Файл main.js

window.document.addEventListener("route.changed", function (e) {
  console.log(e.data.route, "------", e.data.params);
}, false);

Router.map("/foo", "route.changed");
Router.map("/foo/(bar-map)", "route.changed");
Router.map("/foo/(bar)/name", function (e){
  console.log("Direct callback", e);
});

Router.start();

Файл router.js

(function($, win) {
  var Routes = [];
  var doc = win.document;
 
  function fireCustomEvent(name, data) {
    var evt = doc.createEvent("HTMLEvents");
    evt.initEvent(name, true, true ); // event type,bubbling,cancelable
    evt.data = data;
    return !doc.dispatchEvent(evt);
  }

  function hashChange() {
    var url = (win.location.hash || "#").slice(1);
    $.Router.match(url);
  }

  $.Router = {
    map: function (r, e) {
      // Escape anything except () and replace (name) with (.+)
      var es_tmp = r.replace(/[-[\]{}*+?.,\\^$|#\s]/g, "\\$&").replace(/\([^\)]+\)/g, "([^/]+)");
      var r_exp = new RegExp("^"+es_tmp+"$", "g");
     
      // Save names and routes under with expression and variable names
      Routes.push({
        exp: r_exp,
        names: r_exp.exec(r).slice(1),
        event: e
      });
    },
   
    match: function (r) {
      for(var i in Routes) {
        var rout = Routes[i];
       
        if (r.match(rout.exp)){
          var params = {};
          info = rout.exp.exec(r).slice(1);
          for(var k in info) {
            params[rout.names[k].replace(/[()]/g, "")] = info[k];
          }
                                       
          if(typeof rout.event === 'function') {
            rout.event({data: {params: params, route: r}});
          }else if( typeof rout.event === 'string') {
            fireCustomEvent(rout.event, {params: params, route: r});
          }
        }
      }
    },

    start: function() {
      if (window.addEventListener) {
        window.addEventListener("hashchange", hashChange, false);
      }
      else if (window.attachEvent) {
        window.attachEvent("onhashchange", hashChange);  
      }
      hashChange(); // Call first time for loading first URL
    },

    stop: function() {
      if (window.removeEventListener) {
          window.removeEventListener("hashchange", hashChange, false);
        }
        else if (window.attachEvent) {
          window.detachEvent("onhashchange", hashChange);  
        }
      }
  };
})(window, window);

JavaSript Simple Router

var Router = (function () {
    var Router = function () {};

    Router.prototype.add = function (path, callback) {
        this.routes = this.routes || [];
        this.routes.push({
              path: new RegExp(path.replace(/\//g, "\\/").replace(/:(\w*)/g,"(\\w*)"))
            , callback: callback
        });
    };

    Router.prototype.process =  function () {
        var route
           , params;
        for (route in this.routes) {
            params = window.location.pathname.match(route.path);
            if (params) {
                route.callback(params);
                return;
            }
        }
    };

    return Router;

})();

Router = new Router();

Router.add('/blog/:id', blogCallback);

function blogCallback () {
    alert('OK');
}

$(document).ready(function(){
    Router.process();
});

понедельник, 30 декабря 2013 г.

JavaScript Улучшенные Web Worker


Web-worker - это JavaScript-технология, позволяющая запускать изолированные участки кода в отдельном потоке. Код из web worker не тормозит работу графического интерфейса и выполняется быстрее, чем код на странице, что делает использование web worker очень привлекательным для ресурсоёмких расчётов вроде рисования графики или криптографии.

Обычно, если в web worker всего одна функция, то мы просто пишем:

- в коде страницы (снаружи):

var worker = new Worker("myscript.js");
worker.onmessage (event.data){workerСallback(event.data);}
function workerСallback(data){ /*do something with data object;*/}
worker.postMessage({some:"some"});

- в коде web worker (внутри):

onmessage = function (event) {postMessage(mySingleFunction(event.data));}

Однако, если в web worker добавить ещё одну функцию, вызываемую извне, то количество кода, обеспечивающего вызов этих функций возрастёт и он будет выглядеть уже не так изящно:

- в коде страницы (снаружи):

function firstFunctionСallback(data){   /*do something with data object;*/}

function secondFunctionСallback(data){ }

worker.onmessage (msg){
  if(msg.data.callback == "firstFunctionСallback"){
      firstFunctionСallback(msg.data.result);
  }
  if(msg.data.callback == "secondFunctionСallback"){
      firstFunctionСallback(msg.data.result);
  }
}

worker.postMessage({functionName: "firstFunction", data: data);


- в коде web worker (внутри):

onmessage = function (event) {
  var functionName = event.data.functionName;
  if(functionName == "firstFinction"){
      postMessage({callback: "firstFunctionСallback", result: firstFinction(event.data.data)});
  }
  if(functionName == "secondFunction"){
      postMessage({callback: "secondFunctionСallback", result: secondFunction(event.data.data)});
  }
  ...
}


При таком подходе нет возможности использовать в качестве коллбэков анонимные функции и при каждом добавлении новой функции в web worker нужно каждый раз снаружи и внутри web worker писать некоторое количество дополнительного кода, обеспечивающего вызов этой функции.

Чтобы избежать этого web worker можно обернуть в объект, который будет выполнять эту работу.

Назовём такой объект Performer и разместим его в коде страницы (снаружи):

function Performer(scriptSource) {
    var worker = new Worker(scriptSource), callbacks = {}, nextRequestId = 0;
    this.perform = function(functionName, params, callback) {
        callbacks["request_" + (++nextRequestId)] = callback;
        worker.postMessage(
         {functionName: functionName, params: params, requestId: nextRequestId}
        );
    }
    worker.onmessage = function(msg) {
        callbacks["request_" + msg.data.requestId](msg.data.result);
        delete callbacks["request_" + msg.data.requestId];
    }
}


Во внутреннем коде web worker изменим обработчик внешних сообщений:

onmessage = function (event) {    
  var requestId = event.data.requestId;
  var workerFunction = eval(event.data.functionName);
  var params = event.data.params;
  var result =  workerFunction(params);
  postMessage({result: result, requestId: requestId});
}


Теперь можно добавлять в web worker любые функции и вызывать их извне без написания вспомогательного кода, а также использовать анонимные функции в коллбэках:

var performer = new Performer("myscript.js");
performer.perform("firstFunction", {some: "some"}, function(result){console.log("result1="+result);});
performer.perform("secondFunction", {some: "some"}, function(result){console.log("result2="+result);});


Ели web worker размещён не в отдельном файле скрипта, а встроен в страницу,
то в коде перформера должны быть учтены кроссбраузерные различия.

С их учётом часть перформера, которая отвечает за инициализацию web worker, будет выглядеть так:

function Performer(scriptText) {
    var worker = null;  
    try {// Firefox
        var Url = window.webkitURL || window.URL;
        worker = new Worker(Url.createObjectURL(new Blob([ scriptText ])));
    } catch (browserNotSupportWindowUrl) {
        try {// Chrome
            worker = new Worker('data:application/javascript,' + encodeURIComponent(scriptText));
        } catch (browserNotSupportWorkers) {// Opera
            eval(scriptText);
            worker = {
                postMessage : function(data) {
                    var workerFunction = eval(data.functionName);
                    worker.onmessage({
                        data : {
                            result : workerFunction(data.params),
                            requestId : data.requestId
                        }
                    });
                }
            };
        }
    }
...
}


а создание так:

var performer = new Performer($('#myscript').text());

Таким образом, даже в браузерах, не поддерживающих web worker, код web worker всё равно будет выполняться, просто медленнее.

JavaScript Build a vertical scrolling shooter game with HTML5 canvas

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>HTML5 Canvas Game Part 6 || Atomic Robot Design</title>
<link href='http://fonts.googleapis.com/css?family=VT323' rel='stylesheet' />
<style>
body {
  padding:0;
  margin:0;
  background:#666;
}
canvas {
  display:block;
  margin:30px auto 0;
  border:1px dashed #ccc;
  background:#000;
}
</style>
<script>
var canvas,
    ctx,
    width = 600,
    height = 600,
    enemyTotal = 5,
    enemies = [],
    enemy_x = 50,
    enemy_y = -45,
    enemy_w = 50,
    enemy_h = 38,
    speed = 3,
    enemy,
    rightKey = false,
    leftKey = false,
    upKey = false,
    downKey = false,
    ship,
    ship_x = (width / 2) - 25, ship_y = height - 75, ship_w = 50, ship_h = 57,
    laserTotal = 2,
    lasers = [],
    score = 0,
    alive = true,
    lives = 3,
    starfield,
    starX = 0, starY = 0, starY2 = -600,
    gameStarted = false;

//Array to hold all the enemies on screen
for (var i = 0; i < enemyTotal; i++) {
 enemies.push([enemy_x, enemy_y, enemy_w, enemy_h, speed]);
 enemy_x += enemy_w + 60;
}

//Clears the canvas so it can be updated
function clearCanvas() {
 ctx.clearRect(0,0,width,height);
}

//Cycles through the array and draws the updated enemy position
function drawEnemies() {
 for (var i = 0; i < enemies.length; i++) {
   ctx.drawImage(enemy, enemies[i][0], enemies[i][1]);
 }
}

//If an arrow key is being pressed, moves the ship in the right direction
function drawShip() {
 if (rightKey) ship_x += 5;
 else if (leftKey) ship_x -= 5;
 if (upKey) ship_y -= 5;
 else if (downKey) ship_y += 5;
 if (ship_x <= 0) ship_x = 0;
 if ((ship_x + ship_w) >= width) ship_x = width - ship_w;
  if (ship_y <= 0) ship_y = 0;
 if ((ship_y + ship_h) >= height) ship_y = height - ship_h;
  ctx.drawImage(ship, ship_x, ship_y);
}

//This moves the enemies downwards on the canvas and if one passes the bottom of the canvas, it moves it to above the canvas
function moveEnemies() {
  for (var i = 0; i < enemies.length; i++) {
   if (enemies[i][1] < height) {
     enemies[i][1] += enemies[i][4];
   } else if (enemies[i][1] > height - 1) {
      enemies[i][1] = -45;
    }
  }
}

//If there are lasers in the lasers array, then this will draw them on the canvas
function drawLaser() {
  if (lasers.length)
    for (var i = 0; i < lasers.length; i++) {
     ctx.fillStyle = '#f00';
     ctx.fillRect(lasers[i][0],lasers[i][1],lasers[i][2],lasers[i][3])
   }
}

//If we're drawing lasers on the canvas, this moves them up the canvas
function moveLaser() {
 for (var i = 0; i < lasers.length; i++) {
   if (lasers[i][1] > -11) {
      lasers[i][1] -= 10;
    } else if (lasers[i][1] < -10) {
     lasers.splice(i, 1);
   }
 }
}

//Runs a couple of loops to see if any of the lasers have hit any of the enemies
function hitTest() {
 var remove = false;
 for (var i = 0; i < lasers.length; i++) {
   for (var j = 0; j < enemies.length; j++) {
     if (lasers[i][1] <= (enemies[j][1] + enemies[j][3]) && lasers[i][0] >= enemies[j][0] && lasers[i][0] <= (enemies[j][0] + enemies[j][2])) {
       remove = true;
        enemies.splice(j, 1);
        score += 10;
        enemies.push([(Math.random() * 500) + 50, -45, enemy_w, enemy_h, speed]);
      }
    }
    if (remove == true) {
      lasers.splice(i, 1);
      remove = false;
    }
  }
}

//Similar to the laser hit test, this function checks to see if the player's ship collides with any of the enemies
function shipCollision() {
  var ship_xw = ship_x + ship_w,
      ship_yh = ship_y + ship_h;
  for (var i = 0; i < enemies.length; i++) {
   if (ship_x > enemies[i][0] && ship_x < enemies[i][0] + enemy_w && ship_y > enemies[i][1] && ship_y < enemies[i][1] + enemy_h) {
     checkLives();
    }
    if (ship_xw < enemies[i][0] + enemy_w && ship_xw > enemies[i][0] && ship_y > enemies[i][1] && ship_y < enemies[i][1] + enemy_h) {
     checkLives();
    }
    if (ship_yh > enemies[i][1] && ship_yh < enemies[i][1] + enemy_h && ship_x > enemies[i][0] && ship_x < enemies[i][0] + enemy_w) {
     checkLives();
    }
    if (ship_yh > enemies[i][1] && ship_yh < enemies[i][1] + enemy_h && ship_xw < enemies[i][0] + enemy_w && ship_xw > enemies[i][0]) {
     checkLives();
    }
  }
}

//This function runs whenever the player's ship hits an enemy and either subtracts a life or sets the alive variable to false if the player runs out of lives
function checkLives() {
  lives -= 1;
  if (lives > 0) {
    reset();
  } else if (lives == 0) {
    alive = false;
  }
}

//This simply resets the ship and enemies to their starting positions
function reset() {
  var enemy_reset_x = 50;
  ship_x = (width / 2) - 25, ship_y = height - 75, ship_w = 50, ship_h = 57;
  for (var i = 0; i < enemies.length; i++) {
   enemies[i][0] = enemy_reset_x;
   enemies[i][1] = -45;
   enemy_reset_x = enemy_reset_x + enemy_w + 60;
 }
}

//After the player loses all their lives, the continue button is shown and if clicked, it resets the game and removes the event listener for the continue button
function continueButton(e) {
 var cursorPos = getCursorPos(e);
 if (cursorPos.x > (width / 2) - 53 && cursorPos.x < (width / 2) + 47 && cursorPos.y > (height / 2) + 10 && cursorPos.y < (height / 2) + 50) {
   alive = true;
    lives = 3;
    reset();
    canvas.removeEventListener('click', continueButton, false);
  }
}

//holds the cursors position
function cursorPosition(x,y) {
  this.x = x;
  this.y = y;
}

//finds the cursor's position after the mouse is clicked
function getCursorPos(e) {
  var x;
  var y;
  if (e.pageX || e.pageY) {
    x = e.pageX;
    y = e.pageY;
  } else {
    x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
    y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
  }
  x -= canvas.offsetLeft;
  y -= canvas.offsetTop;
  var cursorPos = new cursorPosition(x, y);
  return cursorPos;
}

//Draws the text for the score and lives on the canvas and if the player runs out of lives, it's draws the game over text and continue button as well as adding the event listener for the continue button
function scoreTotal() {
  ctx.font = 'bold 20px VT323';
  ctx.fillStyle = '#fff';
  ctx.fillText('Score: ', 10, 55);
  ctx.fillText(score, 70, 55);
  ctx.fillText('Lives:', 10, 30);
  ctx.fillText(lives, 68, 30);
        if (!gameStarted) {
    ctx.font = 'bold 50px VT323';
    ctx.fillText('Canvas Shooter', width / 2 - 150, height / 2);
    ctx.font = 'bold 20px VT323';
    ctx.fillText('Click to Play', width / 2 - 56, height / 2 + 30);
    ctx.fillText('Use arrow keys to move', width / 2 - 100, height / 2 + 60);
    ctx.fillText('Use the x key to shoot', width / 2 - 100, height / 2 + 90);
  }
  if (!alive) {
    ctx.fillText('Game Over!', 245, height / 2);
    ctx.fillRect((width / 2) - 60, (height / 2) + 10,100,40);
    ctx.fillStyle = '#000';
    ctx.fillText('Continue?', 250, (height / 2) + 35);
    canvas.addEventListener('click', continueButton, false);
  }
}

//Draws and animates the background starfield
function drawStarfield() {
  ctx.drawImage(starfield,starX,starY);
  ctx.drawImage(starfield,starX,starY2);
  if (starY > 600) {
    starY = -599;
  }
  if (starY2 > 600) {
    starY2 = -599;
  }
  starY += 1;
  starY2 += 1;
}

//The initial function called when the page first loads. Loads the ship, enemy and starfield images and adds the event listeners for the arrow keys. It then calls the gameLoop function.
function init() {
  canvas = document.getElementById('canvas');
  ctx = canvas.getContext('2d');
  enemy = new Image();
  enemy.src = '8bit_enemy.png';
  ship = new Image();
  ship.src = 'ship.png';
  starfield = new Image();
  starfield.src = 'starfield.jpg';
  document.addEventListener('keydown', keyDown, false);
  document.addEventListener('keyup', keyUp, false);
        canvas.addEventListener('click', gameStart, false);
  gameLoop();
}

function gameStart() {
  gameStarted = true;
  canvas.removeEventListener('click', gameStart, false);
}

//The main function of the game, it calls all the other functions needed to make the game run
function gameLoop() {
  clearCanvas();
  drawStarfield()
  if (alive && gameStarted && lives > 0) {
   hitTest();
    shipCollision();
    moveLaser();
    moveEnemies();
    drawEnemies();
    drawShip();
    drawLaser();
  }
  scoreTotal();
  game = setTimeout(gameLoop, 1000 / 30);
}

//Checks to see which key has been pressed and either to move the ship or fire a laser
function keyDown(e) {
  if (e.keyCode == 39) rightKey = true;
  else if (e.keyCode == 37) leftKey = true;
  if (e.keyCode == 38) upKey = true;
  else if (e.keyCode == 40) downKey = true;
  if (e.keyCode == 88 && lasers.length <= laserTotal) lasers.push([ship_x + 25, ship_y - 20, 4, 20]);
}

//Checks to see if a pressed key has been released and stops the ships movement if it has
function keyUp(e) {
  if (e.keyCode == 39) rightKey = false;
  else if (e.keyCode == 37) leftKey = false;
  if (e.keyCode == 38) upKey = false;
  else if (e.keyCode == 40) downKey = false;
}

window.onload = init;
</script>
</head>

<body>
  <canvas id="canvas" width="600" height="600"></canvas>
</body>
</html>

JavaScript Определение типа элемента

function getClass (element) {
    return Object.prototype.toString.call(element).slice(8, -1);
}

var a = '';

console.log(getClass(a)); // String

JavaScript Примитивные типы

В JavaScript существует несколько примитивных типов:

null - Специальный тип, содержит только значение null.
undefined - Специальный тип, содержит только значение undefined.
number - Числа: 0, 3.14, а также значения NaN и Infinity
boolean - true, false.
string -  Строки, такие как "Мяу" или пустая строка "".

Все остальные значения являются объектами, включая функции и массивы.

JavaScript Создание функции из строки new Function()

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

var sum = new Function('a,b', ' return a+b; ');

var result = sum(1,2);

alert(result); // 3

То есть, функция создаётся вызовом new Function(params, code):

params - Параметры функции через запятую в виде строки.
code - Код функции в виде строки.


В JavaScript функции можно объявлять условно

var age = prompt('Сколько вам лет?');
var sayHi;

if (age >= 18) {
  sayHi = function() {  alert('Вход разрешен');  }
} else {
  sayHi = function() {  alert('Извините, вы слишком молоды');  }
}

sayHi(); // запустит ту функцию, которая присвоена в if

Устаревший getYear()

Некоторые браузеры реализуют нестандартный метод getYear(). Где-то он возвращает только 2 цифры из года, где-то 4. Так или иначе, этот метод отсутствует в стандарте JavaScript. Не используйте его. Для получения года есть getFullYear().

Пример.

var date = new Date();
var year = date.getFullYear();

console.log(year);

среда, 25 декабря 2013 г.

JavaScript Преобразование данных

В JavaScript есть три преобразования:

Строковое: String(value) - в строковом контексте или при сложении со строкой: '' + 1 или 1 + ''.
Численное: Number(value) - в численном контексте, включая унарный плюс +value.
Логическое: Boolean(value) - в логическом контексте, можно также сделать двойным НЕ: !!value.

JavaScript Build tree from array

Построение дерева посредством JavaScript из плоского массива данных.

”http://jsfiddle.net/elado/yxmKZ/embedded/js,css,result”



Файл index.js

var treeData=[
    {"id":1,"parentId":null,"title":"1"},
    {"id":2,"parentId":1,"title":"1.1"},
    {"id":3,"parentId":null,"title":"2"},
    {"id":4,"parentId":1,"title":"1.2"},
    {"id":5,"parentId":1,"title":"1.3"},
    {"id":6,"parentId":4,"title":"1.2.1"},
    {"id":7,"parentId":4,"title":"1.2.2"},
    {"id":8,"parentId":4,"title":"1.2.3"},
    {"id":9,"parentId":null,"title":"3"},
    {"id":10,"parentId":9,"title":"3.1"},
    {"id":11,"parentId":10,"title":"3.1.1"},
    {"id":12,"parentId":11,"title":"3.1.1.1"},
    {"id":13,"parentId":11,"title":"3.1.1.2"},
    {"id":14,"parentId":11,"title":"3.1.1.3"},
    {"id":15,"parentId":13,"title":"3.1.1.2.1"}
];

function NodeItem(data) {
    this.data=data;

    var container=document.createElement("li"),
        title=container.appendChild(document.createElement("div"));

    title.innerHTML=this.data.title;

    this.element=container;
}

NodeItem.prototype= {
    // lazy children element creation - not all nodes have children
    getChildrenElement:function () {
        return this._childElement = this._childElement || this.element.appendChild(document.createElement("ul"));
    }
};

// convert all nodes to NodeItem instance
var nodeCollection=treeData.map(function (node) { return new NodeItem(node); });

// for faster lookup, store all nodes by their id
var nodesById={};
for (var i=0;i<nodeCollection.length;i++) nodesById[nodeCollection[i].data.id]=nodeCollection[i];

var rootNodeItemsContainer=document.createElement("ul");

// the magic happens here:
// every node finds its parent (by the id), and it's being adopted by the parent's children element
// that, actually, builds the tree, before it's in the document
// all root nodes are appended to a root container which is appended to an element on the document
for (var i=0;i<nodeCollection.length;i++) {
    var node=nodeCollection[i];
    var parentElement=node.data.parentId ? nodesById[node.data.parentId].getChildrenElement() : rootNodeItemsContainer;
    parentElement.appendChild(node.element);
}

document.body.appendChild(rootNodeItemsContainer);

Файл index.css

* { font-family:lucida grande, arial; font-size:11px; }

ul {
    padding:3px;
    border:1px solid #aaa;
}
    ul li {
        padding:5px;
        background-color:#eee;
    }
        ul li > div { padding:3px; margin-bottom:5px; background-color:#ccc; }
        ul li > div + ul { margin-top:5px; }
        ul li ul { margin-left:15px; }

четверг, 19 декабря 2013 г.

NPM JavaScript - Частые команды по работе с Node Package Manager

Установка пакетов в Node Package Manager.

Устанавливает пакет express.

npm install express

Устанавливает все пакеты, перечисленные в package.json.

npm install

Устанавливает express и вносит запись о нем в package.json в секцию dependencies.

npm install express --save

Устанавливает grunt и вносит запись о нем в package.json в секцию devDependencies.

npm install grunt --save-dev

Варианты с --save и --save-dev сделают запись в package.json только, если он уже существует.

Чтобы каждый раз не указывать --save, можно прописать:

npm config set save true

Теперь все устанавливаемый пакеты будут автоматом прописываться в package.json.

Обычно получается быстрее отредактировать package.json, удалить ненужные пакеты, добавить новые, подкорректировать версии и сделать

npm prune
npm install

чем ставить - удалять каждый пакет отдельно.

Сокращенные варианты команд.

Для ускорения процесса ввода команд удобно использовать сокращения:

Ключ Сокращение
install i
uninstall r
config c
update up
list ls
--save -S
--save-dev -D

Пример.

npm install express --save

и то же самое

npm i express -S

Подготовка к npm init

Не очень удобно при создании package.json при помощи npm init каждый раз вводить персональные данные. Чтобы этого избежать, сделаем настройку:

Внесем информацию об авторе "по умолчанию".

npm set init.author.name "$NAME"
npm set init.author.email "$EMAIL"
npm set init.author.url "$SITE"

Вместо переменных среды $NAME и тому подобных можно внести сами данные.

Данная команда выведет список всех возможных настроек.

npm config ls -l

Проверить, не устарели ли пакеты.

npm outdated

Фиксируем версии пакетов.

npm shrinkwrap

Прежде чем передавать продукт в промышленную эксплуатацию, по хорошему, нужно указать в package.json точные версии пакетов с которыми все 100% работает. Эта команда так и сделает. После ее выполнения в вашем package.json все версии пакетов будут жестко зафиксированы.

Обновление версии NPM.

npm update npm -g