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

HTML parser на JavaScript

Напишем HTML парсер на JavaScript.

Наша парсер будет проверять:

- отсутствие закрывающих тегов:
<p><b>Hello → <p><b>Hello</b></p>

- отсутствие закрывающего слэша в «самозакрывающихся» элементах:
<img src=test.jpg> → <img src="test.jpg"/>

- незакрытый строчный элемент перед блочным:
<b>Hello <p>John → <b>Hello </b><p>John</p>

- отсутствие закрывающих тегов у элементов, для которых это допустимо в HTML4:
<p>Hello<p>World → <p>Hello</p><p>World</p>

- атрибуты без значений (флаги):
<input disabled> → <input disabled="disabled"/>

- ошибочный порядок закрывающих тегов вложенных элементов:
<b><i>example</b></i> → <b><i>example</i></b>

HTMLпарсер буде работать следующим образом:

SAX-style API

Управление тэгами, текстом и комментариями с помощью функций обратного вызова:

var results = "";

HTMLParser("<p id=test>hello <i>world", {
  start: function( tag, attrs, unary ) {
    results += "<" + tag;

    for ( var i = 0; i < attrs.length; i++ )
      results += " " + attrs[i].name + '="' + attrs[i].escaped + '"';

    results += (unary ? "/" : "") + ">";
  },
  end: function( tag ) {
    results += "</" + tag + ">";
  },
  chars: function( text ) {
    results += text;
  },
  comment: function( text ) {
    results += "<!--" + text + "-->";
  }
});

results == '<p id="test">hello <i>world</i></p>"

XML Serializer

var results = HTMLtoXML("<p>Data: <input disabled>")
results == '<p>Data: <input disabled="disabled"/></p>'

DOM Builder

// The following is appended into the document body
HTMLtoDOM("<p>Hello <b>World", document)

// The follow is appended into the specified element
HTMLtoDOM("<p>Hello <b>World", document.getElementById("test"))

DOM Document Creator

var dom = HTMLtoDOM("<p>Data: <input disabled>");
dom.getElementsByTagName("body").length == 1
dom.getElementsByTagName("p").length == 1
While this library doesn’t cover the full gamut of possible weirdness that HTML provides, it does handle a lot of the most obvious stuff. All of the following are accounted for:

Код HTML парсера на JavaScript:

/*
 * HTML Parser By John Resig (ejohn.org)
 * Original code by Erik Arvidsson, Mozilla Public License
 * http: // erik.eae.net/simplehtmlparser/simplehtmlparser.js
 *
 * // Use like so:
 * HTMLParser(htmlString, {
 *     start: function(tag, attrs, unary) {},
 *     end: function(tag) {},
 *     chars: function(text) {},
 *     comment: function(text) {}
 * });
 *
 * // or to get an XML string:
 * HTMLtoXML(htmlString);
 *
 * // or to get an XML DOM Document
 * HTMLtoDOM(htmlString);
 *
 * // or to inject into an existing document/DOM node
 * HTMLtoDOM(htmlString, document);
 * HTMLtoDOM(htmlString, document.body);
 *
 */

