Alkalmazások fejlesztése

9. előadás

Horváth Győző
Egyetemi adjunktus
1117 Budapest, Pázmány Péter sétány 1/c., 2.420-as szoba
Tel: (1) 372-2500/1816
horvath.gyozo@inf.elte.hu

Eddig

  • Szerveroldali technológiák
  • Kliensoldali technológiák alapjai
  • DOM, jQuery, események
  • AJAX
  • Progresszív fejlesztés

Tartalomjegyzék

  • Vastagkliens alkalmazások
  • Ember.js
  • Fejlesztői környezet előállítása
  • Router, Route, sablonok
  • Ember.Object

Webes vastagkliens alkalmazások

Egyoldalas alkalmazások

Single Page Applications (SPA)

1990-es évek második fele

  • Webes technológia megjelenése
  • Az alapvető szabványok, technológiák kidolgozása
  • Böngészőháborúk
  • Üzleti szempontból megbízhatatlan a kliensoldal

2000-es évek eleje

  • A szerveroldali technológiák kidolgozása
  • Vállalati megoldások
  • Keretrendszerek
  • Kommunikációs formátumok és koncepciók
  • MVC

Szerveroldal feladatköre

  • Útvonalkezelés (routing)
  • üzleti logika
  • adatbázis-elérés
  • Felhasználói felület renderelése
  • Állapotkezelés
  • Munkamenetkezelés

2000-es évek második fele

  • AJAX
  • Élménygazdagabb kliensek
  • Asztali alkalmazásokhoz hasonló megoldások
  • Több kliensoldali kód
  • JavaScript nyelv újrafelfedezése
  • Modern programozási paradigmák kidolgozása
  • Progresszív fejlesztés (Unobstrusive JavaScript)
  • Böngészők fejlődése

2010-es évek első fele

  • Kliensoldali mint alkalmazásplatform
  • További technológiák (HTML5 API)
  • Keretrendszerek a nagyméretű kliensoldali kód kezelésére
  • Egyoldalas alkalmazások

Webes vastagkliensek feladatköre

  • Útvonalkezelés (routing)
  • üzleti logika (egy része)
  • adatbázis-elérés (lokálisan)
  • Felhasználói felület renderelése
  • Állapotkezelés

SPA-k helyzete

  • Folyamatos fejlődés jellemzi
  • Útkeresés
  • Felmerülő problémák (állapotkezelés, renderelés, aszinkronitás) megoldása

SPA keretrendszerek

  • Ember.js (2012)
  • Backbone (2012)
  • AngularJS (2013)
  • React+Flux (2014)
  • Továbbiak
  • Knockout

TODO MVC

Kitekintés

  • Inga túllengése
  • Szerveroldal visszatérése
  • Izomorfikus (univerzális) alkalmazások

Ember.js

Ember.js

  • Kliens oldali keretrendszer
  • Komplett alkalmazásokhoz
  • Interaktív alkalmazásokhoz
  • MVC

Szükséges ismeretek

  • HTML
  • CSS
  • JavaScript
  • jQuery
  • AJAX

Koncepciók (MVC)

  • Útvonalak (Route és Router)
    • Web alapja az URL
  • Modellek
    • Az útvonalhoz tartozó adat
  • Sablonok (Templates)
    • Kinézet (Handlebars)
  • Komponensek
    • egységbe zárt funkcionalitás (adat + megjelenés + viselkedés)
  • Controllers (deprecated, de szükséges)
    • Kapcsolat a sablonok és modellek között

Ember-CLI

  • Komplett rendszer
  • Command Line Utility (mint a nodejs)
  • Robosztusabb alkalmazásokhoz

ember-cli

Anyagok, dokumentációk

Fejlesztői környezet előállítása

Fejlesztői környezet előállítása

Hivatalosan

npm install ember-cli -g
ember new myapp

Fejlesztői környezet előállítása

Cloud9

  1. Új workspace (Custom)
  2. nvm use v0.12.7 (0.12.x)
  3. ember-cli nem telepíthető (kevés az erőforrás)
  4. ember node_modules letöltése
  5. Új mappa az alkalmazásnak
  6. node_modules feltöltése
  7. bower install -g
  8. ./node_modules/.bin/ember init --skip-npm

Beállítások

.ember-cli

{
  "disableAnalytics": false,
  "liveReload": false,
  "usePods": true
}

Beállítások

config/environment.js

var ENV = {
    modulePrefix: 'myapp',
    podModulePrefix: 'myapp/pods',
    // ...
}

