Skip to content
Snippets Groups Projects
EditItemForm.vue 15.9 KiB
Newer Older
Gilgard's avatar
Gilgard committed
<template>
  <!-- Form for editing an item -->
Gilgard's avatar
Gilgard committed
  <div
    class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full p-4"
  >
    <!-- Component heading -->
    <h3 class="text-xl font-medium text-center text-primary-light mt-4 mb-8">
      Rediger gjenstand
    </h3>

    <!-- Title -->
    <div class="mb-6" :class="{ error: v$.updatedItem.title.$errors.length }">
      <label
        class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
        id="titleLabel"
        >Tittel</label
      >
      <input
        type="text"
        id="title"
        class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light"
        v-model="v$.updatedItem.title.$model"
        required
      />

      <!-- error message for title-->
      <div
        class="text-error-medium"
        v-for="(error, index) of v$.updatedItem.title.$errors"
        :key="index"
      >
        <div class="text-error-medium text-sm">
          {{ error.$message }}
        </div>
      </div>
    </div>

    <!-- Category -->
    <div class="mb-6">
      <label
        class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400"
        id="selectCategoryLabel"
        >Kategori</label
      >
      <select
Gilgard's avatar
Gilgard committed
        @change="onChangeCategory($event)"
Gilgard's avatar
Gilgard committed
        v-model="v$.updatedItem.selectedCategory.$model"
        id="categories"
        class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light"
      >
        <option class="text-gray-400" value="" disabled>
          Velg en kategori
        </option>
        <option
Gilgard's avatar
Gilgard committed
          :value="category"
Gilgard's avatar
Gilgard committed
          :selected="category == updatedItem.selectedCategory"
          v-for="category in categories"
          :key="category"
          class="text-gray-900 text-sm"
        >
          {{ category }}
        </option>
      </select>

      <!-- error message for select box -->
      <div
        class="text-error-medium"
        v-for="(error, index) of v$.updatedItem.selectedCategory.$errors"
        :key="index"
      >
        <div class="text-error-medium text-sm">
          {{ error.$message }}
        </div>
      </div>
    </div>

    <!-- Grupper -->
    <div class="mb-6">
      <label class="block text-sm font-medium text-gray-900 dark:text-gray-400"
        >Grupper</label
      >
      <div
Gilgard's avatar
Gilgard committed
        class="overflow-auto w-full max-h-32 mt-2 text-base list-none bg-white rounded divide-y divide-gray-100 dark:bg-gray-700"
Gilgard's avatar
Gilgard committed
      >
        <ul class="py-1" aria-labelledby="dropdownDefault">
          <li>
            <div
              class="form-check"
              v-for="community in communities"
              :key="community"
            >
              <input
                class="form-check-input appearance-none h-4 w-4 border border-gray-300 rounded-sm bg-white checked:bg-primary-medium focus:outline-none transition duration-200 mt-1 align-top bg-no-repeat bg-center bg-contain float-left mr-2 cursor-pointer"
                type="checkbox"
                :checked="isInSelectedCommunity(community.communityId)"
                :value="community.communityId"
                @change="onChangeCommunity($event)"
              />
              <label class="form-check-label inline-block text-gray-800">
                {{ community.name }}
              </label>
            </div>
          </li>
        </ul>
      </div>
      <label class="text-error-medium text-sm block">{{
        communityErrorMessage
      }}</label>
    </div>

    <!-- price -->
    <div
      class="mb-6 mt-4"
      :class="{ error: v$.updatedItem.price.$errors.length }"
    >
      <label
        class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
        id="priceLabel"
        >Pris</label
      >
      <input
        type="number"
        v-model="v$.updatedItem.price.$model"
        id="price"
        class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light"
        required
      />

      <!-- error message for price -->
      <div
        class="text-error"
        v-for="(error, index) of v$.updatedItem.price.$errors"
        :key="index"
      >
        <div class="text-error-medium text-sm">
          {{ error.$message }}
        </div>
      </div>
    </div>

    <!-- Description -->
    <div
      class="mb-6"
      :class="{ error: v$.updatedItem.description.$errors.length }"
    >
      <label
        class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400"
        id="descriptionLabel"
        >Beskrivelse</label
      >
      <textarea
        id="description"
        rows="4"
        v-model="v$.updatedItem.description.$model"
        class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light"
        required
      ></textarea>

      <!-- error message for description -->
      <div
        class="text-error"
        v-for="(error, index) of v$.updatedItem.description.$errors"
        :key="index"
      >
        <div class="text-error-medium text-sm">
          {{ error.$message }}
        </div>
      </div>
    </div>

    <!-- Address -->
    <div class="mb-6" :class="{ error: v$.updatedItem.address.$errors.length }">
      <label
        class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
        id="addressLabel"
        >Adresse</label
      >
      <input
        type="text"
        class="block w-full px-4 py-2 mt-2 text-gray-700 placeholder-gray-500 bg-white border rounded-md dark:bg-gray-800 dark:border-gray-600 dark:placeholder-gray-400 focus:border-primary-light dark:focus:border-primary-light focus:ring-opacity-40 focus:outline-none focus:ring focus:ring-primary-light"
        v-model="v$.updatedItem.address.$model"
        id="adress"
        required
      />

      <!-- error message for address-->
      <div
        class="text-error"
        v-for="(error, index) of v$.updatedItem.address.$errors"
        :key="index"
      >
        <div class="text-error-medium text-sm">
          {{ error.$message }}
        </div>
      </div>
    </div>

    <!-- Images -->
    <div>
      <label
        class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400"
        id="imageLabel"
      >
