ITHub

Ismerkedés a Vue.js keretrendszerrel - 2. rész - Űrlapok, komponensek

Ismerkedés a Vue.js keretrendszerrel - 2. rész - Űrlapok, komponensek
Rajcsányi Zoltán
Rajcsányi Zoltán
| ~11 perc olvasás

Az első részben bemutattam azt, hogy a Vue.js segítségével hogyan építhetünk fel egy interaktív galériát, most pedig bemutatom a HTML űrlapok és a Vue.js kapcsolatát, továbbá felépítem az első Vue.js komponenst. A komponenst és a Vue egyedet ráveszem, hogy kommunikáljanak egymással, majd végül bemutatom, hogy két testvér komponens hogy tud egymással párbeszédet folytatni egy úgynevezett busz segítségével.

Beviteli mezők

A beviteli mezőknél a Vue egy úgynevezett two-way data bindings - kétoldali adat-összerendelés/összekötés/összeköttetés technikát használ, amit az alábbi példában szemléltetek:

<div id="app">
    Név: <input v-model="name"><br>
    {{ name }}
</div>
<script>
new Vue({
    el: 'app',
	data: {
        name: 'Karesz'
    }
})
</script>

A kód egy egyszerű beviteli mezőt valósít meg, amiben egy nevet adhatunk meg. Újdonság a v-model direktíva, aminek a feladata, hogy a Vue adatváltozót és a beviteli mező értékét szinkronban tartsa. A beviteli mező alatt a név még egyszer ki lesz íratva a string interpoláció segítségével (dupla kacsacsőrös zárójel között egy változó neve). A Vue alapból kitölti a nevet “Karesz” értékkel. Amennyiben a felhasználó a beviteli mezőt módosítja, azonnal alább is megjelenik a változás. Tehát a kétoldalú adat-összerendelés azt jelenti, hogy a változó (name) és a beviteli mező tartalma szinkronban van.

A beviteli mezőn kívül további HTML elemek is rendelkezésünkre állnak, a Vue pedig hasonló logikával tudja összerendelni a saját változóival. Különféle beviteli típusokat tudunk az alábbi kódban kipróbálni:

See the Pen Input examples (Vue.js) by Rajcs&#225;nyi Zolt&#225;n (@rajcsanyiz) on CodePen.&#10;

A program annyit tesz, hogy a beviteli mezőkben (bal oldalt) lévő adatváltozásokat azonnal megjeleníti a képernyőn (jobb oldalt). Nézzünk bele egy picit a kódba:

<!-- rádiógombok -->
<div class="form-group">
    <label class="form-label">Gender:</label> (radio)
    <div v-for="g in genders">
    	<input type="radio" v-model="formData.gender" :value="g.value" />
    	<label class="custom-control-label" for="customRadio1"> {{ g.label }}</label>
    </template>
</div>
<div class="text-right"><span class="badge badge-info">field value: "{{ formData.gender }}"</span></div>

A genders tömbön végiglépkedve kiíratjuk a lehetséges nemeket, majd a rádiógombot kötjük a formData objektum gender tulajdonságához. Ha van változás, a kétoldalú adatösszekötés miatt egyszerűen ki tudjuk íratni a változó értékét a kód végén: {{ formData.gender }}.

A program alsó részében lévő váltó-nyomógombhoz is egy kis magyarázat:

<!-- nyomógomb-->
<label for="subscribe" class="form-label">Subscribe newsletter:</label> (Button)<br />
<button type="button" id="subscribe" :class="{btn: true, 'btn-primary': formData.subscribe, 'btn-default': !formData.subscribe}" @click="formData.subscribe=!formData.subscribe">
	{{ formData.subscribe?'Yes':'No' }}
</button>
<div class="text-right"><span class="badge badge-info">field value: "{{ formData.subscribe }}"</span></div>

A feliratkozás/subscribe nyomógomb-ra ha ráklikkel a felhasználó, akkor a gombon lévő felirat (Yes/No) és a nyomgomb stílusa (btn-primary) is változik egyszerre.

Most pedig jöjjön egy érdekesebb probléma, ahol már bevethetjük a Vue komponenseit, az árkalkulátor példaprogram.

Az árkalkulátor program

A programunk három beviteli mezőt fog tartalmazni, aminek logikáját később könnyedén bővítjük további beviteli mezőkkel. Megadhatjuk a termék árát, a szállítási díjat, illetve opcionálisan a leárazás összegét. Ezekből az adatokból a program kiszámítja, hogy a vásárló összesen mennyit fog fizetni, ami az űrlap alatt jelenik meg. Vázlat:
enter image description here
Láthatjuk, hogy az összes beviteli mezőhöz tartozik egy címke, de ezt leszámítva a funkcionalitás teljesen megegyező. A Vue.js ezt a logikát a komponenseken keresztül segíti egységbe szervezni, a html kódban egy új elem jelenik meg, a <price></price>.

<div id="app">
	<price label="Termék ára" :value="price" @update="valami()"></price>
	<price label="Szállítási díj" :value="price" @update="valami()"></price>
	<price label="Leárazás" :value="price" @update="valami()"></price>
	<hr>
	Összesen: {{ total() }}
