Directives de base Vuejs
Exercice 1, 2 & 3 - affichage dynamisé + échoppe en 2 versions
1°/ myscript.js
4°/ shop.html
5°/ Archive globale
Exercice 1, 2 & 3 - affichage dynamisé + échoppe en 2 versions
1°/ myscript.js
- Par rapport aux TPs 1 & 2, le fichier myscript.js n'est plus vraiment nécessaire.
- En effet, seule la fonction wrapper buy() pourrait avoir son utilité mais son code peut facilement être déplacé dans la fonction buy() de la classe Perso (cf. model.js).
- shop.html a donc été modifié pour ne plus inclure ce fichier myscript.js qui est devenu vide.
2°/ model.js
- Comme indiqué ci-dessus, ce fichier a été modifié pour incorporer tout le code gérant les achats dans la méthode buy() de la classe Perso.
- Cette méthode est par ailleurs un peu bancale du point de vue vuejs. En effet, elle accède à une propriété de la vue, app.idToBuy. Il serait plus logique que le code externe à la vue n'accède pas aux élément de la vue, mais plutôt l'inverse.
- Pour ce faire, il faudrait pouvoir ajouter des méthodes à la vue. C'est le sujet du TD 4.
- A la fin du fichier se trouve la création de l'échoppe, en tirant aléatoirement 10 items pour remplir le tableau shop.
- contenu de model.js :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
function getRandomInt(max) { return Math.floor(Math.random() * Math.floor(max)); } var itemCats = [ 'helmet', 'crown', 'armor', 'clothes', 'weapon', 'lighter', 'purse', 'potion', 'spell', 'food']; var itemLimits = [ {slot:'head', limit:1, types: [ 'helmet' , 'crown' ]}, {slot:'body', limit:1, types: [ 'armor', 'clothes' ]}, {slot:'hands', limit:2, types: [ 'weapon', 'lighter']}, {slot:'belt', limit:3, types: [ 'weapon', 'purse']}, {slot:'bag', limit:10, types: [ 'helmet', 'crown', 'clothes', 'lighter', 'potion', 'spell', 'food', 'purse' ]} ]; class Item { constructor(id, name, type, price, effect) { this.id = id; this.name = name; //let idx = itemCats.findIndex(e => e == type); //var testType = function(el) {return el == type}; function testType(el) { return el == type;} let idx = itemCats.findIndex(testType); if (idx != -1) { this.type = type; } else { this.type = ''; } if (price>=0) { this.price = price; } else { this.price = 0; } this.effect = effect; } } var items = [ new Item(0, 'conic helmet', 'helmet', 200, 'A+10'), new Item(1, 'great crown of apologia', 'crown', 200, 'A+20'), new Item(2, 'band of joy', 'crown', 100, 'L+10'), new Item(3, 'leather armor', 'armor', 100, 'A+10'), new Item(4, 'broigne', 'armor', 200, 'A+20'), new Item(5, 'hauberk', 'armor', 500, 'A+40'), new Item(6, 'plate armor', 'armor', 1000, 'A+60'), new Item(7, 'tuxedo', 'clothes', 600, 'L+1'), new Item(8, 'cursed swimsuit', 'clothes', 10, 'A-10'), new Item(9, 'unicorn cosplay', 'clothes', 200, 'L+10'), new Item(10, 'dagger', 'weapon', 100, 'S+5'), new Item(11, 'cursed dagger', 'weapon', 100, 'S-5'), new Item(12, 'short sword', 'weapon', 200, 'S+10'), new Item(13, 'cursed short sword', 'weapon', 200, 'S-10'), new Item(14, 'long sword', 'weapon', 300, 'S+20'), new Item(15, 'cursed long sword', 'weapon', 300, 'S-20'), new Item(16, 'axe', 'weapon', 100, 'S+10'), new Item(17, 'cursed axe', 'weapon', 100, 'S-10'), new Item(18, 'great axe', 'weapon', 200, 'S+20'), new Item(19, 'cursed great axe', 'weapon', 200, 'S-20'), new Item(20, 'torch', 'lighter', 2, ''), new Item(21, 'oil lamp', 'lighter', 10, ''), new Item(22, 'leather purse', 'purse', 10, ''), new Item(23, 'protection potion', 'potion', 100, 'a+10'), new Item(24, 'health potion', 'potion', 100, 'l+10'), new Item(25, 'strength potion', 'potion', 100, 's+10'), new Item(26, 'fireball', 'spell', 1000, ''), new Item(27, 'ice cone', 'spell', 1000, ''), new Item(28, 'total healing', 'spell', 1000, ''), new Item(29, 'invisibility', 'spell', 1000, ''), new Item(30, 'levitation', 'spell', 1000, ''), new Item(31, 'apple', 'food', 1, 'l+1'), new Item(32, 'chicken', 'food', 10, 'l+5'), new Item(33, 'beef', 'food', 15, 'l+10'), new Item(34, 'wine', 'food', 2, 'l+2') ]; class Perso { constructor(name, level) { this.name = name; this.level = level; this.slots = [ {name:'head', id:1, items:[]}, {name:'body', id:2, items:[]}, {name:'hands', id:3, items:[]}, {name:'belt', id:4, items:[]}, {name:'bag', id:5, items:[]} ]; this.boughtItems = []; // list of item bought but not yet assigned this.life = 50*this.level; // the actual level of life this.gold=500; this.updateCaracs(); } updateCaracs() { this.vitaliy = this.level*50; this.armor = 0; this.strength = this.level*20; for(let i=0;i<this.slots.length;i++) { let slot = this.slots[i]; for(let j=0;j<slot.items.length;j++) { let item = slot.items[j]; // search for armor effects if (item.effect[0] == 'A') { let val = item.effect.substring(2,item.effect.length); if (item.effect[1] == '+') this.armor += eval(val); else if (item.effect[1] == '-') this.armor -= eval(val); } // search for vitality effects if (item.effect[0] == 'L') { let val = item.effect.substring(2,item.effect.length); if (item.effect[1] == '+') this.vitality += eval(val); else if (item.effect[1] == '-') this.vitality -= eval(val); } // search for strength effects if (item.effect[0] == 'S') { let val = item.effect.substring(2,item.effect.length); if (item.effect[1] == '+') this.strength += eval(val); else if (item.effect[1] == '-') this.strength -= eval(val); } } } if (this.life > this.vitality) this.life = this.vitality; } /* modified version: - no parameter needed since the item id is stored in app.idToBuy-1 - confirmation is done here */ buy() { if (shop[app.idToBuy-1].price > players[app.idPlayer].gold) { alert("Not enough gold"); } else { let r = confirm("want to buy " + shop[app.idToBuy-1].name+" for "+shop[app.idToBuy-1].price+"gp ?"); if (r == true) { this.boughtItems.push(shop[app.idToBuy-1]); this.gold -= shop[app.idToBuy-1].price; shop.splice(app.idToBuy-1,1); app.idToBuy=''; } } } /* assign(): try to assign an item to a slot - itemId is the index of item in boughtItem, wichi normally is the same as in the Vue data - slot is the name of the slot (see attribute name in slots) return true if it's possible (i.e. limits and type of item respected) else return false. */ assign(itemId, to) { let item = this.boughtItems[itemId]; if (item == undefined) return false; let slot = this.slots.find(e => e.name == to); let slotLim = itemLimits.find(e => e.slot == to); // if to exists in player's slots and itemLimits if ((slot != undefined) && (slotLim != undefined)) { // check if limits/type is ok or not if (slot.items.length == slotLim.limit) { alert('limit for '+to+' alreay reached'); return false; } let t = slotLim.types.find(e => e == item.type); if (t == undefined) { alert(to+' cannot be assigned with '+item.type); return false; } console.log("assign "+item.name+" to "+slot.name); slot.items.push(item); this.boughtItems.splice(itemId,1); this.updateCaracs(); return true; } return false; } } var players = [ new Perso("toto",1), new Perso("tutu",2) ]; // put some items to test players[0].slots[0].items.push(items[0]); players[0].slots[2].items.push(items[11]); players[0].slots[4].items.push(items[26]); players[0].slots[4].items.push(items[34]); players[1].slots[1].items.push(items[5]); players[1].slots[3].items.push(items[16]); players[1].slots[4].items.push(items[20]); players[1].slots[4].items.push(items[33]); // create the shop, filling with items taken at random var shop = []; for(let i=0;i<10;i++) { let idx = getRandomInt(items.length); shop.push(items[idx]); } |
3°/ index.js
- Tout ce qui concerne l'affichage dynamique est maintenant géré grâce à vuejs.
- Pour cela, il suffit de mettre dans data les propriétés essentielles pour faire ces affichages.
- Il n'y a donc pas besoin de beaucoup de propriétés : l'id du personnage courant, la liste des personnages et le tableau de l'échoppe.
- Remarque : contrairement aux TPs précédents, on a pas de propriété perso accédant directement à l'objet personnage courant. Pour utiliser une telle propriété, il faudrait être capable de mettre à jour cette propriété quand idPlayer change. C'est faisable en JS en appelant une méthode grâce à l'attribut onchange ou oninput de la balise <input>. On verra dans le TD 4 que c'est encore plus simple grâce à vuejs.
- Reste à ajouter une propriété permettant de connaître l'item de l'échoppe sélectionné pour l'achat.
- Remarque : au lieu de fournir deux fichiers pour les exercices 2 & 3, shop.html contient le code pour les 2 versions de l'échoppe. D'où l'ajout d'une propriété pour passer d'une version à l'autre.
- contenu de index.js :
1 2 3 4 5 6 7 8 9 10 |
var app = new Vue({ el: '#mydiv', data: { idPlayer:'', players, // to observe and have an access to players array shop, // to observe the shop idToBuy:'', shopVersion:1 // change to 2 so that shop is shown with radio button } }) |
4°/ shop.html
- Grâce à la "magie" de vuejs, le code devient extrêmement compact par rapport aux versions des TP 1 & 2, tout en faisant bien plus.
- Astuce : au lieu de tester si idPlayer est dans le bon intervalle, on peut profiter du fait qu'accéder à un indice invalide d'un tableau renvoie undefined. Par exemple, la balise <div> à la ligne 9 (ou bien le bouton d'achat) utilise ce principe. Cela dit, on pourrait aussi écrire v-if="idPlayer>=0&&idPlayer<players.length".
- Pour construire la table, on remarque qu'il est possible de faire des v-for imbriqués.
- Pour l'exercice 3, la documentation vuejs indique que l'on peut utiliser v-model dans les boutons radio pour affecter à une variable vuejs la valeur de l'attribut value. Comme de plus, v-for permet de récupérer l'indice de l'élément parcouru, on peut créer une série de boutons radio dont l'attribut value à une valeur calculée à partir d'un indice.
- contenu de shop.html :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<html> <head> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="model.js"></script> </head> <body> <div id="mydiv"> <label for="idperso">#player:</label><input id="idperso" v-model="idPlayer"><br> <div v-if="players[idPlayer] != undefined"> <h1>{{players[idPlayer].name}}, level {{players[idPlayer].level}}, life = {{players[idPlayer].life}}, strength = {{players[idPlayer].strength}}</h1> <table border="1"> <tr v-for="sl in players[idPlayer].slots"> <td>{{sl.name}}</td> <td v-for="it in sl.items">{{it.name}}</td> </tr> </table> <p>Bought items :<span v-for="it in players[idPlayer].boughtItems">{{it.name}} | </span></p> <label for="gold">Gold:</label> <input type="text" readonly id="gold" :value="players[idPlayer].gold"><br> <!-- shop version 1: selection with an id --> <template v-if="shopVersion==1"> <ol> <li v-for="it in shop">{{it.name}}: {{it.price}}gp</li> </ol> <label for="idBuy">Item id to buy:</label><input v-model="idToBuy"><button onclick="players[app.idPlayer].buy()" v-if="shop[idToBuy-1]!=undefined">Buy {{shop[idToBuy-1].name}}</button> </template> <!-- shop version 2: selection with radio buttons --> <template v-else> <p v-for="(it,index) in shop"> <input type="radio" name="itemsToBuy" :id="'ittobuy'+index+1" :value="index+1" v-model="idToBuy"> <label :for="'ittobuy'+index+1">{{it.name}} : {{it.price}} gp</label> </p> <button onclick="players[app.idPlayer].buy()" v-if="shop[idToBuy-1]!=undefined">Buy {{shop[idToBuy-1].name}}</button> </template> </div> </div> <script src="index.js"></script> </body> </html> |
5°/ Archive globale
- Vous pouvez télécharger une archive avec tous les fichiers [ ici ]