Open menu
Directives de base Vuejs

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}}&nbsp;|&nbsp;</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 ]