Jump to content
Geekforum.cz

Hookyns

Uživatel
  • Posts

    155
  • Joined

  • Last visited

  • Days Won

    23

Posts posted by Hookyns

  1. Čaute lidi,

     

    po dlouhé době zde zase vyvinu nějakou tu aktivitu. :D

     

    Už dlouho jsem neměl volný čas, a tak můj Node.js framework (pamatuje si vůbec někdo, že jsem se vám o něm zmiňoval? :D) stagnoval. Nyní se mu opět věnuji, i když zatím v malé míře, ale postupuji. Že jsem měl takovou delší pauzu má svou výhodu a to velký postup Node.js z verze 0.12 na 7.1, které nyní plně využívá ES6 a nějaké drobnosti z ES7.

     

    V souvislosti s plnou podporou ES6 trochu přepisuji jádro frameworku. Zároveň jsem vytvořil nový přístup k private vlastnostem tříd a JS ES6 synaxi singletonu.

     

    Private vlastnosti řeším přes Mixin, který je v ES6 řešený spíše jako dědičnost. Rovnou uvedu kód.

    function Private(refObject, extend) {
    var storage = [];
    var idCounter = 0;
    
    var cls = class extends (extend || class {}) {
    	constructor() {
    		super();
    
    		// Get new ID
    		this.__privateId = idCounter++;
    
    		var clsInstance = this;
    
    		// Define proxy as dynamic prperty accessor
    		storage[this.__privateId] = new Proxy({}, {
    			get: function(target, name) {
    				if (!(name in target)) {
    					return undefined;
    				}
    				return target[name];
    			},
    			set: function(target, name, value) {
    				if (typeof value == "function") {
    					target[name] = function(...args) {
    						return value.apply(clsInstance, args)
    					};
    				} else {
    					target[name] = value;
    				}
    
    				return true;
    			}
    		});
    	}
    };
    
    // Return proxy with access to privae context
    refObject.accessor = function(self) {
    	return storage[self.__privateId];
    };
    
    return cls;
    }
    module.exports = Private;

    Private() je funkce, která vrací třídu, buď prázdnou nebo převzatou určenou k podědění. Na třídu naváže ID pro přístup k private uložišti a také vrátí v ref parametru accesor pro přístup do private uložiště.

     

    Příklad použití (rovnou i s ukázkou tvorby singletonu):

    var Private = require("jumbo-core/utils/private");
    
    var accessor = {}, _;
    
    class Test extends Private(accessor/*, OptionalParentClass*/) {
    constructor() {
    	super();
    
    	if (new.target != Activator) { // new.target is used constructor
    		throw new Error("You cannot call private constructor!");
    	}
    
    	_(this).method = function(name, surname) {
    		console.log("New test: ", name, surname);
    	};
    }
    
    foo() {
    	_(this).test = "Does it work?";
    }
    
    bar() {
    	var $this = _(this); // Shortcut
    
    	$this.method("Foo", "Bar");
    	return $this.test;
    }
    
    static getInstance() {
    	return Reflect.construct(Test, [], Activator); // Create instance of class Test but use Activator's constructor
    }
    }
    
    _ = accessor.accessor; // Just for shorting notation; Otherway you must write accessor.accessor(this).propertyName
    
    // Used for creating instance
    class Activator extends Test { }
    
    // var t = new Test(); throw error
    var t = Test.getInstance();
    console.log(t instanceof Test); // true
    t.foo();
    console.log(t.bar());
    

     

     

    BTW, stejně to asi nikdo nepoužijete, ale kdyby náhodou... :D

  2. Jo v pohodě.

     

    WebSocket je řešení přímo na tohle. Node.js je na to taky ideální. Šlo by to i v PHP, ale musel by si řešit s hostingem deamonský běh PHP scriptu a to ti normální hosting asi nepovolí, takže by si stejně potřeboval VPS a to už rovnou můžeš použít Node.js.

     

    Node.js má balíky na WebSocket, takže tohle rozběhat je max na hodinu, včetně klienta.

     

    http://websockets.github.io/ws/

     

    Můžeš to zkusit nahodit sám ten server. Na WebSocket klienta taky najdeš tutoriál. Minimálně to spojení navážeš sám bez pomoci. Zkus to. Večer sem skáknu a podívám se ti na to a pokud nedáš vůbec nic dohromady, tak ti ten základ napíšu.

     

    Pak se to doladí.

  3. Pro to informování uživatelů se rozhodně hodí WebSocket. Oficiálně už je nejnižší podporované IE IE10, takže s podporou by neměl být problém. (Lidi sice mají třeba IE8 nebo IE9, ale oficiální podpora pro ně prostě skončila)

     

    Posílání takového množství HTTP dotazů (každou sec.) AJAXem je vražda.

     

    Jen trochu nechápu, jak to má fungovat. Systém sám provádí nějaké operace, bez iniciativy uživatele? Jen jde o to ty operace provádět efektivně a následně o tom uživatele informovat?

  4. Zase pár úprav. :)

     

    Jelikož už na tom frameworku něco stavím, tak budou úpravy asi častější a jak v tomto případě, budou to praktické věci.

     

    Přidal jsem možnost live editace kódu. Na node.js je na prd, že když člověk něco upraví, musí restartovat aplikaci, aby se změny projevily. Dvakrát tak otravné to je, když člověk kóduje šablonu. Pro každý pixel restartovat aplikaci,.. To je prostě na prd.

    Já jsem Jumba upravil tak, aby si hlídal změny v souborech a mazal require.cache z Node.js - případ úpravy JS kódu - presentery, modely aj.

    Do Jumbovi template cache jsem přidal detekci no-cache požadavku (ctrl+F5 na stránce), takže při změně template stačí na webu dát ctrl+F5 a aktualizuje se template cache.

    Další věc je LESS. Jumbo primárně podporuje stylování v LESS, které automaticky při startu kompiloval na CSS. Zde byl problém při stylování template. Člověk upraví LESS, ale musí restartovat aplikaci, aby se LESS zkompiloval. Tomu je už konec. Přidal jsem i detekci změn u .less souborů, které se po změně automaticky zkompilují.

     

    Další věc byla úprava u posílání statických souborů. Aby se zrychlila odpověď serveru, jestli soubor existuje nebo ne, jestli má požadavek zpracovávat node-static nebo normálně server, tak byl v Jumbovi při startu vytvářen seznam statických souboru na jehož základě se poté rozhodovalo, jestli bude požadavek zpracovávat node-static nebo Jumbo. Pokud jsem však do složky public přidal obrázek až po startu serveru, tak nebyl v seznamu a požadavek šel na Jumba, který však pod adresou nic nenašel. Takže opět byla přidána detekce změn na složku public a každý nově přidaný soubor se přidá i do seznamu statických souborů.

     

     

    A jedna z hlavních věcí co jsem potřeboval na projekt bylo zprovoznění modulů/sub-applikací. Tak jak Nette podporuje moduly, tak má jumbo sub-apps. Jenže Nette to bere z url domena.tld/modul/presenter/akce a Jumbo je frajer, protože si s URL může dělat co chce, tak podporuje sub-app.domena.tld/presenter/akce.

     

     

    Shrnuto.

    Lze vytvářet sub-apps - můžu mít normálně web a na subdoméně např. admin můžu mít CMS.

    Lze editovat kódy LIVE - automatická live kompilace LESS souborů za běru, ctrl+F5 pro reload template cache, mazání require.cache při změně JS.

  5. Zdravím,

     

    když už vám zde káži slovo boží a snažím se vám JS přiblížit, tak vám zde poskytnu i svou fci, se kterou tvořím třídy.

     

    Donedávna jsem používal celkem jednoduchou fci, která neměla přes 50 řádků, ale na reakci kolegy v práci, že JS nemá privátní vlastnosti u tříd, jsem svou fci trošičku vylepšil. OOP v JS je celkem krkolomné a složitější konstrukce vyžadují hlubší znalosti.

     

    Je několik způsobů jak vytvořit private vlastnosti a metody u tříd, ale většina je jich špatně. Způsob, který jsem vymyslel je imho asi jeden z nejlepších.

     

    BTW, zkoumal jsem zpětně ještě jiné metody řešení private vlastností u tříd v JS a našel jsem skoro to samé řešení jako mé, takže asi nejsem zase takový frajer. :D To řešení však není zrovna jednoduché, takže jsem byl vážně překvapený, že to napadlo i někoho jiného.

     

    Díky mé fci je možné vytvořit třídy s public a private statickými vlastnostmi/metodami a klasickými private a public vlast./met.

     

     

    Jen pro představu. Jeden z celkem rozšířených a poměrně špatných způsobů tvorby tříd je toto:

    var MojeTrida = function(xxx, yyy) {
    var privatniVlastnost = xxx;
    this.publicVlastnost = yyy;
    var privateMethod = function() {
    	return "foo";
    }
    this.publicMethod = function() {
    	return "bar";
    }
    };
    

     

    Použití lokální proměnné zde není špatně, ale abychom se k ní mohly dostat, musíme zde definovat i metody, a to už špatně je. V první řadě se budou metody vytvářet při každém zavolání konstruktoru a zbytečně se budou znovu a znovu vytvářet i když jsou všechny stejné. Druhý problém je dědičnost. Když nevyužijeme prototype, tak nedocílíme dědičnosti. Tohle řešení se dá použít, pokud nemáte v plánu používat dědičnost, nezáleží vám na výkonu a nutně potřebujete private vlastnosti. Jinak je to nepoužitelné.

     

     

    Já třídy vytvářím pomocí své fce Class, která třídy za mě sestaví a umožní mi i pohodlnou dědičnost i implementaci interface.

     

    Má fce:

    /**
    * Funkce pro vytáření tříd
    * @param defs
    * @returns {{class: *, privateAccessor: Function, extends: (Function|*), implements: (Function|*), private: {constructor: *}}}
    */
    var Class = (function() {
    var classIdCounter = 0;
    var classPrivateProperties = {};
    return function(defs) {
    	var classId = classIdCounter++;
    	classPrivateProperties[classId] = {};
    	var privateProperties = classPrivateProperties[classId];
    	var idCounter = 0;
    	var constructor;
    	var hasPrivateConstructor = false;
    	var ctor;
    	var p = function(self) {
    		return classPrivateProperties[self.classprivateCallIdentifier][self.privateCallIdentifier];
    	};
    	if (defs.hasOwnProperty("constructor")) {
    		ctor = defs.constructor;
    		constructor = function() {
    			if (this.constructor == ctor) { // V případě že dědíme, můžeme volat super konstruktor => Pokud bychom ho zavolali, došlo by k namixování private identifikátorů
    				privateProperties[idCounter] = {};
    				// Uložíme privátní vlastnosti
    				if (typeof defs.private === "object") {
    					for (s in defs.private) {
    						if (defs.private.hasOwnProperty(s) && s != "constructor") {
    							privateProperties[idCounter][s] = defs.private[s];
    						}
    					}
    				}
    				this.privateCallIdentifier = idCounter++;
    				this.classprivateCallIdentifier = classId;
    			}
    			defs.constructor.apply(this, arguments);
    		};
    	} else if (defs.hasOwnProperty("private") && defs.private.hasOwnProperty("constructor")) {
    		ctor = function() { throw new Error("This class's constructor is private!"); };
    		constructor = function() {
    			privateProperties[idCounter] = {};
    			// Uložíme privátní vlastnosti
    			if (typeof defs.private === "object") {
    				for (s in defs.private) {
    					if (defs.private.hasOwnProperty(s) && s != "constructor") {
    						privateProperties[idCounter][s] = defs.private[s];
    					}
    				}
    			}
    			this.privateCallIdentifier = idCounter++;
    			this.classprivateCallIdentifier = classId;
    			defs.private.constructor.apply(this, arguments);
    		};
    		hasPrivateConstructor = true;
    	} else {
    		ctor = function() {};
    		constructor = function() {
    			if (this.constructor == ctor) {
    				privateProperties[idCounter] = {};
    				// Uložíme privátní vlastnosti
    				if (typeof defs.private === "object") {
    					for (s in defs.private) {
    						if (defs.private.hasOwnProperty(s) && s != "constructor") {
    							privateProperties[idCounter][s] = defs.private[s];
    						}
    					}
    				}
    			}
    			this.privateCallIdentifier = idCounter++;
    			this.classprivateCallIdentifier = classId;
    		}
    	}
    	// Pokud definice obsahují konstruktor, vytvoříme jej, jinak použijeme prázdnou fci
    	var c = hasPrivateConstructor ? ctor : constructor;
    	/**
    	 * Pole zaznamenávající které objekty třída implementuje
    	 * @type {Array}
    	 * @private
    	 */
    	var implementations = [];
    	var implementsFunc;
    	var extendsFunc;
    	var privateCtor;
    	/**
    	 * Ověřuje, zda třída používá dané rozhraní
    	 * @param {Object} o
    	 * @return {Boolean}
    	 */
    	var implementationFunc = function(o) {
    		if (o !== defs) {
    			if (implementations.indexOf(o) != -1) {
    				return true;
    			}
    		}
    		return false;
    	};
    	/**
    	 * Slouží k podědění třídy
    	 * @param {Function} parent Třída k podědění
    	 * @return {{class: Function, privateAccessor: Function}}
    	 */
    	extendsFunc = function(parent) {
    		if (typeof parent === "function") {
    			c.prototype = Object.create(parent.prototype);
    			c.super = parent.prototype;
    			c.prototype.super = function() {
    				parent.apply(this, arguments);
    				//parent.apply(this, arguments);
    			};
    			// Zkopírujeme také statické vlastnosti z předka
    			for (var prop in parent) {
    				if (parent.hasOwnProperty(prop) && prop != "super" && prop != "constructor") {
    					c[prop] = parent[prop];
    				}
    			}
    			// Musíme znovu nabalit vlastní metody, protože podědění nám smaže existující prototype - bohužel je to nevýhoda pozdního volání Extends
    			implementsFunc(defs);
    			c.prototype.implementationOf = implementationFunc;
    			// Aktualizujeme
    			c.prototype.constructor = ctor;
    		}
    		return {
    			/**
    			 * @type {Function}
    			 */
    			class: c,
    			privateAccessor: p,
    			extends: extendsFunc,
    			implements: implementsFunc,
    			private: {
    				/**
    				 * @type {Function}
    				 */
    				constructor: privateCtor
    			}
    		};
    	};
    	/**
    	 * Slouží k převzení prvků z objektu/interface. Jedná se o mixing což se dá nazvat jako implementace rozhraní.
    	 * @param obj {Object} Objekt/interface
    	 * @return {{class: Function, privateAccessor: Function}}
    	 */
    	implementsFunc = function(obj) {
    		if (obj !== defs) {
    			if (implementations.indexOf(obj) != -1) {
    				console.log("Toto rozhranní je již implementováno");
    				return c;
    			}
    			implementations.push(obj);
    		}
    		// Přidáme vlastnosti na náš prototype
    		var objs = Object.getOwnPropertyNames(obj);
    		var count = objs.length;
    		var objsp;
    		for (var p = 0; p < count; p++) {
    			objsp = objs[p];
    			if (objsp != "static" && objsp != "private" && objsp != "constructor" && objsp != "private_constructor" && (typeof c.prototype[objsp] == "undefined" || obj === defs)) {
    				c.prototype[objsp] = obj[objsp];
    			}
    		}
    		if (obj === defs && !c.prototype.hasOwnProperty("destructor")) {
    			c.prototype.destruct = function() {
    				delete privateProperties[this.privateCallIdentifier];
    			};
    		}
    		return {
    			/**
    			 * @type {Function}
    			 */
    			class: c,
    			privateAccessor: p,
    			extends: extendsFunc,
    			implements: implementsFunc,
    			private: {
    				/**
    				 * @type {Function}
    				 */
    				constructor: privateCtor
    			}
    		};
    	};
    	var s;
    	// Uložíme statické vlastnosti
    	if (typeof defs.static === "object") {
    		for (s in defs.static) {
    			if (defs.static.hasOwnProperty(s)) {
    				c[s] = defs.static[s];
    			}
    		}
    	}
    	// Nabalíme požadované metody na konstruktor, využijeme naší metody Implements
    	implementsFunc(defs);
    	// Nastavíme constructor na konstruktor
    	c.prototype.constructor = ctor;
    	c.constructor = ctor;
    	c.prototype.implementationOf = implementationFunc;
    	// Privátní konstruktor
    	if (hasPrivateConstructor) {
    		var Trida = constructor;
    		Trida.prototype = c.prototype;
    		Trida.constructor = ctor;
    		//Trida.prototype.constructor = c;
    		privateCtor = Trida;
    	}
    	return {
    		/**
    		 * @type {Function}
    		 */
    		class: c,
    		privateAccessor: p,
    		extends: extendsFunc,
    		implements: implementsFunc,
    		private: {
    			/**
    			 * @type {Function}
    			 */
    			constructor: privateCtor
    		}
    	};
    };
    })();
    

     

    250 řádků nadbytečného balastu. :D

    Ale řekněte sami, jestli se to vyplatí.

     

    var Osoba = (function() {
    var p;
    
    var Cls = Class(
    	{
    		private: {
    			jmeno: "",
    			prijmeni: ""
    		},
    
    		constructor: function(jmeno, prijmeni) {
    			p(this).jmeno = jmeno;
    			p(this).prijmeni = prijmeni;
    
    			this.normalniPublicVlastnost = jmeno + " " + prijmeni;
    		},
    
    		getJmeno: function() {
    			return p(this).jmeno;
    		},
    
    		getPrijmeni: function() {
    			return p(this).prijmeni;
    		},
    
    		toString: function() {
    			return "Osoba:" + this.getJmeno() + " " + this.getPrijmeni();
    		}
    	}
    );
    
    p = Cls.privateAccessor;
    
    return Cls.class;
    })();
    

     

    Zápis je kvůli přístupu k privátním vlastnostem trochu složitější. Je třeba, aby má fce Class vracela accessor k private vlastnostem a kvůli tomu musí být také celá třída obalena anonymní fcí, aby tento accessor nebyl veřejný.

     

    BTW dřívější zápis bez private vlastností vypadal takto:


    var Trida = Class({
    constructor: function(foo, bar) {
    this.foo = foo;
    this.bar = bar;
    },
    pblicMethod: function() {
    ...
    }
    });
    
    

    Žádná věda, jen zavolám třídu se seznamem metod a hotovo.

     

    Třídu osoba můžeme jednoduše podědit.

    var Student = (function() {
    var p;
    
    var Cls = Class({
    	private: {
    		skola: ""
    	},
    
    
    	constructor: function(jmeno, prijmeni, skola) {
    		this.super(jmeno, prijmeni);
    		p(this).skola = skola;
    	}
    }).extends(Osoba);
    
    p = Cls.privateAccessor;
    
    return Cls.class;
    })();
    

     

    Funkce Class vrací objekt, který obsahuje pár položek. Hlavní položka je class, která obsahuje výslednou funkci s prototypem, která tvoří třídu. Další položky jsou třeba právě extends a implements. Obě položky jsou fce, které naši třídu upraví. Extends vytvoří potomka třídy. Implements načte položky z "interface" v JS obyčejný objekt. To by samo o sobě ale nemělo moc velký význam, tak je k prototypu všech tříd přidána metoda .implementationOf(), kterou můžeme ověřit, jestli je třída implementací některého rozhranní.

     

    Zde jsme tedy použili fci extends a podědili jsme třídu Osoba. Z konstruktoru i volám konstruktor předka.

    BTW, ony privátní vlastnosti jsou ve skutečnosti spíše protected, protože v JS nemá moc cenu řešit private/protected, protože i kdyby vlastnosti neexistovali, tak si je JS přece vytvoří, když je poprvé použijeme.

     

    A není problém ani vytvořit Singleton.

    var Trida = (function() {
    var p;
    
    
    // Private static property - uchování instance singletonu
    var instance = null;
    
    
    var Cls = Class({
    	private: { // Private
    		jmeno: "unknown",
    
    
    		constructor: function(jmeno) { // Private konstruktor
    			p(this).jmeno = jmeno;
    		}
    	},
    
    
    	static: { // Public static
    		coToJe: "no přece public static property",
    
    		getInstance: function(jmeno) {
    			if (instance === null) {
    				instance = new Cls.private.constructor(jmeno); // Private konstruktor je uložen vždy v return objektu fce Class() tedy Cls.private.constructor
    			}
    
    			return instance;
    		}
    	},
    
    
    	getJmeno: function() {
    		return p(this).jmeno;
    	}
    });
    
    p = Cls.privateAccessor;
    
    return Cls.class;
    })();
    

     

    Dal jsem si s tím práci, takže když budete chtít vytvořit instanci třídy, tak vám to vyhodí vyjímku, protože konstruktor je private. A jak se uvnitř dostanete ke svému private konstruktoru? Uvidíte v public static metodě getInstance(). Private konstruktor dostanete v tom objektu od fce Class.

     

    Abych ještě ukázal tu implementaci interface.

    var Serializable = {
    serialize: function() { throw new Error("Metoda serialize() není implementována."); }
    };
    
    
    var Test = Class({
    constructor: function(jmeno, prijmeni) {
    	this.jmeno = jmeno;
    	this.prijmeni = prijmeni;
    },
    
    serialize: function() {
    	return JSON.stringify({className: "Test", properties: {jmeno: this.jmeno, prijmeni: this.prijmeni}});
    }
    }).implements(Serializable).class;
    
    
    var t1 = new Test("Foo", "Bar");
    console.log("t1.implementationOf(Serializable)", t1.implementationOf(Serializable)," t1.serialize():", t1.serialize());
    

     

    Když implementujeme interface, tak bychom ho asi měli opravdu implementovat, takže když řeknu Class().implements(Serializable), tak si funkce implements kontroluje, jestli jsou položky Interface implementované nebo ne. Pokud ne, vypíše chybu do konzole, ale přitom nechá akce doběhnout a nahraje na chybějící místo to co je v Interface. I když bych tedy neimplementoval metodu serialize, tak by ji fce implements vytvořila, zkopírovala by implementaci přímo z Interface. V interface mám ale v té metodě vyhození vyjímky, takže to ani tak neprojde. Ale kdybych tam to vyhození vyjímky neměl, vše by fungovalo.

     

    Pokud máte dotazy nebo připomínky, ptejte se.

     

    Doufám, že se vám to bude někomu hodit.

     

    I když nevím co si myslím. :D Všichni na to serete. :D

  6. Zdravím,

     

    tak tu mám další díl a vzal jsem to celkem hopem.

     

    Přeskočil jsem původně plánovaný druhý díl, který měl jen rozšířit základ z prvního dílu. Bylo by tam maximálně jen nějaké hraní s třídami, které tedy podle Jamiho není zajímavé, a tak jsem přešel rovnou na díl třetí.

     

    V tomto díle si společně vytvoříme TODO list, kde klient bude v AngularJS a na pozadí bude Node.js server, který bude poskytovat CRUD operace a dohromady tak tvořit REST API.

     

    Hned na úvod říkám, že implementuji pouze CRU operace a D nechám na vás. Chtěl bych vidět vaše implementace, aby se mi dostala nějaká zpětná vazba. D bude opravdu jednoduchý, takže nezoufejte. :)

     

    Aby toho najednou nebylo moc, tak zatím do toho ještě nezapojím databáze. Data budeme uchovávat v operační paměti, tzn. jen ukládat do proměnné.

     

     

    Server

     

    Struktura serveru, kterou si však můžete přizpůsobit dle sebe.

    • core
      • Model.js
      • Server.js

      [*]public

      • app.js
      • controllers.js
      • index.html
      • style.css

      [*]app.js

    Základem je spouštěný script, který jsem si nazval app.js. V souboru bude jen pár řádků na inicializaci serveru. Zbytek bude rozdělen do tříd.

     

    app.js

    
    

    // Náš server modul

    var Server = require("./core/Server.js");

     

    // Spustíme server

    var server = new Server("127.0.0.1", 80);

    server.run();

     

    Nahoře načítáme náš modul Server.js, který ještě nemáme vytvořený.

    Po jeho načtení vytváříme instanci serveru, předáváme mu IP a port a zavoláme jeho metodu run(), která server spustí.

     

     

    core/Server.js - rozhraní

    
    

    var http = require("http");

    var path = require("path");

    var nodeStatic = require("node-static");

     

    /**

    * @type {Model}

    */

    var Model = require("./Model.js");

     

    /**

    * Třída Server vytvářející HTTP server, který odesílá statické soubory a tvoří REST API

    * @param {String} ip

    * @param {Number} port

    * @constructor

    * @class

    */

    var Server = function (ip, port) {

    /**

    * IP serveru

    * @type {String}

    */

    this.ip = ip;

     

    /**

    * Port

    * @type {Number}

    */

    this.port = port;

     

    /**

    * http server

    * @type {http.createServer}

    */

    this.httpServer = null;

     

    /**

    * Model

    * @type {Model}

    */

    this.model = new Model();

     

    /**

    * node-static server pro odesílání statických souborů

    * @type {nodeStatic.Server}

    */

    this.fileServer = new nodeStatic.Server(path.resolve(__dirname, "..", "public"), {cache: 7200});

    };

     

    /**

    * Metoda zpracovávající příchozí requesty

    * @param request

    * @param res

    * @private

    */

    Server.prototype.acceptRequest = function(request, res);

     

    /**

    * Zpracování normálního get požadavku

    * @param request

    * @param response

    */

    Server.prototype.processGet = function(request, response);

     

    /**

    * Posbírá data z body requestu a vrátí je do callbacku

    * @param request

    * @param callback

    */

    Server.prototype.getBodyContent = function(request, callback);

     

    /**

    * Zpracování REST API GET požadavku

    * @param request

    * @param res

    */

    Server.prototype.processRestGet = function(request, res);

     

    /**

    * Zpracování REST API POST požadavku

    * @param request

    * @param res

    */

    Server.prototype.processRestPost = function(request, res);

     

    /**

    * Zpracování REST API PUT požadavku

    * @param request

    * @param res

    */

    Server.prototype.processRestPut = function(request, res);

     

    /**

    * Zpracování REST API DELETE požadavku

    * @param request

    * @param res

    */

    Server.prototype.processRestDelete = function(request, res);

     

    /**

    * Metoda ukončující request a odesílající response

    * @param {Number} code HTTP return code

    * @param {String} contentType

    * @param {String|Object} data

    * @param request

    * @param res

    */

    Server.prototype.return = function(code, contentType, data, request, res);

     

    /**

    * Spuštění serveru

    */

    Server.prototype.run = function();

     

    module.exports = Server;

     

    Nejprve ukazuji "rozhraní" třídy, abyste měli nějaký ucelený přehled o metodách a vlastnostech.

    Kód klidně použijte a zkopírujte si ho do souboru. Postupně jej budeme doplňovat o implementace.

     

    Nahoře načítám dva základní moduly http a path. Http modul již znáte z předchozího dílu. Druhý modul path slouží pro práci s cestami. Umí ověřovat existenci cest a umí i tvořit cross-platform cesty. Třetí modul je node-static. 3rd side modul pro posílání statických souborů. Tento modul si stáhněte pomocí npm viz následující odstavec.

     

    Otevřete si konzoli a přejděte do složky s aplikací, tam kde máte app.js. A spusťte "npm install node-static". Stáhne se vám modul a jeho vazby. Vytvoří se vám složka node_modules, do které se umisťují všechny moduly.

     

    Teď trochu vysvětlím, jak má tato třída fungovat a co má která metoda dělat.

     

    Základem je konstruktor třídy, který přijímá dva parametry, a to port a IP.

    Vytváříme zde node-static Server a říkáme mu odkud bude brát statické soubory. Statické soubory, které node-static bude posílat jsou klientské soubory html, js a css pro naši app.

    Nakonec zde vytváříme to nejdůležitější, http server. Jako parametr mu předávám anonymní callback, který volá naši metodu.

     

    Takže pokaždé když přijde dotaz na náš server, tak se spustí naše metoda acceptRequest, která obdrží 2 parametry, a to request a response.

     

    acceptRequest

    
    

    // Zapíšeme čas startu

    console.time("request-time");

     

    var api = request.url.split("/");

    api.shift();

     

    if (!!api && api[0] == "rest") {

    switch (request.method) {

    case "GET":

    return this.processRestGet(request, res);

    case "POST":

    return this.processRestPost(request, res);

    case "PUT":

    return this.processRestPut(request, res);

    case "DELETE":

    return this.processRestDelete(request, res);

    case "OPTIONS":

    return this.return(200, "application/json", "", request, res);

    default:

    return this.return(501, "application/json", {error: "This type of action is not supported."}, request, res);

    }

    }

     

    this.processGet(request, res);

     

    Co tahle metoda dělá? Myslím že je to na první pohled jasné. Tato metoda je pouze rozcestník našeho serveru. Podle URL adresy z requestu rozhodujeme co se stane.

     

    Na začátku mám console.time(), je to trigger na měření času. Mám to zde jen pro přehled abyste věděli jak dlouho serveru trvá odpověď na vaše budoucí dotazy.

     

    Pod tím si parsuju url do pole. Url je jen jeden hloupý parametr. Když zadáte do prohlížeče libovolnou URL směřovanou na váš server, tak se vždy provede to samé. Vždy zavoláte stejný kód a URL je jen parametr se kterým si můžete dělat co chcete. Kdo říká, že má být formát URL domena.tld/neco/neco?neco=neco&dalsi=neco? Je to sice základní formát, ale je jen na vás jak váš server bude zpracovávat požadavky. Klidně můžete přijímat domena.tld/neco-neco|neco=neco+dalsi=neco. Vždy to bude jen stringový parametr v requestu a je jen na vás, jak si tento string zpracujete.

    Já tedy používám základní lomítkový formát, proto dělím string podle lomítka. Hned v zápětí odstraňuji první položku pole, protože ta bude vždy prázdná, protože url vždy začíná lomítkem, předním nic není.

     

    Po rozdělení URL následuje rozcestí kam dál. Pokud je URL je tvaru /rest/... půjdeme na switch a přesměrujeme dotaz na metodu zpracovávající konkrétní method requestu (POST, GET, PUT, DELETE). AngularJS ještě odesílá před každým XHR (AJAX) dotazem OPTIONs, kterým si ověřuje platnost akce, takže OPTIONS musí vracet kód 200. Pokud bude metoda jiná než těchto pět, vrátíme error.

     

    Celé API stavíme na JSONu, takže pokud náš server bude něco vracet, vždy to musí být ve formátu JSON. I když to bude chyba, musí být zabalená v JSONu, aby s tím potom mohly klienti počítat.

     

    Pokud není dotaz na /rest, tak pokračujeme do metody processGet, která zpracovává normální GET požadavek.

     

    proccessGet

    
    

    var self = this;

     

    this.fileServer.serve(request, response, function (err, res) {

    if (err) {

    console.log("Chyba při zpracování přenosu statického souboru " + request.url, err);

     

    // Vrátíme chybu

    self.return(404, "text/plain", err.message, request, response);

    } else {

    console.log("Přenos statického souboru " + request.url);

    }

    });

     

    Tato metoda vezme požadavek a předá ho node-static serveru. Asi není třeba nic vysvětlovat. Buď bude soubor nalezen a odešleme ho nebo vrátíme 404.

     

    To nás přivádí k metodě return, kterou volám už i v předchozí metodě.

     

    return

    
    

    // Nastavíme hlavičku

    res.writeHead(code, { 'Content-Type': contentType, 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE,OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type'});

     

    // Pokud se nejedná o String

    if (contentType == "application/json" && typeof data != "string") data = JSON.stringify(data);

     

    // Odešleme data

    res.end(data);

     

    // Vypíšeme si dobu zpracování dotazu

    console.timeEnd("request-time");

     

    Zde zpracováváme odpověď na dotaz.

    Na začátku odesíláme hlavičky. Říkáme jaký je návratový kód, content-type a říkáme jaké povolujeme metody, na jaký server a co povolujeme v hlavičce.

    Pak si jen případně upravíme data a výsledek odešleme.

     

    Další metoda z našeho rozcestí acceptRequest je processRestGet

     

    processRestGet

    
    

     

    console.log("GET požadavek na: ", request.url);

     

    var getUrl = request.url.split("/");

    getUrl.shift();

    var what = getUrl[2];

     

    // Get dotaz na /rest

    if (getUrl[0] == "rest" && (typeof getUrl[1] == "undefined" || getUrl[1] == "")) {

    this.return(200, "application/json", {

    get: {

    url: "/rest/get/",

    actions: [

    {

    name: "todolist",

    url: "/rest/get/todolist"

    },

    {

    name: "ukol",

    url: "/rest/get/ukol/",

    params: [

    {

    name: "id",

    type: "number",

    desc: "ID požadovaného úkolu",

    url: "/rest/get/ukol/id"

    }

    ]

    }

    ]

    },

    post: {

     

    },

    put: {

     

    },

    delete: {

     

    }

    }, request, res);

    return;

    }

     

    switch (what) {

    case "todolist":

    return this.return(200, "application/json", this.model.getUkoly(), request, res);

     

    case "ukol":

    return this.return(200, "application/json", this.model.getUkol(getUrl.pop()), request, res);

     

    }

     

    this.return(501, "application/json", { error: "This action is not implemented!" }, request, res);

     

    Původně byla tato metoda podstatně kratší, ale přidal jsem tam podmínku, aby se klient mohl zeptat na API. Na zátku metody si tedy opět rozeberu URL. Pokud je dotaz pouze na /rest/, vrátím mu JSON popisující API. Vytvořil jsem to jen v rychlosti. Ani jsem moc nevěděl jak to popsat. Když poskytujete REST API jen sobě, tak to nepotřebujete. Jen pokud byste dělali nějaké veřejné služby, tak byste asi měli vracet nějaký ten popis.

     

    Pod dotazem na API je switch s možnými akcemi. /rest/get/todolist a /rest/get/ukol/id.

    Tyto akce už rovnou komunikují s Modelem a vytahují z něj požadovaná data. Pokud nebude akce nalezena, vrátíme error.

     

     

     

    No, už se mi to nechce všechno tak moc dopodrobna projíždět, protože je toho celkem dost. Jen jsem se teď zamyslel nad tím, kolik metod ještě je a jaké to má všechno souvislosti, že jsem si řekl, že to zde všechno vypisovat nebudu. Text by byl strašně dlouhý a u konce by vás to už stejně nebavilo číst, protože základní věci stejně ovládáte, takže si funkcionalitu dokážete vyvodit. ;)

     

    Hodím tedy celý projekt na bitbucket, odkud si ho můžete v klidu stáhnout.

    https://bitbucket.org/HookCZ/nodejs-serial-rest-todolist

     

    Pokud byste měli nějaké problémy nebo chtěli něco vysvětlit, tak piště.

     

    Aplikaci nastartujete spuštěním app.js.

    Pak stačí najet v prohlížeči na adresu serveru nastavenou v app.js a stáhnou se vám statické soubory pro klienta a zobrazí se vám gui. Na serveru v modelu jsou staticky uloženy 2 úkoly, takže se nedivte proč už tam 2 úkoly jsou. Můžete přidávat nové úkoly a měnit jejich stav. Smazat je však nemůžete a to je úkol pro vás. Chci abyste sem přidali vaše řešení pro mazání úkolů. Základ je asi řešení na serveru, protože klient není součástí seriálu, ale pokud postnete i úpravu klienta, tím líp.

     

    App si můžete libovolně rozšířit o další funkcionality. Úkol má teď pouze jeden text, tak to můžete rozdělit na title a popisek. Můžete umožnit editaci textů u úkolů. Vše co upravíte sem můžete postnout, pokud se tím budete chtít pochlubit a nechat si to třeba i zkontrolovat.

     

    V dalším díle si do našeho TODO listu přidáme MySQL databázi.

  7. Už mám rozepsaný druhý díl, ve kterém tvořím REST TODO app. Server jak jinak než Node.js a client je v Angularu.

    Ve druhém díle do toho zatím ještě nezapojuji DB, protože už i bez ní je toho celkem dost. Data se uchovávají v proměnné, tedy v RAMce. Je dobré si tím ukázat, že Node.js je prostě něco jiného než PHP, na které jsou všichni zvyklí a že data se dají uchovat i v té RAMce, protože ta app normálně stále běží, tak proč dělat s DB, když to můžu nechat v proměnné. Typické využití pro chat, pokud nechci ukládat historii. Úkoly z TODO listu by se asi měly ukládat, ale zatím to tak stačí.

    O nahození DB do té TODO app bude díl třetí.

  8. Zdravím,

     

    posledních pár let se věnuji JavaScriptu a konkrétně Node.js skoro rok. Předpokládám, že z mých dřívějších příspěvků na fóru již víte, co to Node.js je, takže to nemusím rozvádět.

     

    Hned v počátku mého nástupu na Node.js jsem si vzal velký krajíc, obecně jako vždy když začínám něco nového, a pustil jsem se do tvorby webového frameworku. Webový framework na Node.js je něco co zatím ještě neexistuje nebo alespoň ne v takovém měřítku, jak klasické webové frameworky, například z PHP, vnímáme v dnešní době. To že žádný takový framework zatím ještě neexistuje je má motivace. Node.js je svým způsobem novinka. Spousta lidí říká, že Node.js není určený k tvorbě webových stránek. Ale proč k tomu není určený? No protože třeba pro PHP existuje spousta hotových frameworků a k nim spousta CMS, eshop systémů aj. Proč tedy dělat něco nového? Node.js pro to není určené, protože se to nikomu dělat nechce. Node.js je už spíše na úrovni C#, Javy a jiných nízkoúrovňových jazyků. Takže webový framework se na Node.js vytvoří asi stejně jako v C# nebo Javě. Vytvoříte Socket server, který poslouchá na nějakém portu, přijmete požadavek, uděláte nějakou magii a pošlete výsledek z5. Tak to prostě funguje, avšak s tím rozdílem, že Node.js má připravenou funkcionalitu k http komunikaci, takže vytvořit na něm webový framework bude o chlup jednodušší než třeba v holém C# či Javě.

     

    Motivací pro tento seriál je právě rozšíření znalostí v oblasti Node.js, a tím podpoření mého frameworku. Název mého frameworku je Jumbo. Už teď je Jumbo schopný vytvořit plnohodnotné stránky stejně jednoduše jako to jde s Nette. Nette je mimo jiné předlohou Jumba, protože Nette je v česku široce rozšířený framework, a tak se s ním lidem bude lépe pracovat, když bude mít podobné konstrukce.

     

    Co vše si tedy v seriálu řekneme/ukážeme?

    V úvodu si společně zprovozníme Node.js a vytvoříme jednoduchou aplikaci, ke které budeme moci přistoupit z prohlížeče.

     

     

    Malé opakování JS

    Node.js je eventově řízený jazyk. To v podstatě znamená, že všechny akce - nebo alespoň většina - běží "asynchronně". Proto je Node.js plné callback funkcí bez kterých by to prostě nešlo.

     

    Proto si v rámci opakování ukážeme práci s anonymními fcemi a práci s closure a scope.

    A právě scope a closure jsou dle mého "dvě" věci, které dělají lidem v JS problém, proto ho moc lidí nevyužívá na programování, protože pochopení scope a closure v JS není jednoduché a je příčinou hodně problémů.

     

    První ukážu kód a následně jej vysvětlím.

     

    /**
    * Proměnná definovaná v globálním scope
    * @type {String}
    */
    var globalniPromenna = "global";
    var druhaGlobalniPromenna;
    var tretiGlobalniPromenna;
    (function () {
    var prvniLokalniPromenna = "Ahoj ";
    })();
    (function () {
    druhaGlobalniPromenna = ", Nazdar ";
    })();
    (function () {
    tretiGlobalniPromenna = ", Zdravím ";
    });
    (function (param) {
    var lokalniPromenna = ", Čau ";
    /**
     *
     * @param {String} parametr
     * @param {Function} callback
     * @returns {String}
     */
    var anonymniFunkce = function (parametr, callback) {
    	var prvniGlobalniPromenna = prvniGlobalniPromenna || "";
    	if (!callback) {
    		throw new Error("Nemáte nastaven callback");
    	}
    	if (typeof callback != "function") {
    		throw new Error("Callback není funkce");
    	}
    	setTimeout(function () {
    		if (parametr == "throw") {
    			throw new Error("Je libo nezachytitelnou vyjímku?");
    		}
    		if (parametr == "callback-error") {
    			callback(null, new Error("Tady máš ten svůj error, klidně si ho chyť."));
    			return;
    		}
    		callback(typeof parametr == "string" ? (globalniPromenna + " " + param + ": " + prvniGlobalniPromenna + druhaGlobalniPromenna + tretiGlobalniPromenna + lokalniPromenna + " | " + parametr).toUpperCase() : "Tak když mi nedáš data co chci, tak ti vrátím prd!");
    	}, 1000);
    };
    var result;
    
    // Zavoláme se správným parametrem, ale bez callbacku
    try {
    	result = anonymniFunkce("Test");
    } catch (err) {
    	console.log("Špatné volání vrátilo: ", result);
    	console.log(err.message);
    }
    
    // Zavoláme se správným parametrem, ale se špatným callbackem
    try {
    	result = anonymniFunkce("Test", "Zde má být callback fce, ne string");
    } catch (err) {
    	console.log("Druhé špatné volání vrátilo: ", result);
    	console.log(err.message);
    }
    
    // Zavoláme se špatným parametrem
    try {
    	result = anonymniFunkce(true, function (data) {
    		alert(data);
    	});
    	console.log("Třetí špatné volání vrátilo: ", result);
    } catch (err) {
    	console.log(err.message);
    }
    
    // Konečně zavoláme správně
    try {
    	var dalsiPromenna = "A výsledek je: ";
    	result = anonymniFunkce("Test", function (data) {
    		alert(dalsiPromenna + data);
    	});
    	console.log("Správné volání vrátilo: ", result);
    } catch (err) {
    	console.log(err.message);
    }
    
    // Teď si zkusíme chytit asynchronní Error
    try {
    	result = anonymniFunkce("throw", function (data) {
    		alert(dalsiPromenna + " | " + data);
    	});
    	console.log("Throw volání vrátilo: ", result);
    } catch (err) {
    	console.log(err.message);
    }
    
    // Teď si zkusíme chytit callback Error
    try {
    	result = anonymniFunkce("callback-error", function (data, err) {
    		if (err instanceof Error) {
    			console.log("Callback chyba: ", err.message);
    			return;
    		}
    		alert(dalsiPromenna + " | " + data);
    	});
    	console.log("Callback err volání vrátilo: ", result);
    } catch (err) {
    	console.log(err.message);
    }
    })("pozdravy");
    

     

    Tak script je delší než jsem původně plánoval, ale je to tak dobře. Script ukazuje práci se scope a closure a zároveň ukazuje fci callbacků a zachytávání chyb v Node.js.

     

    Když se na script podíváte, uvidíte několik anonymních fcí a několik proměnných.

    Dokážete si tipnout, jaké výsledné data vrátí callback správného volání?

     

    Script si vyzkoušejte normálně v prohlížeči a podívejte se, co vrátí konzole.

     

    tmp.html:48 Špatné volání vrátilo:  undefined
    tmp.html:49 Nemáte nastaven callback
    tmp.html:57 Druhé špatné volání vrátilo:  undefined
    tmp.html:58 Callback není funkce
    tmp.html:67 Třetí špatné volání vrátilo:  undefined
    tmp.html:81 Správné volání vrátilo:  undefined
    tmp.html:93 Throw volání vrátilo:  undefined
    tmp.html:110 Callback err volání vrátilo:  undefined
    tmp.html:29 Uncaught Error: Je libo nezachytitelnou vyjímku?(anonymous function) @ tmp.html:29
    tmp.html:103 Callback chyba:  Tady máš ten svůj error, klidně si ho chyť.
    

     

    Všechny návratové hodnoty jsou undefined, protože volaná fce jednoduše nic nevrací. To co má fce vrátit, tak předává do callbacku jako parametr.

     

    Vysvětlení scriptu

    Ještě před vysvětlením scriptu vám ukážu jednoduché příklady na dílčí problémy

     

    Scope a closure

    Closure je zapouzdření, které vytvoříme pomocí function() {}

    Vytvořením closure nám v podstatě vzniknou 2 scope, a to globální a lokální.

    Proměnné, které jsou deklarovány uvnitř funkce jsou lokální a mimo tuto funkci nejsou viditelné.

    Naopak vše co bylo vytvořeno nad function() {} je pro closure jeho globální scope.

     

    Příklad

    
    var opravduGlobalniPromenna = ", která je definovaná ve skutečném globálním scopu aplikace, není zapouzdřena žádnou fcí.";
    
    // Vytvoříme closure; anonymní fci, kterou i ihned zavoláme
    (function() {
    	var lokalniPromenna = "Lokální promenná pro toto closure";
    
    	var funkce = function() {
    		var dalsiLokalniPromenna = "Lokální promenná pro toto closure";
    
    		// dalsiLokalniPromenna je pro toto closure lokální proměnnou a localniPromenna je globální proměnnou společně s opravduGlobalniPromennou
    		// Zde jednoduše vidím vše co bylo definováno zde i dříve; Co však bylo definováno zde, není vidět venku
    	};
    })();
    

     

    Schválně jsem se díval ještě na internet, jak bych to mohl pěkně vysvětlit a líbil se mi příklad podobný tomuto.

    <script>
    function soucet(a) {
    return function(b) {
    	return a + b;
    }
    }
    
    var prictiPetKCislu = soucet(5);
    console.log(prictiPetKCislu(10)); // Vrátí 15
    
    var pristiOsmKCislu = soucet(8);
    console.log(prictiPetKCislu(3)); // Vrátí 11
    </script>
    

    Vytvořil jsem funkci soucet, která přijímá jeden parametr. Uvnitř této funkce v lokálním scope je dostupný tento parametr a.

    Funkce nedělá nic jiného, než že vrací anonymní fci, která také přijímá jeden parametr.

    Uvnitř anonymní fce je dostupný parametr b, který je v lokálním scope, a parametr a, který fce zdědila z globálního scope.

    Je jedno, že jsme funkci soucet zavolali dříve než fci, kterou nám soucet vrátil. Vrácená anonymní fce si svá scope pamatuje celý život aplikace.

     

    Když tedy v prvním případě použití zavolám fci soucet() s parametrem 5, uloží se mi do proměnné prictiPetKCislu anonymní fce, tedy:

    prictiPetKCislu = function(b) {
    return a + b;
    };
    

    K tomu si tato fce pamatuje její globální scope, tedy ten scope ve kterém byla vytvořena. Tím pádem si bude celý zbytek aplikace pamatovat, že existuje nějaká proměnná a a že její hodnota je 5. Může tedy vrátit součet proměnné a a svým parametrem b.

     

    Konečně vysvětlení scriptu

    Doufám, že jste již problematiku scope pochopili, tak pro vás již nebude problém pochopit hlavní script.

     

    Na začátku sciptu deklaruju 3 globální proměnné. Hodnotu přiřazji jen první proměnné. Hodnota ostatních zůstává undefined.

     

    Poté tam mám 3 anonymní fce. První vytváří jen lokální proměnnou, takže dál ve scriptu nebude vůbec dostupná a dotaz na tuto proměnnou vyhodí error.

    Druhá anonymní fce v pořádku přistoupí ke globální proměnné a přiřadí ji hodnotu.

    Třetí fce by udělala to samé, jenže ji pouze vytvářím, ale nevolám. Na konci chybí závorky () naznačující volání. Hodnota této proměnné tedy zůstane undefined.

     

    Pak vytvářím anonymní fci na zapouzdření zbytku kódu. Tato fce přijímá jeden parametr.

    Uvnitř je deklarována lokální proměnná a je jí také přiřazena hodnota.

     

    Dále vytvářím fci a ukládám si ji do proměnné. Tato fce přijímá 2 parametry.

    Na začátku fce dělám nějakou magii s prvniGlobalniPromennou. Musím to udělat pro vaše zmatení. Kdybych to neudělal, tak později aplikace vyhodí error, protože do výpisu chci přiřadit tuto proměnnou, ale není deklarovaná. Proto ji deklaruji zde.

     

    Pod manévrem na zmatení je ověření callbacku. První testuji, jestli je vůbec callback nastaven. Pokud není, tak vyhodím vyjímku.

    To samé hned pod tím. Testuji, jestli je callback function.

    Tyto vyhozené vyjímky je možné zachytit try-catch blokem, protože je vyjímka vyhozena v době volání funkce.

     

    Pod ověřením mám setTimeout(), abych nasymuloval nějakou tu asynchronní akci.

    Uvnitř setTimeout() první ověřuji parametr. Parametrem vlastně říkám co se má stát, je to kvůli simulaci.

    První podmínka vyhodí vyjímku, kterou však nepůjde zachytit, protože script už je dávno za voláním této funkce, takže už je dávno mimo try-catch blokem. Vyjímka tedy nebude zachycena a script skončí chybou a dále nepokračuje.

    Druhá podmínka také hodí chybu, ale už stylem Node.js. Jednoduše ji předá do callbacku jako poslední parametr.

     

    No a nakunec tu máme náš výpis, jehož výsledek jste měli uhodnout.

     

    Pokud je parametr string, tak vracím:

    (globalniPromenna + " " + param + ": " + prvniGlobalniPromenna + druhaGlobalniPromenna + tretiGlobalniPromenna + lokalniPromenna + " | " + parametr).toUpperCase()
    

    globalniPromenna je normálně dostupná a obsahuje global

    param je pro volanou fci z globálního scope, je to parametr předaný anonymní zapoudřující fci. Je také dustupný a obsahuje pozdravy.

    prvniGlobaniPromenna obsahuje prázdný řetězec. Byla původně deklarována uplně nahoře, ale v lokálním scope. Já jsem ji předefinoval na prázdný řetězec, jinak by zde vyletěla chyba a script by skončil.

    druháGlobalniPromenna obsahuje , Nazdar.

    tretiGlobalniPromenna je undefined.

    localniPromenna obsahuje , Čau.

    a parametr se různí podle volání.

     

    Tohle všechno spojím a převedu na velké znaky a máme výsledek.

     

    Doufám že to chápete. Pokud ne, ptejte se. :)

     

     

    Instalace Node.js

    Instalace Node.js je poměrně jednoduchá.

    Jděte na adresu https://nodejs.org/en/download/stable/ a stáhněte si to co máte.

    Instalátory obsahují samotné Node.js a npm (package manager), který si rozhodně také nainstalujte.

     

    Myslím že instalaci nemusím nijak složitě vysvětlovat. Pokud by se vyskytly nějaké neočekávané problémy, tak vám jistě strejda google rád poradí.

     

     

    První Node.js script

    Tak a jdeme "noudit".

     

    Kdekoliv na disku si vytvořte soubor, například prvni-node-script.js

    Do toho souboru můžete již psát jakýkoliv JavaScript.

     

    Já jsem pro vás napsal základní scriptík na otestování:

    var a = 5;
    var b = 6;
    console.log("a = ", a);
    console.log("b = ", b);
    console.log("a + b = ", a + b);
    

     

    Myslím že script nemusím složitě popisovat.

    Vytvořil jsem 2 proměnné a vypsal jsem jejich součet.

     

    Jak script spustit?

    Normálně z konzole/terminálu.

     

    /misto/kde/mate/node.exe /misto/kde/mate/vas/prvni-node-script.js

     

    Můžete si zkusit hrát a trochu si to osahat.

    Ale s využitím základního JavaScriptu toho moc neuděláte. Obzvlášť když zde není žádné window, document a celá DOM struktura.

    Každopádně si můžete hrát s třídama a vytvořit si něco. Třeba nějakou hru s rytířem a drakem. :)

     

     

    HTTP server

    Zde se dostaneme k praktickému využití Node.js a už se konečně seznámíme i s nějakými věcmi, které Node.js k JavaScriptu přidává.

     

    První věc co musím vysvětlit je fce require(). Dělá přesně to co si asi myslíte. Načítá jiné soubory. Ale nedělá to asi tak jak si myslíte.

     

    Fce require opravdu načítá soubory, ale nevrací její obsah a už vůbec ho nevloží do místa volání jako require v PHP. Funkce require zavolá daný soubor a samostatně jej spustí. Tím samostatně myslím opravdu samostatně, protože se v daném souboru nelze dostat k proměnným ani jiným věcem ze souboru, který jej volá. -- Lze to přes globální objekt global, na který můžete přidávat vlastnosti, ale o tom se moc nemluví. --

     

    Na co tedy require je, když z něj nic nedostanu?

    Dostanete. Vše co chcete ze souboru (v Node.js jsou tyto requirované soubory spíše moduly) pustit ven, musíte nastavit do objektu module.exports.

     

    Pomocí require tedy načítáme moduly. Pokud načítáme základní moduly z Node.js, udáváme pouze jejich název, bez cesty, bez přípony souboru. Pokud chceme načíst naše vlastní moduly, musíme to udělat i s cestou a příponou souboru.

     

    Takže.. Abychom mohli vytvořit nějaký http server, budeme potřebovat modul http. Můžete si k němu dohledat informace v API, které je asi největším zdrojem informací.

     

    Podle API, http má metodu createServer, která pžijímá jeden parametr. Parametrem je funkce, která je zaregistrována jako requestListener -> tzn. když dojde na server request, zavolá se váše callback funkce.

    Váš callback dostane 2 parametry, request a response, díky kterým získáte informace o příchozím dotazu a dostanete možnost a něj i odpovědět.

     

    Rovnou ukážu základní příklad ze stránek Node.js

    const http = require('http');
    const hostname = '127.0.0.1';
    const port = 80;
    http.createServer(function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.write("A jak jinak začít než se známou klasikou.\n");
    res.end('Hello World\n');
    }).listen(port, hostname, function() {
    console.log("Server running at http://", hostname, ":", port);
    });
    

    Ve scriptu tedy vytváříme http server. Serveru dáváme callback, ve kterém zpracujeme každé příchozí spojení.

    Na serveru poté zavoláme metodu listen() a předáme jí IP a port, na kterém má server běžet.

     

    Nyní si můžete v prohlížeči normálně otevřít http://127.0.0.1 a uvidíte vaše Hello World!

     

    Nastavujeme content-type odpovědi na plain text, takže budou špatně vykresleny diakritické znaky.

    Trochu si to upravíme a vrátíme už nějaké to HTML.

     

    const http = require('http');
    
    const hostname = '127.0.0.1';
    const port = 80;
    
    http.createServer(function(req, res) {
    // Zapíšeme čas startu
    console.time("request-time");
    
    res.writeHead(200, { 'Content-Type': 'text/html' });
    
    res.write("<!DOCTYPE html>" +
    	"<html>" +
    		"<head>" +
    			"<meta charset='UTF-8'>" +
    			"<title>Hello World page - special HTML edition</title>" +
    	"<style>" +
    	"body, html {margin:0;padding:0;} body { background: #222; color: #eee; padding: 10px;}" +
    	"</style>" +
    		"</head>" +
    		"<body>" +
    			"<h1>Hello World</h1>" +
    			"<p>A co ta naše speciální diakritika?</p>" +
    		"</body>" +
    	"</html>"
    );
    
    res.end(); // Odešleme odpověď
    
    // Vypíšeme si dobu zpracování dotazu
    console.timeEnd("request-time");
    }).listen(port, hostname, function() {
    console.log("Server running at http://", hostname, ":", port);
    });
    

    Zde už vracíme plnohodnoutnou HTML stránku, plus jsem přidal výpis do konzole s dobou provádění dotazu.

     

    To je asi v tomto dílu vše. Pokud máte nějaké dotazy či připomínky, klidně piště.

    Budu rád, když vám to bude všem fungovat, zalíbí se vám to a u Node.js zůstanete. :)

     

    Kdy bude další díl těžko říct. Možná ho napíšu ještě dnes.

    V dalším díle bych bych už vytvořil nějaký vlastní modul. Nějak bych rozšířil tento základ.

     

    Až tak ve třetím díle bych se pustil třeba do využití Node.js jako pozadí pro AJAX.

    Vytvořil bych třeba AngularJS clienta a všechna data by tahal z Node.js serveru.

     

    No a samozřejmě jak jsem měl tento díl skoro hotový, tak jsem zmáčkl nějakou kravinu a přepsala se mi záložka. Když jsem dal z5, tak se objevil automaticky uložený obsah 20 minut z5. :D Teď už fakt nemám náladu to dopisovat, takže doplním zítra.

     

    EDIT: Doplnil jsem zatím část s opakováním.

    EDIT: Doplněno

  9. Po delší odezvě jsem zase něco málo udělal na frameworku. :)

     

    Vylepšil jsem formuláře, Locator, přidal jsem RAM cache pro templaty, podporu cookie, db, aj.

    Jako testovací app v repu je teď chat se správou místností.

     

    Do Jumba (btw název toho frameworku) se teď opřu, protože jsem s jedním člověkem dohodnutý, že mu na tom postavím e-shop.

    Samozřejmě jsem ho obeznámil s tím, co je Node.js zač a nabídl jsem mu za to podstatnou slevu, protože to budu brát i jako vlastní projekt.

     

    Tím pádem vlastně i zvýším potencionální zájem o Node.js - konkrétně o Jumba - protože už bude existovat i CMS a eshop systém, takže už by to mohlo mít i nějaké využití.

     

    Jinak s přidanou cache je jeden dotaz zpracovaný za cca 4ms v průměru. ;)

  10. Ty modré čtverečky ti přeskakují po určitém čase, ne jen po kliknutí. Takže si pravděpodobně klikl hned po tom, co se sám přesunul, tzn. klikl si na bílé. Zkus zapnout tu hru a na nic neklikej, jen se dívej. Cca po minutě je to už šílené. :D

  11. Tak základ frameworku prošel větším refaktoringem a jelikož jsem si totálně dosral repo, tak jsem byl nucen vytvořit nové. Teď už si snad udržím pořádek. https://bitbucket.or...Z/jumbo-node.js

     

    EDIT: A dokončil jsem linkování. FW už parsuje všechny možné URL adresy, které jsou povolené Locatorem. Zároveň jdou taky vytvořit všechny možné URL v template pomocí {link Presenter::action::parametrKteryBudeVURLOddelenyLomitkem::dalsiOddelenyLomitkem tohleUzJeQueryParametr,dalsi,5,6}

  12. Vytvořil jsem repo viz https://bitbucket.or...kCZ/jumbo-node. Můžete to prolézt a kritizovat.

     

    Mám zatím hotový jen nějaký základ. Zatím jsem se vůbec nedostal k modelům ani DB.

    Ale presentery a templaty jsou funkční. Samozřejmě templaty mají zatím jen základní funkce jako výpis proměnné z presenteru, if/else na proměnné, bloky a includy, linky a definice ({define nazev}obsah{/define}), což je vlastně block, ale obsah se zobrazí i na místě kde byl definován. Takto vytvořený block jde pak použít pomocí {use nazev} a jde ověřit existence definice pomocí {defined nazev}lorem ipsum {use nazev}...{/defined}.

     

    Pokud by to někdo zkoušel, tak jak je napsané v README v repu, tak je třeba potom nainstaloval moduly less a node-static (npm install less a npm install node-static). Pak to bude hlásit chybějící adresáře, protože je bitbucket nebere, tak je třeba je vytvořit ručně.

     

    BTW jeden požadavek je zpracovaný za 20ms a méně a to se zpracovávají templaty. ;)

  13. Na kompilaci a minifikaci LESSu používám přímo npm package od LESSu. Pak používám ještě node-static package na defaultní posílání statických souborů s tím, že v configu je to možné vypnout, aby si člověk mohl sám nakonfigurovat třeba nginx, který je na to rozhodně lepší. Zbytek věcí už si píšu sám, už jen kvůli tomu, abych se to pořádně naučil.

    Včera jsem napsal třeba pěkný lazy autoloader, kvůli require hell.

     

    Celkově už mám cca vytvořený základ frameworku a teď dělám na části aplikace, tzn. zpracování příchozích dotazů, načtení příslušného kontroléru a zavolání akce.

    Úplně konkrétně mám teď rozepsaný Locator, taková obdoba routů v Nette, samozřejmě i s podporou subdomén, protože u Node.js to za nás nezpracuje Apache, což je dle mého výhoda, to samé URL, které si člověk může nadefinovat úplně vlastní a zase není třeba řešit .htaccess. Subdomény budou sub-aplikace/moduly, něco jako má Nette, ale bude to víc stand-alone. Bude to mít i vlastní modely s tím, že klidně bude možné využít i ty z hlavní app.

     

    Sám jsem na to zvědavý co z toho vznikne. Jak si řekl, tak na to tedy asi žádný webový framework neexistuje (já jsem se tedy po žádném nedíval, tak spoléhám na tvou informaci), takže to může být taky ta příčina, proč se to na weby nepoužívá, protože aby si někdo napsal něco svého kvůli jednomu webu,.. to asi nemá cenu. Proto si myslím, že má cenu to alespoň zkusit, dát tomu šanci. Já si s tím celkem fandím, takže doufám, že se to pak uchytí. Pokud se mi to tedy povede. :D Každý chce po sobě přece zanechat nějaký odkaz ne? :)

  14. Framework bude MVC, možná s nějakou úpravou, protože bude dělaný na RIA, takže něco bude asi i u klienta, ale to bude záležet zase na aplikaci.

     

    Chci dělat templatovací systém á la Latte, aby bylo kradení české komunity od Nette jednodušší. :D

     

    Pak chci směřovat stylování na LESS. Už jsem tam udělal automatickou kompilaci LESS na CSS při spuštění serveru (+ se rovnou minifikuje), takže nikdo nic nemusí dělat, jen bude psát LESS a nemusí se starat o kompilaci, přeukládání, atd.. Samozřejmě není problém přidat třeba SASS, který se bude taky automaticky kompilovat. A kdo chce CSS, tak může psát jen CSS, je to šumák.

     

    Pak se u Node.js celkem řeší stahování/posílání statických souborů. Používám pro to node-static modul, který je v configu možné vypnout, aby si člověk mohl nastavit třeba nginx a statické soubory posílat přes něj, je minimálně 2x rychlejší než řešení v Node.js.

     

    Dále jsem přidal do configu možnost omezení počtu dotazů za sekundu, pokud se překročí, server bude vracet err 503 po zbytek sekundy. Společně s tím je tam "obrana" proti (D)DOS. Zase možnost nastavení počtu dotazů za sekundu, ale vzhledem k IP s tím, že pokud bude překročen, IP bude blokována po určenou dobu.

     

    Samozřejmě tam mám logování, statistiku (ještě nevím co v ní všechno bude, ale už ji mám v configu :D) a cachování s tím, že cache půjde vypnout nebo logicky zapnout :D a na výběr je jestli se má cachovat do RAMky nebo na disk.

     

    Pak chci apelovat na testování. Zase nastavitelné v configu, ale defaultně bude testování zapnuté. To znamená, že server při zapnutí bude hledat testovací třídu pro každou třídu v aplikaci. Pokud budou nějaké testy chybět nebo nebude vše správně, server se nezapne.

     

    Pokud máte nějaké tipy či přání, klidně říkejte. :)

     

    EDIT:

    Do týdne bych mohl mít alespoň nějaký základ, který můžu hodit na git k nahlédnutí.

    Ocenil bych, kdyby to pak nějací "návrháři" projeli a řekli svůj názor co se návrhu týká.

  15. Jo, na hostingy jsem se díval. Našel jsem právě Roští a jeden solidní zahraniční absolutně free hosting, který na vlastní počáteční aplikace bohatě stačí. Těžko říct, jestli by komerční aplikace neporušovala pravidla, ty jsem si bohužel nečetl.

     

    Ale jo, Node.js chce čas. Pomalu se to vyvíjí a až tak za ty 2 až 3 roky to bude použitelné, budou na to hostingy, aj.

     

    Info čerpám z https://nodejs.org/api/, případně google no. Ale jelikož jsem IMHO zkušený JavaScripter, tak pro mě ty konstrukce nejsou nové. Nové jsou jen ty přidané věci, práce se soubory, aj.

  16. Určo se mrknu. ;)

    Můj odhad na Node.js v ČR je tak 3-5 let. Tak či tak mám dost času na to postavit pořádný český framework, který by se mohl chytit. :)

     

    Jsem rád alespoň za jednoho nadšence Node.js a doufám, že se potom někdy připojíš ke komunitě frameworku, pokud ho tedy dodělám, bude za něco stát a nebude na ostudu. :D A pokud mi to tedy někdo nerozmluví. :D

  17. Čaute,

     

    nedávno jsem se pustil do Node.js a jak už to u mě bývá, tak znovu vynalézám kolo a píšu na něj framework. :D Chci vytvořit framework, který bude umožňovat vytvoření plně dynamické responzivní stránky na pár kliknutí, neboť je to dnes IN.

     

    Chci se vás zeptat, jaké máte vy zkušenosti a názory na Node.js, popř. jestli jste o něm vůbec slyšeli a víte co to je. :)

     

    Dle mého je Node.js suprová a hodně výkonná věc, ale ve světě plného hotových PHP řešení těžko hledá své místo. To je důvod proč chci vytvořit takový framework, aby lidi měli důvod k Node.js přejít, aby se to vyplatilo, aby opuštění hromady hotových frameworků, CMS, aj. mělo smysl. Tak jak v ČR udělalo bum Nette (protože je české,.. češi mají rádi české věci, komunita, podpora,..), tak by mohl udělat bum i můj framework, ale to už si moc fandím. :D Každopádně je to teoreticky možné. :)

     

    Tak do mě,.. Ukamenujte mě.

     

    EDIT: Upravil jsem název topicku.

×
×
  • Create New...