Gilgard's avatar
Gilgard committed
        Legg til flere bilder (bildene må være .png)
Gilgard's avatar
Gilgard committed
      </label>

      <input
        type="file"
        ref="file"
        style="display: none"
        @change="addImage"
Gilgard's avatar
Gilgard committed
        accept="image/png"
Gilgard's avatar
Gilgard committed
      <ColoredButton :text="'Velg bilde'" @click="$refs.file.click()" />
Gilgard's avatar
Gilgard committed

Gilgard's avatar
Gilgard committed
      <div v-for="image in updatedItem.images" :key="image" class="m-2">
        <form-image-display :image="image" @remove="removeImage(image)" />
Gilgard's avatar
Gilgard committed
      </div>
    </div>

    <!-- Save item button -->
    <div class="float-right">
Gilgard's avatar
Gilgard committed
      <ColoredButton :text="'Lagre'" @click="saveClicked" id="saveButton" />
Gilgard's avatar
Gilgard committed
    </div>
  </div>
</template>

<script>
import useVuelidate from "@vuelidate/core";
Gilgard's avatar
Gilgard committed
import ColoredButton from "@/components/BaseComponents/ColoredButton";
Gilgard's avatar
Gilgard committed
import FormImageDisplay from "@/components/BaseComponents/FormImageDisplay.vue";
Gilgard's avatar
Gilgard committed
import ListingService from "@/services/listing.service";
import CommunityService from "@/services/community.service";
Gilgard's avatar
Gilgard committed
import ImageService from "@/services/image.service";
Gilgard's avatar
Gilgard committed
import { parseCurrentUser } from "@/utils/token-utils";

import {
  required,
  helpers,
  maxLength,
  between,
  minLength,
} from "@vuelidate/validators";

