Laravel and Vue: Creating a Portfolio website with a CRUD admin panel

Laravel and Vue: Creating a Portfolio website with a CRUD admin panel

  • 872

Laravel and Vue: Creating a Portfolio website with a CRUD admin panel. Vue.js is a library for building interactive web interfaces. It provides data-reactive components with a simple and flexible API.

In Chapter Seven, we had created a form to submit portfolio data and used it to submit the portfolio data. At the end of the chapter, we saw that we still need to do the following:

  • Front End Validation
  • Back End Validation
  • Read, Update and Delete functionality
  • Refactoring the PortfolioController

The validations are related to the form we are using to add data. Let’s tackle the validations.

Front-End Validation (for adding data)

We can copy the validationErrors() computed method from ProfileEditor.vue and remove the ‘please fill at least one field’ rule.

NAME CHANGE: The name of the method has been changed from _validationErrors()_ to _validation()_.

The front-end validation is going to be similar to ProfileEditor’s front-end validation. The difference is that instead of requiring at least one field, we now require image and project name. The description is optional.

Validation Rules: We need a file and we need a name but the description is optional; so there will be a validation rule for file and a validation rule for name but none for description.

  • Name Required
  • Image Required
  • Submit button is disabled until we have a name and image
computed: {
    validation(){
        this.disabled = true;
        i̶f̶ ̶(̶t̶h̶i̶s̶.̶n̶a̶m̶e̶ ̶=̶=̶=̶ ̶'̶'̶ ̶&̶&̶ ̶t̶h̶i̶s̶.̶f̶i̶l̶e̶.̶l̶e̶n̶g̶t̶h̶ ̶<̶=̶ ̶0̶)̶ ̶{̶
̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶r̶e̶t̶u̶r̶n̶ ̶'̶p̶l̶e̶a̶s̶e̶ ̶f̶i̶l̶l̶ ̶a̶t̶ ̶l̶e̶a̶s̶t̶ ̶o̶n̶e̶ ̶f̶i̶e̶l̶d̶'̶;̶
̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶}̶
        if (this.name === '') {
            return 'please fill in the project name';
        }
        if (!this.file) {
            return 'please select an image';
        }
        if (this.file && !this.file.type.startsWith("image/")) {
            return 'file must be an image';
        }
        else {
            this.disabled = false;
            return  ':)'
        }
    }
}

For the submit button, have a disabled attribute and bind the disabled attribute to **disabled**

<button @click.prevent="onSubmit" :disabled="disabled">Submit</button>

Insert **disabled** in data()

data() {
    return {
        file: '',
        name: '',
        description: '',
        disabled: true,
    }
},

Put an errorBar below the submit button in the form

<div class="errorBar">{{validation}}</div>

We can tick front-end validation

  • Front End Validation ✔️
  • Back End Validation
  • Read, Update and Delete functionality
  • Refactoring the PortfolioController

Back End Validation (for adding data)

Let’s do the laravel validation slightly differently. Last time, we saw that it can be done in the store function. This time, we would like to refactor the validation into a separate request.

php artisan make:request StorePortfolioItemRequest

This will create a file at:

App\Http\Requests\StorePortfolioItemRequest.php

This request file will handle your validation.

If the authorize() function returns false, change it to true. This function allows you to use the validation that this file provides.

We can later change this function to only authorize if we are logged in.

public function authorize()
{
    return true;
}

Apply the **required** rule to both itempic and name. The name should be **unique** in the portfolio_items table.

public function rules()
{
    return [
        'itempic' => 'required',
        'name' => 'required|unique:portfolio_items',
    ];
}

and now change the Request $request in store()’s brackets to **StorePortfolioItemRequest** $request

public function store(R̶e̶q̶u̶e̶s̶t̶ StorePortfolioItemRequest $request) {
    $file = $request->itempic;
    $hashName = $file->hashName();
    $this->resizeAndStore($file, $hashName);
    $this->storeDetails($hashName);
}

Back-end validation gets a tick

We can tick front-end validation

  • Front End Validation ✔️
  • Back End Validation ✔️
  • Read, Update and Delete functionality
  • Refactoring the PortfolioController

The Read, Update and Delete functionality is going to be implemented in a table.

Let’s create a table that allows us to perform CRUD operations.

Read — We need to render the read into table

Update — We need to use a form similar to the create form to update the information

Delete — We need a delete button and a confirm delete popup.

Creating the CRUD Table — READ

We need a table with project names, the option to remove the project from the portfolio, and the option to change the project’s details etc.

Let’s have that in bullet points:

  • Project Name
  • Edit Button
  • Delete Button
  • Add Button

Our table is going to look like this:

This is image title

Let’s have a table element

<table>
    
</table>

Within the table element, we need a **thead** and a **tbody** element. **thead** is the element that will contain the headings i.e. Project Name, Edit and Delete in the image above. **tbody**will contain the other rows.

<table>
    <thead></thead>
    <tbody></tbody>
</table>

table-headings within thead element using the **th** element:

