Ismerkedés a Vue.js keretrendszerrel - 2. rész - Űrlapok, komponensek
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ányi Zoltán (@rajcsanyiz) on CodePen.
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:
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ányi Zoltán (@rajcsanyiz) on CodePen.
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ányi Zoltán (@rajcsanyiz) on CodePen.
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:
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"></script>
<!-- Sablon: Component -->
<script type="text/x-template" id="temp2"></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))
}
});
A 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ányi Zoltán (@rajcsanyiz) on CodePen. A program kipróbálható itt: https://codepen.io/rajcsanyiz/pen/RQdxRq
Hasonló témák:
- Készítsünk modális űrlapot (saját cikkem)
- Kétirányú adatösszeköttetés
- Component basic - angol nyelvű kézikönyv
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/