Laravel Nova Card Options Missing Function - laravel

I was recently given a Laravel app that uses Nova and Vue in it - all three of which I've never worked with before, so apologies in advance if I ask something that should be obvious. Inside the app there is a custom card that was already setup by the previous developer that works fine and I'm just trying to add an extra property for the Vue side to read. The structure of the card is pretty basic:
public $width = 'full';
public function currentUser()
{
return $this->withMeta(['currentUser' => Auth()->user()->id]);
}
public function is_active()
{
return $this->withMeta(['is_active' => Auth()->user()->is_active]);
}
public function information()
{
$information_id = Information::where('user_id', Auth()->user()->id)->first()->id;
return $this->withMeta(['information' => $information_id]);
}
public function component()
{
return 'overview';
}
This card shows up on the dashboard and using the Network tab in debug tools, I can see where the JSON response that goes to Vue when the card's API component is hit. The problem is, the one property I'm trying to add (information) doesn't show up. The full response is:
{
"label": "Dashboard",
"cards": [
{
"width": "full",
"component": "overview",
"prefixComponent": false,
"onlyOnDetail": false,
"currentUser": 780,
"is_active": 1
}
]
}
I've tried running npm install, npm dev and php artisan nova:publish - all are successful, but that JSON isn't picking up the new functions I added to the card's PHP file. For the sake of just trying, I even added these two:
public function testing()
{
return 'test';
}
/**
* Indicates that the analytics should show current visitors.
*
* #return $this
*/
public function currentVisitors()
{
return $this->withMeta(['currentVisitors' => true]);
}
Despite recompiling everything, those functions also don't seem to do anything - the JSON returned stays the same. I'm positive I'm doing something very basic wrong, but for the life of me can't quite figure it out.
Any suggestions?

I personally did create 2 components for Laravel Nova 3.x (one of them using VueJS), and what I did was:
Go inside the component root folder and run npm run prod (or dev ir you are testing)
Then, you must have your component registered as a composer package, so when you load the Component's ServiceProvider, you should have:
public function boot(): void
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'component-view-name');
Nova::serving(function (ServingNova $event) {
Nova::script('component-view-name', __DIR__.'/../dist/js/field.js');
});
}
That Nova::serving is the one "attaching" or sharing your desired *.js compiled file.
I am not sure about cards. My component was just a new field, not a card, but should be exactly the same.

Related

PhpStorm No Usages for get Attribute

I'm using the latest version of PhpStorm (2022.3.1) and Laravel 9+. This is not a huge issue, but rather a major eyesore.
For some reason when I use a Model to create a custom Attribute like this:
public function getFormattedStartDateAttribute(): string
{
if (!$this->start_date) {
return 'TBD';
} else {
return $this->start_date->format('M. d, Y');
}
}
And use in the view like this or this:
Date {{ $event->formattedStartDate }}
Date {{ $event->formatted_start_date }}
PhpStorm still says the method has no usages?
Image:
Is there a way to fix this? I've tried reindexing my project. This is a new feature called Code Vision.
The issue is very easy to solve, you can use barryvdh/laravel-ide-helper and then run php artisan ide:model, that will go over the models and create a PHPDoc block (you can add options and create them in the same model file or in a new file where you only have this PHPDock blocks), and PHPStorm will read this doc block and know when you are calling attributeWhatever is what type.
It will add this in your case:
/**
* #property string $formattedStartDate
*/
This way, any IDE that is capable of understanding PHPDock blocks, will understand that when you do $class->formattedStartDate, you are refering to that one, and it is of type string.
BUT, no IDE (unless using a plugin that I am not aware of) will understand that getFormattedStartDateAttribute -> formattedStartDate, so you will still get no usages for getFormattedStartDateAttribute, but at least you can track formattedStartDate and do whatever you want with it.
One quick tip, if you are using Laravel 9+, please change that code from:
public function getFormattedStartDateAttribute(): string
{
if (!$this->start_date) {
return 'TBD';
} else {
return $this->start_date->format('M. d, Y');
}
}
To:
public function formattedStartDate(): \Illuminate\Database\Eloquent\Casts\Attribute
{
return Attribute::get(
function ($value, $attributes) {
if (! $attributes['start_date']) {
return 'TBD';
} else {
return $attributes['start_date']->format('M. d, Y');
}
}
);
}
Why? Because using getFormattedStartDateAttribute is the old way, Laravel 9+ made it easier, read about Accessors.
See that getXXXXAttribute and setXXXXAttribute is not even present on the documentation anymore.