<table>
    <thead>
        <tr>
            <th>Project Name</th>
            <th>Edit</th>
            <th>Delete</th>
        </tr>
    </thead>
    <tbody></tbody>
</table>

Within tbody

<table>
    <thead>
        <tr>
            <th>Project Name</th>
            <th>Edit</th>
            <th>Delete</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Item Name</td>
            <td>
                <button>EDIT</button>
            </td>
            <td>
                <button>DELETE</button>
            </td>
        </tr>
    </tbody>
</table>

Now you should have something that looks like this:

This is image title

CRUD Table at beginning

This is how you would create a table that has nothing to do with a database. For a table that is linked to databases, we need to use a for loop. In Vue js, the for loop is called v-for loop. To we get to the v-for method, we need to go through the ‘Database-Route-Script-Template’ road.

This is image title

We should submit some items and then the database part is done.✅

This is image title

Let’s go to the route and see the portfolio items

This is image title

The route part is done ✅

This is image title

The script tag’s getPortfolio() method

getPortfolio() {
    axios.get('/api/portfolio')
        .then(response => {
            this.items = response.data;
        })
        .catch(error => {
            console.log(error);
        });
},

data() in script tag

data() {
    return {
        items: '',
    }
},

The script tag part is done ✅

This is image title

Time to use v-for

If we use vue-devtools, we can see the items that we are getting from the route

This is image title

We can use v-for to go through these items and display parts of the **items** data. The part that we want to display in this case is the name that is found within every item.

How a v-for loop works:

  1. In the above image, you can see an Object number 0 and an Object number 1.
  2. We come up with a **variable** for these objects.
  3. The **variable** is then used as the first word in the v-for loops’s quotation marks i.e. **""**
v-for="variable in object"

The object we want to loop through is called **items**

v-for="variable in o̶b̶j̶e̶c̶t̶ items"

A suitable word for an ‘object in items’ is ‘item’ so let’s change **variable** to **item**

v-for="item in items"

Apply this v-for to the **tr** within **tbody** to render all the projects and get the name within every item using {{item.name}}

<tr v-for="item in items">
    <td>{{item.name}}</td>
    <td>
        <button>EDIT</button>
    </td>
    <td>
        <button>DELETE</button>
    </td>
</tr>

Data from both the projects is rendered in the table

This is image title

Let’s build the add button above the **thead**

<caption><button>+ Add New Item</button></caption>
<thead>
...
</thead>

This is image title

This is image title

Creating the CRUD Table —UPDATE

Upon clicking the edit button, we want a modal to popup and the modal should contain the update form.

<button @click="editItemPopup(item)">EDIT</button>

In methods:

editItemPopup(item) {
    this.item = item;
    if (this.updateItemModal === false) {
        this.updateItemModal = true;
    }
    else if (this.updateItemModal === true) {
        this.updateItemModal = false;
    }
},

Code Explanation: editItemPopup is the method that will be used. item in the brackets is the v-for **variable**.

We can see that v-for can also be used to run methods.

Before the PortfolioEditor.vue gets too big, we should make the table a child component and pass the items as a prop (similar to Chapter Three).

<template>
    <section>
        <h1>Portfolio Editor</h1>
        <form>...</form>
        <portfolio-editor-table :items="items"/>
    </section>
</template>

<script>
    import portfolioEditorTable from './portfolioEditorTable';
    export default {
        name: "PortfolioEditor",
        data() {
            return {
                items: '',
            }
        },
        components: {portfolioEditorTable},
        mounted() {
            this.getPortfolio();
        },
        methods: {
            getPortfolio() {
                axios.get('/api/portfolio')
                    .then(response => {
                        this.items = response.data;
                    })
                    .catch(error => {
                        console.log(error);
                    });
            }
        }
    }
</script>

Passing the prop

<script>
    export default {
        name: "portfolioEditorTable",
        props: {
          items: ''
        },
        data() {
            return {
                p: this.$parent
            }
        }
    }
</script>

You may have noticed that within data there is **p: this.$parent** . This is just so that you don’t have to type **this.$parent**when calling a method in the parent, you can just shorten it to **p**(or anything of your choosing).

<button @click="p.editItemPopup(item)">EDIT</button>

We should keeep the editItemPopup method in the parent component because we want to have the CRUD api methods in one file and the “non-api” methods in other files.

So if you’ve got the editItemPopup(item) in the table component, you should move it to the parent component.

Import the update-item-modal and place it above the table component within PortfolioEditor.vue

<section>
    <h1>Portfolio Editor</h1>
    <update-item-modal v-if="updateItemModal" :item="item"/>
    <portfolio-editor-table :items="items"/>
</section>

We will create this modal in the next chapter.

At the end of the last chapter, we had a list of what to do in future chapters. From that list we have tackled front-end and back-end validations (for the add/create functionality)

We are yet to to the following:

  • Front End Validation (for update functionality)
  • Back End Validation (for update functionality)
  • Update and Delete functionalities (Read is done)
  • Refactoring the PortfolioController

And we are yet to create a Portfolio.vue component which will consist of

  • Images with text
  • Modal with bigger image and description
  • Close button on modal