I am working on a Laravel 8 app with Jetstream and Inertia and I created a form that has two text fields and one image upload field. The problem is when I edit the resource and upload a new image everything works but when I try to save the resource again immediately after the first time it looks like it is uploading the image again even though the data after the first upload for the field is a URL to the image. The goal is to prevent the second image upload on the second edit as the image is the same - there is no change. If you reload the page after the first edit and save the data again it works fine there is no image upload.
I've uploaded a video here.
I assume the loading indicator below my bookmarks is somehow done by InertiaJS when making ajax requests. Or maybe it is doing a full page reload?
I've spend hours on this, googled various things, looked and tinkered with the code, read InertiaJS documentation and found nothing!
I am using S3 to upload the images to. Also, I am using Spatie's media-library package to handle the images.
here is my Edit page component:
<template>
<app-layout title="Edit author information">
<div>
<div class="max-w-7xl mx-auto py-5 sm:px-6 lg:px-8">
<div>
<create-or-update-author-form :author="author" method="PUT" :blank_avatar_image="$page.props.assets.blank_avatar_image"/>
</div>
</div>
</div>
</app-layout>
</template>
<script>
import AppLayout from '#/Layouts/AppLayout.vue'
import CreateOrUpdateAuthorForm from '#/Pages/Authors/Partials/CreateOrUpdateAuthorForm.vue'
export default {
props: ['sessions', 'author'],
components: {
AppLayout,
CreateOrUpdateAuthorForm,
},
}
</script>
here is my CreateOrUpdateAuthorForm component:
<template>
<jet-form-section #submitted="save">
<template #title>
<span v-if="method === 'POST'">Create new author</span>
<span v-else-if="method === 'NONE'">Viewing author</span>
<span v-else>Update author information</span>
</template>
<template #form>
<!-- Photo -->
<div class="col-span-6 sm:col-span-4">
<!-- Photo File Input -->
<input type="file" class="hidden"
ref="photo"
#change="updatePhotoPreview">
<jet-label for="photo" value="Photo" />
<!-- Current Photo -->
<div class="mt-2" v-show="! photo_preview">
<img v-if="photo_or_blank_image" :src="photo_or_blank_image" :alt="author.name" class="rounded-full h-20 w-20 object-cover">
</div>
<!-- New Photo Preview -->
<div class="mt-2" v-show="photo_preview">
<span class="block rounded-full w-20 h-20"
:style="'background-size: cover; background-repeat: no-repeat; background-position: center center; background-image: url(\'' + photo_preview + '\');'">
</span>
</div>
<jet-secondary-button v-if="method !== 'NONE'" class="mt-2 mr-2" type="button" #click.prevent="selectNewPhoto">
Select A New Photo
</jet-secondary-button>
<jet-secondary-button v-if="author.photo && method !== 'NONE' && !author_photo_is_blank" type="button" class="mt-2" #click.prevent="deletePhoto">
Remove Photo
</jet-secondary-button>
<jet-input-error :message="form.errors.photo" class="mt-2" />
</div>
<!-- Name -->
<div class="col-span-6 sm:col-span-4">
<jet-label for="name" value="Name" />
<jet-input id="name" type="text" class="mt-1 block w-full disabled:bg-gray-100" v-model="form.name" autocomplete="name" :disabled="disabled" />
<jet-input-error :message="form.errors.name" class="mt-2" />
</div>
<!-- Email -->
<div class="col-span-6 sm:col-span-4">
<jet-label for="bio" value="Bio" />
<jet-input id="bio" type="text" class="mt-1 block w-full disabled:bg-gray-100" v-model="form.bio" :disabled="disabled" />
<jet-input-error :message="form.errors.bio" class="mt-2" />
</div>
</template>
<template v-if="!disabled" #actions>
<jet-button :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
<span v-if="method === 'POST'">Create</span>
<span v-else>Save</span>
</jet-button>
<progress-bar :progress="form.progress"/>
<jet-action-message :on="form.wasSuccessful" class="ml-3">
<span v-if="method === 'POST'">Created.</span>
<span v-else>Saved.</span>
</jet-action-message>
</template>
</jet-form-section>
</template>
<script>
import JetButton from '#/Jetstream/Button.vue'
import JetFormSection from '#/Jetstream/FormSection.vue'
import JetInput from '#/Jetstream/Input.vue'
import JetInputError from '#/Jetstream/InputError.vue'
import JetLabel from '#/Jetstream/Label.vue'
import JetActionMessage from '#/Jetstream/ActionMessage.vue'
import JetSecondaryButton from '#/Jetstream/SecondaryButton.vue'
import ProgressBar from '#/Shared/Elements/ProgressBar.vue'
import Utils from '#/Shared/Utils'
import Forms from '#/Shared/Forms'
export default {
components: {
JetActionMessage,
JetButton,
JetFormSection,
JetInput,
JetInputError,
JetLabel,
JetSecondaryButton,
ProgressBar,
},
props: {
author: {
type: Object,
default: {
id: null,
name: '',
bio: '',
photo: null,
}
},
blank_avatar_image: {
type: String,
default: null,
},
method: {
type: String,
default: 'POST',
},
disabled: {
default: false
}
},
data() {
return {
form: this.$inertia.form({
_method: this.method,
id: this.author.id,
name: this.author.name,
bio: this.author.bio,
photo: this.author.photo,
}),
formDetails: {
routes: {
store: route('authors.store'),
update: this.author.id
? route('authors.update', { author : this.author.id})
: null,
},
errorBag: 'createOrUpdateAuthor',
},
photo_preview: null,
}
},
methods: Forms,
computed: {
photo_or_blank_image() {
return this.author.photo ? this.author.photo : this.blank_avatar_image;
},
author_photo_is_blank() {
return Utils.isBlankAvatarImage(this.author.photo);
}
}
}
</script>
here is my Forms class:
var Forms = {
save() {
var self = this;
// if method is NONE don't submit form
if (this.method === 'NONE') {
return false;
}
if (this.$refs.photo && this.$refs.photo.files[0]) {
this.form.photo = this.$refs.photo.files[0];
}
var request = {
errorBag: this.formDetails.errorBag,
preserveScroll: true,
forceFormData: true,
};
var route = this.formDetails.routes.store;
if (this.method === 'PUT') {
route = this.formDetails.routes.update;
}
this.form.wasSuccessful = false;
this.form.post(route, request);
},
selectNewPhoto() {
this.$refs.photo.click();
},
updatePhotoPreview() {
const photo = this.$refs.photo.files[0];
if (! photo) return;
this.author.photo = photo;
const reader = new FileReader();
reader.onload = (e) => {
this.photo_preview = e.target.result;
};
reader.readAsDataURL(photo);
},
deletePhoto() {
this.author.photo = null;
this.form.photo = null;
this.photo_preview = this.blank_avatar_image;
this.clearPhotoFileInput();
},
clearPhotoFileInput() {
if (this.$refs.photo?.value) {
this.$refs.photo.value = null;
}
}
}
export default Forms;
here is my AuthorsController which handles the requests, I've only copied the edit and update methods as they are the only ones relevant:
<?php
namespace App\Http\Controllers;
use App\Actions\Zavoon\Authors\AuthorActions;
use App\Models\Author;
use Illuminate\Http\Request;
use Laravel\Jetstream\Jetstream;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Inertia\Inertia;
class AuthorsController extends Controller
{
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function edit($id)
{
$author = Author::where('id', $id)
->where('team_id', Auth::user()->currentTeam->id)
->first();
if (!$author) {
abort(404);
}
return Inertia::render('Authors/Edit', ['author' => $author->id])
->with('author', $author);
}
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param int $id
* #return \Illuminate\Http\Response
*/
public function update(Request $request, $id, AuthorActions $action)
{
$author = $action->update($request->all());
$success = 'Author information updated.';
session()->flash('success', $success);
return Redirect::route('authors.edit', ['author' => $author->id]);
}
}
here is my AuthorActions class which handles authors related logic:
<?php
namespace App\Actions\Zavoon\Authors;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use App\Models\Author;
use App\Rules\Rules;
use Illuminate\Validation\Rule;
class AuthorActions
{
/**
* Creates new author
*/
public function create(array $input)
{
if ($this->validate($input, 'create')) {
$user = Auth::user();
$input['user_id'] = $user->id;
$input['team_id'] = $user->currentTeam->id;
$author = new Author();
$author->fill($input);
$author->save();
$author->updatePhoto($input);
return $author;
}
return false;
}
/**
* Updates author
*
* #param array $input
* #return boolean|Author
*/
public function update(array $input)
{
if ($this->validate($input, 'update')) {
$author = Author::find($input['id']);
$author->fill($input);
$author->save();
$author->updatePhoto($input);
return $author;
}
return false;
}
/**
* Validates input
*/
public function validate(array $input, $type)
{
$user = Auth::user();
$rules = [
'name' => ['required', 'string', 'max:255'],
'bio' => ['required', 'string', 'max:4000'],
'photo' => Rules::photo($type),
];
if ($type === 'update') {
$rules['id'] = [
'required',
Rule::exists('authors')->where(function ($query) use ($input, $user) {
return $query
->where('id', $input['id'])
->where('team_id', $user->currentTeam->id);
}),
];
}
Validator::make($input, $rules)->validateWithBag('createOrUpdateAuthor');
return true;
}
}
here is my Rules class which gives me some of the validation rules:
<?php
namespace App\Rules;
use App\Rules\ImageOrLink;
class Rules
{
public static function photo($type = 'create')
{
if ($type === 'create') {
return ['nullable', self::mimes(), self::maxImageSize()];
} else {
return ['nullable', new ImageOrLink()];
}
}
public static function mimes()
{
return 'mimes:jpg,jpeg,png';
}
public static function maxImageSize()
{
return 'max:5120';
}
}
and finally here is my ImageOrLink rule which is used in validating the image uploads:
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Validator;
use App\Rules\Link;
class ImageOrLink implements Rule
{
/**
* Create a new rule instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Determine if the validation rule passes.
* True if it is an image upload or a valid url.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
if (Link::isLink($value)) {
return true;
} else {
$validator = Validator::make([
'image' => $value
], [
'image' => ['required', Rules::mimes(), Rules::maxImageSize()],
]);
if ($validator->fails()) {
return false;
}
return true;
}
return false;
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return 'This needs to be an image upload or a link to an image.';
}
}
Any ideas appreciated! I really don't get why it's happening.
EDIT 1: It seems it is something to do with Inertia's Form helper. When the form is submitted once then the form.photo does not get updated with the new value. No idea why though.
The solution was strange, still don't know why exactly it happens. Maybe it is an Inertia bug. So, after the request was submitted successfully just do this:
this.form.photo = this.author.photo;
this goes in its own method in Forms.js and to repeat myself, it is called in the onSuccess callback.
Related
I am new to laravel and vue.js. I am fetching data from API using resources. The idea is am using 2 div's GOAL and VALUE.
The Value should be updated when there is a change in the value from the server through PUSHER..
without refreshing the page.
here is my code
1.model
class Progress extends Model
{
protected $fillable = [
'id', 'name', 'goal', 'description'
];
public $timestamps = false;
}
2.Controller
class ProgressController extends Controller
{
public function index()
{
return ProgressResource::collection(Progress::paginate(4));
event(new UpdatedValue());
}
}
3.Resource.php
class ProgressResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return[
'the_custom_id' => $this->id,
'name' => $this->name,
'goal' => $this->goal,
'description' => $this->description,
'value' => ProgressResource::mockData($this->goal),
];
}
public static function mockData($goal=1000)
{
// 0 to $goal takes 17 minutes
$multiplier = ($goal + 7) / 1000;
$count = substr(time(), -3);
return intval(round($multiplier * $count, 0));
}
}
4.Events
class UpdatedValue implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $value, $goal;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($value)
{
//$this->progress = ProgressResource::collection(Progress::paginate(4));
$this->value = ProgressResource::mockData($this->goal);
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new Channel('progress');
}
}
5.Components/Front.vue
<template>
<div class="container">
<h1> Progress </h1>
<div class= "progress" v-for = "progressdata in progress" v-bind:id="progressdata.id">
<div id="div1">{{ progressdata.goal }}</div>
<div id="div2" class="value">{{ progressdata.value }}</div>
</div>
</div>
</template>
<script>
export default {
data: function() {
return {
progress: [],
}
},
mounted() {
this.loadContents();
this.listen();
},
methods: {
loadContents: function() {
//load Api
axios.get('/api/progress')
.then((response) => {
this.progress = response.data.data;
})
.catch(function (error) {
console.log(error);
});
},
listen(){
Echo.channel('progress')
.listen('UpdatedValue', (e) =>{
this.value = e.value;
console.log(this.value);
//console.log(e);
});
}
}
}
</script>
6.BLade.php
<div id ="app">
<front-page ></front-page>
</div>
<script src = "{{ mix('js/app.js') }}"></script>
</body>
Once the event has been triggered, the value should be updated in the front end without refreshing the page. I have installed PUSHER PACKAGES , PUSHER-JS AND Laravel Echo THROUGH NPM.I couldn't get the value which is updated through event in the front end..Could anyone help to solve this issue?.
Thanks.
loadContents: function() {
//load Api
let self = this ; // we are storing VueComponent object here.Cause this wont work in callback it will be undefined.
axios.get('/api/progress')
.then((response) => {
self.progress = response.data.data;
})
.catch(function (error) {
console.log(error);
});
You didn't define value in data properties. Define Value there and use self object to store this(object).
data: function() {
return {
progress: [],
value:null,
}
},
I'm trying to add a new field to the shipping address in Magento 2.3.4.
I would like to add it "Magento way" that's why I used these tutorials:
https://devdocs.magento.com/guides/v2.3/howdoi/checkout/checkout_new_field.html
https://www.edmondscommerce.co.uk/handbook/Platforms/Magento-2/Guides/Custom-Shipping-Address-Field/
The field appears correctly on the frontend, but after adding new address and filling that field and click "Ship here":
I got that error:
Here is my code:
1) At first, I thought that it is not necessary (this step is not in Magento 2 devdocs but appears in second tutorial) - app/code/Company/Module/Setup/Patch/Data/AddVipCodeAttribute.php:
<?php
namespace Company\Module\Setup\Patch\Data;
use Magento\Catalog\Model\Product;
use Magento\Customer\Setup\CustomerSetupFactory;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchRevertableInterface;
class AddVipCodeAttribute implements DataPatchInterface, PatchRevertableInterface
{
/**
* #var ModuleDataSetupInterface
*/
private $moduleDataSetup;
/**
* #var EavSetupFactory
*/
private $eavSetupFactory;
/**
* #var CustomerSetupFactory
*/
private $customerSetupFactory;
/**
* Constructor
*
* #param ModuleDataSetupInterface $moduleDataSetup
* #param EavSetupFactory $eavSetupFactory
*/
public function __construct(
ModuleDataSetupInterface $moduleDataSetup,
EavSetupFactory $eavSetupFactory,
CustomerSetupFactory $customerSetupFactory
) {
$this->moduleDataSetup = $moduleDataSetup;
$this->eavSetupFactory = $eavSetupFactory;
$this->customerSetupFactory = $customerSetupFactory;
}
/**
* #inheritdoc
*/
public function apply()
{
$this->moduleDataSetup->getConnection()->startSetup();
/** #var CustomerSetup $customerSetup */
$customerSetup = $this->customerSetupFactory->create(['setup' => $this->moduleDataSetup]);
$customerSetup->addAttribute('customer_address', 'vip_code', [
'label' => 'Vip account code',
'input' => 'text',
'type' => Table::TYPE_TEXT,
'source' => '',
'required' => false,
'position' => 333,
'visible' => true,
'system' => false,
'is_used_in_grid' => false,
'is_visible_in_grid' => false,
'is_filterable_in_grid' => false,
'is_searchable_in_grid' => false,
'backend' => ''
]);
$attribute = $customerSetup->getEavConfig()->getAttribute('customer_address', 'vip_code')
->addData(['used_in_forms' => [
'adminhtml_customer_address',
'adminhtml_customer',
'customer_address_edit',
'customer_register_address',
'customer_address',
]]);
$attribute->save();
$this->moduleDataSetup->getConnection()->endSetup();
}
/**
* #inheritDoc
*/
public function revert()
{
}
/**
* #inheritdoc
*/
public function getAliases()
{
return [];
}
/**
* #inheritdoc
*/
public static function getDependencies()
{
return [];
}
}
2)I created a plugin class:
etc/frontend/di.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Checkout\Block\Checkout\LayoutProcessor">
<plugin name="add_custom_field_checkout_form" type="Company\Module\Plugin\Checkout\LayoutProcessor" sortOrder="100"/>
</type>
</config>
And layout processor app/code/Company/Module/Plugin/Checkout/LayoutProcessor.php
<?php
namespace Company\Module\Plugin\Checkout;
use Magento\Checkout\Block\Checkout\LayoutProcessor as LayoutProcessorCore;
use Magento\Customer\Model\Session;
class LayoutProcessor
{
/**
* #var Session
*/
protected $session;
public function __construct(
Session $session
) {
$this->session = $session;
}
/**
* #param LayoutProcessorCore $subject
* #param array $jsLayout
*
* #return array
*/
public function afterProcess(
LayoutProcessorCore $subject,
array $jsLayout
) {
$customAttributeCode = 'vip_code';
$customField = [
'component' => 'Magento_Ui/js/form/element/abstract',
'config' => [
// customScope is used to group elements within a single form (e.g. they can be validated separately)
'customScope' => 'shippingAddress.custom_attributes',
'customEntry' => null,
'template' => 'ui/form/field',
'elementTmpl' => 'ui/form/element/input',
'tooltip' => [
'description' => 'Vip accounts code. Example: 123123123ASD',
],
],
'dataScope' => 'shippingAddress.custom_attributes' . '.' . $customAttributeCode,
'label' => 'Vip code',
'provider' => 'checkoutProvider',
'sortOrder' => 0,
'validation' => [
'required-entry' => false
],
'options' => [],
'filterBy' => null,
'customEntry' => null,
'visible' => true,
];
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']
['shippingAddress']['children']['shipping-address-fieldset']['children'][$customAttributeCode] = $customField;
return $jsLayout;
}
}
3)JS files:
app/code/Company/Module/view/frontend/requirejs-config.js
var config = {
config: {
mixins: {
'Magento_Checkout/js/action/set-shipping-information': {
'Company_Module/js/add-new-field': true
}
}
}
};
app/code/Company/Module/view/frontend/web/js/add-new-field.js
/*jshint browser:true jquery:true*/
/*global alert*/
define([
'jquery',
'mage/utils/wrapper',
'Magento_Checkout/js/model/quote'
], function ($, wrapper, quote) {
'use strict';
return function (setShippingInformationAction) {
return wrapper.wrap(setShippingInformationAction, function (originalAction) {
var shippingAddress = quote.shippingAddress();
if (shippingAddress['extension_attributes'] === undefined) {
shippingAddress['extension_attributes'] = {};
}
shippingAddress['extension_attributes']['vip_code'] = shippingAddress.customAttributes['vip_code'];
console.log(shippingAddress);
// pass execution to original action ('Magento_Checkout/js/action/set-shipping-information')
return originalAction();
});
};
});
[UPDATE]
app/code/BartCompany/VipAccounts/etc/extension_attributes.xml:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
<extension_attributes for="Magento\Quote\Api\Data\AddressInterface">
<attribute code="vip_code" type="string" />
</extension_attributes>
</config>
Can anybody help me with that, I'm working on it almost a week, and tried a lot of solutions, but it is for me very important to do that by custom_attributes field.
I will be grateful, thank You.
I think i found the issue, here is a follow:
https://github.com/magento/magento2/issues/26740
Trying to implement a comments system using pusher on my laravel project. Everything seems to be in order, however every post request which is meant to send the input data to the database returns with error 500.
Used F12 on firefox to monitor what gets sent to /tip/select, it seems that it passes the text of the comment just fine, so it could be the issue with the controller.
Routes
Route::get('/tip/select','TipController#select');
Route::post('/tip/select', 'TipController#addComment');
Comment model
namespace App;
use Illuminate\Database\Eloquent\Model;
use Zttp\Zttp;
use App\model\User;
use App\Tip;
class Comment extends Model
{
protected $guarded = [];
protected $table='comments';
//protected $fillable=['tip_id','user_id','body'];
public static function moderate($comment)
{
$response = Zttp::withoutVerifying()->post("https://commentator.now.sh", [
'comment' => $comment,
'limit' => -3,
])->json();
if ($response['commentate']) {
abort(400, "Comment not allowed");
}
}
public function tip(){
return $this->belongsTo('App\Tip');
}
public function user(){
return $this->belongsTo('App\model\User');
}
}
Controller
use Pusher\Laravel\Facades\Pusher;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\File;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Input;
use DB;
use Image;
use App\Tip;
use App\model\User;
use App\Subcategories;
use App\Category;
use App\City;
use App\Comment;
use App\CityAreas;
//use App\Http\Requests\TipFormRequest;
class TipController extends Controller
{
public function select(){
//$db=DB::table('tips')->orderBy('created_at','desc');
$data=Tip::get();
$url = Storage::disk('s3');
//$data=Tip::paginate(10);
//$data=$db->get();
// dd($data);
$comments = Comment::orderBy('id')->get();
return view('tip', compact('data', 'url','comments'));
}
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function create(){
$categories=Category::all();
$cities=City::all();
return view('tip.create',compact('categories','cities'));
}
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function store(Request $request, City $city, Category $category){
$this->validate($request, [
'image' => 'image|nullable|max:1999']);
$tipsnew = new Tip;
$tipsnew->title = $request->title;
$tipsnew->description = $request->description;
$tipsnew->category_id = $request->category;
$tipsnew->city_id = $request->city;
$tipsnew->user_id = auth()->id();
$tipsnew->url = $request->url;
if ($request->hasFile('image')) {
try{
$file = $request->file('image');
$name = time() . '.' . $file->getClientOriginalExtension();
$img = \Image::make($file->getRealPath());
$img->fit(1080);
$img->stream();
Storage::disk('s3')->put('tip'.'/'.$name, $img->__toString());
$tipsnew->image = 'tip'.'/'.$name;
}
catch (\Exception $e)
{
$response = [
'information' => 'Error. Something went wrong. Please try again',
];
$statusCode = 400;
return response()->json($response, $statusCode);
}
}
$tipsnew->save();
return redirect ('tip/create')->with('status','your tip is created');
}
public function edit($id){
$tip=Tip::whereId($id)->firstOrFail();
$categories=Category::all();
$selectedCategories=$tip->categories->lists('id')->toArray();
return view('tip.edit',compact('tip','categories','selectedCategories'));
}
public function search(Request $request, City $city, Category $category, User $user){
$q = $request->get('q');
if ($q != ""){
$tips = Tip::where('title','LIKE','%'.$q.'%')
->orWhere('description','LIKE','%'.$q.'%')
->orWhereHas('user', function($id) use($q){
return $id->where('name', 'LIKE','%'.$q.'%');
})
->orWhereHas('city', function($id) use($q){
return $id->where('name', 'LIKE','%'.$q.'%');
})
->orWhereHas('category', function($id) use($q){
return $id->where('name', 'LIKE','%'.$q.'%');
})
->get();
if(count($tips) > 0)
return view('tip.search', ['tips' => $tips]);
}
}
public function addComment(Request $request)
{
$data = $request;
Comment::moderate($data['text']);
$comment = Comment::create($data);
Pusher::trigger('comments', 'new-comment', $comment, request()->header('X-Socket-Id'));
//add creation of new comment to DB
$commentnew = new Comment;
$commentnew->user_id = Auth::user()->id();
//$commentnew->tip_id= $request->post(['tip_id']);
$commentnew->body = $request->text;
$commentnew->save();
return $comment;
}
}
Snippet of the blade
<h3>Comments</h3>
<form onsubmit="addComment(event);">
<input type="text" placeholder="Add a comment" name="text" id="text" required>
<input type="hidden" name="tip_id" id="tip_id" value="{{$val->tip_id}}">
<input type="hidden" name="username" id="username" value="{{Auth::user()->name}}">
<button id="addCommentBtn">Comment</button>
</form>
<div class="alert" id="alert" style="display: none;"></div>
<br>
<div id="comments">
#foreach($comments as $comment)
<div>
<small>{{ $comment->username }}</small>
<br>
{{ $comment->text }}
</div>
#endforeach
</div>
<!--jQuery script used to be here -->
<script>
function displayComment(data) {
let $comment = $('<div>').text(data['text']).prepend($('<small>').html(data['username'] + "<br>"));
$('#comments').prepend($comment);
}
function addComment(event) {
function showAlert(message) {
let $alert = $('#alert');
$alert.text(message).show();
setTimeout(() => $alert.hide(), 4000);
}
event.preventDefault();
$('#addCommentBtn').attr('disabled', 'disabled');
var data = {
text: $('#text').val(),
username: $('#username').val(),
tipid: $('#tip_id').val(),
};
fetch('/tip/select', {
body: JSON.stringify(data),
credentials: 'same-origin',
headers: {
'content-type': 'application/json',
'x-csrf-token': $('meta[name="csrf-token"]').attr('content'),
'x-socket-id': window.socketId
},
method: 'POST',
mode: 'cors',
}).then(response => {
$('#addCommentBtn').removeAttr('disabled');
displayComment(data);
showAlert('Comment posted!');
})
}
</script>
I am using laravel 5.4 and Vue.js 2 to save some information along with file(video) in DB.But i got error like this
error: http://imgur.com/a/7XGERPUT
http://localhost:8000/videos/null 404 (Not Found)
Uncaught (in promise) TypeError: Cannot read property 'uid' of undefined
NotFoundHttpException in Handler.php line 131:
No query results for model [App\Models\Video].
Route:
Route::group(['middleware'=>['auth']],function(){
Route::get('/upload','VideoUploadController#index');
Route::post('/videos','VideoController#store');
Route::put('/videos/{video}','VideoController#update');
Route::get('/channel/{channel}/edit','ChannelSettingsController#edit');
Route::put('/channel/{channel}/edit','ChannelSettingsController#update');
});
Controler:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests\VideoUpdateRequest;
use App\Models\Video;
class VideoController extends Controller
{
public function store(Request $request)
{
$uid = uniqid(true);
$channel = $request->user()->channel()->first();
$video = $channel->video()->create([
'uid'=>$uid,
'title'=>$request->title,
'description'=>$request->description,
'visibility'=>$request->visibility,
'video_filename'=>"{$uid}.{$request->extension}",
]);
return response()->json([
'data' => [
'uid' => $uid
]
]);
}
public function update(VideoUpdateRequest $request, Video $video)
{
//authentication checked here .......
$video->update([
'title' => $request->title,
'description' => $request->description,
'visibility' => $request->visibility,
'allow_votes' => $request->has('allow_votes'),
'allow_comments' => $request->has('allow_comments')
]);
if ($request->ajax()) {
return response()->json(null, 200);
}
return redirect()->back();
}
}
Uplod.vue
<template>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Example Component</div>
<div class="panel-body">
<input type="file" name="video" id="video" #change="changfileInputChange" v-if="!uploading">
<div id="video-form" v-if="uploading&&!failed">
<div class="form-group">
<label for="title">Title</label>
<input type="" name="" v-model="title" class="form-control">
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea class="form-control"v-model="description"></textarea>
</div>
<div class="form-group">
<label for="visibility">Visibility</label>
<select class="form-control" v-model="visibility">
<option value="private">Private</option>
<option value="public">Public</option>
<option value="unlisted">Unlisted</option>
</select>
</div>
<span class="help-block pull-right">{{saveStatus}}</span>
<button class="btn btn-default" type="submit"#click.prevent="update">Save changes</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted.')
},
data()
{
return{
uid:null,
uploading:false,
uplodComplete:false,
failed:false,
title:'unlisted',
description:null,
visibility:'private',
saveStatus:null
}
},
methods:{
changfileInputChange()
{
this.uploading=true;
this.failed=false;
this.file=document.getElementById('video').files[0];
this.storeVideo().then(()=>{
})
},
storeVideo(){
return this.$http.post('/videos',{
title:this.title,
description:this.description,
visibility:this.visibility,
extension:this.file.name.split('.').pop()
}).then((response)=>{
this.uid = response.json().data.uid;
});
},
update() {
this.saveStatus = 'Saving changes.';
return this.$http.put('/videos/' + this.uid, {
title: this.title,
description: this.description,
visibility: this.visibility
}).then((response) => {
this.saveStatus = 'Changes saved.';
setTimeout(() => {
this.saveStatus = null
}, 3000)
}, () => {
this.saveStatus = 'Failed to save changes.';
});
}
}
}
</script>
In first i save video to db along with some default value for title,description etc with ajax request,Please see Video.vue code for this
I am getting uid(unique id for video)in console from store method but when i update by clicking the save chages button i got error like this:
when i used i default uid in controller in
public function store(Request $request)
{
$uid = '158cfff622e1b7';
//....some code here
//finally return $uid
return response()->json([
'data' => [
'uid' => $uid
]
]);
}
and in Upload.vuein this line if i change uid to default 158cfff622e1b7,it works fine ie:result updated
return this.$http.put('/videos/' +'158cfff622e1b7', {
//code
});
it means i am not getting `uid` ,or something is wrong,please help me
Here is screenshot of error: http://imgur.com/a/7XGER
Error: PUT http://localhost:8000/videos/null 404 (Not Found)
I can see you are using route model binding.
Route::put('/videos/{video}','VideoController#update');
Route model binding works out of the box with ID field. If you use a different key, you need to tell your model to use that key for route model binding.
In Video model add
public function getRouteKeyName() {
return 'uid';
}
Update
In providers/RouteServiceProvider, add this inside boot()
Route::bind('video', function ($value) {
return App\Models\Video::where('uid', $value)->first();
});
If it still does not work, simply get the video and update it, the good old way
public function update(VideoUpdateRequest $request, $uid)
{
$video = Video::where('uid', $uid)->first();
$video->title = $request->title;
$video->description = $request->description;
...
$video->update();
if ($request->ajax()) {
return response()->json(null, 200);
}
return redirect()->back();
}
Replace this this.uid = response.json().data.uid; by this.uid = response.body.data.uid;
it must work,if not let me hear
Read Docs for more
I've been trying to override the ProfileFormType from the FOSUserBundle. I followed the steps from my previous problem which has been solved since my current task is somehow similar to the previous one. (I use AngularJS for frontend)
Unfortunately, I can't seem to override the buildForm() function from the parent class.
I've logged the form errors and it says:
ERROR: This form should not contain extra fields.
current_password:
ERROR: This value should be the user's current password.
services.yml
services:
acme.profile.form.type:
class: Acme\BulletinBundle\Form\Type\ProfileFormType
arguments: ["%fos_user.model.user.class%"]
tags:
- { name: form.type, alias: acme_user_profile }
config.yml
fos_user:
profile:
form:
type: acme_user_profile
ProfileFormType.php
namespace Acme\BulletinBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ProfileFormType extends AbstractType {
private $class;
public function __construct($class) {
$this->class = $class;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('username', null, array('label' => 'form.username', 'translation_domain' => 'FOSUserBundle'))
->add('email', 'email', array('label' => 'form.email', 'translation_domain' => 'FOSUserBundle'));
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => $this->class,
'intention' => 'profile',
'csrf_protection' => false,
));
}
public function getParent() {
return 'fos_user_profile';
}
public function getName() {
return 'acme_user_profile';
}
}
UserController.php
/**
* #Method("POST")
* #Route("/user/edit", name="user_edit", options={"expose"=true})
*/
public function editAction(Request $request) {
$id = $request->request->get('id');
$user = $this->getDoctrine()
->getManager()
->getRepository("AcmeBulletinBundle:User")
->find($id);
/** #var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
$formFactory = $this->get('fos_user.profile.form.factory');
$form = $formFactory->createForm();
$form->setData($user);
$form->handleRequest($request);
if ($form->isValid()) {
/** #var $userManager \FOS\UserBundle\Model\UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');
$userManager->updateUser($user);
return new JsonResponse(['valid' => true]);
}
$errs = (string) $form->getErrors(true, false);
return new JsonResponse(['valid' => $errs]);
}
edit.html
<form class="form-group text-left" ng-submit="submit()" novalidate name="userFrm">
<div class="form-group">
<label for="user.email" class="required">Email</label>
<input id="user.email" name="user.username" class="form-control" required type="text" ng-model="user.email" />
</div>
<div class="form-group">
<label for="user.username" class="required">Username</label>
<input id="user.username" name="user.username" class="form-control" required type="text" ng-model="user.username" />
</div>
<input type="submit" value="Update" ng-disabled="userFrm.$invalid" class="btn btn-primary center-block col-lg-2" />
</form>
edit.js
angular.module('myApp', [])
.controller('EditUserCtrl', ["$scope", "$http", "$state", "$stateParams", function ($scope, $http, $state, $stateParams) {
//returns {username: data1, email: data2, enabled: data3}
$http.get(Routing.generate('get_user', {id: $stateParams.id}))
.then(function (response) {
var user = response.data.user;
$scope.user = user;
});
$scope.submit = function () {
var formData = {
fos_user_profile_form: $scope.user,
id : $stateParams.id
};
var success = function (response) {
var valid = response.data.valid;
console.log(valid);
};
var error = function (reason) {
alert('error');
};
console.log($scope.user);
$http.post(Routing.generate('user_edit'), $.param(formData), {
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
})
.then(success, error);
}
}
}]);
The problem is that you extending profile form, but in your form template none of main fields exist. Remove this lines from your form type:
public function getParent() {
return 'fos_user_profile';
}
or add missing field current_password to edit.html.