I build a Task Management project using laravel & vue js which is worked very nicely.But edit function can't work properly in this application.I tried a lot but failed.Even when clicked edit option this would work to same as create/add function.How can I solve this problem.I will share my code below:
resource/js/app.js
require("./bootstrap");
window.Vue = require("vue");
/ / Register our components
Vue.component("kanban-board", require("./components/KanbanBoard.vue").default);
const app = new Vue({
el: "#app"
});
resource/js/components/KanbanBoard.vue
<template>
<div class="relative p-2 flex overflow-x-auto h-full">
<!-- Columns (Statuses) -->
<div
v-for="status in statuses"
:key="status.slug"
class="mr-6 w-4/5 max-w-xs flex-shrink-0"
>
<div class="rounded-md shadow-md overflow-hidden">
<div class="p-1 flex justify-between items-baseline bg-green-500 ">
<h4 class="font-medium text-white">
{{ status.title }}
</h4>
</div>
<div class="p-2 bg-green-300">
<!-- AddTaskForm -->
<AddTaskForm
v-if="newTaskForStatus === status.id"
:status-id="status.id"
v-on:task-added="handleTaskAdded"
v-on:task-canceled="closeAddTaskForm"
/>
<!-- ./AddTaskForm -->
<!-- EditTaskForm -->
<AddTaskForm
v-if="editTaskForStatus === status.id"
:status-id="status.id"
v-on:task-edit="handleTaskEdit"
v-on:task-canceled="closeEditTaskForm"
/>
<!-- ./EditTaskForm -->
<!-- Tasks -->
<draggable
class="flex-1 overflow-hidden"
v-model="status.tasks"
v-bind="taskDragOptions"
#end="handleTaskMoved"
>
<transition-group
class="flex-1 flex flex-col h-full overflow-x-hidden overflow-y-auto rounded shadow-xs"
tag="div"
>
<div
v-for="task in status.tasks"
:key="task.id"
class="mb-3 p-4 flex flex-col bg-white rounded-md shadow transform hover:shadow-md cursor-
pointer"
>
<button
type="submit"
#click="openEditTaskForm(status.id)"
class="py-1 px-2 text-sm text-black-500 hover:underline"
>
Edit Task
</button>
<span class="block mb-2 text-xl text-gray-900">
Task-100{{ task.id }}
</span>
<span class="block mb-2 text-xl text-gray-900">
{{ task.title }}
</span>
<p class="text-gray-700">
{{ task.description }}
</p>
</div>
<!-- ./Tasks -->
</transition-group>
</draggable>
<!-- No Tasks -->
<div
v-show="!status.tasks.length && newTaskForStatus !== status.id"
class="flex-1 p-4 flex flex-col items-center justify-center"
>
<!-- <span class="text-gray-600">No tasks yet</span> -->
<button
class="mt-1 text-sm text-orange-600 hover:underline"
#click="openAddTaskForm(status.id)"
>
Add one
</button>
</div>
<!-- ./No Tasks -->
</div>
</div>
<button
type="submit"
#click="openAddTaskForm(status.id)"
class="px-12 py-1 leading-5 text-white bg-orange-600 hover:bg-orange-500 rounded"
>
Add
</button>
</div>
<!-- ./Columns -->
</div>
</template>
<script>
import draggable from "vuedraggable";
import AddTaskForm from "./AddTaskForm";
import EditTaskForm from "./EditTaskForm";
export default {
components: { draggable, AddTaskForm,EditTaskForm },
props: {
initialData: Array
},
data() {
return {
statuses: [],
newTaskForStatus: 0,
editTaskForStatus: 0
};
},
computed: {
taskDragOptions() {
return {
animation: 200,
group: "task-list",
dragClass: "status-drag"
};
}
},
mounted() {
// 'clone' the statuses so we don't alter the prop when making changes
this.statuses = JSON.parse(JSON.stringify(this.initialData));
},
methods: {
openAddTaskForm(statusId) {
this.newTaskForStatus = statusId;
},
closeAddTaskForm() {
this.newTaskForStatus = 0;
},
handleTaskAdded(newTask) {
// Find the index of the status where we should add the task
const statusIndex = this.statuses.findIndex(
status => status.id === newTask.status_id
);
// Add newly created task to our column
this.statuses[statusIndex].tasks.push(newTask);
// Reset and close the AddTaskForm
this.closeAddTaskForm();
},
handleTaskMoved(evt) {
axios.put("/tasks/sync", { columns: this.statuses }).catch(err => {
console.log(err.response);
});
},
openEditTaskForm(statusId) {
this.editTaskForStatus = statusId;
},
closeEditTaskForm() {
this.editTaskForStatus = 0;
},
handleTaskEdit(editTask) {
// Find the index of the status where we should add the task
const statusIndex = this.statuses.findIndex(
status => status.id === editTask.status_id
);
// Add newly created task to our column
this.statuses[statusIndex].tasks.push(editTask);
// Reset and close the AddTaskForm
this.closeEditTaskForm();
}
}
};
</script>
<style scoped>
.status-drag {
transition: transform 0.5s;
transition-property: all;
}
</style>
resource/js/components/AddTask.vue
<template>
<form
class="relative mb-3 flex flex-col justify-between bg-white rounded-md shadow overflow-
hidden"
#submit.prevent="handleAddNewTask"
>
<div class="p-3 flex-1">
<input
class="block w-full px-2 py-1 text-lg border-b border-blue-800 rounded"
type="text"
placeholder="Enter a title"
v-model.trim="newTask.title"
/>
<textarea
class="mt-3 p-2 block w-full p-1 border text-sm rounded"
rows="2"
placeholder="Add a description"
v-model.trim="newTask.description"
></textarea>
<div v-show="errorMessage">
<span class="text-xs text-red-500">
{{ errorMessage }}
</span>
</div>
</div>
<div class="p-3 flex justify-between items-end text-sm bg-gray-100">
<button
#click="$emit('task-canceled')"
type="reset"
class="py-1 leading-5 text-gray-600 hover:text-gray-700"
>
cancel
</button>
<button
type="submit"
class="px-3 py-1 leading-5 text-white bg-orange-600 hover:bg-orange-500 rounded"
>
Add
</button>
</div>
</form>
</template>
<script>
export default {
props: {
statusId: Number
},
data() {
return {
newTask: {
title: "",
description: "",
status_id: null
},
errorMessage: ""
};
},
mounted() {
this.newTask.status_id = this.statusId;
},
methods: {
handleAddNewTask() {
// Basic validation so we don't send an empty task to the server
if (!this.newTask.title) {
this.errorMessage = "The title field is required";
return;
}
// Send new task to server
axios
.post("/tasks", this.newTask)
.then(res => {
// Tell the parent component we've added a new task and include it
this.$emit("task-added", res.data);
})
.catch(err => {
// Handle the error returned from our request
this.handleErrors(err);
});
},
handleErrors(err) {
if (err.response && err.response.status === 422) {
// We have a validation error
const errorBag = err.response.data.errors;
if (errorBag.title) {
this.errorMessage = errorBag.title[0];
} else if (errorBag.description) {
this.errorMessage = errorBag.description[0];
} else {
this.errorMessage = err.response.message;
}
} else {
// We have bigger problems
console.log(err.response);
}
}
}
};
</script>
resource/js/components/EditTask.vue
<template>
<form
class="relative mb-3 flex flex-col justify-between bg-white rounded-md shadow overflow-
hidden"
#submit.prevent="handleEditTask"
>
<div class="p-3 flex-1">
<input
class="block w-full px-2 py-1 text-lg border-b border-blue-800 rounded"
type="text"
#blur="handleEditTask(task)"
v-model="editTask.title"
/>
<textarea
class="mt-3 p-2 block w-full p-1 border text-sm rounded"
rows="2"
p
#blur="handleEditTask(task)"
v-model="editTask.description"
></textarea>
<div v-show="errorMessage">
<span class="text-xs text-red-500">
{{ errorMessage }}
</span>
</div>
</div>
<div class="p-3 flex justify-between items-end text-sm bg-gray-100">
<button
#click="$emit('task-canceled')"
type="reset"
class="py-1 leading-5 text-gray-600 hover:text-gray-700"
>
cancel
</button>
<button
type="submit"
class="px-3 py-1 leading-5 text-white bg-orange-600 hover:bg-orange-500 rounded"
>
Add
</button>
</div>
</form>
</template>
<script>
export default {
props: {
statusId: Number
},
data() {
return {
editTask: {
title:task.title,
description:task.description,
status_id: null
},
errorMessage: ""
};
},
mounted() {
this.editTask.status_id = this.statusId;
},
methods: {
handleEditTask() {
// Basic validation so we don't send an empty task to the server
if (!this.editTask.title) {
this.errorMessage = "The title field is required";
return;
}
// Send edit task to server
axios
.patch("tasks/{task}", this.editTask)
.then(res => {
// Tell the parent component we've added a new task and include it
this.$emit("task-edit", res.data);
})
.catch(err => {
// Handle the error returned from our request
this.handleErrors(err);
});
},
handleErrors(err) {
if (err.response && err.response.status === 422) {
// We have a validation error
const errorBag = err.response.data.errors;
if (errorBag.title) {
this.errorMessage = errorBag.title[0];
} else if (errorBag.description) {
this.errorMessage = errorBag.description[0];
} else {
this.errorMessage = err.response.message;
}
} else {
// We have bigger problems
console.log(err.response);
}
}
}
};
</script>
app/Http/Controllers/TaskController
<?php
namespace App\Http\Controllers;
use App\Task;
use Illuminate\Http\Request;
class TaskController extends Controller
{
public function index()
{
$tasks = auth()->user()->statuses()->with('tasks')->get();
return view('tasks.index', compact('tasks'));
}
public function create()
{
//
}
public function store(Request $request)
{
$this->validate($request, [
'title' => ['required', 'string', 'max:56'],
'description' => ['nullable', 'string'],
'status_id' => ['required', 'exists:statuses,id']
]);
return $request->user()
->tasks()
->create($request->only('title', 'description', 'status_id'));
}
public function sync(Request $request)
{
$this->validate(request(), [
'title' => ['required', 'string', 'max:56'],
'description' => ['nullable', 'string'],
'columns' => ['required', 'array']
]);
foreach ($request->columns as $status) {
foreach ($status['tasks'] as $i => $task) {
$order = $i + 1;
if ($task['status_id'] !== $status['id'] || $task['order'] !== $order) {
request()->user()->tasks()
->find($task['id'])
->update(['status_id' => $status['id'], 'order' => $order],$request-
>only('title', 'description',
'status_id'));
}
}
}
return $request->user()->statuses()->with('tasks')->get();
}
public function show(Task $task)
{
//
}
public function edit(Request $request)
{
}
public function update(Request $request, Task $task)
{
$this->validate($request, [
'title' => ['required', 'string', 'max:56'],
'description' => ['nullable', 'string'],
'status_id' => ['required', 'exists:statuses,id']
]);
return $request->user()
->tasks()
->update($request->only('title', 'description', 'status_id'));
}
public function destroy(Task $task)
{
//
}
}
resource/views/tasks/index.blade.php
#extends('layouts.app')
#section('content')
<div class="md:mx-4 relative overflow-hidden">
<main class="h-full flex flex-col overflow-auto">
<kanban-board :initial-data="{{ $tasks }}"></kanban-board>
</main>
</div>
#endsection
web.php
Route::group(['middleware' => 'auth'], function () {
Route::get('tasks', 'TaskController#index')->name('tasks.index');
Route::post('tasks', 'TaskController#store')->name('tasks.store');
Route::put('tasks/sync', 'TaskController#sync')->name('tasks.sync');
Route::put('tasks/{task}', 'TaskController#update')->name('tasks.update');
});
Route::group(['middleware' => 'auth'], function () {
Route::post('statuses', 'StatusController#store')->name('statuses.store');
Route::put('statuses', 'StatusController#update')->name('statuses.update');
});
Related
Blog post
Above is my blog post, Below I will post my current set up.
New blog form
I'm new to this so, I want to be able to upload images and each of my blog post to pull the requested images. Currently the file selector isn't linked so it creates a fake path.
This is my blogs controller
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Http\Requests\BlogsUpdateRequest;
use App\Http\Requests\DatatablesRequest;
use App\Modules\Blogs\BlogsService;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
class BlogsController extends Controller
{
public function __construct(
private readonly BlogsService $service
)
{
}
public function index(DatatablesRequest $request): JsonResponse
{
try {
return response()->json($this->service->index($request->data()));
} catch (Exception $error) {
return response()->json(
[
"exception" => get_class($error),
"errors" => $error->getMessage()
],
Response::HTTP_BAD_REQUEST
);
}
}
public function get(int $id): JsonResponse
{
try {
return response()->json($this->service->get($id));
} catch (Exception $error) {
return response()->json(
[
"exception" => get_class($error),
"errors" => $error->getMessage()
],
Response::HTTP_BAD_REQUEST
);
}
}
public function update(BlogsUpdateRequest $request): JsonResponse
{
try {
return response()->json($this->service->update($request));
} catch (Exception $error) {
return response()->json(
[
"exception" => get_class($error),
"errors" => $error->getMessage()
],
Response::HTTP_BAD_REQUEST
);
}
}
public function delete(int $id): JsonResponse
{
try {
$this->service->delete($id);
return response()->json(null, Response::HTTP_NO_CONTENT);
} catch (Exception $error) {
return response()->json(
[
"exception" => get_class($error),
"errors" => $error->getMessage()
],
Response::HTTP_BAD_REQUEST
);
}
}
}
This is my blogsupdaterequest
<?php
declare(strict_types=1);
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class BlogsUpdateRequest extends FormRequest
{
public function rules(): array
{
return [
"id" => "nullable|numeric",
"is_trending" => "required|boolean",
"author" => "required|string",
"author_image_url" => "required|string",
"image_url_portrait" => "required|string",
"image_url_landscape" => "required|string",
"date" => "required|string",
"title" => "required|string",
"tags" => "required|string",
"description" => "required|string",
"content" => "required|string",
];
}
public function data(): array
{
$id = $this->input("id", null);
return [
"id" => ($id === null) ? null : (int)$id,
"is_trending" => $this->input("is_trending", 0),
"author" => $this->input("author"),
"author_image_url" => $this->input("author_image_url"),
"image_url_portrait" => $this->input("image_url_portrait"),
"image_url_landscape" => $this->input("image_url_landscape"),
"date" => $this->input("date"),
"url" => $this->generateUrl($this->input("title")),
"title" => $this->input("title"),
"tags" => array_map(function($row) {
return trim($row);
}, explode(",", $this->input("tags", []))),
"description" => $this->input("description"),
"content" => $this->input("content"),
];
}
private function generateUrl(string $title): string {
$newUrl = trim(strtolower($title));
$newUrl = str_replace(" ", " ", $newUrl);
$newUrl = str_replace(" ", "-", $newUrl);
return preg_replace("/[^A-Za-z0-9\-]/", "", $newUrl);
}
}
Blogs model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Blogs extends Model
{
protected $table = "blogs";
protected $fillable = [
"id",
"is_trending",
"author",
"author_image_url",
"image_url_portrait",
"image_url_landscape",
"title",
"date",
"description",
"content",
"created_at",
"updated_at",
];
public function tags (){
return $this->hasMany(BlogTags::class);
}
}
Blogs.blade
<div class="col-lg-8 col-12">
#foreach($blogs as $blog)
<span onclick="redirectto('{{$blog['url']}}')">
<div class="row mt-4 mb-4 blog-card border rounded">
<div class="col-lg-4 col-12 p-0 m-0">
<img src="{{$blog ['img_url_portrait']}}" alt="" class="rounded w-100 h-100">
</div>
<div class="col-lg-8 col-12 p-lg-5">
<div class="row h-100 pt-4 align-item-center">
<div class="col-12 mx-auto">
<small class="text-muted fs-8">
{{$blog ['date']}}
</small>
<br>
#foreach ($blog['tags'] as $tag)
<span class="text-primary fw-bolder fs-6 pe-1">{{$tag['$tag']}}</span>
#if($loop->iteration < count($blog['tags'])))
<span class="text-primary fw-bolder fs-6 pe-1">•</span>
#endif
#endforeach
<h2 class="fw-lighter fs-2">{{$blog['title'] }}</h2>
<p class="text-muted">{{$blog['description'] }}</p>
<p>
<img src="{{$blog['author_image_url']}}" alt="author image" class="rounded-circle" height="35" width="35">
<span class="ps-1">{{$blog['author']}}</span>
</p>
</div>
</div>
</div>
</div>
</span>
#endforeach
</div>
Images model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Images extends Model
{
protected $table = "image";
protected $fillable = [
"id",
"name",
"location",
"created_at",
"updated_at",
];
}
Form.blade
<div class="modal fade" id="BlogsModal">
<div class="modal-dialog modal-fullscreen text-dark">
<div class="modal-content">
<div class="modal-content container" style="overflow-y:scroll;">
<div class="row">
<div class="col-12 text-end">
<span type="button" class="btn-close" data-bs-dismiss="modal"></span>
</div>
<div class="col-12">
<div id="BlogsErrorsContainer" class="alert alert-danger errorContainer" style="display:none;">
<h5 class="font-weight-bolder">Error!</h5>
<ul></ul>
</div>
</div>
<div class="col-12">
<hr>
<h5 class="modal-title font-weight-bolder">New Blog</h5>
<hr>
<form id="BlogsForm">
<input type="hidden" class="form-control" id="itemId">
<div class="row">
<div class="col-12 mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="1" id="blogsIsTrending">
<label class="form-check-label">Trending</label>
</div>
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Title</label>
<input type="text" class="form-control" id="blogsTitle">
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Date</label>
<input type="text" class="form-control" id="blogsDate">
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Author Name</label>
<input type="text" class="form-control" id="blogsAuthorName">
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Author Image URL</label>
<input type="file" class="form-control" id="blogsAuthorImageUrl">
</div>
<div class="col-12">
<hr>
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Image Url Landscape</label>
<input type="file" class="form-control" id="blogsImageUrlLandscape">
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Image Url Portrait</label>
<input type="file" class="form-control" id="images[]">
</div>
<div class="col-12">
<hr>
</div>
<div class="col-12">
<label class="form-label font-weight-bolder">Tags</label>
<input type="text" class="form-control" id="blogsTags">
<div class="form-text">Tags must be seperated by a comma. Example: <i>Laravel, Hosting</i></div>
</div>
<div class="col-12">
<hr>
</div>
<div class="col-12">
<label class="form-label font-weight-bolder">Description</label>
<textarea rows="2" type="text" class="form-control" id="blogsDescription"></textarea>
<div class="form-text">1-3 sentences max.</div>
</div>
<div class="col-12 mb-5">
<label class="form-label font-weight-bolder">Content</label>
<div id="blogsContent"></div>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="modal-footer container">
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Close</button>
<button id="SubmitBlogsForm" type="button" class="btn btn-sm btn-primary">Save Changes</button>
</div>
</div>
</div>
</div>
<script src="//cdn.quilljs.com/1.3.6/quill.min.js"></script>
<script>
let formSubmitted = false;
let blogsEndpoint = "/api/dashboard/blogs";
let quillToolbar = [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
[{
'header': 1
}, {
'header': 2
}], // custom button values
[{
'list': 'ordered'
}, {
'list': 'bullet'
}],
[{
'script': 'sub'
}, {
'script': 'super'
}], // superscript/subscript
[{
'indent': '-1'
}, {
'indent': '+1'
}], // outdent/indent
[{
'direction': 'rtl'
}], // text direction
[{
'size': ['small', false, 'large', 'huge']
}], // custom dropdown
[{
'header': [1, 2, 3, 4, 5, 6, false]
}],
[{
'color': []
}, {
'background': []
}], // dropdown with defaults from theme
[{
'font': []
}],
[{
'align': []
}],
['clean'] // remove formatting button
];
let quill = new Quill(
"#blogsContent", {
modules: {
toolbar: quillToolbar
},
placeholder: "Compose an epic ...",
theme: "snow"
}
);
function clearForm() {
$("#itemId").val(null);
$("#blogsTitle").val(null);
$("#blogsAuthorName").val(null),
$("#blogsAuthorImageUrl").val(null);
$("#blogsImageUrlLandscape").val(null);
$("#blogsImageUrlPortrait").val(null);
$("#blogsTags").val(null);
$("#blogsDescription").val(null);
$("#blogsDate").val(null);
$("#blogsIsTrending").prop("checked", false);
$("#blogsContent").val(null);
quill.setContents([{
insert: "\n"
}]);
clearErrors();
}
function clearErrors() {
$("#BlogsErrorsContainer ul").empty();
$("#BlogsErrorsContainer").hide();
}
function showErrors(errorsList = []) {
Object.keys(errorsList).forEach(key => {
$("#BlogsModal .errorContainer ul").append(
"<li><b>" + key + ": </b>" + errorsList[key] + "</li>"
);
$("#BlogsModal .errorContainer").show();
});
}
$(document).ready(function() {
$("#SubmitBlogsForm").click(function() {
event.preventDefault();
clearErrors();
if (formSubmitted !== true) {
formSubmitted = true;
$.ajax({
type: "POST",
url: blogsEndpoint,
dataType: "json",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({
id: $("#itemId").val(),
title: $("#blogsTitle").val(),
author: $("#blogsAuthorName").val(),
author_image_url: $("#blogsAuthorImageUrl").val(),
image_url_landscape: $("#blogsImageUrlLandscape").val(),
image_url_portrait: $("#blogsImageUrlPortrait").val(),
tags: $("#blogsTags").val(),
description: $("#blogsDescription").val(),
date: $("#blogsDate").val(),
is_trending: $("#blogsIsTrending").prop("checked"),
content: quill.root.innerHTML.trim(),
}),
success: function(response) {
formSubmitted = false;
$BlogsModal.hide();
$("#dataTable").DataTable().ajax.reload();
}
}).fail(function(response) {
formSubmitted = false;
if (response.status === 422) {
showErrors(response.responseJSON["errors"]);
} else {
showErrors({
Error: "Could not process your request! Please try again later."
});
}
});
}
});
});
</script><div class="modal fade" id="BlogsModal">
<div class="modal-dialog modal-fullscreen text-dark">
<div class="modal-content">
<div class="modal-content container" style="overflow-y:scroll;">
<div class="row">
<div class="col-12 text-end">
<span type="button" class="btn-close" data-bs-dismiss="modal"></span>
</div>
<div class="col-12">
<div id="BlogsErrorsContainer" class="alert alert-danger errorContainer" style="display:none;">
<h5 class="font-weight-bolder">Error!</h5>
<ul></ul>
</div>
</div>
<div class="col-12">
<hr>
<h5 class="modal-title font-weight-bolder">New Blog</h5>
<hr>
<form id="BlogsForm">
<input type="hidden" class="form-control" id="itemId">
<div class="row">
<div class="col-12 mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="1" id="blogsIsTrending">
<label class="form-check-label">Trending</label>
</div>
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Title</label>
<input type="text" class="form-control" id="blogsTitle">
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Date</label>
<input type="text" class="form-control" id="blogsDate">
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Author Name</label>
<input type="text" class="form-control" id="blogsAuthorName">
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Author Image URL</label>
<input type="text" class="form-control" id="blogsAuthorImageUrl">
</div>
<div class="col-12">
<hr>
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Image Url Landscape</label>
<input type="text" class="form-control" id="blogsImageUrlLandscape">
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Image Url Portrait</label>
<input type="text" class="form-control" id="blogsImageUrlPortrait">
</div>
<div class="col-12">
<hr>
</div>
<div class="col-12">
<label class="form-label font-weight-bolder">Tags</label>
<input type="text" class="form-control" id="blogsTags">
<div class="form-text">Tags must be seperated by a comma. Example: <i>Laravel, Hosting</i></div>
</div>
<div class="col-12">
<hr>
</div>
<div class="col-12">
<label class="form-label font-weight-bolder">Description</label>
<textarea rows="2" type="text" class="form-control" id="blogsDescription"></textarea>
<div class="form-text">1-3 sentences max.</div>
</div>
<div class="col-12 mb-5">
<label class="form-label font-weight-bolder">Content</label>
<div id="blogsContent"></div>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="modal-footer container">
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Close</button>
<button id="SubmitBlogsForm" type="button" class="btn btn-sm btn-primary">Save Changes</button>
</div>
</div>
</div>
</div>
<script src="//cdn.quilljs.com/1.3.6/quill.min.js"></script>
<script>
let formSubmitted = false;
let blogsEndpoint = "/api/dashboard/blogs";
let quillToolbar = [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
[{
'header': 1
}, {
'header': 2
}], // custom button values
[{
'list': 'ordered'
}, {
'list': 'bullet'
}],
[{
'script': 'sub'
}, {
'script': 'super'
}], // superscript/subscript
[{
'indent': '-1'
}, {
'indent': '+1'
}], // outdent/indent
[{
'direction': 'rtl'
}], // text direction
[{
'size': ['small', false, 'large', 'huge']
}], // custom dropdown
[{
'header': [1, 2, 3, 4, 5, 6, false]
}],
[{
'color': []
}, {
'background': []
}], // dropdown with defaults from theme
[{
'font': []
}],
[{
'align': []
}],
['clean'] // remove formatting button
];
let quill = new Quill(
"#blogsContent", {
modules: {
toolbar: quillToolbar
},
placeholder: "Compose an epic ...",
theme: "snow"
}
);
function clearForm() {
$("#itemId").val(null);
$("#blogsTitle").val(null);
$("#blogsAuthorName").val(null),
$("#blogsAuthorImageUrl").val(null);
$("#blogsImageUrlLandscape").val(null);
$("#blogsImageUrlPortrait").val(null);
$("#blogsTags").val(null);
$("#blogsDescription").val(null);
$("#blogsDate").val(null);
$("#blogsIsTrending").prop("checked", false);
$("#blogsContent").val(null);
quill.setContents([{
insert: "\n"
}]);
clearErrors();
}
function clearErrors() {
$("#BlogsErrorsContainer ul").empty();
$("#BlogsErrorsContainer").hide();
}
function showErrors(errorsList = []) {
Object.keys(errorsList).forEach(key => {
$("#BlogsModal .errorContainer ul").append(
"<li><b>" + key + ": </b>" + errorsList[key] + "</li>"
);
$("#BlogsModal .errorContainer").show();
});
}
$(document).ready(function() {
$("#SubmitBlogsForm").click(function() {
event.preventDefault();
clearErrors();
if (formSubmitted !== true) {
formSubmitted = true;
$.ajax({
type: "POST",
url: blogsEndpoint,
dataType: "json",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({
id: $("#itemId").val(),
title: $("#blogsTitle").val(),
author: $("#blogsAuthorName").val(),
author_image_url: $("#blogsAuthorImageUrl").val(),
image_url_landscape: $("#blogsImageUrlLandscape").val(),
image_url_portrait: $("#blogsImageUrlPortrait").val(),
tags: $("#blogsTags").val(),
description: $("#blogsDescription").val(),
date: $("#blogsDate").val(),
is_trending: $("#blogsIsTrending").prop("checked"),
content: quill.root.innerHTML.trim(),
}),
success: function(response) {
formSubmitted = false;
$BlogsModal.hide();
$("#dataTable").DataTable().ajax.reload();
}
}).fail(function(response) {
formSubmitted = false;
if (response.status === 422) {
showErrors(response.responseJSON["errors"]);
} else {
showErrors({
Error: "Could not process your request! Please try again later."
});
}
});
}
});
});
</script>
I have settings table with 3 columns (id, property, content).
I have seeded some data into settings table and I want to update this table. I am filling some form where each input is presenting one row of the table settings. After submitting that form, I want my table to be updated...
For example, some of my property-content pairs (some of my inputs in this form) are:
background_image - 'image.jpg'
header - 'this is the header'
I am confused because I don't really know what should I send to backend and also I am not sure what should endpoint be...
I hope some of you can help me. If you need more questions, be free to ask me. Thanks in advance, and here is my code:
SettingsController.php
<?php
namespace App\Http\Controllers\Api;
use App\Http\Requests\UpdateSettings;
use App\Http\Resources\SettingResource;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Setting;
class SettingController extends Controller
{
public function index()
{
$settings = Setting::all();
return SettingResource::collection($settings);
}
public function store(Request $request)
{
//
}
public function show($id)
{
//
}
public function update(UpdateSettings $request, Setting $setting)
{
$setting->where('property', $request->property)
->update([ 'content' => $request->content ]);
return new SettingResource($setting);
}
public function destroy($id)
{
//
}
}
Settings.vue
<template>
<div class="ml-4 container">
<button #click="submit" id="saveBtn" class="btn btn-primary mt-2">Save Admin Settings</button>
<div class="custom-file mt-3">
<label for="backgroundImage" class="custom-file-label input">Add Background Image</label>
<input id="backgroundImage" #input="errors.clear('background_image')" #change="uploadImageName" type="file" class="custom-file-input btn btn-primary"
style="background: #1d68a7">
<span class="text-danger" v-if="errors.get('background_image')">{{ errors.get('background_image') }}</span>
</div>
<div class="form-group mt-3">
<label for="header">Header</label>
<input :class="{'is-invalid' : errors.get('header')}" #input="errors.clear('header')" v-model="form.header" type="text" class="form-control input" id="header">
<span class="text-danger" v-if="errors.get('header')">{{ errors.get('header') }}</span>
</div>
<div class="form-group mt-3">
<label for="videoOne">Video one</label>
<textarea :class="{'is-invalid' : errors.get('video')}" #input="errors.clear('video')" v-model="form.video" type="text" class="form-control videoBox input" id="videoOne"></textarea>
<span class="text-danger" v-if="errors.get('video')">{{ errors.get('video') }}</span>
<div class="d-flex">
<toggle-button class="mt-2 toggleButton"
#click="toggleBtn()"
v-model="form.active_video"
color="#82C7EB"
:sync="true"
:labels="{checked: 'Active', unchecked: 'Deactive'}"
/>
</div>
</div>
<div class="mt-3">
<label for="sectionOneText">Section One Text</label>
<editor
#input="errors.clear('section_one')"
v-model="form.section_one"
type="text"
class="form-control"
id="sectionOneText"
>
</editor>
<span class="text-danger" v-if="errors.get('section_one')">{{ errors.get('section_one') }}</span>
<div class="d-flex">
<toggle-button class="mt-2 toggleButton"
#click="toggleBtn()"
v-model="form.active_section_one"
color="#82C7EB"
:sync="true"
:labels="{checked: 'Active', unchecked: 'Deactive'}"
/>
</div>
</div>
<div class="mt-3">
<label for="editableBoxContainer">Editable Box Container</label>
<editor
#input="errors.clear('editable_box')"
v-model="form.editable_box"
type="text"
class="form-control"
id="editableBoxContainer"
>
</editor>
<span class="text-danger" v-if="errors.get('editable_box')">{{ errors.get('editable_box') }}</span>
<div class="d-flex">
<toggle-button class="mt-2 toggleButton"
#click="toggleBtn()"
v-model="form.active_editable_box"
color="#82C7EB"
:sync="true"
:labels="{checked: 'Active', unchecked: 'Deactive'}"
/>
</div>
</div>
<div class="mt-3">
<label for="sectionTwoText">Section Two Text</label>
<editor
#input="errors.clear('section_two')"
v-model="form.section_two"
type="text"
class="form-control"
id="sectionTwoText"
>
</editor>
<span class="text-danger" v-if="errors.get('section_two')">{{ errors.get('section_two') }}</span>
<div class="d-flex">
<toggle-button class="mt-2 toggleButton"
#click="toggleBtn()"
v-model="form.active_section_two"
color="#82C7EB"
:sync="true"
:labels="{checked: 'Active', unchecked: 'Deactive'}"
/>
</div>
</div>
<div class="mt-3">
<label for="sectionThreeText">Section Three Text</label>
<editor
#input="errors.clear('section_three')"
v-model="form.section_three"
type="text"
class="form-control"
id="sectionThreeText"
>
</editor>
<span class="text-danger" v-if="errors.get('section_three')">{{ errors.get('section_three') }}</span>
<div class="d-flex">
<toggle-button class="mt-2 toggleButton"
#click="toggleBtn()"
v-model="form.active_section_three"
color="#82C7EB"
:sync="true"
:labels="{checked: 'Active', unchecked: 'Deactive'}"
/>
</div>
</div>
<div class="form-group mt-3">
<label for="videoTwo">Video two</label>
<textarea :class="{'is-invalid' : errors.get('video_two')}" #input="errors.clear('video_two')" v-model="form.video_two" type="text" class="form-control videoBox input" id="videoTwo"></textarea>
<span class="text-danger" v-if="errors.get('video_two')">{{ errors.get('video_two') }}</span>
<div class="d-flex">
<toggle-button class="mt-2 toggleButton"
#click="toggleBtn()"
v-model="form.active_video_two"
color="#82C7EB"
:sync="true"
:labels="{checked: 'Active', unchecked: 'Deactive'}"
/>
</div>
</div>
<div class="form-group mt-3">
<label for="linkOne">Link One</label>
<input :class="{'is-invalid' : errors.get('link_one')}" #input="errors.clear('link_one')" v-model="form.link_one" type="text" class="form-control input" id="linkOne">
<span class="text-danger" v-if="errors.get('link_one')">{{ errors.get('link_one') }}</span>
</div>
<div class="form-group mt-3">
<label for="linkTwo">Link Two</label>
<input :class="{'is-invalid' : errors.get('link_two')}" #input="errors.clear('link_two')" v-model="form.link_two" type="text" class="form-control input" id="linkTwo">
<span class="text-danger" v-if="errors.get('link_two')">{{ errors.get('link_two') }}</span>
</div>
</div>
</template>
<script>
import Editor from '#tinymce/tinymce-vue'
import {ToggleButton} from 'vue-js-toggle-button'
import Errors from "../helpers/Errors";
Vue.component('ToggleButton', ToggleButton)
export default {
name: "Settings",
components: {Editor},
data() {
return {
errors: new Errors(),
form: {
background_image: '',
header: '',
video: '',
active_video: null,
section_one: '',
active_section_one: null,
editable_box: '',
active_editable_box: null,
section_two: '',
active_section_two: null,
section_three: '',
active_section_three: null,
video_two: '',
active_video_two: null,
link_one: '',
link_two: '',
},
}
},
mounted() {
},
methods: {
toggleBtn() {
if (this.sectionTwotext) {
this.sectionTwotext = false;
} else {
this.sectionTwotext = true;
}
},
async submit() {
try {
const form = Object.assign({}, this.form);
console.log(form);
let result = Object.keys(form).map(function (key) {
return [key, form[key]];
});
console.log(result[0])
result._method = 'PUT';
for(let i=0; i<=15; i++) {
let objectResult = Object.assign({}, result[i]);
console.log('objectResult')
console.log(objectResult)
objectResult._method = 'PUT';
const {data} = await axios.post(`/api/setting/${i}`, objectResult);
}
} catch (error) {
console.log(error.response.data.errors);
this.errors.record(error.response.data.errors);
}
},
uploadImageName() {
let image = document.getElementById("backgroundImage");
this.form.background_image = image.files[0].name;
console.log(image.files[0].name);
},
}
}
</script>
<style scoped>
#saveBtn {
border-radius: 0;
}
.input {
border-radius: 0;
}
.videoBox {
height: 300px;
}
.toggleButton {
margin-left: auto;
}
</style>
My async Submit function is not OK, I was just trying something...
Thanks in advance!
I would consider having a single endpoint on your api where you submit the entire form.
api.php
Create a route that we can submit our axios request to:
Route::put('/settings', 'Api\SettingController#update')->name('api.settings.update');
Note that I have namespaced the controller as you will likely have a web and api version of this controller. Mixing your api and web actions in a single controller is not advisable.
web.php
This is a standard web route which will display a blade view containing the vue component. We pass through the settings which will then be passed to the Settings.vue component as a prop.
Route::get('/settings', 'SettingController#edit')->name('settings.edit');
edit.blade.php
<settings :settings="{{ $settings }}"></settings>
Settings.vue
axios.put('/api/settings', {
header: this.form.header,
background_image: this.form.background_image
... other fields to be submitted
})
.then(success => {
console.log(success);
})
.catch(error => {
console.log(error);
});
SettingController.php
public function edit()
{
return view('settings.edit', ['settings' => Setting::all()]);
}
Api\SettingController.php
public function update(Request $request)
{
$validator = Validator::make(
$request->all(),
[
'header' => ['required', 'string'],
'background_image' => ['required', 'string']
... other fields to be validated
]
);
if ($validator->fails()) {
return response()->json(['errors' => $validator->errors()], 422);
}
foreach ($validator->validated() as $property => $content) {
Setting::where('property', $property)->update(['content' => $content]);
}
return response()->json(['success' => true], 200);
}
You might want to consider splitting settings into groups and submitting groups rather than the entire form, or submitting each setting individually (where it makes sense to do so).
i have questions about data binding in vuejs and i hope everyone here can help me to solve this problem.
I'm in the stage of learning to use vuejs with laravel. I have problems doing data bindings in the data editing process, I can not display any information in the select box.
Next I include the data response that i want to display and the code.
data response
{
"data":{
"id":101,
"kode":"B100",
"nama":"Bendung Jules Rutherford",
"desa":{
"id":"5103050018",
"district_id":"5103050",
"name":"BONGKASA PERTIWI",
"district":{
"id":"5103050",
"city_id":"5103",
"name":"ABIANSEMAL",
"city":{
"id":"5103",
"province_id":"51",
"name":"KABUPATEN BADUNG",
"province":{
"id":"51",
"name":"BALI",
}
}
}
}
}
}
and this is the code :
<template>
<div>
<div v-if="!loading">
<div class="form-group row">
<label class="col-sm-3">Kode</label>
<div class="col-sm-9">
<input :class="{'is_invalid' : errors.kode}" v-model="bendung.kode" type="text" class="form-control" placeholder="B0001">
<div class="invalid-feedback" v-if="errors.kode">{{ errors.kode[0] }}</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3">Nama</label>
<div class="col-sm-9">
<input :class="{'is-invalid': errors.nama}" v-model="bendung.nama" type="text" class="form-control" placeholder="Bendungan Mengwi">
<div class="invalid-feedback" v-if="errors.nama">{{ errors.nama[0] }}</div>
</div>
</div>
<div :class="['form-group row', {'has-error' : errors.provinces }]">
<label class="col-sm-3">Provinsi</label>
<div class="col-sm-9">
<select #change="province" v-model="bendung.desa.district.city.province_id" class="form-control">
<option value="">Pilih Provinsi</option>
<option v-for="province in provinces" :value="province.id">
{{ province.name }}
</option>
</select>
</div>
</div>
<div :class="['form-group row', {'has-error' : errors.cities }]">
<label class="col-sm-3">Kota / Kabupaten</label>
<div class="col-sm-9">
<select #change="city" v-model="bendung.desa.district.city_id" class="form-control">
<option value="">Pilih Kota/Kabupaten</option>
<option v-for="city in cities" :value="city.id">
{{ city.name }}
</option>
</select>
</div>
</div>
</div>
<div class="row" v-else>
<div class="col-sm-12">
<content-placeholders>
<content-placeholders-text/>
</content-placeholders>
</div>
</div>
<div class="form-group row">
<div class="col-sm-3"></div>
<div class="col-sm-9">
<a class="btn btn-success" href="#" :disabled="submiting" #click.prevent="update">
<font-awesome-icon :icon="['fas', 'spinner']" v-if="submiting" />
<font-awesome-icon :icon="['fas', 'check']" v-else/>
<span class="ml-1">Perbaharui</span>
</a>
<a href="/sda/bendung" class="btn btn-danger">
<font-awesome-icon :icon="['fas', 'times-circle']" /> Batal</a>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
errors: {},
bendung: {},
provinces: [],
cities:[],
districts: [],
state: {
province: '',
city: '',
district: '',
},
loading: true,
submiting: false
}
},
mounted() {
this.getBendung();
this.getProvinces();
},
methods: {
getBendung() {
this.loading = true;
let str = window.location.pathname;
let res = str.split("/");
axios.get(`${process.env.MIX_WEBSERVICE_URL}/sda/bendung/${res[3]}`)
.then(response => {
this.bendung = response.data.data;
this.state.province = this.bendung.desa.district.city.province_id;
})
.catch(error => {
this.$toasted.global.error('Bendung tidak ditemukan!');
location.href = '/sda/bendung';
})
.then(() => {
this.loading = false;
});
},
getProvinces() {
axios.get(`${process.env.MIX_WEBSERVICE_URL}/location/province`).then(response => {
this.provinces = response.data;
}).catch(error => console.error(error));
},
province() {
this.state.city = '';
const params = {
province: this.state.province
}
axios.get(`${process.env.MIX_WEBSERVICE_URL}/location/city`, {params}).then(response => {
this.cities = response.data;
}).catch(error => console.error(error));
},
city() {
this.state.district = '';
const params = {
city: this.state.city
};
// /location/district?city=cityId
axios.get(`${process.env.MIX_WEBSERVICE_URL}/location/district`, {params}).then(response => {
this.districts = response.data;
}).catch(error => console.error(error));
}
}
}
</script>
<style scoped>
</style>
the result is like this picture.
i want to show city name on select box but i got blank selectbox and when i change the selectbox (e.g provinsi/province), the other selectbox (e.g. kabupaten/kota/city) will change it data.
You could fetch new data when previous data has been changed.
Here is the working example: https://codesandbox.io/embed/vue-template-2zv2o
Have you tried to use the v-bind:key prop within your v-for loop?
v-bind:key="city.id"
or better with an additional index:
v-for="(city, index) in cities"
[...]
v-bind:key="`${index}-${city.id}`"
I'm building a CRUD web app using Laravel/Vue.js for the first time. I'm using a MySQL database and I used many Vue.js components, and each one can access a table in the database. Now I need to make some components to get data from other components to use it in a drop down, but I can't figure it out.
I tried using props but always get errors.
This is in the child vue:
<div class="form-group">
<select v-model="form.fabnom" type="text" name="fabnom" id="fabnom" class="form-control" :class="{ 'is-invalid': form.errors.has('fabnom') }">
<option v-for="fabriquant in fabriquants" :key="fabriquant.id" :value="fabriquant.fabnom">
</option>
</select>
<has-error :form="form" field="fabnom"></has-error>
</div>
<script>
export default {
data(){
return{
editmode: false,
machines :{},
form: new Form({
id:'',
code:'',
nom: '',
type:'',
serie:'',
date:'',
fabnom:'',
section:'',
unite:''
})
}
} ,
This is the API:
Route::apiResources([
'user' => 'API\UserController',
'fabriquant' => 'API\FabriquantController',
'machine' => 'API\MachineController',]);
Child controller(Parent one is nearly the same):
public function index()
{
//$this->authorize('isAdmin');
if (\Gate::allows('isAdmin')) {
return Machine::latest()->paginate(5);
}
}
public function store(Request $request)
{
$this->validate($request,[
'code' => 'required|string|max:191|unique:machines',
'nom' => 'required|string|max:191',
'type' => 'max:191',
'serie' => 'max:191',
'date' => 'max:191',
'fabnom' => 'max:191',
'section' => 'max:191',
'unite' => 'max:191',
]);
return Machine::create([
'code'=> $request['code'],
'nom'=> $request ['nom'],
'type'=> $request['type'],
'serie'=> $request['serie'],
'date'=> $request['date'],
'fabnom'=> $request['fabnom'],
'section'=> $request['section'],
'unite'=> $request['unite'],
]);
}
public function show($id)
{
//
}
public function update(Request $request, $id)
{
$machine = Machine::findOrFail($id);
$this->validate($request,[
'code' => 'required|string|max:191|unique:machines,code,'.$machine->id,
'nom' => 'max:191',
'type' => 'max:191',
'serie' => 'max:191',
'date' => 'max:191',
'fabnom' => 'max:191',
'section' => 'max:191',
'unite' => 'max:191',
]);
$machine->update($request->all());
return ['message' => 'Updated'];
}
public function destroy($id)
{
$machine = Machine::findOrFail($id);
// delete
$machine->delete();
return ['message' => 'Deleted'];
}
}
EDIT: You can see in the child component there is a string called fabnom and in the parent there is one also: so lets just say now I'm in the parent component and I added 3 items to its database via a modal each item has 6 columns in the database, one of them is called fabnom, now I passed to the child component page I opened an 'addNew' model and there is a dropdown box labeled fabnom which should have the 3 options that I already added I choose one of them and this value is going to be stored in the fabnom column of the database of the child component (I hope you get the idea guys)
This the parent.vue(The child one looks pretty the same the only difference that it has also a dropdown box in its 'addNew' model as mentioned up which is causing the problem):
<template>
<div class="container">
<div class="row mt-5" v-if="$gate.isAdmin()">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Liste des Fabriquants</h3>
<div class="card-tools">
<button class="btn btn-success" #click="newModal">
Ajouter</button>
</div>
</div>
<!-- /.card-header -->
<div class="card-body table-responsive p-0">
<table class="table table-hover">
<tbody>
<tr>
<th>Nom</th>
<th>Adresse</th>
<th>Téléphone</th>
<th>Fax</th>
<th>E-mail</th>
</tr>
<tr v-for="fabriquant in fabriquants.data" :key="fabriquant.id">
<td>{{fabriquant.fabnom}}</td>
<td>{{fabriquant.adresse}}</td>
<td>{{fabriquant.tel}}</td>
<td>{{fabriquant.fax}}</td>
<td>{{fabriquant.email}}</td>
<td>
<a href="#" #click="editModal(fabriquant)">
<i class="fa fa-edit"></i>
</a>
/
<a href="#" #click="deleteFabriquant(fabriquant.id)">
<i class="fa fa-trash"></i>
</a>
</td>
</tr>
</tbody></table>
</div>
<!-- /.card-body -->
<div class="card-footer">
<pagination :data="fabriquants" #pagination-change-page="getResults"></pagination>
</div>
</div>
<!-- /.card -->
</div>
</div>
<div v-if="!$gate.isAdmin()">
<not-found></not-found>
</div>
<!-- Modal -->
<div class="modal fade" id="Ajouter" tabindex="-1" role="dialog" aria-labelledby="AjouterLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" v-show="!editmode" id="AjouterLabel">Ajouter</h5>
<h5 class="modal-title" v-show="editmode" id="AjouterLabel">Modifier</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<form #submit.prevent="editmode ? updateFabriquant() : createFabriquant()">
<div class="modal-body">
<div class="form-group">
<input v-model="form.fabnom" type="text" name="fabnom"
placeholder="Nom"
class="form-control" :class="{ 'is-invalid': form.errors.has('fabnom') }">
<has-error :form="form" field="fabnom"></has-error>
</div>
<div class="form-group">
<input v-model="form.adresse" type="text" name="adresse"
placeholder="Adresse"
class="form-control" :class="{ 'is-invalid': form.errors.has('adresse') }">
<has-error :form="form" field="adresse"></has-error>
</div>
<div class="form-group">
<input v-model="form.tel" type="text" name="tel"
placeholder="Téléphone"
class="form-control" :class="{ 'is-invalid': form.errors.has('tel') }">
<has-error :form="form" field="tel"></has-error>
</div>
<div class="form-group">
<input v-model="form.fax" type="text" name="fax"
placeholder="Fax"
class="form-control" :class="{ 'is-invalid': form.errors.has('fax') }">
<has-error :form="form" field="fax"></has-error>
</div>
<div class="form-group">
<input v-model="form.email" type="email" name="email"
placeholder="E-mail"
class="form-control" :class="{ 'is-invalid': form.errors.has('email') }">
<has-error :form="form" field="email"></has-error>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal">Fermer</button>
<button v-show="editmode" type="submit" class="btn btn-success">Modifier</button>
<button v-show="!editmode" type="submit" class="btn btn-primary">Ajouter</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return{
editmode: false,
fabriquants :{},
form: new Form({
id:'',
fabnom:'',
adresse: '',
tel:'',
fax:'',
email:''
})
}
},
methods: {
getResults(page = 1) {
axios.get('api/fabriquant?page=' + page)
.then(response => {
this.fabriquants = response.data;
});
},
updateFabriquant(){
this.$Progress.start();
// console.log('Editing data');
this.form.put('api/fabriquant/'+this.form.id)
.then(() => {
// success
$('#Ajouter').modal('hide');
Swal.fire(
'Modifié!',
'Informations modifiés!',
'success'
)
this.$Progress.finish();
Fire.$emit('AfterCreate');
})
.catch(() => {
this.$Progress.fail();
});
},
editModal(fabriquant){
this.editmode = true;
this.form.reset();
$('#Ajouter').modal('show');
this.form.fill(fabriquant);
},
newModal(){
this.editmode = false;
this.form.reset();
$('#Ajouter').modal('show');
},
deleteFabriquant(id){
Swal.fire({
title: 'Voulez vous vraiment supprimer cet fabriquant?',
text: "You won't be able to revert this!",
type: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Oui, Supprimer!',
}).then((result) => {
// Send request to the server
if (result.value) {
this.form.delete('api/fabriquant/'+id).then(()=>{
Swal.fire(
'Supprimé!',
'Element supprimé.',
'success'
)
Fire.$emit('AfterCreate');
}).catch(()=> {
Swal.fire("Echec!", "Il y'a un problème.", "warning");
});
}
})
},
loadFabriquants(){
if(this.$gate.isAdmin()){
axios.get("api/fabriquant").then(({ data }) => (this.fabriquants = data));
}
},
createFabriquant(){
this.$Progress.start();
this.form.post('/api/fabriquant')
.then(()=>{
Fire.$emit('AfterCreate');
$('#Ajouter').modal('hide');
toast.fire({
type: 'success',
title: 'Fabriquant ajouté',
})
this.$Progress.finish();
})
.catch(()=>{
})
}
},
created() {
this.loadFabriquants();
Fire.$on('AfterCreate',()=>{
this.loadFabriquants();
});
}
}
</script>
If I understand your main issue, you could use something like an instance property or a javascript file to hold variables in client-side storage.
From what I understand, what you need is a way to first get data from your MySQL using one component, and then second, use that data in all the rest of your components. If so, I had this need with my current project about 2 months ago.
The only problem is that I'm not using Laravel/Vue but Vue.js, VueApollo, and GraphQL. Although, I'm pretty sure --and hope-- the way that I fixed my issue will only differ in syntax from the way you could solve yours.
I used a component to query some info from the user right after they log in.
Vue/Apollo Query
apollo: {
// Simple query that gets user info
me: {
query: gql` //GraphQL
{
me {
id
fullName
}
}
`,
loadingKey: "isLoading" //tracks results that are still loading.
}
}
Then I wanted to store the user's 'fullName' somewhere so that my navigation component could always use it. So I created a file:
src/config/credentialStore.js
In this file I created a displayName variable like this:
export const credentialStore = {
displayName: "",
userID: ""
};
Back in the component where I have the 'me' query, I destructured the data that was returned from the query with a function.
apollo: {
// Simple query that gets user info
me: {
query: gql`
{
me {
id
fullName
}
}
`,
loadingKey: "isLoading",
result({ data }) { //data is basically an object with the results of the query
this.$credentials.userId = data.me.id;
this.$credentials.displayName = data.me.fullName;
} //using this.$credentials.displayName will let me use that string from any component.
}
}
So you would do something like that and then maybe something like this:
<script>
export default {
data(){
return{
editmode: false,
fabriquants :{},
form: new Form({
id: this.$credentialStore.id,
fabnom:this.$credentialStore.fabnom, //what's a fabnom? 🤔
• • •
})
}
},
I hope this helps you or someone else.
I have a few application, when I am sending formdata using axios post data is successfully sent to API. but when i am using put request its not working with formData.
<template>
<div class="container">
<div class="container-fluid" style="background:#fff">
<div class="page-header">
<h4 class="page-title">
<i class="flaticon-users"></i> Leads
</h4>
<ul class="breadcrumbs">
<li class="nav-home">
<a href="/">
<i class="flaticon-home"></i>
</a>
</li>
<li class="separator">
<i class="flaticon-right-arrow"></i>
</li>
<li class="nav-item">
<router-link to="/leads">Leads</router-link>
</li>
<li class="separator">
<i class="flaticon-right-arrow"></i>
</li>
</ul>
</div>
<template>
<div class="btn-wrapper">
<button v-on:click="seen = !seen" class="btn btn-primary btn-md">
<i class="flaticon-interface-1"></i>Add New Lead
</button>
</div>
<p></p>
</template>
<div class="row">
<div class="col-md-12" v-if="!seen">
<div class="card">
<div class="card-header">
<h4 class="card-title">
<i class="flaticon-interface-1"></i> New Leads
</h4>
</div>
<div class="card-body">
<form
#submit.prevent="addLeads"
id="leadform"
class="mb-3"
enctype="multipart/form-data"
>
<div class="col-md-12">
<div class="row">
<div class="col-md-6">
<label>Lead</label>
<div class="form-group">
<input
type="text"
id="name"
name="lead_name"
class="form-control"
placeholder="Lead Name"
v-model="form.name"
>
<template v-if="errors">
<span v-for="(fieldsError, fieldName) in errors" :key="fieldName">
<template v-if="fieldName == 'name'">
<p class="errors">
<strong>{{ fieldsError.join('\n') }}</strong>
</p>
</template>
</span>
</template>
</div>
</div>
<div class="col-md-6">
<label>Source</label>
<div class="form-group">
<textarea
type="text"
id="source"
name="source"
class="form-control"
placeholder="lead Souve"
v-model="form.source"
></textarea>
<template v-if="errors">
<span v-for="(fieldsError, fieldName) in errors" :key="fieldName">
<template v-if="fieldName == 'source'">
<p class="errors">
<strong>{{ fieldsError.join('\n') }}</strong>
</p>
</template>
</span>
</template>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label>Value</label>
<div class="form-group">
<input
type="text"
id="value"
name="value"
class="form-control"
placeholder="lead Value"
v-model="form.value"
>
<template v-if="errors">
<span v-for="(fieldsError, fieldName) in errors" :key="fieldName">
<template v-if="fieldName == 'value'">
<p class="errors">
<strong>{{ fieldsError.join('\n') }}</strong>
</p>
</template>
</span>
</template>
</div>
</div>
<div class="col-md-6">
<label>Notes</label>
<div class="form-group">
<textarea
type="text"
id="notes"
name="notes"
class="form-control"
placeholder="lead Notes"
v-model="form.notes"
></textarea>
<template v-if="errors">
<span v-for="(fieldsError, fieldName) in errors" :key="fieldName">
<template v-if="fieldName == 'notes'">
<p class="errors">
<strong>{{ fieldsError.join('\n') }}</strong>
</p>
</template>
</span>
</template>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="exampleFormControlSelect1">Assigned To</label>
<template v-if="!userlist">
<select class="form-control" id="assigned_to">
<option value>No User Found</option>
</select>
</template>
<template v-else>
<select
v-model="form.assigned_to"
name="assigned_to"
class="form-control"
id="assigned_to"
>
<option value>Please Select</option>
<option v-for="user in userlist" :key="user.id" :value="user.id">
<template v-if="user.id == currentUser.id">Me</template>
<template v-else>{{ user.name }}</template>
</option>
</select>
</template>
<template v-if="errors">
<span v-for="(fieldsError, fieldName) in errors" :key="fieldName">
<template v-if="fieldName == 'assigned_to'">
<p class="errors">
<strong>{{ fieldsError.join('\n') }}</strong>
</p>
</template>
</span>
</template>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label>Close Date</label>
<div class="clearfix"></div>
<date-picker v-model="form.date" name="close_date"></date-picker>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label>Email</label>
<div class="form-group">
<input
type="text"
id="email"
name="email"
class="form-control"
placeholder="User Email"
v-model="form.email"
>
<template v-if="errors">
<span v-for="(fieldsError, fieldName) in errors" :key="fieldName">
<template v-if="fieldName == 'email'">
<p class="errors">
<strong>{{ fieldsError.join('\n') }}</strong>
</p>
</template>
</span>
</template>
</div>
</div>
<div class="col-md-6">
<label>Phone</label>
<div class="form-group">
<input
type="text"
id="phone"
name="phone"
class="form-control"
placeholder="User Phone Number"
v-model="form.phone"
>
<template v-if="errors">
<span v-for="(fieldsError, fieldName) in errors" :key="fieldName">
<template v-if="fieldName == 'phone'">
<p class="errors">
<strong>{{ fieldsError.join('\n') }}</strong>
</p>
</template>
</span>
</template>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<input
type="file"
multiple="multiple"
id="attachments"
#change="uploadFieldChange"
>
<hr>
<div class="col-md-12">
<div
class="attachment-holder animated fadeIn"
v-cloak
v-for="(attachment, index) in attachments"
>
<template v-if="attachment.file_name">
<span class="label label-primary">{{ attachment.file_name}}</span>
</template>
<template v-else>
<span
class="label label-primary"
>{{ attachment.name + ' (' + Number((attachment.size / 1024 / 1024).toFixed(1)) + 'MB)'}}</span>
</template>
<span
class
style="background: red; cursor: pointer;"
#click="removeAttachment(attachment)"
>
<button class="btn btn-xs btn-danger">Remove</button>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-check">
<label>Status</label>
<br>
<label class="form-radio-label">
<input
class="form-radio-input"
v-model="form.status"
type="radio"
name="status"
value="open"
checked
>
<span class="form-radio-sign">Open</span>
</label>
<label class="form-radio-label ml-3">
<input
class="form-radio-input"
v-model="form.status"
type="radio"
name="status"
value="sent"
>
<span class="form-radio-sign">Proposal Sent</span>
</label>
<label class="form-radio-label ml-3">
<input
class="form-radio-input"
v-model="form.status"
type="radio"
name="status"
value="won"
>
<span class="form-radio-sign">Won</span>
</label>
<label class="form-radio-label ml-3">
<input
class="form-radio-input"
v-model="form.status"
type="radio"
name="status"
value="lost"
>
<span class="form-radio-sign">lost</span>
</label>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<button type="submit" class="btn btn-success">Save</button>
<button #click="clearForm()" class="btn btn-danger">Cancel</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid" style="background:#fff;">
<kanban-board :stages="stages" :blocks="blocks" #update-block="updateBlock">
<div v-for="stage in stages" :slot="stage">
<h2>{{ stage }}</h2>
</div>
<div v-for="block in blocks" :slot="block.id">
<div>
<strong>{{ block.name }}</strong>
</div>
<p></p>
<button class="btn btn-danger">UKDH</button>
<button class="btn btn-warning">£ {{ block.value }}</button>
<router-link :to="`/account/${block.id}/convert`" class="btn btn-primary">create account</router-link>
<div class="text-right">
<router-link :to="`/leads/${block.id}`" class="btn btn-link btn-info">
<i class="la la-street-view"></i>
</router-link>
<a href="#" #click="deleteLead(block.id)" class="btn btn-link btn-danger">
<i class="la la-times"></i>
</a>
<a href="#" #click="editLead(block)" class="btn btn-link btn-primary">
<i class="la la-edit"></i>
</a>
</div>
</div>
</kanban-board>
</div>
</div>
</template>
<script>
import { addLeadsAPI } from "../../helpers/api";
import { updateStatus } from "../../helpers/api";
import { getCommonAPI } from "../../helpers/api";
import { deleteAPI } from "../../helpers/api";
import validate from "validate.js";
import DatePicker from "vue2-datepicker";
export default {
name: "leads",
components: {
DatePicker
},
data() {
return {
leads: [],
userlist: [],
attachments: [],
percentCompleted: 0,
upload_size: 0,
result: {},
stages: ["open", "sent", "lost", "won"],
blocks: [],
form: {
id: "",
name: "",
source: "",
value: 0,
notes: "",
user_id: "",
assigned_to: 1,
date: new Date(),
email: "",
phone: "",
status: ""
},
lead_id: "",
pagination: {},
edit: false,
isOpen: 0,
seen: true,
errors: null
};
},
created() {
this.fetchLeads();
this.getusers();
},
mounted() {
this.$store.dispatch("leads");
},
methods: {
getusers(page_url) {
let vm = this;
getCommonAPI("/users", "get", {
headers: {
Authorization: `Bearer ${this.currentUser.token}`,
Accept: "application/json"
}
}).then(res => {
vm.userlist = res.data;
});
},
fetchLeads(page_url) {
let vm = this;
page_url = page_url || "/leads/lead";
getCommonAPI(page_url, "get", {
headers: {
Authorization: `Bearer ${this.currentUser.token}`,
Accept: "application/json"
}
}).then(res => {
vm.blocks = res.data.data;
//vm.makePagination(res.meta, res.links);
});
},
makePagination(meta, links) {
let pagination = {
current_page: meta.current_page,
last_page: meta.last_page,
next_page_url: links.next,
prev_page_url: links.prev
};
this.pagination = pagination;
},
editLead(form) {
console.log(form);
this.edit = true;
this.seen = false;
this.form.id = form.id;
this.form.name = form.name;
this.form.lead_sid = form.lead_sid;
this.form.status = form.status;
this.form.type = form.type;
this.form.source = form.source;
this.form.value = form.value;
this.form.notes = form.notes;
this.form.email = form.email;
this.form.phone = form.phone;
this.form.assigned_to = form.assigned_to;
this.form.date = form.close_date;
this.attachments = form.uploads;
},
clearForm() {
this.edit = false;
this.form.id = null;
this.form.user_id = null;
this.form.assigned_to = "";
this.form.type = "";
this.form.status = true;
this.form.name = "";
this.form.source = "";
this.form.value = "";
this.form.notes = "";
this.form.email = "";
this.form.phone = "";
this.attachments = [];
},
addLeads() {
if (this.edit === false) {
// add new leads
this.errors = null;
const constraints = this.getConstraints();
const errors = validate(this.$data.form, constraints);
if (errors) {
this.errors = errors;
return;
}
// multiple file uploading
this.lead = document.getElementById("leadform");
const formData = new FormData(this.lead);
if (this.attachments.length > 0) {
for (var i = 0; i < this.attachments.length; i++) {
let attachment = this.attachments[i];
formData.append("attachments[]", attachment);
}
}
var config = {
headers: { "Content-Type": "multipart/form-data" },
onUploadProgress: function(progressEvent) {
this.percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
this.$forceUpdate();
}.bind(this)
};
//end
this.$store.dispatch("lead");
addLeadsAPI(formData, "post").then(res => {
swal("Good job!", "You clicked the button!", "success");
this.clearForm();
this.fetchLeads();
//this.attachments = [];
});
} else {
this.errors = null;
const constraints = this.getConstraints();
const errors = validate(this.$data.form, constraints);
if (errors) {
this.errors = errors;
return;
}
console.log("i am in edit");
// multiple file uploading
this.lead = document.getElementById("leadform");
let formData = new FormData(this.lead);
if (this.attachments.length > 0) {
for (var i = 0; i < this.attachments.length; i++) {
let attachment = this.attachments[i];
formData.append("attachments[]", attachment);
}
}
console.log(formData);
var config = {
headers: { "Content-Type": "multipart/form-data" },
onUploadProgress: function(progressEvent) {
this.percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
this.$forceUpdate();
}.bind(this)
};
//end
console.log(formData);
this.$store.dispatch("lead");
//update
addLeadsAPI(formData, "put").then(res => {
swal("Good job!", "You clicked the button!", "success");
this.clearForm();
this.fetchLeads();
//this.attachments = [];
});
}
},
getConstraints() {
return {
name: {
presence: true,
length: {
minimum: 6,
message: "Must be at least 6 characters long"
}
},
source: {
presence: true,
length: {
minimum: 6,
message: "Must be at least 6 characters long"
}
},
value: {
presence: true,
length: {
minimum: 1,
message: "Must be at least 1 characters long"
}
},
notes: {
presence: true,
length: {
minimum: 6,
message: "Must be at least 6 characters long"
}
}
};
},
updateBlock(id, status) {
//api call axios
updateStatus(id, status, "get").then(res => {
this.clearForm();
this.fetchLeads();
});
this.blocks.find(b => b.id === Number(id)).status = status;
},
deleteLead(id) {
swal({
title: "Are you sure?",
text: "Do you really want to delete Lead!",
type: "warning",
buttons: {
confirm: {
text: "Yes, delete it!",
className: "btn btn-success"
},
cancel: {
visible: true,
className: "btn btn-danger"
}
}
}).then(Delete => {
if (Delete) {
deleteAPI(`/lead/${id}`, "delete", {
headers: {
Authorization: `Bearer ${this.currentUser.token}`,
Accept: "application/json"
}
}).then(res => {
swal({
title: "Deleted!",
text: "Your lead has been deleted.",
type: "success",
buttons: {
confirm: {
className: "btn btn-success"
}
}
});
this.fetchLeads();
});
} else {
this.fetchLeads();
swal.close();
}
});
},
getAttachmentSize() {
this.upload_size = 0; // Reset to beginningƒ
this.attachments.map(item => {
this.upload_size += parseInt(item.size);
});
this.upload_size = Number(this.upload_size.toFixed(1));
this.$forceUpdate();
},
removeAttachment(attachment) {
this.attachments.splice(this.attachments.indexOf(attachment), 1);
this.getAttachmentSize();
},
// This function will be called every time you add a file
uploadFieldChange(e) {
console.log(this.attachments);
var files = e.target.files || e.dataTransfer.files;
if (!files.length) return;
for (var i = files.length - 1; i >= 0; i--) {
this.attachments.push(files[i]);
}
console.log("out");
// Reset the form to avoid copying these files multiple times into this.attachments
document.getElementById("attachments").value = [];
}
},
computed: {
users() {
return this.$store.getters.users;
},
currentUser() {
return this.$store.getters.currentUser;
}
}
};
</script>
<style lang="scss">
#import "../assets/board.scss";
</style>
<style scoped>
.vue-js-switch#changed-font {
font-size: 30px;
}
.hide {
display: none;
}
.errors {
color: lightcoral;
border-radius: 5px;
padding: 21px 0 2px 0;
}
</style>
when edit option true. I am calling method addLeadsAPI for posting data with axios put but Formdata is empty $request->all().
Anyone can help me with this?seems axios put is not working for editing data. through formdata.
Laravel can not handle multipart-formdata well with PUT method. See Input from PUT requests sent as multipart/form-data is unavailable #13457.
If your code actually uses the PUT method, it seems to be affected by this problem.
There are several workarounds.
Dealing with the client side:
Instead of PUT method, use POST method with _method parameter value set to PUT (called 'method spoofing')
Dealing with the server side:
Add a package that performs multipart processing to Laravel. (ex. illuminatech/multipart-middleware)
Use pecl/apfd extension which provides ability to parse 'multipart/form-data' HTTP requests for any request method.
I have changed the axioscall into post and set the value _method:put
addLeads() {
if (this.edit === false) {
// add new leads
this.errors = null;
const constraints = this.getConstraints();
const errors = validate(this.$data.form, constraints);
if (errors) {
this.errors = errors;
return;
}
// multiple file uploading
this.lead = document.getElementById("leadform");
const formData = new FormData(this.lead);
if (this.attachments.length > 0) {
for (var i = 0; i < this.attachments.length; i++) {
let attachment = this.attachments[i];
formData.append("attachments[]", attachment);
}
}
var config = {
headers: { "Content-Type": "multipart/form-data" },
onUploadProgress: function(progressEvent) {
this.percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
this.$forceUpdate();
}.bind(this)
};
//end
this.$store.dispatch("lead");
formData.append("_method", "post");
addLeadsAPI(formData, "post", config).then(res => {
swal("Good job!", "You clicked the button!", "success");
this.clearForm();
this.fetchLeads();
//this.attachments = [];
});
} else {
this.errors = null;
const constraints = this.getConstraints();
const errors = validate(this.$data.form, constraints);
if (errors) {
this.errors = errors;
return;
}
console.log("i am in edit");
// multiple file uploading
this.lead = document.getElementById("leadform");
let formData = new FormData(this.lead);
if (this.attachments.length > 0) {
for (var i = 0; i < this.attachments.length; i++) {
let attachment = this.attachments[i];
formData.append("attachments[]", attachment);
}
}
formData.append("_method", "put");
formData.append("id", this.form.id);
console.log(formData);
var config = {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
onUploadProgress: function(progressEvent) {
this.percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
this.$forceUpdate();
}.bind(this)
};
//end
console.log(formData);
this.$store.dispatch("lead");
//update
addLeadsAPI(formData, "put", config).then(res => {
swal("Good job!", "You clicked the button!", "success");
this.clearForm();
this.fetchLeads();
//this.attachments = [];
});
}
},
Well, I had an issue trying to update records using axios & vue.js.
The solution is to set the method value on the formData to putand send the request using the post method. Something like this:
console.log("i am in edit");
// multiple file uploading
this.lead = document.getElementById("leadform");
let formData = new FormData(this.lead);
if (this.attachments.length > 0) {
for (var i = 0; i < this.attachments.length; i++) {
let attachment = this.attachments[i];
formData.append("attachments[]", attachment);
}
}
formData.append("_method", "put");
console.log(formData);
In your axios request:
axios({
method: "POST", // this is important
url: {$your_destination_url},
data: formData,
headers: { "Content-Type": "multipart/form-data" }
})
.then(r => console.log(r.data))
.catch(e => console.log(e));
It may be related to this, https://laravel.com/docs/5.0/routing#method-spoofing.
when using PUT, PATCH or DELETE, you may need to also let laravel know the form method you are using. Try adding "_method" property with the value "PUT", and let me know if that works for you
My previous wrong code -
let data = new FormData()
data.append('message', 'AnyMessage')
Instead of FormData use the following String that works fine -
let data = "message=AnyMessage"