Setting up Laravel-excel and PhpSpreadsheet macro

I am using Maatwebsite Laravel-excel version 3.1. And I want to set the default styling of the sheet. I have read the documentation about including a macro in your laravel app by setting this in my AppServiceProvider boot() method. :
Sheet::macro('getDefaultStyle',function(Sheet $sheet){
$sheet->getDefaultStyle();
});
But when everytime i reload the page it crashes the page and the laravel server in my cmd stops and re run. Here is my Export.php looks like:
public function registerEvents():array
{
return[
AfterSheet::class=>function(AfterSheet $event){
$header_style_array = [
'font'=>['bold'=>true]
];
$style_array = [
'font'=>['bold'=>true]
];
$event->sheet->getStyle('A1:B1')->getAlignment()->setHorizontal('center');
$event->sheet->getStyle('A1:B1')->applyFromArray($header_style_array);
$event->sheet->getDefaultStyle()->getFont()->setSize(5);
}];
}
I already included use Maatwebsite\Excel\Concerns\WithEvents; use Maatwebsite\Excel\Events\AfterSheet; above my Export.php file.
Is there something that I missed? I find this so hard to set up. And there's little article about setting this up.
Any help would be much appreciated
Refs: https://phpspreadsheet.readthedocs.io/en/latest/topics/recipes/#styles
https://docs.laravel-excel.com/3.1/exports/extending.html
If you examine the documentation for PhpSpreadsheet, I think you will find that the getDefaultStyle() method is not accessible from the active sheet.
To Laravel Excel, $event->sheet is equivalent to $spreadsheet->getActiveSheet(). This is why your current configuration will not work.
// this doesn't work
// $spreadsheet->getActiveSheet()->getDefaultStyle()->getFont()->setSize(5);
// this does
$spreadsheet->getDefaultStyle()->getFont()->setSize(5);
You should set default styles through the writer in BeforeWriting.
public function registerEvents():array
{
return [
BeforeWriting::class=>function(BeforeWriting $event){
$event->writer->getDefaultStyle()->getFont()->setSize(5);
},
];
}
If you want to turn this into a macro, you should use a Writer macro rather than a Sheet macro.
https://docs.laravel-excel.com/3.1/exports/extending.html#writer
public function registerEvents():array
{
Writer::macro('setDefaultStyle', function (Writer $writer) {
$writer->getDefaultStyle()->getFont()->setSize(5);
});
return [
BeforeWriting::class=>function(BeforeWriting $event){
$event->writer->setDefaultStyle();
},
];
}

Laravel nova - redirect from Dashboard