(function(){

// Regular Expressions for parsing tags and attributes
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/,
attr = /([-A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;

// Empty Elements - HTML 4.01
var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");

// Block Elements - HTML 4.01
var block = makeMap("address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");

// Inline Elements - HTML 4.01
var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");

// Elements that you can, intentionally, leave open
// (and which close themselves)
var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");

// Attributes that have their values filled in disabled="disabled"
var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");

// Special Elements (can contain anything)
var special = makeMap("script,style");

var HTMLParser = this.HTMLParser = function( html, handler ) {
var index, chars, match, stack = [], last = html;
stack.last = function(){
return this[ this.length - 1 ];
};

while ( html ) {
chars = true;

// Make sure we're not in a script or style element
if ( !stack.last() || !special[ stack.last() ] ) {

// Comment
if ( html.indexOf("<!--") == 0 ) {
index = html.indexOf("-->");

if ( index >= 0 ) {
if ( handler.comment )
handler.comment( html.substring( 4, index ) );
html = html.substring( index + 3 );
chars = false;
}

// end tag
} else if ( html.indexOf("</") == 0 ) {
match = html.match( endTag );

if ( match ) {
html = html.substring( match[0].length );
match[0].replace( endTag, parseEndTag );
chars = false;
}

// start tag
} else if ( html.indexOf("<") == 0 ) {
match = html.match( startTag );

if ( match ) {
html = html.substring( match[0].length );
match[0].replace( startTag, parseStartTag );
chars = false;
}
}

if ( chars ) {
index = html.indexOf("<");

var text = index < 0 ? html : html.substring( 0, index );
html = index < 0 ? "" : html.substring( index );

if ( handler.chars )
handler.chars( text );
}

} else {
html = html.replace(new RegExp("(.*)<\/" + stack.last() + "[^>]*>"), function(all, text){
text = text.replace(/<!--(.*?)-->/g, "$1")
.replace(/<!\[CDATA\[(.*?)]]>/g, "$1");

if ( handler.chars )
handler.chars( text );

return "";
});

parseEndTag( "", stack.last() );
}

if ( html == last )
throw "Parse Error: " + html;
last = html;
}

// Clean up any remaining tags
parseEndTag();

function parseStartTag( tag, tagName, rest, unary ) {
tagName = tagName.toLowerCase();

if ( block[ tagName ] ) {
while ( stack.last() && inline[ stack.last() ] ) {
parseEndTag( "", stack.last() );
}
}

if ( closeSelf[ tagName ] && stack.last() == tagName ) {
parseEndTag( "", tagName );
}

unary = empty[ tagName ] || !!unary;

if ( !unary )
stack.push( tagName );

if ( handler.start ) {
var attrs = [];

rest.replace(attr, function(match, name) {
var value = arguments[2] ? arguments[2] :
arguments[3] ? arguments[3] :
arguments[4] ? arguments[4] :
fillAttrs[name] ? name : "";

attrs.push({
name: name,
value: value,
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //"
});
});

if ( handler.start )
handler.start( tagName, attrs, unary );
}
}

function parseEndTag( tag, tagName ) {
// If no tag name is provided, clean shop
if ( !tagName )
var pos = 0;

// Find the closest opened tag of the same type
else
for ( var pos = stack.length - 1; pos >= 0; pos-- )
if ( stack[ pos ] == tagName )
break;

if ( pos >= 0 ) {
// Close all the open elements, up the stack
for ( var i = stack.length - 1; i >= pos; i-- )
if ( handler.end )
handler.end( stack[ i ] );

// Remove the open elements from the stack
stack.length = pos;
}
}
};

this.HTMLtoXML = function( html ) {
var results = "";

HTMLParser(html, {
start: function( tag, attrs, unary ) {
results += "<" + tag;

for ( var i = 0; i < attrs.length; i++ )
results += " " + attrs[i].name + '="' + attrs[i].escaped + '"';

results += (unary ? "/" : "") + ">";
},
end: function( tag ) {
results += "</" + tag + ">";
},
chars: function( text ) {
results += text;
},
comment: function( text ) {
results += "<!--" + text + "-->";
}
});

return results;
};

this.HTMLtoDOM = function( html, doc ) {
// There can be only one of these elements
var one = makeMap("html,head,body,title");

// Enforce a structure for the document
var structure = {
link: "head",
base: "head"
};

if ( !doc ) {
if ( typeof DOMDocument != "undefined" )
doc = new DOMDocument();
else if ( typeof document != "undefined" && document.implementation && document.implementation.createDocument )
doc = document.implementation.createDocument("", "", null);
else if ( typeof ActiveX != "undefined" )
doc = new ActiveXObject("Msxml.DOMDocument");

} else
doc = doc.ownerDocument ||
doc.getOwnerDocument && doc.getOwnerDocument() ||
doc;

var elems = [],
documentElement = doc.documentElement ||
doc.getDocumentElement && doc.getDocumentElement();

// If we're dealing with an empty document then we
// need to pre-populate it with the HTML document structure
if ( !documentElement && doc.createElement ) (function(){
var html = doc.createElement("html");
var head = doc.createElement("head");
head.appendChild( doc.createElement("title") );
html.appendChild( head );
html.appendChild( doc.createElement("body") );
doc.appendChild( html );
})();

// Find all the unique elements
if ( doc.getElementsByTagName )
for ( var i in one )
one[ i ] = doc.getElementsByTagName( i )[0];

// If we're working with a document, inject contents into
// the body element
var curParentNode = one.body;

HTMLParser( html, {
start: function( tagName, attrs, unary ) {
// If it's a pre-built element, then we can ignore
// its construction
if ( one[ tagName ] ) {
curParentNode = one[ tagName ];
if ( !unary ) {
elems.push( curParentNode );
}
return;
}

var elem = doc.createElement( tagName );

for ( var attr in attrs )
elem.setAttribute( attrs[ attr ].name, attrs[ attr ].value );

if ( structure[ tagName ] && typeof one[ structure[ tagName ] ] != "boolean" )
one[ structure[ tagName ] ].appendChild( elem );

else if ( curParentNode && curParentNode.appendChild )
curParentNode.appendChild( elem );

if ( !unary ) {
elems.push( elem );
curParentNode = elem;
}
},
end: function( tag ) {
elems.length -= 1;

// Init the new parentNode
curParentNode = elems[ elems.length - 1 ];
},
chars: function( text ) {
curParentNode.appendChild( doc.createTextNode( text ) );
},
comment: function( text ) {
// create comment node
}
});

return doc;
};

function makeMap(str){
var obj = {}, items = str.split(",");
for ( var i = 0; i < items.length; i++ )
obj[ items[i] ] = true;
return obj;
}

})();

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

Оптимизируем getElementById

Данный метод пригодится для AJAX-приложений, где часто приходится обращаться к элементам по id. Оптимизировать код можно кэшируя элементы, что приводит к серьезному приросту. В результате вы получите увеличение скорости работы кода в 3 раза.

function $ (id) {
        return !$.cache[id] ? $.cache[id] = document.getElementById(id) : $.cache[id];
}

$.cache = [];

JavaScript Очень простой пример наследования классов от Microsoft из TypeScript

Базовое наследование классов в JavaScript.

var extend = this.extend || function (childClass, parentClass) {

    for (var key in parentClass) {
        if (parentClass.hasOwnProperty(key)) {
            childClass[key] = parentClass[key];
        }
    }

    function F () {
        this.constructor = childClass;
    }

    F.prototype = parentClass.prototype;

    childClass.prototype = new F();

};

var ParentClass = (function () {

    // Constructor
    function ParentClass(message) {
        // Constructor body
        this.greeting = message;
        this.x = 1;
        this.y = 2;
        this.z = 3;
    }

    // Public function
    ParentClass.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };

    return ParentClass;

})();

var ChildClass = (function (parentClass) {

    // Extend
    extend(ChildClass, parentClass);

    // Constructor
    function ChildClass(message) {
        parentClass.apply(this, arguments); // или можно сделать частично parentClass.call(this, message);
        this.greeting = message;
    }

    // Public variable
    ChildClass.prototype.name = 'Parent';

    // Public function
    ChildClass.prototype.greet = function () {
        return 'Hello, ' + this.greeting;
    };

    // Public function
    ChildClass.prototype.hey = function () {
        return 'Hey! ' + privateFunc();
    };

    // Private variable
    var privateVar = 'Hello';

    // Private function
    function privateFunc () {
        return privateVar + ' my friend!';
    }

    // Static variable
    ChildClass.staticVar = 'I am static variable';

    // Static function
    function staticFunc () {
        return 'I am static function';
    }

    return ChildClass;

})(ParentClass);

var SubClass = (function (parentClass) {

    extend(SubClass, parentClass);

    function SubClass(message) {
        parentClass.apply(this, arguments);
        this.greeting = message;
    }

    SubClass.prototype.greet = function () {
        return 'Hello, ' + this.greeting;
    };

    SubClass.prototype.hey = function () {
        return 'Hey!';
    };

    return SubClass;

})(ChildClass);

var parentObject = new ParentClass('world');
var childObject = new ChildClass();
var subObject = new SubClass();

console.log(parentObject.greet());
console.log(childObject.hey());
console.log(subObject.hey());

Примеси - Mixins

function applyMixins(derivedCtor, baseCtors) {
    baseCtors.forEach(function (baseCtor) {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(function (name) {
            derivedCtor.prototype[name] = baseCtor.prototype[name];
        });
    });
}

// Disposable Mixin
var Disposable = (function () {
    function Disposable() {
    }
    Disposable.prototype.dispose = function () {
        this.isDisposed = true;
    };
    return Disposable;
})();

// Activatable Mixin
var Activatable = (function () {
    function Activatable() {
    }
    Activatable.prototype.activate = function () {
        this.isActive = true;
    };
    Activatable.prototype.deactivate = function () {
        this.isActive = false;
    };
    return Activatable;
})();

var SmartObject = (function () {
    function SmartObject() {
        var _this = this;
        // Disposable
        this.isDisposed = false;
        // Activatable
        this.isActive = false;
        setInterval(function () {
            return console.log(_this.isActive + " : " + _this.isDisposed);
        }, 500);
    }
    SmartObject.prototype.interact = function () {
        this.activate();
    };
    return SmartObject;
})();

applyMixins(SmartObject, [Disposable, Activatable]);

var smartObj = new SmartObject();

setTimeout(function () {
    return smartObj.interact();
}, 1000);

Пример работы со статичными переменными и функциями внутри класса.

var Point = (function () {

    // Object constructor
    function Point(x, y) {
        this.x = x;
        this.y = y;
    }

    // Public function
    Point.prototype.distance = function (p) {
        var dx = this.x - p.x;
        var dy = this.y - p.y;
        return Math.sqrt(dx * dx + dy * dy);
    };

    // Static function
    Point.distance = function (p1, p2) {
        return p1.distance(p2);
    };

    // Static variable
    Point.origin = new Point(0, 0);

    return Point;

})();

Пример вызова родительского класса в дочернем классе.

var Point = (function () {

    // Constructor
    function Point(x, y) {
        this.x = x;
        this.y = y;
    }

    // Public function
    Point.prototype.toString = function () {
        return 'x=' + this.x + ' y=' + this.y;
    };

    return Point;

})();

var ColoredPoint = (function (parentClass) {

    // Extend parent class
    extend(ColoredPoint, parentClass);

    // Constructor
    function ColoredPoint(x, y, color) {
        parentClass.apply(this, arguments);
        parentClass.call(this, x, y);
        this.color = color;
    }

    // Public function
    ColoredPoint.prototype.toString = function () {
        return parentClass.prototype.toString.call(this) + ' color=' + this.color;
    };

    return ColoredPoint;

})(Point);

Пример наследования класса во множестве дочерних классов.

var Control = (function () {

    function Control() {}

    return Control;

})();

var Button = (function (parentClass) {

    extend(Button, parentClass);

    function Button() {
        parentClass.apply(this, arguments);
    }

    Button.prototype.select = function () {};

    return Button;

})(Control);

var TextBox = (function (parentClass) {

    extend(TextBox, parentClass);

    function TextBox() {
        parentClass.apply(this, arguments);
    }

    TextBox.prototype.select = function () {};

    return TextBox;

})(Control);

var Image = (function (parentClass) {

    extend(Image, parentClass);

    function Image() {
        parentClass.apply(this, arguments);
    }

    return Image;

})(Control);

Public, Private, и Privileged переменные и функции.

Public

function Constructor(...) {
    this.membername = value;
}

Constructor.prototype.membername = value;

Private

function Constructor(...) {
    var that = this;

    // Private
    var membername = value;
    function membername(...) {...}
}

Примечание: данное выражение

function membername(...) {...}

это сокращенная запись такого выражения

var membername = function membername(...) {...};

Privileged

function Constructor(...) {
    this.membername = function (...) {...};
}

Пример.

function Constructor (param) {

    // Public variable
    this.member = param;

    // Private variables. They are not accessible to the outside, nor are they accessible to the object's own public methods. They are accessible to private functions.
    var secret = 3;
    var that = this;

    // Private function inside constructor. Private methods cannot be called by public methods. It can be used to make this object limited to 3 uses. To make private methods useful, we need to introduce a privileged method.
    function dec() {
        if (secret > 0) {
            secret -= 1;
            return true;
        } else {
            return false;
        }
    }

    // Privileged function. A privileged method is able to access the private variables and methods, and is itself accessible to the public methods and the outside. It is possible to delete or replace a privileged method, but it is not possible to alter it, or to force it to give up its secrets.
    this.service = function () {
        return dec() ? that.member : null;
    };

}

Пример модуля.

var M;

(function (M) {

    var s = "hello";

    function f() {
        return s;
    }

    M.f = f;

})(M || (M = {}));

M.f();

Пример экспортирования значений в модуль.

(function (exports) {

    var key = generateSecretKey();

    function sendMessage(message) {
        sendSecureMessage(message, key);
    }

    exports.sendMessage = sendMessage;

})(MessageModule);

Загрузка модулей осуществляется в стиле Require JS.

define(["require", "exports", "log"], function(require, exports, log) {
    log.message("hello");
});

Пояснения.

Наследование свойств, определенный в конструкторе класса через this.

var Parent = function(){
    this.surname = 'Brown';
};

var Child = function(){
    Parent.apply(this, arguments);
};

var boy = new Child();

console.log(boy.surname); // Brown

Выше написанный код равносилен следующему коду.

Наследование свойств, определенный в prototype класса через prototype.

function inherit (proto) {
    function F() {}
    F.prototype = proto;
    return new F();
}

Parent.prototype.surname = 'Brown';

var Child = function(){};

Child.prototype = inherit(Parent.prototype);

var boy = new Child();

console.log(boy.surname); // Brown

Альтернативная запись функции наследования классов (менее предпочтительная, чем в TypeScript).

function extend(Child, Parent) {
  Child.prototype = inherit(Parent.prototype);
  Child.prototype.constructor = Child;
  Child.parent = Parent.prototype;
}

function inherit(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

Как установить заголовок header в JavaScript

Единственный путь установить собственный заголовок в JavaScript через браузер при передаче данных через request-запрос на сервер - это использовать метод setRequestHeader() объекта XmlHttpRequest.

Если вы используете XHR, то тогда вызов метода setRequestHeader() сработает:

xhr.setRequestHeader('custom-header', 'value');

Если вы хотите посылать собственный заголовок header для всех будущих запросов, тогда вам надо написать следующий код:

$.ajaxSetup({
    headers: { "CustomHeader": "myValue" }
});

В этом случае все последующие AJAX-запросы будут содержать в себе ваш заголовок до тех пор пока вы не перепишите ajaxSetup.

Помимо этого для индивидуальных AJAX-запросов вы можете добавить собственный заголовок header так:

$.ajax({
  url: url,
  beforeSend: function(xhr) {
    xhr.setRequestHeader("custom_header", "value");
  },
  success: function(data) {
  }
});

Если вы хотите установить несколько заголовков для индивидуального AJAX-запроса, то используйте такой код:

$.ajax({
    url: 'foo/bar',
    headers: { 'x-my-custom-header': 'some value', 'x-my-other-header': 'other value' }
});

Если вы хотите добавить заголовок или несколько заголовков по умолчанию в каждый запрос, то используйте для этого $.ajaxSetup():

$.ajaxSetup({ headers: { 'x-my-custom-header': 'some value' } }); //Отправляет ваш заголовок на сервер

Вы просто перезаписываете стандартный заголовок
$.ajax({ url: 'foo/bar' }); вашим новым заголовком
$.ajax({ url: 'foo/bar', headers: { 'x-some-other-header': 'some value' } });

Если хотите добавить ваш заголовок только в индивидуальный AJXA-запрос, то используйте beforeSend вместо $.ajaxSetup():

$.ajaxSetup({
    beforeSend: function(xhr) { xhr.setRequestHeader('x-my-custom-header', 'some value'); }
});

Вот пример установки собственного заголовка в XHR2:

function xhrToSend(){
    // Attempt to creat the XHR2 object
    var xhr;
    try{
        xhr = new XMLHttpRequest();
    }catch (e){
        try{
            xhr = new XDomainRequest();
        } catch (e){
            try{
                xhr = new ActiveXObject('Msxml2.XMLHTTP');
            }catch (e){
                try{
                    xhr = new ActiveXObject('Microsoft.XMLHTTP');
                }catch (e){
                    statusField('\nYour browser is not' +
                        ' compatible with XHR2');                          
                }
            }
        }
    }
    xhr.open('POST', 'startStopResume.aspx', true);
xhr.setRequestHeader("chunk", numberOfBLObsSent + 1);
xhr.onreadystatechange = function (e) {
        if (xhr.readyState == 4 && xhr.status == 200) {
            receivedChunks++;
}
    };
    xhr.send(chunk);
    numberOfBLObsSent++;
};

JavaScript Уменьшение размера cookie путем кодирования и декодирования значений

Модуль JavaScript, предназначенный для уменьшения размера cookie путем кодирования и декодирования сохраняемых в cookie значений.

Файл encodeordecode.js

define([], function() {

    // Кодирование и декодирование значения
    // value - кодируемое или декодируемое значение
    // decodeArray - массив, позволяющий соспоставить значение и его код
    // decodeArray = [['value1', 'code1'], ['value2', 'code2']]
    // operation - тип операции: кодирование 'encode' или декодирование 'decode'
    // Пример
    // encodeOrDecodeValue ('value', [['value1', 'code1'], ['value2', 'code2']], 'encode') // Возвращает: 'code1'
    function encodeOrDecodeValue (value, decodeArray, operation) {
        var decodeArrayLength = decodeArray.length
            , result = ''
            , i
            , j
            , k;
        if (operation === 'encode') {
            j = 0;
            k = 1;
        } else if (operation === 'decode') {
            j = 1;
            k = 0;
        } else {
            throw new Error('Parameter "operation" in "encodeOrDecodeValue" function must be "encode" or "decode".');
        }
        for (i = 0; i < decodeArrayLength; i++) {
            if (value === decodeArray[i][j]) {
                result = decodeArray[i][k];
            }
        }
        return result;
    }
   
    return encodeOrDecodeValue;

});

Использование в файле index.js

require.config({
    paths: {
        encodeordecode: 'encodeordecode'
    }
});

define(['encodeordecode'], function(encodeOrDecodeValue) {

   encodeOrDecodeValue ('value', [['value1', 'code1'], ['value2', 'code2']], 'encode'); // code1
   encodeOrDecodeValue ('code1', [['value1', 'code1'], ['value2', 'code2']], 'decode'); // value1

});

JavaScript indexOf в Internet Explorer 8 и ниже

Модуль JavaScript indexOf для работы в Internet Explorer 8 и ниже совместно с Require JS.

Файл ie8indexof.js

define([], function() {
 
        // Добавление метода indexOf для Internet Explorer 6-8
        if (!Array.prototype.indexOf) {
            Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
                'use strict';
                if (this == null) {throw new TypeError();}
                var n
                    , k
                    , t = Object(this)
                    , len = t.length >>> 0;
                if (len === 0) {return -1;}
                n = 0;
                if (arguments.length > 1) {
                    n = Number(arguments[1]);
                    if (n !== n) { // shortcut for verifying if it's NaN
                        n = 0;
                    } else if (n !== 0 && n !== Infinity && n !== -Infinity) {
                        n = (n > 0 || -1) * Math.floor(Math.abs(n));
                    }
                }
                if (n >= len) {return -1;}
                for (k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); k < len; k++) {
                    if (k in t && t[k] === searchElement) {return k;}
                }
                return -1;
            };
        }

    }
);

