Ever wanted to expose static content of an "About" page as editable fields in your app's administration without having to create specific models & migrations? Using this package, you'll be able to do so. By default, it will store the content in JSON files in the application's resources/lang
directory, making them available for version control.
This package adds basic flat-file CMS features to Laravel Nova in a breeze using template configurations as if it were administrable Laravel Models, meaning it allows the usage of all the available Laravel Nova fields and tools.
composer require whitecube/nova-page
If using Laravel >= 5.5, the service providers and aliases will register automatically.
If for some reason you must do it manually, register the package in the providers
section of the app config file in app/config/app.php
and also add the facade:
'providers' => [
// ...
Whitecube\NovaPage\NovaPageServiceProvider::class,
],
'aliases' => [
// ...
'Page' => Whitecube\NovaPage\NovaPageFacade::class,
],
Finally, register the Nova tool in app/Providers/NovaServiceProvider.php
:
/**
* Get the tools that should be listed in the Nova sidebar.
*
* @return array
*/
public function tools()
{
return [
\Whitecube\NovaPage\NovaPageTool::make(),
];
}
You can publish the package's configuration file with the php artisan vendor:publish
command. This will add the app/config/novapage.php
file containing the package's default configuration.
A database source is available if you do not wish to make use of the package's flat-file capabilities.
In the config file, change the default_source
option to \Whitecube\NovaPage\Sources\Database::class
.
You will need to run the following command:
php artisan vendor:publish --tag=nova-page-migrations
Doing so will copy the migration to create the static_pages
table into your project's database/migrations
directory. Then, simply run:
php artisan migrate
You can customize the table name in the migration file and then update the table_name
parameter in the novapage.php
config file, if you wish.
In order to assign fields (and even cards!) to a page's edition form, we'll have to create a Template
class and register this class on one or more routes. You'll see, it's quite easy.
Each Template is defined in an unique class that resembles regular Nova Resources. You can store those classes wherever you want, but the package is configured to use app/Nova/Templates
by default.
You can use the artisan command to generate the file:
php artisan make:template About
Which will result in:
namespace App\Nova\Templates;
use Illuminate\Http\Request;
use Whitecube\NovaPage\Pages\Template;
class About extends Template
{
/**
* Get the fields displayed by the resource.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function fields(Request $request)
{
return [];
}
/**
* Get the cards available for the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function cards(Request $request)
{
return [];
}
}
Fields and cards definition is exactly the same as regular Laravel Nova Resources.
Once the template is defined, simply assign it to the routes you want using the template()
method (which is added to the original Route
api by this package):
Route::get('/about-me', 'AboutController@show')
->template(\App\Nova\Templates\About::class)
->name('about');
Important: Do not forget to name the routes you'll be using with NovaPage templates. Route names are used for retrieving and naming the route's JSON files.
Most websites or applications have repeated content, such as a copyright or contact information. These attributes should not change from page to page, that's why you can define Option "templates". These templates are not bound to a specific route or page and are loaded on request, making it convenient to use them in the app's layouts. You can add as many Option "pages" as you want.
First, create a regular Template
as described above (e.g.: php artisan make:template FooterOptions
) and fill the template's fields()
method with all the wanted Nova fields.
Now, register the template in NovaPage's Manager using the registerOption(string $name, string $template)
. A good place to do this is in a ServiceProvider's boot
method:
use Whitecube\NovaPage\Pages\Manager;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(Manager $pages)
{
$pages->registerOption('footer', \App\Nova\Templates\FooterOptions::class);
}
}
It is possible to load the page's static content automatically using the LoadPageForCurrentRoute
middleware. This way, the application will fetch the current page's data using the current route name as identifier.
Add \Whitecube\NovaPage\Http\Middleware\LoadPageForCurrentRoute::class
to the routeMiddleware
array located in the App\Http\Kernel
file:
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
// ...
'loadNovaPage' => \Whitecube\NovaPage\Http\Middleware\LoadPageForCurrentRoute::class,
];
You can now assign the loadNovaPage
middleware to all routes that need it, or even add it to the web
middleware group in the same App\Http\Kernel
file:
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
// ...
'loadNovaPage',
],
// ...
};
At any time, pages can be loaded using the package's Page Manager. Simply type-hint the Whitecube\NovaPage\Pages\Manager
dependency in a controller and call its load($name, $template, $locale = null, $current = true)
method:
use App\Nova\Templates\Aboutpage;
use Whitecube\NovaPage\Pages\Manager;
class AboutController extends Controller
{
public function show(Manager $page)
{
$page->load('about', About::class);
return view('pages.about');
}
}
In most cases, this will probably not be very convenient since we alreay registered the current route's template in our routes definitions. Therefore, it is also possible to load the page's content with an Illuminate\Routing\Route
instance using the loadFromRoute($route, $locale = null, $current = true)
method:
use Illuminate\Http\Request;
use Whitecube\NovaPage\Pages\Manager;
class AboutController extends Controller
{
public function show(Request $request, Manager $page)
{
$page->loadFromRoute($request->route());
return view('pages.about');
}
}
Anyway, if no locale is provided, NovaPage will use the application's current locale (using App::getLocale()
). By default, loading a page's content will define it as the current page, making its attributes accessible with the Page
facade. If you just want to load content without setting it as the current page, you should call load()
or loadFromRoute()
with $current
set to false
.
Retrieving the page's static values in your application's blade templates is made possible using the Page
facade and its different methods:
@extends('layout')
@section('pageName', Page::name())
@section('content')
<h1>{{ Page::title('Default title', 'My website: ', ' • Awesome appended string') }}</h1>
<p>Edited on <time datetime="{{ Page::date('updated_at')->format('c') }}">{{ Page::date('updated_at')->toFormattedDateString() }}</time></p>
<p>{{ Page::get('introduction') }}</p>
<a href="{!! Page::get('cta.href') !!}">{{ Page::get('cta.label') }}</a>
<p>Copyright: {{ Page::option('footer')->copyright }}</p>
@endsection
Alternatively, we also added two useful Blade directives, @get($attribute)
and @option($key)
, which allow to access static attributes in a shorter way:
@extends('layout')
@section('pageName', Page::name())
@section('content')
<p>@get('introduction')</p>
<a href="@get('cta.href')">@get('cta.label')</a>
<p>Copyright: @option('footer.copyright')</p>
@endsection
Page::name()
Returns the page's name (usually the route's name).
Page::title($default = null, $prepend = null, $append = null)
Returns and optionally formats the page's title. The title is an automatic & required field and is not linked to or overwritten by an alternative title
attribute you could add to the page's fields.
Page::locale()
Returns the locale code of the loaded page. Usually (and should be) the same as App::getLocale()
.
Page::date($timestamp = 'created_at')
Returns a Carbon instance of the requested timestamp. Possible timestamps are:
created_at
updated_at
Note: Since most UNIX systems do not have a creation date for their files, the
created_at
andupdated_at
timestamps are stored in the file's JSON attributes. Keep this in mind if you want to edit the files in a text editor.
Page::get($attribute, $callback = null)
Returns a defined field's value. Optionally, you can provide a callback Closure
that will be applied to the returned value.
Page::find($name)
Returns a previously loaded page template.
Page::option($name)
Loads and/or returns an option template. Can be used to retrieve an option template's attributes (Page::option('footer')->copyright
).
Alternatively, it's also possible to type-hint the current Whitecube\NovaPage\Page\Template
in classes resolved by Laravel's Service Container, such as controllers. The page needs to be loaded before the Page\Template
is requested, which can be easily achieved using the package's LoadPageForCurrentRoute
middleware.
use Whitecube\NovaPage\Pages\Manager;
use Whitecube\NovaPage\Pages\Template;
class HomepageController extends Controller
{
public function show(Template $template, Manager $novapage)
{
// Load other pages or options using
// Manager::load(string $name, string $type = 'route' | 'option', bool $current = true)
$novapage->load('contact', 'route', false);
return view('pages.home', [
// $template contains the loaded template for the current route.
// If needed, manipulate $template's attribute before passing it to the view.
'page' => $template,
// Include other pages or option templates using NovaPage's Manager
'contact' => $novapage->find('contact'),
'footer' => $novapage->option('footer'),
]);
}
}
And use it as a regular object in the pages.home
template:
@extends('layout')
@section('pageName', $page->getName())
@section('content')
<h1>{{ $page->getTitle('Default title', 'My website: ', ' • Awesome appended string') }}</h1>
<p>Edited on <time datetime="{{ $page->getDate('updated_at')->format('c') }}">{{ $page->getDate('updated_at')->toFormattedDateString() }}</time></p>
<p>{{ $page->introduction }}</p>
<a href="{!! $page->cta->href !!}">{{ $page->cta->label }}</a>
<p>Copyright: {{ $footer->copyright }}</p>
@endsection
As you can see, for convenience regular attributes (= defined fields) can be directly retrieved as properties of the Whitecube\NovaPage\Pages\Template
instances.
Since MySQL has a JSON data type, some Nova fields take advantage of the nested behavior of these "JSON attributes". As you already know, NovaPage stores its data in the filesystem using JSON files (unless you change the default_source
option, of course). Most Laravel Packages or Nova fields exploiting the JSON data type expect a raw JSON string as input, that's why NovaPage leaves attributes containing arrays and objects as strings.
If your application needs to access these attributes as parsed associative arrays, you can declare them using the jsonAttributes
array in your Template class:
namespace App\Nova\Templates;
use Whitecube\NovaPage\Pages\Template;
class Home extends Template
{
/**
* The JSON attributes that should be automatically decoded
*
* @var array
*/
protected $jsonAttributes = ['my_json_attribute', 'another_json_attribute'];
}