</div>

A price elemhez két tulajdonság, a label (címke) és a value (ár/érték) tartozik, illetve az a logika a Vue oldalról, amitől a komponens működni fog. Továbbá mivel lehetőségünk van rá, sablonba szervezzük a mögötte lévő kódot. A sablonunkat azért, hogy működjön, a Vue egyed hatókörén kívül kell elhelyeznünk, így:

<div id="app">
	<!-- nem ide kerül a sablon! -->
</div>

<!-- price sablonunk -->
<template id="price-template">
	<div class="form-group">
		<label>{{ label }}</label>
		<input type="text" class="form-control" v-model.number="value" @change="update" placeholder="0">
	</div>
</template>

A sablonunkat a <template></template> elemek közé helyeztük, de tudnunk kell, hogy vannak további lehetőséginek is, amiről bővebb információ itt olvasható: https://vuejs.org/v2/guide/components.html#DOM-Template-Parsing-Caveats

A példában a Bootstrap CSS frameworköt használtam. A komponens jól szemlélteti, hogy a bootstrap terjengős html szerkezetét milyen jól el lehet fedni a komponensek használatával.
Érdekesség: Bootstrap-Vue projekt, amiben a jQuery JavaScript logikát cserélte le a fejlesztő a Vue.js-szel.

Hozzuk létre komponens JavaScript részét

Vue.component('price', {
	template: '#price-template', // <template id="price-template">
	props: ['label', 'value'], // <template label="..." value="...">
	methods: {
		update: function() {
  			this.$emit('update', this.value);
		}
	}
});