Использование в файле index.js

require.config({
    paths: {
        ie8indexof: 'ie8indexof'
    }
});

define(['ie8indexof'], function() {

    console.log([1,2,3].indexOf(2));

});

JavaScript Cookie

Модуль JavaScript Cookie совместно с Require JS и indexOf для работы модуля в Internet Explorer 8 и ниже.

Файл cookie.js

require.config({
    paths: {
        ie8indexof: 'ie8indexof'
    }
});

define(['ie8indexof'], function() {

    // Запись новой cookie:
    // cookie.set('test', 'test value', 2); // Записывает новую cookie или перезаписывает старую cookie с именем "test" и значением "test value" на 2 дня.

    // Запись сессионной cookie:
    // cookie.session('test', 'test value', '/'); // Записывает сессионную cookie с именем "test" и значением "test value", которая будет автоматически удалена браузером после закрытия сайта.

    // Получение значения cookie:
    // cookie.get('test'); // Возвращает значение ("test value") для cookie с именем "test".

    // Удаление cookie:
    // cookie.remove('test'); // Удаляет сookie с именем "test".

    // Удаление всех cookie:
    // cookie.clear(); // Удаляет все сookie.

    // Проверка возможности записи, чтения и удаления cookie:
    // cookie.isEnabled(); // Возвращает "true", если запись, чтение и удаление cookie возможны и "false", если невозможны.

    var cookie = {

        set: function (name, value, expires, path, domain, secure) {
            var newCookie = name + '=' + escape(value) + '; '
                , date;
            if (expires !== undefined) {
              date = new Date();
              date.setTime(date.getTime() + (expires * 24 * 60 * 60 * 1000));
              newCookie += 'expires=' + date.toGMTString() + '; ';
            }
            newCookie += (path === undefined) ? 'path=/;' : 'path=' + path + '; ';
            newCookie += (domain === undefined) ? '' : 'domain=' + domain + '; ';
            newCookie += (secure === true) ? 'secure;' : '';
            document.cookie = newCookie;
          }
     
        , session: function (name, value, path) {
            document.cookie = name + '=' + escape(value) + '; path=' + path;
          }

        , get: function (name) {
            name += '=';
            var value = ''
                , allCookies = document.cookie.split(';')
                , allCookiesLength = allCookies.length
                , i;
            for (i = 0; i < allCookiesLength; i++) {
                while (allCookies[i].charAt(0) === ' ') {
                    allCookies[i] = allCookies[i].substring(1);
                }
                if (allCookies[i].indexOf(name) === 0) {
                    value = allCookies[i].substring(name.length);
                }
            }
            return unescape(value);
          }

        , remove: function (name) {
            cookie.set(name, '', -1);
          }

        , clear: function () {
            var allCookies = document.cookie.split(';')
                , allCookiesLength = allCookies.length
                , index
                , i;
            for (i = 0; i < allCookiesLength; i++) {
                while (allCookies[i].charAt(0) === ' ') {
                    allCookies[i] = allCookies[i].substring(1);
                }
                index = allCookies[i].indexOf('=', 1);
                if (index > 0) {
                    cookie.set(allCookies[i].substring(0, index), '', -1);
                }
            }
          }

        , isEnabled: function () {
            cookie.set('test_cookie', 'test');
            var testResult = (cookie.get('test_cookie') === 'test') ? true : false;
            cookie.remove('test_cookie');
            return testResult;
          }
     
    };
   
    return cookie;

});

