Custom Publish Forms
When creating or editing content (entries, pages, etc), you are presented with a form view. This is what we call the "Publish" form. As of Statamic 2.8, it's possible for you to use this component in your addons.
Overview
The Publish component takes some data, pre-processes it through a fieldset, renders it within the appropriate fieldtypes, and post-processes it upon submission.
Typically, the data comes from within Statamic - for example a page may be stored in site/content/pages
.
However, in your addon, you may want to edit data coming directly from an external source. That can be anything you want: a database, an API, etc.
The flow is essentially this:
- You retrieve data from somewhere, however you want.
- Pass that data along with a fieldset into the
preProcessFields
method. This will run each value through their fieldtype’s pre-processor. - Pass that data along with the fieldset to the
addBlankFields
method. This will add any blank/default values that might be missing from your data so the reactivity in the Vue component will be initialized correctly. - (The above two steps can be combined using the
preProcessWithBlankFields
method) - Render the Vue component by passing in some props.
- Pass the submitted form fields along with the fieldset into the
processFields
method. This will prepare the data for saving to your source by running it through the fieldtype post-processors. - Save the data somewhere, however you want.
Example
Below you will find an example controller that handles creating, storing, editing, and updating items.
It uses a fictional ContentStore
class with get
and save
methods which would get or save the data to and from some source like an API.
Of course, however you choose to implement this part is up to you.
<?php
namespace Statamic\Addons\ContentStore;
use Statamic\API\Helper;
use Statamic\API\Fieldset;
use Illuminate\Http\Request;
use Statamic\Extend\Controller;
use Statamic\CP\Publish\ProcessesFields;
class ContentStoreController extends Controller
{
use ProcessesFields;
private $store;
public function __construct(ContentStore $store)
{
$this->store = $store;
}
public function create()
{
return $this->view('index', [
'id' => null,
'data' => $this->prepareData([]),
'submitUrl' => route('contentstore.store')
]);
}
public function edit(Request $request)
{
$id = $request->id;
$data = $this->prepareData($this->store->get($id));
return $this->view('index', [
'id' => $id,
'data' => $data,
'submitUrl' => route('contentstore.update')
]);
}
public function update(Request $request)
{
$id = $request->uuid;
$this->save($id, $request);
return $this->successResponse($id);
}
public function store(Request $request)
{
$id = Helper::makeUuid();
$this->save($id, $request);
return $this->successResponse($id);
}
/**
* Prepare the data for the view.
*
* Vue needs to have at least null values available from the start in order
* to properly set up the reactivity. The data in the contentstore is not
* guaranteed to have every single field. This method will add the
* appropriate null values based on the provided fieldset.
*
* @return array
*/
private function prepareData($data)
{
return $this->preProcessWithBlankFields(Fieldset::get('post'), $data);
}
private function save($id, $request)
{
// Vue submits data slightly differently to how it should be saved.
// Each field's fieldtype will process the data appropriately.
$data = $this->processFields(Fieldset::get($request->fieldset), $request->fields);
$this->store->save($id, $data);
}
private function successResponse($id)
{
$message = 'Entry saved';
if (! request()->continue || request()->new) {
$this->success($message);
}
return [
'success' => true,
'redirect' => route('contentstore.edit') . '?id=' . $id,
'message' => $message
];
}
}
routes:
edit:
uses: edit
as: contentstore.edit
post@update:
uses: update
as: contentstore.update
create:
uses: create
as: contentstore.create
post@store:
uses: store
as: contentstore.store
The Blade view should place the JSON encoded data in the Statamic.Publish.contentData
object, and pass some props into the publish
Vue component.
@extends('layout')
@section('content')
<script>
Statamic.Publish = {
contentData: {!! json_encode($data) !!},
};
</script>
<publish title="{{ $id ? $data['title'] : 'New entry' }}"
:is-new="{{ bool_str($id === null) }}"
fieldset-name="post" <!-- use either fieldset-name or fieldset props. not both -->
fieldset="{{ $fieldset->toPublishArray() }}"
content-type="entry"
submit-url="{{ $submitUrl }}"
id="{{ $id }}"
extra='{"collection": "blog"}'
:remove-title="true"
></publish>
@endsection
Prop | Description |
---|---|
title |
The heading of the form. This is typically the entry’s title, or “New Blog Post”. |
is-new |
A boolean that controls a couple of feature such as whether slugs should be auto-generated. |
fieldset-name |
The name of the fieldset to be rendered. If specified, an AJAX request will be made to get the fieldset. Alternatively, use the fieldset prop. |
fieldset |
An array representation of the fieldset. Use $fieldset->toPublishArray() . This will prevent an AJAX request. Alternatively, use the fieldset-name prop. |
content-type |
Generally an entry or page . If you choose entry , you can pass extra.collection to make it editable based whether the user can edit that collection. If you choose page it’ll be editable if they can edit pages. You may omit this prop. |
submit-url |
Where the form will be submitted. You should point it at one of your controller routes. |
id |
The ID of the item. It’ll get posted at submission. |
remove-title |
A boolean that hides the title field if one exists in the fieldset. A special title field is added to the component that’ll be before the sidebar meta on narrow viewports. |
extra |
An object containing some extra data specific to certain cases. |
extra.collection |
When using content-type: entry this will be the corresponding collection. |
extra.order_type |
When using content-type: entry this will be the the way the collection is ordered. eg, date . |
extra.datetime |
When using content-type: entry this is the date (and time) of the entry. eg, 2017-07-31 10:42 |
extra.datetime |
When using content-type: taxonomy for editing taxonomy terms, this is name of the taxonomy. |
extra.parent_url |
When using content-type: page this will be the parent page’s URL. |