export default {
  name: "EditNewItem",

  components: {
Gilgard's avatar
Gilgard committed
    ColoredButton,
Gilgard's avatar
Gilgard committed
    FormImageDisplay,
Gilgard's avatar
Gilgard committed
  },

  setup() {
    return { v$: useVuelidate() };
  },

  validations() {
    return {
      updatedItem: {
        title: {
          required: helpers.withMessage(
            () => "Tittelen kan ikke være tom",
            required
          ),
          max: helpers.withMessage(
            () => `Tittelen kan inneholde max 50 tegn`,
            maxLength(50)
          ),
        },
        description: {
          required: helpers.withMessage(
            () => "Beskrivelsen kan ikke være tom",
            required
          ),
          max: helpers.withMessage(
            () => `Beskrivelsen kan inneholde max 200 tegn`,
            maxLength(200)
          ),
          min: helpers.withMessage(
            () => `Beskrivelsen kan ikke være tom`,
            minLength(0)
          ),
        },
        price: {
          required,
          between: helpers.withMessage(
            () => `Leieprisen kan ikke være større enn 25000`,
            between(0, 25000)
          ),
        },
        selectedCategory: {
          required: helpers.withMessage(() => `Velg en kategori`, required),
        },
        address: {
          required: helpers.withMessage(
            () => "Addressen kan ikke være tom",
            required
          ),
          max: helpers.withMessage(
            () => `Addressen kan inneholde max 50 tegn`,
            maxLength(50)
          ),
        },
      },
    };
  },

  data() {
    return {
      updatedItem: {
        title: "",
        description: "",
        address: "",
        price: "",
        category: "",
        selectedCategory: "",
        selectedCategories: [],
        userId: -1,
        selectedCommunityId: -1,
        selectedCommunities: [],
Gilgard's avatar
Gilgard committed
        images: [],
Gilgard's avatar
Gilgard committed
      },
      categories: [
        "Antikviteter og kunst",
        "Dyr og utstyr",
        "Elektronikk og hvitevarer",
        "Foreldre og barn",
        "Fritid, hobby og underholdning",
        "Hage, oppussing og hus",
        "Klær, kosmetikk og tilbehør",
        "Møbler og interiør",
        "Næringsvirksomhet",
        "Sport og friluftsliv",
        "Utstyr til bil, båt og MC",
      ],
      initialItem: {},
      communities: [],
      communityErrorMessage: "",
      images: [],
    };
  },

  methods: {
    checkValidation() {
      this.v$.updatedItem.$touch();
      if (
        this.v$.updatedItem.$invalid ||
        this.updatedItem.selectedCommunities.length === 0
      ) {
        if (this.updatedItem.selectedCommunities.length === 0) {
          this.communityErrorMessage = "Velg gruppe/grupper";
        }
        return false;
      }
      return true;
    },

    /**
     * Validation gets checked, and if it returns true
     * the item and the images gets updated.
     */
Gilgard's avatar
Gilgard committed
    async saveClicked() {
      if (this.checkValidation()) {
        let itemInfo = {
          listingID: parseInt(this.initialItem.listingID),
          title: this.updatedItem.title,
          description: this.updatedItem.description,
          pricePerDay: this.updatedItem.price,
          address: this.updatedItem.address,
          userID: this.updatedItem.userId,
          categoryNames: this.updatedItem.selectedCategories,
          communityIDs: this.updatedItem.selectedCommunities,
        };
        await ListingService.putItem(itemInfo);
Gilgard's avatar
Gilgard committed
        await ImageService.putListingImages(
          this.initialItem.listingID,
          this.updatedItem.images
        );
Gilgard's avatar
Gilgard committed
        this.$router.push("/itempage/" + this.initialItem.listingID);
      }
    },

    /**
     * Adds image when an image is selected from file explorer.
     * Posts it to the db and gets the id of the image posted in return.
     * Adds that id to an image URL and saves it in an array.
     * That array containing image URLs gets posted to the db when save is clicked.
     */
Gilgard's avatar
Gilgard committed
    async addImage(event) {
      var that = this;
      let image = event.target.files[0];
      let fileReader = new FileReader();
      fileReader.onloadend = async function () {
        const res = fileReader.result;
        const id = await ImageService.postNewImage(res);

        const API_URL = process.env.VUE_APP_BASEURL;
Gilgard's avatar
Gilgard committed
        that.updatedItem.images.push(API_URL + "images/" + id);
Gilgard's avatar
Gilgard committed
      };
      fileReader.readAsArrayBuffer(image);
    /**
     * Runs every time a chech box under 'grupper' is changed(checked/unchecked).
     * Finds out if it was checked or unchecked and adds/removes the community from
     * the array based on that.
     */
Gilgard's avatar
Gilgard committed
    onChangeCommunity(e) {
Gilgard's avatar
Gilgard committed
      this.updatedItem.selectedCommunityId = e.target.value;
Gilgard's avatar
Gilgard committed
      let alreadyInGroupList = false;

      for (let i = 0; i <= this.updatedItem.selectedCommunities.length; i++) {
        if (
Gilgard's avatar
Gilgard committed
          this.updatedItem.selectedCommunityId ==
          this.updatedItem.selectedCommunities[i]
Gilgard's avatar
Gilgard committed
        ) {
          const index = this.updatedItem.selectedCommunities.indexOf(
Gilgard's avatar
Gilgard committed
            this.updatedItem.selectedCommunityId
Gilgard's avatar
Gilgard committed
          );
          if (index > -1) {
Gilgard's avatar
Gilgard committed
            this.updatedItem.selectedCommunities.splice(index, 1);
Gilgard's avatar
Gilgard committed
          }
          alreadyInGroupList = true;
        }
      }

      if (!alreadyInGroupList) {
Gilgard's avatar
Gilgard committed
        this.updatedItem.selectedCommunities.push(
          this.updatedItem.selectedCommunityId
        );
Gilgard's avatar
Gilgard committed
        this.communityErrorMessage = "";
      }
    },

    /**
     * Updates the selected category when it gets changed changes.
     */
Gilgard's avatar
Gilgard committed
    onChangeCategory(e) {
      this.updatedItem.selectedCategory = e.target.value;
      this.updatedItem.selectedCategories = [e.target.value];
    },

    /**
     * pre-selects (check marks) the community/communities the item
     * is posted in so the user can see where the item already is posted and
     * then change the community/communities
     */
Gilgard's avatar
Gilgard committed
    isInSelectedCommunity(id) {
      for (let i in this.updatedItem.selectedCommunities) {
        if (this.updatedItem.selectedCommunities[i] == id) {
          return true;
        }
      }
      return false;
    },

    /**
     * Removes image from item
     */
Gilgard's avatar
Gilgard committed
    async removeImage(image) {
Gilgard's avatar
Gilgard committed
      let newImages = [];
Gilgard's avatar
Gilgard committed
      for (let i in this.updatedItem.images) {
        if (this.updatedItem.images[i] != image) {
Gilgard's avatar
Gilgard committed
          newImages.push(this.images[i]);
Gilgard's avatar
Gilgard committed
      this.updatedItem.images = newImages;
Gilgard's avatar
Gilgard committed
    },
  /**
   * Gets the item before the page gets mounted so the item info
   * is filled in and ready to be displayed to user.
   */
Gilgard's avatar
Gilgard committed
  async beforeCreate() {
Gilgard's avatar
Gilgard committed
    let itemID = await this.$router.currentRoute.value.params.id;
    let item = await ListingService.getItem(itemID);

    // Check if user is the owner of the item
    let userID = await parseCurrentUser().userId;
    if (item.userID == userID) {
      this.$router.push(this.$router.options.history.state.back);
    }

    this.initialItem = item;
    this.communities = await CommunityService.getUserCommunities();
Gilgard's avatar
Gilgard committed

Gilgard's avatar
Gilgard committed
    this.images = await ListingService.getItemPictures(itemID);
Gilgard's avatar
Gilgard committed
    let imageURLS = [];
    for (let i in this.images) {
      imageURLS.push(this.images[i].picture);
    }
Gilgard's avatar
Gilgard committed

    let initialCategories = [];
    for (let i in this.initialItem.categoryNames) {
      initialCategories.push(this.initialItem.categoryNames[i]);
    }
    let selectedCategory =
      initialCategories.length > 0 ? initialCategories[0] : "";

    let initialCommunities = [];
    for (let i in this.initialItem.communityIDs) {
      initialCommunities.push(this.initialItem.communityIDs[i]);
    }

    this.updatedItem = {
      title: this.initialItem.title,
      description: this.initialItem.description,
      address: this.initialItem.address,
      price: this.initialItem.pricePerDay,
      selectedCategories: initialCategories,
      selectedCategory: selectedCategory,
Gilgard's avatar
Gilgard committed
      images: imageURLS,
Gilgard's avatar
Gilgard committed
      userId: this.initialItem.userID,
      selectedCommunityId: 0,
      selectedCommunities: initialCommunities,
    };
  },
};
</script>