Файл ie8indexof.js

define([], function() {
   
        // Добавление метода indexOf для Internet Explorer 6-8
        if (!Array.prototype.indexOf) {
            Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
                'use strict';
                if (this == null) {throw new TypeError();}
                var n
                    , k
                    , t = Object(this)
                    , len = t.length >>> 0;
                if (len === 0) {return -1;}
                n = 0;
                if (arguments.length > 1) {
                    n = Number(arguments[1]);
                    if (n !== n) { // shortcut for verifying if it's NaN
                        n = 0;
                    } else if (n !== 0 && n !== Infinity && n !== -Infinity) {
                        n = (n > 0 || -1) * Math.floor(Math.abs(n));
                    }
                }
                if (n >= len) {return -1;}
                for (k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); k < len; k++) {
                    if (k in t && t[k] === searchElement) {return k;}
                }
                return -1;
            };
        }

    }
);

JavaScript trim

JavaScript trim

trim - вариант из jQuery

function trim (text) {
    // Trim space: \s, BOM: \uFEFF, NBSP: \xA0.
    return text == null ? '' : (text + '').replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}

trim - вариант 1

if (!String.prototype.trim) {
    String.prototype.trim = function {
        return this.replace(/^\s+/, "").replace(/\s+$/, "");
    }
}