A Vue.component metódussal tudunk komponenst létrehozni. Egy új jellemző, a props segít a kommunikációban. A Vue egyedünk kizárólag a HTML tulajdonságokon keresztül (<price label="Termék ára" :value="price"></price>) tudja elérni a komponenshez tartozó kódokat, de csak akkor, ha a komponensben a props jellemzőben felsoroltuk azokat (props: ['label', 'value']). Amikor változás fog történni a beviteli mezőben, akkor az update metódus fog meghívódni. Itt van egy újdonság, a Vue saját this.$emit parancsa (metódusa). A komponens a külvilággal kizárólag az $emit paranccsal tud kommunikálni. Első paramétere a kiváltandó esemény neve (update), másodikként pedig az átadandó értékeket adhatjuk meg (például: this.$emit('update', {message: 'How are you?'}).

Habár logikus és átlátható a Vue.js komponens koncepciója, első olvasatra azért nem könnyű a témát megérteni, mert a HTML, JavaScript és a sablon kódja együttesen váltja ki a kívánt logikát.

A Vue egyedünk

new Vue({
	el: '#app',
	data: {
		price:     '', // termék ára
		shipping:  '', // szállítási költség
		discount:  ''  // kedvezmény
	},
	computed: {
		// összesen fizetendő
		total: function() {
			var total = this.price + this.shipping - this.discount;
			return total-this.shipping < 0 ? this.shipping : total;
		}
	}
});

Újdonság a computed (számított) tulajdonság, ami alatt felvehetünk metódusokat. A sablonban a származtatott értékekre úgy hivatkozunk, mintha változók lennének, nem pedig metódusok, azaz {{ total }}

Már csak egy feladat van hátra, a komponensben megadott értéket valahogy vissza kell a Vue egyed részére juttatni. Erre a megoldás:

<price label="Price" :value="price" @update="price=arguments[0]"></price>

Amennyiben a komponens beviteli mezőjében megváltozik az érték, akkor azonnal meghívódik a this.$emit("update") parancs, ami kivált egy egyedi @update eseményt. A Vue egyed elkapja az @update eseményt, majd a termék ára úgy módosul, hogy az $emit parancs első paramétere, azaz a beviteli mező értéke a price (ár) változóba kerül (price=arguments[0]).

A teljes program:

See the Pen Price calculator (Vue.js) by Rajcs&#225;nyi Zolt&#225;n (@rajcsanyiz) on CodePen.&#10;

Talán egy kicsit hasonlít a jQuery egyedi esemény kiváltása, és annak elkapása a Vue.js $emit és $on metódusaihoz. Az alábbi példa csak részben valósítja meg a logikát (jQuery-ben), hiszen nincsenek mögötte változók:

var $price = $('#price');

// eredeti esemény elkapása
$price.on('change', function(e) {
	// egyedi esemény kiváltása
	$price.trigger('update', this.val()));
};

// egyedi esemény elkapása
$price.on('update', function() {
	...
}

Egy másik megközelítés

Készítettem egy másik példát is a komponensek használatára, a probléma nagyon hasonló.

See the Pen Component communication - part 1 (Vue.js) by Rajcs&#225;nyi Zolt&#225;n (@rajcsanyiz) on CodePen.&#10;

Itt a Vue egyeddel kommunikál egy komponens, amiből két példányt készítettünk (Component-1 és Component-2 dobozok). A Vue egyedben törölhetőek az üzenetek (1-1 kuka ikon).

A sablon meghatározására most a text/x-template formát használtam:

<script type="text/x-template" id="temp">
    ...
</script>

Komponensek “beszélgetése”

Az első példában a Vue komponens és a Vue egyed kommunikált egymással. Megtanultuk, hogy a Vue egyedünk képes a HTML kódban a tulajdonságokon keresztül a komponenseket elérni, a komponensek pedig egyedi eseményeken keresztül a Vue egyedet elérni.

Most a kérdés, az, hogy két testvér, vagy bármilyen alá-fölé rendelt kapcsolatban lévő Vue komponens képes-e kommunikálni egymással?

A válasz NEM. A Vue keretrendszer nem ad kész megoldást arra, hogy a komponensek egymással beszélgessenek.
Azonban a JavaScript számos megoldást ad, illetve rendelkezésünkre állnak a Vue kiegészítők is a probléma megoldására, például a Vuex.

Kommunikáció busszal

A kommunikáció kialakításához az egyik lehetőségünk a busz bevezetése. Míg a valódi buszon utasok utaznak egyik településről a másikra, addig a JavaScript virtuális világában változók utaznak komponenstől-komponensig.
A busz maga is egy Vue objektum, amit még a komponensek és a Vue egyedünk bevezetése előtt kell létrehoznunk. A programváz a következő lesz:

// busz definiálása
var bus = new Vue();

// komponensek definiálása
new Vue.component1({...});
new Vue.component2({...});

// vue egyed definiálása
new Vue({...});

A programterv

Két komponenst fogok készíteni, az egyikbe beírhatunk egy üzenetet, míg a másik fogadja azt. A kommunikáció visszafele is meg fog történni, amikor a fogadó komponens megkéri a küldő komponenst az üzenet törlésére (reset gomb). A vázlat:
enter image description here

Kódoljunk

Nézzük meg a kód egy egyszerűsített, stíluslapok és formázások nélküli változatát:

<!-- Vue egyedünk (fő program) -->
<div id="app">
	<component1 label="Component-1" :message="message"></component1>
	<component2 label="Component-2"></component2>
</div>

<!-- Sablon: Component 1 -->
<script type="text/x-template" id="temp">
	<div>
		<h3 class="component">{{ label }}</h3>

		<label for="message">Message</label>
		<input type="text" class="form-control" id="message" v-model="message">

		<button @click="sendMessage" class="btn btn-primary" :disabled="message==''">Send</button>
	</div>
</script>

<!-- Sablon: Component -->
<script type="text/x-template" id="temp2">
	<div>
		<h3 class="component">{{ label }}</h3>
		<strong>{{message}}</strong></p>
		<button @click="sendReset" class="btn btn-danger" :disabled="message==''">Reset</button>
	</div>
</script>

Hozzuk először létre a buszt:

var bus = new Vue();

Hozzuk létre az első komponenst:

Vue.component('component1', {
	template: '#temp',
	props: [ 'label', 'message' ],
	methods: {
		sendMessage: function() {
		  // esemény kiváltása (trigger)
		  bus.$emit('getmsg', this.message);
		}
	},
	created: function() {
		// esemény lehallgatása (listener)
		bus.$on('resetmsg', function() {
		  this.message = '';
		}.bind(this))
	}
});

sendMessage() függvény küldi el az üzenetet, de most nem az alkalmazásunknak (this.$emit()), hanem a busz (bus.$emit()) segítségével a másik komponensünk részére. A komponens létrejöttekor a created() függvényben létrehozunk egy figyelőt a bus.$on segítésével, ami arra figyel, hogy kiváltottak-e egy törlő (resetmsg) eseményt a buszon. Még ami feltűnhet a kódban, hogy a this kulcsszó nem a Vue objektumunkra mutat, hanem a buszra, ezért hogy használni tudjuk a komponenst, a Vue komponensre mutató this kulcsszót kötöttem a .bind(this) metódussal.
A második komponenshez és a Vue egyedünkhöz tartozó JavaScript kódban már nincs magyarázatra szoruló újdonság:

// második komponens
Vue.component('component2', {
	template: '#temp2',
	props: ['label'],
	data: function() {
		return {
		    message: 'waiting for the message ...'
		}
	},
	methods: {
		// törlés esemény kiváltása (trigger reset)
		sendReset: function() {
			this.message='';
			bus.$emit('resetmsg');
		},
	},
	created: function() {
		// esemény lehallgatása (listening)
		bus.$on('getmsg', function(message) {
			this.message = message
		}.bind(this));
	}
});

// Vue egyed
new Vue({
	el: '#app',
	data: {
		message: 'Hi! How are you?',
	}
});

A program működés közben:

See the Pen Component communication - Part 2 (Vue.js) by Rajcs&#225;nyi Zolt&#225;n (@rajcsanyiz) on CodePen.&#10;A program kipróbálható itt: https://codepen.io/rajcsanyiz/pen/RQdxRq

Hasonló témák:

Zárszó

A következő részben egy WebShop rendszert fogunk építeni. Amennyiben tetszett a téma, és a további példakódjaimat is szeretnéd megnézni, várlak a Vue.js codepen gyűjteményemben: https://codepen.io/collection/DwJBEZ/