|
@@ -45,7 +45,20 @@ const productTemplate = `<div class="product">
|
|
|
<button v-on:click="removeFromCart">Remove</button>
|
|
|
</div>
|
|
|
|
|
|
- <div>
|
|
|
+ <product-tabs :reviews="reviews"></product-tabs>
|
|
|
+ </div>
|
|
|
+`;
|
|
|
+
|
|
|
+const tabsTemplate = `
|
|
|
+ <div>
|
|
|
+ <span class="tab"
|
|
|
+ :class="{ activeTab: selectedTab == tab }"
|
|
|
+ v-for="(tab, index) in tabs" :key="index"
|
|
|
+ @click="selectedTab = tab">
|
|
|
+ {{ tab }}
|
|
|
+ </span>
|
|
|
+
|
|
|
+ <div v-show="selectedTab === 'Reviews'">
|
|
|
<h2>Reviews</h2>
|
|
|
<p v-if="!reviews.length">There are no reviews yet.</p>
|
|
|
<ul>
|
|
@@ -56,191 +69,212 @@ const productTemplate = `<div class="product">
|
|
|
<p>Review: {{ review.review }}</p>
|
|
|
</li>
|
|
|
</ul>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <product-review v-show="selectedTab === 'Make a Review'" ></product-review>
|
|
|
</div>
|
|
|
- <product-review @review-submitted="addReview"></product-review>
|
|
|
- </div>
|
|
|
`;
|
|
|
|
|
|
+let eventBus = new Vue();
|
|
|
+
|
|
|
Vue.component("product-details", {
|
|
|
- props: {
|
|
|
- details: {
|
|
|
- type: Array,
|
|
|
- required: true,
|
|
|
- },
|
|
|
+ props: {
|
|
|
+ details: {
|
|
|
+ type: Array,
|
|
|
+ required: true,
|
|
|
},
|
|
|
- template: detailsTemplate,
|
|
|
+ },
|
|
|
+ template: detailsTemplate,
|
|
|
});
|
|
|
|
|
|
Vue.component("product-review", {
|
|
|
- template: `
|
|
|
- <form class="review-form" @submit.prevent="onSubmit">
|
|
|
- <p v-if="errors.length">
|
|
|
- <b>Please correct the following errror(s):</b>
|
|
|
- <ul>
|
|
|
- <li v-for="error in errors">{{ error }}</li>
|
|
|
- </ul>
|
|
|
- </p>
|
|
|
-
|
|
|
- <p>
|
|
|
- <label for="name">Name:</label>
|
|
|
- <input id="name" v-model="name">
|
|
|
- </p>
|
|
|
- <p>
|
|
|
- <label for="review">Review:</label>
|
|
|
- <textarea id="review" v-model="review"></textarea>
|
|
|
- </p>
|
|
|
-
|
|
|
- <label for="rating">Rating:</label>
|
|
|
- <select id="rating" v-model.number="rating">
|
|
|
- <option>5</option>
|
|
|
- <option>4</option>
|
|
|
- <option>3</option>
|
|
|
- <option>2</option>
|
|
|
- <option>1</option>
|
|
|
- </select>
|
|
|
-
|
|
|
- <p>Would you recommend this product?</p>
|
|
|
- <div>
|
|
|
- <label for="reco-no">No</label> <input type="radio" v-model="recommend" id="reco-no" value="no">
|
|
|
- <label for="reco-maybe">Maybe</label><input type="radio" v-model="recommend" id="reco-maybe" value="maybe">
|
|
|
- <label for="reco-yes">Yes</label> <input type="radio" v-model="recommend" id="reco-yes" value="yes">
|
|
|
- </div>
|
|
|
-
|
|
|
- <p>
|
|
|
- <input type="submit" value="Submit">
|
|
|
- </p>
|
|
|
- </form>
|
|
|
- `,
|
|
|
- data() {
|
|
|
- return {
|
|
|
- name: null,
|
|
|
- recommend: null,
|
|
|
- review: null,
|
|
|
- rating: null,
|
|
|
- errors: [],
|
|
|
- }
|
|
|
- },
|
|
|
- methods: {
|
|
|
- onSubmit(evt) {
|
|
|
- this.errors = [];
|
|
|
- if (this.name && this.review && this.rating && this.recommend) {
|
|
|
- const productReview = {
|
|
|
- name: this.name,
|
|
|
- rating: this.rating,
|
|
|
- recommend: this.recommend,
|
|
|
- review: this.review,
|
|
|
- };
|
|
|
- this.$emit("review-submitted", productReview);
|
|
|
- this.name = null;
|
|
|
- this.rating = null;
|
|
|
- this.recommend = null;
|
|
|
- this.review = null;
|
|
|
- } else {
|
|
|
- for (const field of ["name", "rating", "recommend", "review"]) {
|
|
|
- if (!this[field]) {
|
|
|
- this.errors.push(`${field} required`);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ template: `
|
|
|
+ <form class="review-form" @submit.prevent="onSubmit">
|
|
|
+ <p v-if="errors.length">
|
|
|
+ <b>Please correct the following errror(s):</b>
|
|
|
+ <ul>
|
|
|
+ <li v-for="error in errors">{{ error }}</li>
|
|
|
+ </ul>
|
|
|
+ </p>
|
|
|
+
|
|
|
+ <p>
|
|
|
+ <label for="name">Name:</label>
|
|
|
+ <input id="name" v-model="name">
|
|
|
+ </p>
|
|
|
+ <p>
|
|
|
+ <label for="review">Review:</label>
|
|
|
+ <textarea id="review" v-model="review"></textarea>
|
|
|
+ </p>
|
|
|
+
|
|
|
+ <label for="rating">Rating:</label>
|
|
|
+ <select id="rating" v-model.number="rating">
|
|
|
+ <option>5</option>
|
|
|
+ <option>4</option>
|
|
|
+ <option>3</option>
|
|
|
+ <option>2</option>
|
|
|
+ <option>1</option>
|
|
|
+ </select>
|
|
|
+
|
|
|
+ <p>Would you recommend this product?</p>
|
|
|
+ <div>
|
|
|
+ <label for="reco-no">No</label> <input type="radio" v-model="recommend" id="reco-no" value="no">
|
|
|
+ <label for="reco-maybe">Maybe</label><input type="radio" v-model="recommend" id="reco-maybe" value="maybe">
|
|
|
+ <label for="reco-yes">Yes</label> <input type="radio" v-model="recommend" id="reco-yes" value="yes">
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <p>
|
|
|
+ <input type="submit" value="Submit">
|
|
|
+ </p>
|
|
|
+ </form>
|
|
|
+ `,
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ name: null,
|
|
|
+ recommend: null,
|
|
|
+ review: null,
|
|
|
+ rating: null,
|
|
|
+ errors: [],
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ onSubmit(evt) {
|
|
|
+ this.errors = [];
|
|
|
+ if (this.name && this.review && this.rating && this.recommend) {
|
|
|
+ const productReview = {
|
|
|
+ name: this.name,
|
|
|
+ rating: this.rating,
|
|
|
+ recommend: this.recommend,
|
|
|
+ review: this.review,
|
|
|
+ };
|
|
|
+ eventBus.$emit("review-submitted", productReview);
|
|
|
+ this.name = null;
|
|
|
+ this.rating = null;
|
|
|
+ this.recommend = null;
|
|
|
+ this.review = null;
|
|
|
+ } else {
|
|
|
+ for (const field of ["name", "rating", "recommend", "review"]) {
|
|
|
+ if (!this[field]) {
|
|
|
+ this.errors.push(`${field} required`);
|
|
|
+ }
|
|
|
}
|
|
|
+ }
|
|
|
}
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+Vue.component("product-tabs", {
|
|
|
+ props: {
|
|
|
+ reviews: {
|
|
|
+ type: Array,
|
|
|
+ required: true,
|
|
|
+ }
|
|
|
+ },
|
|
|
+ template: tabsTemplate,
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ selectedTab: "Reviews",
|
|
|
+ tabs: ["Reviews", "Make a Review"],
|
|
|
+ }
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
Vue.component("product", {
|
|
|
- props: {
|
|
|
- isPremium: {
|
|
|
- type: Boolean,
|
|
|
- required: true,
|
|
|
- }
|
|
|
+ props: {
|
|
|
+ isPremium: {
|
|
|
+ type: Boolean,
|
|
|
+ required: true,
|
|
|
+ }
|
|
|
+ },
|
|
|
+ template: productTemplate,
|
|
|
+
|
|
|
+
|
|
|
+ computed: {
|
|
|
+ image() {
|
|
|
+ const image = this.variants[this.selectedVariant].variantImage;
|
|
|
+ return image;
|
|
|
},
|
|
|
- template: productTemplate,
|
|
|
-
|
|
|
-
|
|
|
- computed: {
|
|
|
- image() {
|
|
|
- const image = this.variants[this.selectedVariant].variantImage;
|
|
|
- return image;
|
|
|
- },
|
|
|
- inStock() {
|
|
|
- return this.variants[this.selectedVariant].variantQuantity > 0;
|
|
|
- },
|
|
|
- shipping() {
|
|
|
- if (this.isPremium) {
|
|
|
- return "Free";
|
|
|
- }
|
|
|
- return "2.99 €";
|
|
|
- },
|
|
|
- title() {
|
|
|
- return [this.brand, this.product].join(' ');
|
|
|
- }
|
|
|
+ inStock() {
|
|
|
+ return this.variants[this.selectedVariant].variantQuantity > 0;
|
|
|
},
|
|
|
- data() {
|
|
|
- return {
|
|
|
- brand: "Vue Mastery",
|
|
|
- description: "Chaussettes montantes",
|
|
|
- details: [
|
|
|
- "80% cotton",
|
|
|
- "20% polyester",
|
|
|
- "Gender-neutral",
|
|
|
- ],
|
|
|
- inventory: 15,
|
|
|
- link: "https://www.google.com/search?q=vuejs+socks",
|
|
|
- product: "Socks",
|
|
|
- reviews: [],
|
|
|
- selectedVariant: 0,
|
|
|
- sizes: [34, 36, 38, 40, 42, 44, 46],
|
|
|
- variants: [
|
|
|
- {
|
|
|
- variantId: 2234,
|
|
|
- variantColor: "green",
|
|
|
- variantImage: "./assets/img/vmSocks-green-onWhite.jpg",
|
|
|
- variantQuantity: 10,
|
|
|
- },
|
|
|
- {
|
|
|
- variantId: 2235,
|
|
|
- variantColor: "blue",
|
|
|
- variantImage: "./assets/img/vmSocks-blue-onWhite.png",
|
|
|
- variantQuantity: 0,
|
|
|
- },
|
|
|
- ]
|
|
|
- }
|
|
|
+ shipping() {
|
|
|
+ if (this.isPremium) {
|
|
|
+ return "Free";
|
|
|
+ }
|
|
|
+ return "2.99 €";
|
|
|
},
|
|
|
- methods: {
|
|
|
- addToCart() {
|
|
|
- this.variants[this.selectedVariant].variantQuantity--;
|
|
|
- this.$emit("add-to-cart", this.variants[this.selectedVariant].variantId);
|
|
|
- },
|
|
|
- addReview(productReview) {
|
|
|
- this.reviews.push(productReview);
|
|
|
+ title() {
|
|
|
+ return [this.brand, this.product].join(' ');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ brand: "Vue Mastery",
|
|
|
+ description: "Chaussettes montantes",
|
|
|
+ details: [
|
|
|
+ "80% cotton",
|
|
|
+ "20% polyester",
|
|
|
+ "Gender-neutral",
|
|
|
+ ],
|
|
|
+ inventory: 15,
|
|
|
+ link: "https://www.google.com/search?q=vuejs+socks",
|
|
|
+ product: "Socks",
|
|
|
+ reviews: [],
|
|
|
+ selectedVariant: 0,
|
|
|
+ sizes: [34, 36, 38, 40, 42, 44, 46],
|
|
|
+ variants: [
|
|
|
+ {
|
|
|
+ variantId: 2234,
|
|
|
+ variantColor: "green",
|
|
|
+ variantImage: "./assets/img/vmSocks-green-onWhite.jpg",
|
|
|
+ variantQuantity: 10,
|
|
|
},
|
|
|
- removeFromCart() {
|
|
|
- this.variants[this.selectedVariant].variantQuantity++;
|
|
|
- this.$emit("remove-from-cart", this.variants[this.selectedVariant].variantId);
|
|
|
+ {
|
|
|
+ variantId: 2235,
|
|
|
+ variantColor: "blue",
|
|
|
+ variantImage: "./assets/img/vmSocks-blue-onWhite.png",
|
|
|
+ variantQuantity: 0,
|
|
|
},
|
|
|
- updateImage(index) {
|
|
|
- this.selectedVariant = index;
|
|
|
- }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ addToCart() {
|
|
|
+ this.variants[this.selectedVariant].variantQuantity--;
|
|
|
+ this.$emit("add-to-cart", this.variants[this.selectedVariant].variantId);
|
|
|
},
|
|
|
+ removeFromCart() {
|
|
|
+ this.variants[this.selectedVariant].variantQuantity++;
|
|
|
+ this.$emit("remove-from-cart", this.variants[this.selectedVariant].variantId);
|
|
|
+ },
|
|
|
+ updateImage(index) {
|
|
|
+ this.selectedVariant = index;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ eventBus.$on('review-submitted', productReview => {
|
|
|
+ this.reviews.push(productReview);
|
|
|
+ });
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
const app = new Vue({
|
|
|
- el: "#app",
|
|
|
- data: {
|
|
|
- cart: [],
|
|
|
- isPremium: false,
|
|
|
+ el: "#app",
|
|
|
+ data: {
|
|
|
+ cart: [],
|
|
|
+ isPremium: false,
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ removeItem(id) {
|
|
|
+ console.log("removeItem", id);
|
|
|
+ const index = this.cart.indexOf(id);
|
|
|
+ if (index > -1) {
|
|
|
+ this.cart.splice(index, 1);
|
|
|
+ }
|
|
|
},
|
|
|
- methods: {
|
|
|
- removeItem(id) {
|
|
|
- console.log("removeItem", id);
|
|
|
- const index = this.cart.indexOf(id);
|
|
|
- if (index > -1) {
|
|
|
- this.cart.splice(index, 1);
|
|
|
- }
|
|
|
- },
|
|
|
- updateCart(id) {
|
|
|
- console.log("updateCart", id);
|
|
|
- this.cart.push(id);
|
|
|
- }
|
|
|
+ updateCart(id) {
|
|
|
+ console.log("updateCart", id);
|
|
|
+ this.cart.push(id);
|
|
|
}
|
|
|
+ }
|
|
|
});
|
|
|
|