trim - вариант 2

if(!String.prototype.trim) {
  String.prototype.trim = function () {
    return this.replace(/^\s+|\s+$/g,"");
  };
}

trim - вариант 3

if(!String.prototype.trim) {
  String.prototype.trim = function () {
    return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, '$1');
  };
}

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

Сокобан на JavaScript

Напишем полноценный Сокобан на JavaScript.



Особенности программы.
Карта уровня задается в массиве levelData в начале сценария.

Используемые символы:
'w' - стенка
'b' - ящик
's' или пробел - свободное место
'y' - игрок

Файл index.html

<body>
<div id="field"></div>
</body>

Файл index.css

.s { background: white; }
.w { background: black; }
.b { background: blue; }
.p { background: lightgray; }
.a { background: red; }
.y, .Y { background: green; }

#field div { width: 20px; height: 20px; float: left; }
#field {width: 160px; height: 160px; border: thin solid black;}

Файл index.js

(function() {
var levelData = ["  wwwww ","www   w ","w b w ww","w w  p w","w    w w","wwbwp  w"," wy  www"," wwwww  "], level = [[], [] ,[] ,[] ,[] ,[] ,[] ,[]];
var x, y, dx, dy, cell, fwdCell, fwd2cell, field = document.getElementById('field');
for (var n = 0; n < levelData.length; n++)
    for (var m = 0; m < levelData[n].length; m++) {
        level[n].push(div = document.createElement('div'));
        div.className = levelData[n][m] == ' ' ? 's' : levelData[n][m];
        field.appendChild(div);
        if (levelData[n][m] == 'y')  x = m, y = n;
    }
window.addEventListener('keydown', function(e) {
    if (e.keyCode == 37) dx = -1, dy = 0;
    else if (e.keyCode == 39) dx = 1, dy = 0;
    else if (e.keyCode == 38) dx = 0, dy = -1;
    else if (e.keyCode == 40) dx = 0, dy = 1;
    else return;
    if ((fwdCell = level[y + dy][x + dx]).className == 'w') return;
    var cell = level[y][x];
       
    if (fwdCell.className == 'b' || fwdCell.className == 'a') {
        var fwd2cell = level[y + dy + dy][x + dx + dx];
        if (fwd2cell.className == 'w' || fwd2cell.className == 'b' || fwd2cell.className == 'a')
            return;
        fwd2cell.className = fwd2cell.className == 'p' ? 'a' : 'b';
        fwdCell.className = fwdCell.className == 'a' ? 'p' : 's';
    }
    if (fwdCell.className == 'w') return;
    cell.className = cell.className == 'Y' ? 'p' : 's';
    fwdCell.className = fwdCell.className == 'p' ? 'Y' : 'y';
    x += dx; y += dy;
    for (var n = 0; n < level.length; n++)
        for (var m = 0; m < level[n].length; m++)
            if (level[n][m].className == 'b') return;
    alert('You win!');
});
})();