I would like to remove dashboard from my Laravel Nova app.
I found it easy to remove it from sidebar-menu - simply comment /views/dashboard/navigation.blade.php code.
However, I want to add a redirection logic (landing page depends on user role) so when navigating to / user will be redirected to a resource or tool which corresponds him.
(I have already implemented a redirection after login (https://stackoverflow.com/a/54345123/1039488)
I tried to do it with cards, but looks like this is not the right solution.
Any idea where can I place the redirection logic?
Nova 4; You can override the initialPath like so:
class NovaServiceProvider extends NovaApplicationServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
parent::boot();
Nova::initialPath('/resources/users');
}
// ...
}
This way, you get redirected to the Users resource upon logging in.
Pre nova 4 method:
To NovaServiceProvider.php add to boot method:
Nova::script('menuFix', __DIR__.'/../../resources/js/fixMenu.js');
Create file fixMenu.js with following:
if (location.pathname == '/' || location.pathname == '/dashboards/main'){
location.href = '/whereToRedirect'
}
A cleaner and safe way for Nova 3.x or below:
Copy vendor/laravel/nova/resources/views/layout.blade.php to resources/views/vendor/nova/
Now open resources/views/vendor/nova/layout.blade.php and edit it
Replace this line with the code below window.Nova = new CreateNova(config);
window.Nova = new CreateNova(config);
window.Nova.booting((Vue, router, store) => {
/** This fixes showing an empty dashboard. */
router.beforeEach((to, from, next) => {
if (to.name === 'dashboard.custom') {
next({ name: 'index', params: { resourceName: 'users'}});
}
next();
});
});
Replace users with your entity name's plural like products
Now save the file and refresh the nova dashboard and you should see the new page.
The solution was taken from here with clear steps.
The solution may also work for 4.x, but I haven't checked it yet.
Happy Coding :)
Just figured this out myself. In your Routes/web.php file, add a redirect route:
Route::redirect('/','/resources/{resource_name}');
where {resource_name} is the plural form of the resource. For example, '/resources/posts'.
In your case, you may want to redirect to your own control file, where the redirect logic can be placed.
Route::get('/', 'YourController#rootRedirectLogic');
Then in the controller YourController, add the method:
public function rootRedirectLogic(Request $request) {
// some logic here
return redirect()->route('YourRoute');
}
where 'YourRoute' is the name of the route you want to send the user to.
(Found clues to this solution in a comment by dillingham here: https://github.com/laravel/nova-issues/issues/393)
i came across this link : Laravel Nova - Point Nova path to resource page
Not sure it's a permanent solution but editing LoginController.php will do.
public function redirectPath()
{
return Nova::path().'/resources/***<resource_name>***;
}
**change to your own resource name

Vuex and Laravel 5: how to remove Tags from Articles

I have a small blog app that has Articles and Tags. Nothing fancy so far. Every Article can have many Tags.
The Laravel backend delivers the data via API calls from Axios in the Vue Frontend. In the Laravel models Article has a method
public function tags(){
return $this->belongsToMany('App\Tag');
}
and vice versa for tags. I have a pivot table and all this follow pretty much the example given in https://laracasts.com/series/laravel-5-fundamentals/episodes/21
All this works fine.
Now let´s say I want to call in Vue the method deleteTag() which should remove the connection between Article and Tag. Things are behind the scenes a bit more complicated as "addTag" in PHP also adds a new Tag Model AND the connection between Tag and Article in the Pivot table OR connects - if the Tag exists already - an existing Tag with Article.
What is the best way to achieve this?
What I´m doing so far:
ArticleTags.vue
deleteTag(tagName){
let articleId = this.articleId;
this.$store.dispatch('removeTagFromArticle', { articleId, tagName });
},
index.js (Vuex store)
actions: {
removeTagFromArticle(context,param) {
axios.post('api/articles/delete-tag/', param)
.then((res) => {
let article = context.getters.getArticleById(param.articleId);
let tagName = param.tagName;
context.commit('deleteTag', {article, tagName} );
})
.catch((err) => console.error(err));
} }
mutations : { deleteTag (state, { article, tag }) {
let tagIndex = article.tags.indexOf(tag);
article.tags.splice(tagIndex,1);
} }
ArticleController.php
/**
* Remove Tag from Article
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function deleteTag(Request $request)
{
$tag = \App\Tag::firstOrCreate(array('name' => $request->tagName));
$article = Article::findOrFail($request->articleId);
$result = $article->tags()->detach([$tag->id]);
$this->cleanUpTags();
return response('true', 200);
}
routes/web.php
Route::post('api/articles/delete-tag', 'ArticleController#deleteTag');
This works so far. The code does exactly what it should. Only it feels really clumsy. And probably to complicated. Maybe it´s because the example is simple but the whole setup is big.
Nonetheless I´m trying to improve my coding. :)
So my questions are:
1) Would it be better to pass the article object in Vue to the store instead of the articleId?
2) Is the idea of using Array.slice() in the store too complicated? This could be done straight in the components.
3) Does it make sense to reload the whole store from Laravel after deleting the tag PHP-wise?
Edit: in case someone is looking for this question and how I solved it at the end. The source code for this app can be found at https://github.com/shopfreelancer/streamyourconsciousness-laravel
Personally I like to use ID's to reference any database resource aswell as keeping my objects in javascript somewhat the same as my API.
1
In this case I would have changed my tags to objects instead of strings and send an ID of the tag to my API.
An example of my article would look like:
let article = {
tags: [{ id: 1, name: 'tag 1' }, { id: 2 ... }]
}
Using objects or IDs as parameters are in my opinion both fine. I should stick with objects if you like "cleaner" code, there will only be one place to check if the ID is present in your object aswell as the selection of the ID.
Case:
// Somehwere in code
this.deleteArticle(article);
// Somehwere else in code
this.deleteArticle(article);
// Somewhere else in code
this.deleteArticle(article);
// Function
deleteArticle(article) {
// This check or other code will only be here instead of 3 times in code
if (!article.hasOwnProperty('id')) {
console.error('No id!');
}
let id = article.id;
...
}
2
Normally I would keep the logic of changing variables in the components where they are first initialized. So where you first tell this.article = someApiData;. Have a function in there that handles the final removal of the deleted tag.
3
If you are looking for ultimate world domination performance I would remove the tag in javascript. You could also just send the updated list of tags back in the response and update all tags of the article with this data and keep your code slim. However I still like to slice() the deleted tag from the array.
Remember this is my opinion. Your choises are completely fine and you should, like I do myself, never stop questioning yours and others code.

How to render a cms page with default theme AND variables from controllers in OctoberCMS?

I'm wondering how I can render a view, or display a page with my default theme in OctoberCMS, via a route that executes a function in a controller.
If I have the following route:
Route::get('bransje', [
'uses' => 'Ekstremedia\Cityportal\CPController#bransje'
]);
And in my controller CPController ive tried several things, like I used to with Laravel:
public function bransje() {
$stuff = Stuff::with('info');
return View::make('cms::bransje')->with('stuff',$stuff);
}
But I cannot seem to get it to work, and I've tried to search the web, but it's hard to find answers. I have found a workaround, and that is to make a plugin component, then I can include that component and do:
public function onRun()
{
$this->eventen = $this->page['stuff'] = $this->stuff();
}
protected function stuff()
{
return ...
}
Is there any way so I can make pages without using the Cms, and that are wrapped in my default theme? I've tried
return View::make('my-theme-name::page');
and a lot of variants but no luck.
I know I can also do a:
==
public function onRun()
{
}
in the start of my page in the cms, but I'm not sure how to call a function from my plugin controller via there.
You can bypass frontend routing by using routes.php file in your plugin.
Full example in this video turotial.
If this answer can still be useful (Worked for October v434).
I have almost the same scenerio.
What I want to achieve is a type of routing like facebook page and profile.
facebook.com/myprofile is the same url structure as facebook.com/mypage
First I create a page in the CMS for each scenario (say catchpage.htm)
Then a created a catchall route at the buttom of routes.php in my plugin that will also not disturb the internal working of octobercms.
if (!Request::is('combine/*') && !Request::is('backend/*') && !Request::is('backend')) {
// Last fail over for looking up slug from the database
Route::get('{slug}/{slug2?}', function ($slug, $slug2 = null) {
//Pretend this are our routes and we can check them against the database
$routes = ["bola", "sade", "bisi", "ade", "tayo"];
if(in_array($slug, $routes)) {
$cmsController = new Cms\Classes\Controller;
return $cmsController->render("/catchpage", ['slug' => $slug]);
}
// Some fallback to 404
return Response::make(View::make('cms::404'), 404);
});
}
The if Request::is check is a list of all the resource that october uses under the hood, please dont remove the combine as it is the combiner route. Remove it and the style and script will not render. Also the backend is the url to the backend, make sure to supply the backend and the backend/*.
Finally don't forget to return Response::make(View::make('cms::404'), 404); if the resource is useless.
You may put all these in a controller though.
If anyone has a better workaround, please let us know.

Resources