Livewire and getting the element id from wire:id - laravel

I'm using a livewire full-page component to display a list of tasks along with all the CRUD stuff associated, and using dragula to allow for drag and drop.
I initially had all this running through a TaskListComponent, and was having trouble getting some of the functionality to work so I now have a child component that just handles the task itself. So there's a TaskListComponent that's the page, and and then I have a TaskComponent that's added to the page in a foreach loop. my thought is that all the task list type functionality would happen in the main component, and anything related to the specific task (show, update, delete, etc) would be in the TaskComponent. Tell me if that's how you would structure it?
My question now is, based on that structure, I have some javascript that's running on the page to listen for the dragula drop event and handle reordering/changing status of tasks and it relies on the element ID, but since I made each task a livewire component now the element id is being replaced with wire:id which is some hash value. I don't know how to access that in javascript and get the original html element ID.
This is what's in my blade file:
#foreach ($tasks as $task)
#if ($task->task_status_id === $status->id)
<livewire:tasks.task-component wire:key="{{ $task->id }}" :task="$task" data-task-id="{{ $task->id }}" />
#endif
#endforeach
And here's the bit of js using dragula:
drag.on('drop', function(el, target, source, sibling) {
console.log(el);
});
The above console.log outputs:
<div wire:id="1pPdyV6eX8kgtePfXBzk" ...></div>
Sorry I know this is a noob question, but it's not clear from the docs how to get to what I'm after. I've tried using Livewire.find() but it returns undefined. I can use Livewire command in the console, so I know it's available.
Also possibly related, any kind of $wire command I call says $wire is undefined. I've got this in my app.js file that's loaded after livewire scripts:
import Alpine from 'alpinejs'
window.Alpine = Alpine
Alpine.start()
And I can do Alpine in the console and it knows what that is.
Any help would be greatly appreciated.

You should load your app.js file in the head of your document with a defer attribute.
You can then use $wire or this.$wire to access to the Livewire component. This only works when your Alpine component is inside of a Livewire component though.

Related

New Laravel/VueJS developer missing a fundamental bit of understanding

I'm trying to create a simple app to learn Laravel with VueJS. I created a JetStream sample app with InertiaJS but seem to have gotten stuck on something that is probably just related to a fundamental misunderstanding.
I have added a link to my navigation section, which renders a Vue component, which uses GridJS to display a list of all users. Now when I check in Chrome's DevTools Network tab, the request seems to route correctly, but the template in the page doesn't get added to the DOM. I have some JS in the same document which gets a link hook, and it then uses that to look up a querySelector, but doesn't find it. The link is in the same file but within a template block, so the template block contents are obviously not being added to the DOM.
Here is my route:
Route::get('/user/view', function() {
return Inertia::render('UserList');
})->name('user.view');
Here is my Vue component (just relevant part):
<template>
<div class="row">
<div class="col-lg-12">
<div js-hook-url="{{ route('user/view') }}" js-hook-table-users></div>
</div>
</div>
</template>
<script>
import { Grid, html } from "gridjs";
import "gridjs/dist/theme/mermaid.css";
const USER_TABLE = '[js-hook-table-users]'
const TABLE_USERS_WRAPPER = document.querySelector(USER_TABLE);
const TABLE_USERS_URL = TABLE_USERS_WRAPPER.getAttribute('js-hook-url');
So the error happens on the last line there, because the node does not exist in the DOM and so is not picked up by the querySelector, so getAttribute gets called on null. Again, I'm sure this is a fundamental issue being new to Laravel and Vue. TIA

How to enhance an existing Laravel project with VueJS?