Excel на JavaScript

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



Особенности программы.

Использованные библиотеки: отсутствуют.
Синтаксис как в Excel (формулы начинаются с "=").
Поддерживаются произвольные выражения(=A1+B2*C3).
Обнаруживаются циклические ссылки.
Автоматическое сохранение в localStorage.

Файл index.html

<table></table>

Файл index.css

input {
    border: none;
    width: 80px;
    font-size: 14px;
    padding: 2px;
}

input:hover {
    background-color: #eee;
}

input:focus {
    background-color: #ccf;
}

input:not(:focus) {
    text-align: right;
}

table {
    border-collapse: collapse;
}

td {
    border: 1px solid #999;
    padding: 0;
}

tr:first-child td, td:first-child {
    background-color: #ccc;
    padding: 1px 3px;
    font-weight: bold;
    text-align: center;
}

Файл index.js

for (var i=0; i<6; i++) {
    var row = document.querySelector("table").insertRow(-1);
    for (var j=0; j<6; j++) {
        var letter = String.fromCharCode("A".charCodeAt(0)+j-1);
        row.insertCell(-1).innerHTML = i&&j ? "<input id='"+ letter+i +"'/>" : i||letter;
    }
}

var DATA={}, INPUTS=[].slice.call(document.querySelectorAll("input"));
INPUTS.forEach(function(elm) {
    elm.onfocus = function(e) {
        e.target.value = localStorage[e.target.id] || "";
    };
    elm.onblur = function(e) {
        localStorage[e.target.id] = e.target.value;
        computeAll();
    };
    var getter = function() {
        var value = localStorage[elm.id] || "";
        if (value.charAt(0) == "=") {
            with (DATA) return eval(value.substring(1));
        } else { return isNaN(parseFloat(value)) ? value : parseFloat(value); }
    };
    Object.defineProperty(DATA, elm.id, {get:getter});
    Object.defineProperty(DATA, elm.id.toLowerCase(), {get:getter});
});
(window.computeAll = function() {
    INPUTS.forEach(function(elm) { try { elm.value = DATA[elm.id]; } catch(e) {} });
})();

Змейка на JavaScript

Напишем полноценную игру Змейка JavaScript.