Bootstrap

bower install --save bootstrap bootswatch

ember-cli-build.js

module.exports = function(defaults) {
  var app = new EmberApp(defaults, {
    // Add options here
  });

  app.import('bower_components/bootstrap/dist/css/bootstrap.css');
  app.import('bower_components/bootswatch/sandstone/bootstrap.min.css');
  app.import('bower_components/bootstrap/dist/js/bootstrap.js');

  return app.toTree();
};

Parancsok

./node_modules/.bin/ember generate route alma
./node_modules/.bin/ember generate template alma
./node_modules/.bin/ember generate component alma
./node_modules/.bin/ember generate resource alma

Útvonalkezelés

Router és route

  • Egyetlen objektum
  • Útvonalak regisztrációja
Router.map(function() {
  this.route('about', { path: '/about' });
  this.route('favorites', { path: '/favs' });
});

Hivatkozások a sablonban

{{#link-to "index"}}<img class="logo">{{/link-to}}

<nav>
  {{#link-to "about"}}About{{/link-to}}
  {{#link-to "favorites"}}Favorites{{/link-to}}
</nav>


A link in {{#link-to "index"}}Block Expression Form{{/link-to}},
and a link in {{link-to "Inline Form" "index"}}.

<p>
  {{link-to "Edit this photo" "photo.edit" photo class="btn btn-primary"}}
</p>

Komponensek

ember generate component my-component-name

Definíciós sablon

<article class="blog-post">
  <h1>{{title}}</h1>
  <p>{{yield}}</p>
  <p>Edit title: {{input type="text" value=title}}</p>
</article>

Használat

{{#blog-post title=post.title}}
  {{post.body}}
{{/blog-post}}

Feladatok

Készítsük el a hibakezelő alkalmazás oldalait a belső hivatkozásokkal és beégetett adatokkal!

Lépések

  1. Főoldal
  2. About oldal
  3. Listaoldal
  4. Új hiba felvitele oldal

További feladatok

Bontsuk komponensekre az oldalakat!

Object Model

Ember.Object

  • Alap osztály
  • Saját osztálynál ebből származtatunk
Person = Ember.Object.extend({
  say(thing) {
    alert(thing);
  }
});

Származtatás

  • Ősosztály fv-re hivatkozás: this._super
Person = Ember.Object.extend({
  say(thing) {
    var name = this.get('name');
    alert(`${name} says: ${thing}`);
  }
});
Soldier = Person.extend({
  say(thing) {
    this._super(thing + ', sir!');
  }
});

Példányosítás/Instancia

var connor = Soldier.create({
  name: 'John Connor'
});

connor.say('Yes'); // "John Connor says: Yes, sir!"

Attribútumok

  • Gyakorlatilag a Dictionary-ként működnek
  • get() és set() függvények
var person = Person.create();
var name = person.get('name');
person.set('name', 'John Connor');

Osztály módosítása

  • Osztály definíciója a módosítható
  • reopen() metódus
Person.reopen({
  isPerson: true
  isMachine: false
});

Person.create().get('isPerson') // true
Person.create().get('isMachine') // false

Osztály módosítása

  • Statikus property: reopenClass()-al adható
Person.reopenClass({
  mainOrder: "Protect John Connor"
});

Person.mainOrder // "Protect John Connor"

Metódus túlterhelés

  • Példányosítás után túlterhelünk
Person.reopen({
  say(thing) {
    this._super(thing + '!');
  }
});

Számított mezők

Person = Ember.Object.extend({
  firstName: null,
  lastName: null,

  fullName: Ember.computed('firstName', 'lastName', function() {
    return `${this.get('firstName')} ${this.get('lastName')}`;
  })
});

var johnConnor = Person.create({
  firstName: 'John',
  lastName:  'Connor'
});

johnConnor.get('fullName'); // "John Connor"

Számított mezők (lánc)

Person = Ember.Object.extend({
  firstName: null, lastName: null, born: null, country: null,

  fullName: Ember.computed('firstName', 'lastName', function() {
    return `${this.get('firstName')} ${this.get('lastName')}`;
  }),

  description: Ember.computed('fullName', 'born', 'country', function() {
    return `${this.get('fullName')}; Born: ${this.get('born')}; Country: ${this.get('country')}`;
  })
});

var johnConnor = Person.create({
  firstName: 'John', lastName: 'Connor',
  born: 1981, country: 'USA'
});

johnConnor.get('description'); // "John Connor; Born: 80; Country: USA"

Számított mező settere

Person = Ember.Object.extend({
  firstName: null, lastName: null,

  fullName: Ember.computed('firstName', 'lastName', {
    get(key) {
      return `${this.get('firstName')} ${this.get('lastName')}`;
    },
    set(key, value) {
      var [firstName, lastName] = value.split(/\s+/);
      this.set('firstName', firstName);
      this.set('lastName',  lastName);
      return value;
    }
  })
});

var johnConnor = Person.create();
johnConnor.set('fullName', 'John Connor');
johnConnor.get('firstName'); // John
johnConnor.get('lastName'); // Connor

Megfigyelő (Observer)

  • Attribútum módosulásakor lefutó esemény
  • Szinkron hajtódik végre (valójában amit lehetséges)
Person = Ember.Object.extend({
  // ...
  fullNameChanged: Ember.observer('fullName', function() {
    // Módosításkor lefutó kód
  })
});

var johnConnor = Person.create({
  firstName: 'John',
  lastName: 'Connor'
});

johnConnor.set('firstName', 'John'); // observer esemény

Megfigyelő (Observer)

  • Több property is megadható
  • Ilyenkor mindegyik módosításakor lefut
Person.reopen({
  partOfNameChanged: Ember.observer('firstName', 'lastName', function() {
    // Két property esetén is végrehajtódik az esemény
  })
});

johnConnor.set('firstName', 'John'); // observer esemény
johnConnor.set('lastName', 'Connor'); // observer esemény

Megfigyelő (Observer)

  • Megfigyelőt utólag is hozzáadhatunk
johnConnor.addObserver('fullName', function() {
  // ...
});

Kötések

  • Egyes osztályok attribútumai összeköthetőek.
  • A hivatkozott objektum módosulásakor módosul a hivatkozó objektum.
  • Nem frissül azonnal
  • Ember.computed.alias(...)
  • Kétirányú!

Kétirányú kötés

connor = Ember.Object.create({
  modelVersion: 800
});

Terminator = Ember.Object.extend({
  modelVersion: Ember.computed.alias('connor.modelVersion')
});

terminator = Terminator.create({
  modelVersion: connor
});

terminator.get('modelVersion'); // 800
connor.set('modelVersion', 1000);
terminator.get('modelVersion'); // 1000

Egyirányú kötés

Ember.computed.oneWay(...)

john = Ember.Object.create({
  fullName: 'John Connor'
});

Person = Ember.Object.extend({
  fullName: Ember.computed.oneWay('john.fullName')
});

person = Person.create({
  user: john
});

// Megváltozttja a person nevét, "John Connor" lesz
john.set('fullName', 'John Connor');
// Ez nem hat vissza
person.set('fullName', 'John Reese');
john.get('fullName'); // "John Connor"

Enumerátorok

  • Ember.Enumerable API
  • Javascriptes Array kiegészítés
  • Komfortosabb kezelés végett
  • Ember.Enumerable

Ember.Enumerable API

  • JS array Metódusok
    • pop, push, reverse, shift, unshift
  • Megfigyelt metódusok (Ember.js)
    • popObject, pushObject, reverseObjects, shiftObject, unshiftObject
  • További egyéb hasznos megfigyelt metódusok
    • myArray.get('firstObject')
    • myArray.get('lastObject')

Ember.Enumerable API Áttekintés

Iterálás

var food = ['Poi', 'Ono', 'Adobo Chicken'];

food.forEach(function(item, index) {
  console.log(`Menu Item ${index+1}: ${item}`);
});

Első, utolsó elem

var animals = ['rooster', 'pig'];

animals.get('lastObject');
//=> "pig"

animals.pushObject('peacock');

animals.get('lastObject');
//=> "peacock"

Transzformáció (map)

var words = ['goodbye', 'cruel', 'world'];

var emphaticWords = words.map(function(item) {
  return item + '!';
});
// ["goodbye!", "cruel!", "world!"]

Attribútum szelektálás (mapBy)

var hawaii = Ember.Object.create({
  capital: 'Honolulu'
});

var california = Ember.Object.create({
  capital: 'Sacramento'
});

var states = [hawaii, california];

states.mapBy('capital');
//=> ["Honolulu", "Sacramento"]

Szűrés

var arr = [1,2,3,4,5];

arr.filter(function(item, index, self) {
  return item < 4;
})

// returns [1,2,3]