I just finished my project using only the Laravel framework. Now I want to add vue.js into my project to render the views without loading them. I went through some tutorials and found that I need to convert my blade files into Vue components to achieve this. But as I know it's a big process as some of the functions won't work in VueJS. And I don't know how to do it. Please, someone, guide me here on how to do that. Thanks in advance.
Rebuild your basic structure into a Vue template:
// MyTemplate.vue
<template>
<div> <!-- keep this single "parent" div in your template -->
<!-- your Blade layout here -->
</div>
</template>
<script>
export default {
props: [ 'data' ]
}
</script>
Add your Vue template as a global component in app.js:
// app.js
import Vue from 'vue';
window.Vue = Vue;
Vue.component('my-template', require('./MyTemplate.vue').default);
const app = new Vue({
el: '#app'
});
Use this new Vue template in your Blade template as below:
<my-template :data="{{ $onePhpVarThatHoldsAllYourData }}"></my-template>
Once inside the Vue template, you will not be able to reach back to your Laravel methods (e.g. #auth) or for additional data without an Ajax request, so make sure you package all the data you need in your Vue template up front. All of the data you send in will be prefixed inside the Vue template with the name of the prop you assign it to, in this case data.
Note, once you get more familiar with Vue you will likely start segregating the individual data values being passed to your template. For that, you will need to specify additional props in the props array in step 1, e.g.
props: ['a', 'b', 'c'],
and then individually pass their values with
<my-template :a="{{ $a }}" :b="{{ $b }}" :c="{{ $c }}"></my-template>
Convert your Blade directives to Vue directives:
Loops (e.g. #foreach) to v-fors:
#foreach ($items as $item)
<li>{{ $item }}</li>
#endforeach
becomes
<li v-for="item in data.items">{{ item }}</li>
Control structures (e.g. #if ($something !== $somethingElse) ... #endif) to v-ifs:
<div v-if="data.something !== data.somethingElse">
...
</div>
In general, as it was already mentioned in comments, there's no short way for converting your application from blades to VueJS components. Anyway, if you consider migrating to VueJS, I'd recommend you to make a full migration instead of partially using Vue components.
The main idea of migration to VueJS is to transfer all logic that you did in blade templates (like foreach's, if's etc) to Vue components and fetch all data using AJAX requests (e.g. with help of axios or nativelly).
In this case, your controllers should return all data needed for page rendering and Vue components will take care of rest rendering logic.
Also, it's a good option to use vue-router to handle rounding and make your application behave as SPA. In this case, you should create one wildcard route in your application that will return only one blade template. Inside of this template you should insert root tag that will initiate VueJS. The rest will be on the VueJS side.
If you are planning to migrate the whole application then start with authentication.
Part #1: https://codebriefly.com/laravel-jwt-authentication-vue-ja-spa-part-1/
Part #2 https://codebriefly.com/laravel-jwt-authentication-vue-js-spa-part-2/
This tutorial helped me in the past to getting started. After that split your code into components.
If you want to learn basics first then you can go with this tutorial I found this useful.
https://www.youtube.com/playlist?list=PL4cUxeGkcC9gQcYgjhBoeQH7wiAyZNrYa
Hope this helps!

Blade templating variable reference

I have a list of items which I want to render in the following way:
#foreach($campaignList as $campaign)
<div class="col-md-{{12/$columns}}">
#include('admin.includes.campaign_card',['campaign'=>$campaign,'link'=>true])
</div>
#endforeach
The admin.includes.campaign_card template (with debug)
#extends('admin.includes.base_campaign_card')
{{$campaign}} // Here the data is okay
#section('options')
{{$campaign}} //Here however I get the first item on each loop
#endsection
Basically the campaign object within the section remains the same when looping.
It seems that the issue is related to the fact that you can have just one section with a given name at a time.
There is way to force blade to rerender with #overwrite directive (which is not in the docs apparently) instead of #endsection.
Github comment

How to add a Vuejs component to a Laravel Spark application?

I am working on a Laravel 5.2 project with Laravel Spark (which still in beta as at time of writing) and trying to add some Vuejs functionality using the default layouts and views.
My first attempt failed because I simply tried to create a new div within the home view and bind my Vue code to that div. Here is the div:
<div id="my-stuff">
<p>#{{ test }}</p>
</div>
And here is the corresponding JS code:
new Vue( {
el: '#my-stuff',
data: {
test: 'This is a test'
}
});
What I expected to see were the words "This is a test" appear within that div on the home screen, but of course nothing appeared because, as mentioned, Vue gets bound to a div immediately after the body tag (well, I'm assuming that's why anyway).
I think the solution to my problem is to use Vue components, which themselves look fairly straightforward, but I have no idea where to put my code, how to integrate my code with the Gulp process, which Spark file I need to modify (if any?) to register my component and how to ensure that my component gets registered before the Vue instance gets created.
Any help would be much appreciated.
Addendum 1
To reproduce the exact same set-up as I'm using, one would need to install a fresh copy of Laravel 5.2, then use the spark installer to add the spark stuff, then add app.js containing the code below to the public folder, add the corresponding div anywhere in the home view and add a script tag to include app.js right below the script tag that imports the main javascript file produced by gulp.
Whilst it is impractical to reproduce that entire setup in a fiddle, I think the following fiddle illustrates the essence of the problem:
https://jsfiddle.net/5oLLte2e/
From memory you have the same limitation in AngularJS. It is completely reasonable to me why this wouldn't work and the solution in Vuejs is most likely to use components, but the challenge in this situation is knowing how to bundle the component and where to save it in order to integrate it with the gulp config, or if that is even necessary.
Vuejs Components
If you want to have more than one vue instance the short answer is: yes, you need components.
<div id="main-app">
<p>{{ mainMessage }}</p>
<my-app>
<p>Some composable content</p>
</my-app>
</div>
And the scripts will have to be loaded components first:
Vue.component('my-app', {
template: '<div>{{myMessage}}<br/><slot></slot></div>',
data: function() {
return {
myMessage: 'This is my message'
}
}
});
new Vue( {
el: '#main-app',
data: {
mainMessage: 'This is the main module'
}
});
The output will be:
This is the main module
This is my message
Some composable content
Here is the fiddle: Components with Vue
Remember that you can always put the template in the page using a unique id or, more idiomatically using something like:
<script type="x/template" id="my-app">
Your template here
{{ your component variables }}
</script>
Laravel Spark Integration
The steps to adding a component within a Sparkified Laravel application are as follows:
(1) Add the placeholder HTML with the custom tag anywhere on the page, even if a surrounding div has already been Vue-ified. The HTML with the custom component might look like this:
<div id="example">
<my-component></my-component>
</div>
(2) Implement the Vue component and save the JavaScript file in resources/assets/js. By way of example, we might save the following code as my-component.js:
var MyComponent = Vue.extend({
data: function() {
return { message: 'This is a test' }
},
template: '{{ message }}'
})
Vue.component('my-component', MyComponent)
new Vue({
el: '#example'
})
(3) Add one require statement (the second line below) to the code in resources/assets/js/app.js so that the file looks like this:
require('laravel-spark/core/bootstrap');
require('./my-component.js'); // This is the key!
new Vue(require('laravel-spark'));
Note that it is super-important to include the leading ./ in front of the filename, otherwise Browserify will assume it is looking for a npm module instead of a raw file and will fail.
(4) Run gulp and once it has finished, refresh the page. Gulp will call Browserify, which processes resources/assets/js/app.js, which now includes our custom JavaScript to be processed and included in the final public/js/app.js file.
If you carry out these steps on a clean Laravel installation that has had the Spark installer treatment (I made my mods to home.blade.php), you should see the sample text appear on the page.

How do I bind events directly to existing HTML, without using any kind of views, in Ember.js?

Ember.js has a great mechanism of binding data to views, of setting triggered event handling in the view, or using a Router. But what I would need is to be able to handle events triggered in already created HTML code (by PHP, server-side).
Let me show you a simple example. I have this code:
<a id="login" href="#">Login</a>
I need to be able to route/handle the click on this link so that it gets into my Ember application.
I have been looking for ways to do this, but I can't find any.
How can I do this?
If this link is inside a DOM element which is a child of the Ember managed element, then you can use the action helper:
<a id="login" href="#" {{action doSomeStuff}}>Login</a>
This doSomeStuff event will be sent to your Ember.Router, which has to implement the handler in the appropriated route:
...: Ember.Route.extend({
doSomeStuff: function (router) {
//...
}
}),
If the link is outside your app's scope, you can register handlers on the app-related elements using JQuery:
$('a#login').click(function () {
App.router.transitionTo('the.route.path');
});
The App.router being injected at Ember app's initialization, you can access it from anywhere.
But let me say that it is not a best practice to transition from outside the router.
Last but not least, you can also pass a context to the transitionTo call.

Resources