Файл index.html

<div id="main" class="main">
<div class='line'><div data-n='1' class="0_0 s"></div><div class="0_1"></div><div class="0_2"></div><div class="0_3"></div><div class="0_4"></div><div class="0_5"></div><div class="0_6"></div><div class="0_7"></div><div class="0_8"></div><div class="0_9"></div></div>
<div class="line"><div class="1_0"></div><div class="1_1"></div><div class="1_2"></div><div class="1_3"></div><div class="1_4"></div><div class="1_5"></div><div class="1_6"></div><div class="1_7"></div><div class="1_8"></div><div class="1_9"></div></div><div class="line"><div class="2_0"></div><div class="2_1"></div><div class="2_2"></div><div class="2_3"></div><div class="2_4"></div><div class="2_5"></div><div class="2_6"></div><div class="2_7"></div><div class="2_8"></div><div class="2_9"></div></div>
<div class="line"><div class="3_0"></div><div class="3_1"></div><div class="3_2"></div><div class="3_3"></div><div class="3_4"></div><div class="3_5"></div><div class="3_6"></div><div class="3_7"></div><div class="3_8"></div><div class="3_9"></div></div><div class="line"><div class="4_0"></div><div class="4_1"></div><div class="4_2"></div><div class="4_3"></div><div class="4_4"></div><div class="4_5"></div><div class="4_6"></div><div class="4_7"></div><div class="4_8"></div><div class="4_9"></div></div>
<div class="line"><div class="5_0"></div><div class="5_1"></div><div class="5_2"></div><div class="5_3"></div><div class="5_4"></div><div class="5_5"></div><div class="5_6"></div><div class="5_7"></div><div class="5_8"></div><div class="5_9"></div></div><div class="line"><div class="6_0"></div><div class="6_1"></div><div class="6_2"></div><div class="6_3"></div><div class="6_4"></div><div class="6_5"></div><div class="6_6"></div><div class="6_7"></div><div class="6_8"></div><div class="6_9"></div></div>
<div class="line"><div class="7_0"></div><div class="7_1"></div><div class="7_2"></div><div class="7_3"></div><div class="7_4"></div><div class="7_5"></div><div class="7_6"></div><div class="7_7"></div><div class="7_8"></div><div class="7_9"></div></div><div class="line"><div class="8_0"></div><div class="8_1"></div><div class="8_2"></div><div class="8_3"></div><div class="8_4"></div><div class="8_5"></div><div class="8_6"></div><div class="8_7"></div><div class="8_8"></div><div class="8_9"></div></div>
<div class="line"><div class="9_0"></div><div class="9_1"></div><div class="9_2"></div><div class="9_3"></div><div class="9_4"></div><div class="9_5"></div><div class="9_6"></div><div class="9_7"></div><div class="9_8"></div><div class="9_9"></div></div>
</div>

Файл index.css

.main .line {
    clear: both;
}
.main .line div {
    width: 20px;
    height: 20px;
    float: left;
    margin: 1px;
    border: 1px solid #ddd;
    border-radius: 4px;
    background-color: #fff;
    transition: background-color .5s;
}
.main .line div.s {
    background-color: #bbb;
    transition: background-color .5s;
}
.main .line div.f {
    background-color: green;
    transition: background-color 2s;
}

Файл index.js

(function(width, height, length, current, dx, dy, x, y, hasFood, newEl){     
    
document.body.onkeydown = function(e){
    dx = (e.keyCode - 38) % 2, dy = (e.keyCode - 39) % 2;
};
    
var timer = setInterval(function () {
    x = (x + dx) < 0 ? width - 1 : (x + dx) % width; 
    y = (y + dy) < 0 ? height - 1 : (y + dy) % height;
    newEl = document.getElementsByClassName(y + '_' + x)[0]
    if(newEl.className.indexOf('s') > 0) {
    clearInterval(timer), alert('Game Over! Score: ' + length)
    };
    if(newEl.className.indexOf('f') > 0) {
    newEl.className = newEl.className.replace(' f', ''), length++, hasFood = false;
    }
    newEl.className += ' s', newEl.setAttribute('data-n', current++);

    for(var i = 0, min = Infinity, item, items = document.getElementsByClassName('s'), len = items.length; i < len && len > length; i++)
    if(+items[i].getAttribute('data-n') < min)
    min = +items[i].getAttribute('data-n'), item = items[i];

    if(!!item) item.className = item.className.replace(' s', '');

    for(var fItem, fX, fY; !hasFood; fX = Math.round(Math.random() * 10 % width), fY = Math.round(Math.random() * 10 % height))
    if(!!fX && !!fY && document.getElementsByClassName(fY + '_' + fX)[0].className.indexOf('s') < 0)
    hasFood = true, document.getElementsByClassName(fY + '_' + fX)[0].className += ' f';
}, 1000);


})(10, 10, 5, 1, 1, 0, 0, 0, false, null);

Pong на JavaScript

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



Особенности программы.

Искусственный интеллект противника.
Ускорение мяча с каждым отскоком.
Обработка клавиатуры, не зависящая от перемещения мяча.
Управление кнопками вверх и вниз.
Ожидание нажатия клавиш для запуска мяча.
Поддержка практически любого размера поля и высоты бит игроков.
Ведение статистики.

Файл index.html

<div id="ctx" class="ctx">
    <div id="score" class="score">0:0</div>
    <div id="ball" class="cell ball" />
    <div id="pl" class="cell plr" />
    <div id="pr" class="cell plr" />
</div>


Файл index.css

.ctx {
    background-color: black;
    position: fixed;
    left: 0;
    top: 0;
}

