Vuejs and laravel -NaN value? - laravel

I want to recover a variable from controller and show it but its Show NaN instead of the value.
The idea is to claculate the the progress of my projects in function call progress in my controller and than show it in table in my Html code.
This is my function in controller and it return the correct value:
public function progress($id){
$tasks=Task::where(['projet_id'=>$id])->get();
$x=0;
$i=0;
foreach ($tasks as $task) {
$x = $x + $task->progress;
$i++ ;
}
$progress=$x/$i;
return $progress;
}
and this where i want to show the progress in project.vue:
<tr v-for="projet in projets.data" :key="projet.id" >
<td #change="avancemant(projet.id)">
{{ parseInt(100 * progress ) }}%
<img :src="`/img/icon/verif.png`" style="width:15px;
v-if="`${parseInt(100*progress)}`==100" >
</img>
<div class="progress">
<div class="progress-bar bg-success" role="progressbar" aria-valuenow="0"
id="progress" v-model="form.progress"
aria-valuemin="`${parseInt(100*task.progress)}`":style="
{'width':`${parseInt(100*progress)}%`}" aria-valuemax="100">
</div>
</div>
</td>
and this is my project.vue script:
export default {
data(){
return{
progress:'',
projets:{},
projet:{
id:''
},
}}
created(){
this.avancement(this.projet.id);
}
methods:{
avancemant($id){
axios.get('/api/progress/'+$id).then(({data})=>(this.progress =data.data));;
},
}
P.S : it show me in NaN%

In your created function, you tell vue to run the functioin this.avancement with a parameter of this.projet.id. Since you setup your data object to be the following:
data() {
return {
progress:'',
projets:{},
projet:{
id:''
},
}
}
This means that when your code executes the code inside the created hook, it will use the current state of your data.
created(){
this.avancement(this.projet.id); // this.projet.id's value at this point is '' (empty string)
}
So when your function then runs the http request, you are sending this:
axios.get('/api/progress/'+'').then(({data})=>(this.progress =data.data));
This probably breaks your API because it requires an id of some sorts.
At this point, I dont have enough info from your application or goal to know why you run this at created. But a solution to fix this as it is right now would be to add a condition inside your avancement function to not run it if the id is not valid.
methods:{
avancemant($id){
if(isNaN($id)) {
return
}
axios.get('/api/progress/'+$id).then(({data})=>(this.progress =data.data));;
},
}

Related

I am stuck in Laravel Livewire in which I am using multiple select2 and it is a wizard form

I have a wizard in laravel livwire with 4 steps.
in the first step I have select box of select2 for multiple selection, everything works fine but when I go to step 2 and return back, the problems comes
Select2 distorts and loose it's styling
Selectbox does not contain it's selected values
I am new to livwire so do not enough about it.
Here is my blade component
I don't exactly know what is the purpose of wire:ignore but I used it from internet.
<div class="col-sm-12 col-md-6 col-xl-6 col-lg-6 mb-10">
<x-label for="vehicle_groups" class="required">Vehicle Groups</x-label>
<div wire:ignore>
<x-select-two class="form-select-solid" id="vehicle_groups" multiple>
<option value=""></option>
#foreach($vehicleGroups as $vehicleGroup)
<option value="{{ $vehicleGroup->id }}">{{ $vehicleGroup->group_name }}</option>
#endforeach
</x-select-two>
</div>
#error('vehicle_groups')
<x-error>{{ $message }}</x-error>
#enderror
</div>
<script>
document.addEventListener("livewire:load", () => {
$('#vehicle_groups').select2().on('change', function (e) {
var data = $('#vehicle_groups').select2("val");
#this.set('vehicle_groups', data);
});
});
</script>
here is my livewire component
<?php
namespace App\Http\Livewire;
use App\Models\Vgroup;
use Livewire\Component;
class Vehicle extends Component
{
public $currentPage = 1;
public $type, $vehicle_groups;
public function mount()
{
}
public function render()
{
$vehicleGroups = Vgroup::get(['id', 'group_name']);
return view('admin.livewire.vehicle', compact('vehicleGroups'));
}
public function gotToNextPage()
{
if ($this->currentPage === 1) {
$this->validate([
'type' => ['required'],
'vehicle_groups' => ['required']
]);
} elseif ($this->currentPage === 2) {
} else {
}
$this->currentPage++;
}
public function gotToPreviousPage()
{
$this->currentPage--;
}
public function submitForm()
{
$this->currentPage = 1;
}
}
wire:ignore you will use it, if you don't want to rerender a part of your code, for example imagine you have a js code, and when using it with livewire it can not work well, because livewire makes requests every time and the js code can't do it's job, now, you have to know that select2, creates it's own DOM and livewire keeps making requests(so you can see it's not a good combination), for that reason you need to add wire:ignore(to ignore select2).
So the problem that the styles doesn't render again, you could try adding the select 2 inside a DOMContentLoaded event:
document.addEventListener("DOMContentLoaded", () => {
$('#vehicle_groups').select2();
})
like this, the styles will be added when the page is loaded
on the other side, to get the data, you could try and add the ready event:
$(document).ready(function() {
$('#vehicle_groups').select2("val");
})
or
$(document).ready(function() {
$('#vehicle_groups').select2();
$('#vehicle_groups').val();
})
I see that you are using an event "livewire:load", in my case this event didn't work for me(in the inspector it throw me an error that the event didn't exist), you could try and use a hook instead of "livewire:load"
Livewire.hook('message.processed', (message, component) => {
})
this event will be called when all the requests of livewire are finished
https://laravel-livewire.com/docs/2.x/lifecycle-hooks
here is the link to the documentation if you want to check it
And last i want to comment something, i saw that you are using $vehicleGroup (i guess it's an array), you are getting the data on the view, all okei there, however you using the array on select 2, to overrite the data, is that what you want to do?(#this.set('vehicle_groups', data))
you could use the attribute wire:model(if you don't need to make requests every time in that select better use wire:model.defer, like this the only request that is going to make is when it's submited), to call a variable on livewire and store the value of the select
<x-select-two class="form-select-solid" wire:model="vehicle_variable" id="vehicle_groups" multiple>
<option value=""></option>
#foreach($vehicleGroups as $vehicleGroup)
<option value="{{ $vehicleGroup->id }}">
{{$vehicleGroup->group_name }}
</option>
#endforeach
</x-select-two>
wire:model="vehicle_variable"
now on livewire:
public $vehicle_variable
and on select2:
#this.set('vehicle_variable', data)
now you have the value on a variable and not an array(if it's an array)
to check if the data exists, you could use the debugger of laravel "ddd()" inside the function mount (the mount function, will be called when the page is being loaded):
public function mount()
{
ddd($vehicle_variable);
}
if it's not null, then select2 should work however, if the value is null you need to check your livewire
if you have any question, just ask and i will try to answer!

How to show data of a single id using Vue js?

I'm displaying all records on a page at this URI xxx.test/employer/search/filter-by. I'm using Algolia Search to display all of the records/results. I added a button called View Profile and attached an empty method to it called showProfile.
When a user clicks on this button, I want to display this specific profile/record on a new page by itself. If I was fetching data on my own, i.e. without Algolia's code I would be able to do this, but in this case I'm not really sure how to do this.
EmployerSearchComponent.vue:
<ais-instant-search
:search-client="searchClient"
index-name="candidate_profiles"
>
<ais-search-box v-model="query" class="mb-3" placeholder="Search by job title, company name or city..." />
<ais-configure
:restrictSearchableAttributes="['job_title', 'employment_type']"
:hitsPerPage="25"
/>
<b-row class="job-search-card">
<ais-hits class="w-100">
<template slot="item" slot-scope="{ item }">
<b-col cols="12" class="mb-3">
<b-card>
<div class="float-right">
<i class="flaticon-paper-plane"></i> View Profile
</div>
<h4 style="margin-bottom: 20px;"><router-link to="/">{{item.job_title}}</router-link></h4>
<p>Type: {{item.employment_type}}</p>
<b-card-text class="mt-2"><span v-if="item.experience && item.experience.length < 300">{{item.experience}}</span><span v-else>{{item.experience && item.experience.substring(0,300)+"..."}}</span></b-card-text>
</b-card>
</b-col>
</template>
</ais-hits>
<ais-pagination />
</b-row>
</ais-instant-search>
If I click on the network tab in the console, and on the algolia query search URI, I can see that the search results are in results[0].hits. Below is a screenshot of this data.
P.S. My data is empty it just contains algolia client ID's.
How can I display a single id on a new page? I understand that I need to get the id from the record that is being displayed, and show this information on a new page, but I don't know how to put it all together.
Again I think I'll need a route, so I created this
Route::get('/employer/search/filter-by/show/{id}', 'EmployerSearchController#show')->name('employer.search.show');
I'm using Laravel for my backend. Thanks in advance.
------------------------------------- UPDATED: -------------------------------------
I feel like I'm really close, but $itemId in my controller after I die and dump returns "undefined" for some reason.
router.js (Vue Router):
{
path: '/employer/search/filter-by/:itemId/show',
name: 'employer-search-index',
component: SearchIndex,
meta: {
breadcrumb: 'Search Candidates',
requiresAuthEmployer: true,
employerHasPaid: true
},
},
EmployerSearchComponent.vue - with the <router-link> button:
<template slot="item" slot-scope="{ item }">
<b-col cols="12" class="mb-3">
<b-card>
<div class="float-right">
<router-link class="apply-job-btn btn btn-radius theme-btn apply-it" :to="'/employer/search/filter-by/' + item.id + '/show'">View Profile</router-link>
</div>
<h4 style="margin-bottom: 20px;"><router-link to="/">{{item.job_title}}</router-link></h4>
<p>Type: {{item.employment_type}}</p>
<b-card-text class="mt-2"><span v-if="item.experience && item.experience.length < 300">{{item.experience}}</span><span v-else>{{item.experience && item.experience.substring(0,300)+"..."}}</span></b-card-text>
</b-card>
</b-col>
</template>
EmployerSearchController.php:
public function show ($itemId)
{
$candidateProfiles = CandidateProfile::with(['user', 'photo', 'resume', 'video'])
->where('id', '=', $itemId)->get();
return Response::json(array(
'candidateProfiles' => $candidateProfiles,
), 200);
}
api.php:
Route::get('/employer/search/filter-by/{itemId}/show', 'EmployerSearchController#show')->name('employer.search.show');
And Finally, in the .vue file that shows the Full Single Profile, I'm loading the data like this.
mounted() {
this.loadCandidateProfileData();
},
methods: {
loadCandidateProfileData: async function () {
try {
const response = await employerService.loadCandidateProfileData();
this.candidateProfiles = response.data.candidateProfiles;
} catch (error) {
this.$toast.error("Some error occurred, please refresh!");
}
},
}
And the employerService.js file from the above code:
export function loadCandidateProfileData(itemId, data) {
return http().get(`employer/search/filter-by/${itemId}/show`, data);
}
As you suggest, you'll need an API endpoint to fetch the data from, returning it as a JSON object. You'll need to add a route to your client-side routes that takes the job ID (or slug) as a parameter. In your job component, you can retrieve the route param (e.g. in the created() method) as $route.params.id, for example, and use that to fetch the data from your API.
If your Algolia search is returning all the data that you want to display on your single job listing page, you could just put that in your Vuex store and display it without having to make another HTTP request. The downside of that would be that if a user bookmarked the page to return to later, there'd be no data in the store to display.
Thank you to Nilson Jacques responses. If you follow our conversation.
Finally I got the itemId param from the route and passed it to loadCandidateProfileData() like this:
loadCandidateProfileData: async function() {
try {
const itemId = this.$route.params.itemId;
const response = await employerService.loadCandidateProfileData(itemId);
this.candidateProfiles = response.data.candidateProfiles;
console.log(this.candidateProfiles);
if (response.data.candidateProfiles.current_page < response.data.candidateProfiles.last_page) {
this.moreExists = true;
this.nextPage = response.data.candidateProfiles.current_page + 1;
} else {
this.moreExists = false;
}
} catch (error) {
this.$toast.error("Some error occurred, please refresh!");
}
},

Cloning item. The GET method is not supported for this route. Supported methods: POST

I'm trying to clone a model but when I try to I get the error in the title. This is what I'm doing
Button in vue.js
<a class="btn-link-clone action-button" :href="'survey/clone/'+scope.row.id">
<i class="fas fa-clone col-margin"></i>
</a>
Route in web.php
Route::post('survey/clone/{id}', 'SurveyController#cloneSurvey');
CloneSurvey in SurveyController
public function cloneSurvey($id)
{
$survey = Survey::findOrFail($id);
DB::beginTransaction();
$now = Carbon::now();
$activePeriod = Period::whereDate('start_date', '<=', $now)
->whereDate('end_date', '>=', $now)->get();
$clone = new Survey();
$clone->fill($survey->toArray());
$clone->name .= ' Clonado';
$clone->save();
$eval = Evaluation::findOrFail($clone->id);
if (empty($eval)) {
$eval = new Evaluation();
}
$eval->survey_id = $clone->id;
if (!empty($activePeriod)) {
$eval->init_date = $activePeriod->start_date;
$eval->end_date = $activePeriod->end_date;
}
$report = $activePeriod->end_date;
$date = strtotime($report);
$date = strtotime('+ 1 week', $date);
$eval->report_date = $date;
$eval->save();
$questions = $survey->surveyQuestions()->get()->pluck('survey_question_id')->toArray();
if (count($questions) > 0) {
$clone->surveyQuestions()->sync($questions);
}
DB::commit();
return back();
}
What is making this happen?
I've also tried this
button in vue.js
<div class="btn-link-clone action-button"
#click="clone(scope.row)">
<i class="fas fa-clone col-margin"></i>
</div>
method in vue.js
clone(row) {
this.$http.post('survey/clone/' + row.id)
.then(
() => {
this.surveys = this.$page.surveys;
},
(res) => {}
)
},
with the route the same and I get a 419 (unknown status)
You need your Route to use the get method rather than the post method. Like so:
Route::get('survey/clone/{id}', 'SurveyController#cloneSurvey');
When a user clicks on a link, that is almost always a GET request.
EDIT:
Based on the comments, I agree that changes should not be done via a get request anyway. This example should be a POST request.
Your Vue component:
<template>
...
<form>
<button
class="btn-link-clone action-button"
#click.prevent="submit(scope.row.id)"
>
<i class="fas fa-clone col-margin"></i>
</button>
</form>
...
</template>
<script>
export default {
...
methods: {
submit (id) {
// use whatever http request library you choose to execute POST HTTP request.
}
}
...
</script>
Alternatively, you could use #submit.prevent on the form tag instead of the #click.prevent on the button.
Then, as long as scope.row.id was defined on the frontend, you can use:
Route::post('survey/clone/{id}', 'SurveyController#cloneSurvey');
The GET method is not supported for this route. Supported methods: POST
You are making a GET request to a Route that only supports the POST method.
Either your vue app is making a ajax request to your server with missing headers or form incorrect form action.
You may also be passing something like a GET parameter as part of your request
i.e. http://my-app/api/example?var1=asdasdasdas

ajax datatable laravel Linking

I have a datatable using yajrabox package with links to various strings. When I click on a link, it takes me to a 404 page with "/teams/"string". How do I make this string a viewable page? I have tried using a slug, but I could be using it incorrectly and honestly I don't know where my error is. Here is the impt. parts of my code:
Route:
Route::get('/teams/display/{$slug}', 'TeamsController#display');
TeamsController:
public function display($slug)
{
$teamdatas = LoserData::findBySlugOrFail($slug);
return view('teams/display', compact('teamdatas'));
}
DataTable.blade
{data: 'homeTeam', name: 'homeTeam', "fnCreatedCell": function (nTd, sData, oData, iRow, iCol) {
$(nTd).html(""+oData.homeTeam+"");
}},
teams/display.blade.php
<div class="container">
<div class="row">
<div class="col-sm-8">
<h1> Welcome the {{$teamdatas->slug}} profile page</h1>
</div>
</div>
I guess I should have checked the routing docs because it was definitely a routing issue. To solve it put this in your routes:
Route::get('/teams/display/{teamdatas?}', 'TeamsController#display', function ($teamdatas = 'Oilers-84') {
return $teamdatas;
});
Here is the info from the Laravel Docs 5.5:
Optional Parameters
Occasionally you may need to specify a route parameter, but make the presence of that route parameter optional. You may do so by placing a ? mark after the parameter name. Make sure to give the route's corresponding variable a default value:
Route::get('user/{name?}', function ($name = null) {
return $name;
});
Route::get('user/{name?}', function ($name = 'John') {
return $name;
});

Subscription reactive on Session changes causes #each to redraw every entity

So here's the catch: I store the user's coordinates using this neat solution. Here is my implementation:
updateLoc = function () {
var position = Geolocation.latLng() || {lat:0,lng:0};
Session.set('lat', position.lat);
Session.set('lon', position.lng);
};
Meteor.startup(function() {
updateLoc(); // set at 0, 0 to begin with
Meteor.setTimeout(updateLoc, 1000); // get first coordinates 1 second in
Meteor.setInterval(updateLoc, 5000); // then, every 5 seconds
});
I have an entitiesList route waiting on entities to be subscribed, according to those two session variables:
this.route('entitiesList', {
path: '/',
waitOn: function() {
if (Meteor.userId())
return Meteor.subscribe('entities', {lat: Session.get('lat'),lon: Session.get('lon')});
},
data: function() {
return {entities: Entities.find()};
}
});
Here is the publication:
Meteor.publish('entities', function (position) {
if (position.lon !== null && position.lat !== null) {
return Entities.find({location: {
$near: {$geometry:{type: "Point", coordinates: [position.lon, position.lat]},$maxDistance:500}}
}});
}
this.ready();
});
Finally, the entitiesList template :
<template name="entitiesList">
<div class="entities">
<h1>Entities list</h1>
{{#each entities}}
{{> entityItem}}
{{else}}
<p>No entity found. Looking up...</p>
{{>spinner}}
{{/each}}
</div>
</template>
Now! This solution works. Entities are listed correctly, updated every 5 seconds according to the user's location.
The only issue lies in rendering: when the reactivity is due to an update of Session variables, the entire set of entities is deleted and redrawn. But when a change occurs in the Entity Collection (say, an entity gets deleted / created) only this change is re-rendered accordingly in the template.
What this produces is a list that flashes very annoyingly every 5 seconds. I thought of removing the #each block and sort of write it myself using this.autorun() in the rendered function of the template, and to redraw the list in a more optimized fashion using jQuery, but it would be an obnoxious hack, with HTML chunks of code outside of the template files... Surely there's gotta be another way!
Each time you change your session variables, your subscription is loading and Iron Router sets his loading template and that's why it's flickering.
Instead of using iron-router you could do:
Template.entitiesList.created=function()
{
var self=this
this.isLoading=new ReactiveVar(false)
this.isFirstLoading=new ReactiveVar(true)
this.autorun(function(){
self.isLoading.set(true)
Meteor.subscribe('entities', {lat: Session.get('lat'),lon: Session.get('lon')},function(err){
self.isLoading.set(false)
self.isFirstLoading.set(false)
});
})
}
Template.entitiesList.helpers({
entities:function(){return Entities.find()}
isLoading:function(){Template.instance().isLoading.get()
isFirstLoading:function(){Template.instance().isFirstLoading.get()
})
<template name="entitiesList">
<div class="entities">
<h1>Entities list</h1>
{{#if isFirstLoading}}
<p>Looking up...<p/>
{{>spinner}}
{{else}}
{{#each entities}}
{{> entityItem}}
{{else}}
<p>No entity found</p>
{{/each}}
{{#if isLoading}}
{{>spinner}}
{{/if}}
{{/if}}
</div>
</template>
Fiddling through with iron-router, I found that there actually is an option to not render the loading template on every new subscription triggered: the subscriptions option. Just had to replace waitOn by subscriptions and I get the desired result.

Resources