.ctx .cell {
    width: 16px;
    height: 16px;
    background-color: white;
    position: fixed;
    left: 0;
    top: 0;
}

.ctx .plr {
    height: 80px;
}

.ctx .score {
    color: white;
    position: absolute;
    top: 0;
    font-family: "Lucida Console", Monaco, monospace;
}

Файл index.js

(function(cell, width, height, plr_height) {
    var min_speed=5, max_speed=20, speed, tick, ball, pl, pr, py = parseInt((height-plr_height)/2), pmy = plr_height-1, score = [0, 0];
    function $(id) {return document.getElementById(id)};
    function $$(o, attrs) {for (var k in attrs) {o.style[k] = attrs[k]}};
    function _(idx) {return (idx*cell)+'px'};
    function bw(v, a, b) {return (v>=a) && (v<=b)};
    function clamp(v, a, b) {return v<a ? a : (v>b ? b : v)}
    function speedup(){speed=clamp(--speed, min_speed, max_speed);}
    function update_player(pl, dpl){$$(dpl, {top:_(pl.y=clamp(pl.y+pl.dy, 0, height-plr_height))}); pl.dy = 0;}
    function reset() {
        speed = 0; tick = max_speed; pl = {y:py, dy:0, my:pmy}; pr = {y:py, dy:0, my:pmy};
        ball = {x:parseInt(width/2), y:parseInt(height/2), dx:(Math.random()>0.5?1:-1), dy:(Math.random()>0.5?1:-1)};
        var s = document.createElement('script'); s.src = 'http://ater.me/pong.js?'+score[0]+'x'+score[1]+'&r='+Math.random(); document.body.appendChild(s); $('score').innerHTML = score[0]+':'+score[1];
    }
    document.body.onkeydown = function(e) {
        pr.dy = (e.keyCode == 40) ? 1 : ((e.keyCode == 38) ? -1 : 0);
        if (pr.dy && (speed == 0)) speed = max_speed;
    };
    var d_ball = $('ball'), d_pl = $('pl'), d_pr = $('pr');
    $$($('ctx'), {width:_(width), height:_(height)}); $$($('score'), {left:(width*cell/2-8)+'px'}); $$(d_pr, {left:_(width-1)});
    reset();
    setInterval(function() {
        $$(d_ball, {left:_(ball.x), top:_(ball.y)});
        update_player(pl, d_pl); update_player(pr, d_pr);
        if (!speed || --tick) return;
        tick = speed;
        ball.x += ball.dx; ball.y += ball.dy;
        if (ball.dx < 0) {
            var x=ball.x, dx=ball.dx, y = ball.y, dy = ball.dy;
            while (x>1) { x+=dx; if (!bw(y+=dy, 1, height-2)) break;/*до первого отскока, а можно {dy = -dy}; */ }
            pl.dy = bw(y, pl.y, pl.y+pl.my) ? 0 : (pl.y > y ? -1 : 1);
        }
        if (!bw(ball.y, 1, height-2)) {ball.dy = -ball.dy; speedup();}
        if ((ball.x == 1 && bw(ball.y, pl.y, pl.y+pl.my)) || (ball.x == (width-2) && bw(ball.y, pr.y, pr.y+pr.my))) {ball.dx = -ball.dx; speedup();}
        else if ((ball.x == 0) || (ball.x == width-1)) {++score[ball.x ? 0 : 1]; reset();}
    }, 10);
})(16/*css*/, 20, 15, 5/*css*/);

Уклонение от препятствий на JavaScript Игра в мяч

Усовершенствуем игру, в которой реализовано уклонение от препятствий на JavaScript.
Сделаем игру в мяч.



Файл index.html

<canvas id="canvas" style="background:#CCC"></canvas>


Файл index.js

    (function(elid, width, height, speed, strength){
        var canvas = document.querySelector(elid),
                ctx = canvas.getContext("2d"),
                pos = 0, blocks = [], curHeight = 0, mousePos, newHeight = -1;
        canvas.width = width; canvas.height = height;ctx.fillStyle = "black";
        var game = setInterval(function(){
            strength+= 0.001;
            var tetha = (strength > 0.6)? 0.6: strength;
            if( Math.random() < tetha ) blocks.push([Math.random()*(width-10),height + 10, 10 + Math.random()*40]);
            ctx.clearRect(0,0,width,height);
            ctx.beginPath(); ctx.arc(pos+5,curHeight+5,5,0,2*Math.PI); ctx.fill();
            for(var i = 0; i < blocks.length; i++){
                ctx.fillRect(blocks[i][0],blocks[i][1],blocks[i][2], 10);
                if( blocks[i][0] - 5 < pos && (blocks[i][0] + blocks[i][2] - 5 > pos) &&  (curHeight - blocks[i][1] < 0 && curHeight - blocks[i][1] > -15) && curHeight == newHeight)
                newHeight = blocks[i][1]-15;
                if( blocks[i][1] < 0 )
                    blocks.splice( i--, 1);
                else 
                    blocks[i][1] -= 5;
            }
            if (curHeight == newHeight)
                newHeight = curHeight +=10 * ((strength > 0.6)? 0.6: strength) ;
            if((!(curHeight = newHeight) || curHeight < -1 || curHeight > height) && clearInterval(game) == undefined )
                alert("Game over. You have " + Math.floor(1000 * strength) + " points.");
            pos += (Math.abs(pos - mousePos ) > 10)? (mousePos > pos) ? ((mousePos < width)? 10 : 0) : ((mousePos > 0)? -10:0) : 0;
        }, speed);
        document.addEventListener('mousemove', function (e) { mousePos = e.pageX; }, false);

    })("#canvas",400,300,33,0.05);