Read and understand the essentials section of the Vue.js guide before continuing.
The UI Library is built with Vue.js. It provides reusable components to create consistent, accessible user interfaces for all PKP applications. This chapter describes how to pass data into the root Vue.js component and manage state in the browser.
The Page component is the root component on a page that manages data and passes props down to child components. Whenever a backend template is used, a Page
component is automatically mounted to the DOM. This means that global components can be used without any additional configuration.
{extends file="layouts/backend.tpl"}
{block name="page"}
<badge :is-success="true">Published</badge>
{/block}
Smarty syntax can be mixed with components from the UI Library. The template below shows a badge when a submission is published.
{extends file="layouts/backend.tpl"}
{block name="page"}
{if $isPublished}
<badge :is-success="true">Published</badge>
{/if}
{/block}
Sometimes the publication status will change before the Smarty template is reloaded. When actions are taken in the browser that change the publication status, we need to show or hide the <badge>
by using state.
Data that may change after the page is loaded is called state. For example, when an editor publishes or unpublishes a submission the template needs to update to reflect the new submission status.
State is another name for the
data
properties of the root Vue.js component.
Initialize state on the server by using the setState
method to pass data to the Page
component.
use APP\submission\Submission;
use APP\template\TemplateManager;
import('classes.handler.Handler');
class WorkflowHandler extends Handler
{
public function distribution(array $args, Request $request)
{
$templateMgr = TemplateManager::getManager($request);
$templateMgr->setState([
'isPublished' => $publication->getData('status') === Submission::STATUS_PUBLISHED,
]);
return $templateMgr->display('/workflow.tpl');
}
}
State can be accessed in templates by using the Vue.js template syntax. The example below will show or hide the <badge>
when the isPublished
state changes.
{extends file="layouts/backend.tpl"}
{block name="page"}
<badge
v-if="isPublished"
:is-success="true"
>
Published
</badge>
{/block}
Use the Vue.js dev tools for Firefox or Chrome to toggle state and see how the template changes.
State should only be used when data changes the UI must update to reflect that change without reloading the page. It is not always easy to determine which data should be managed by Vue.js as state and which data should be managed by Smarty.
To help make the distinction, consider a city street. At any given moment, the number and location of cars on the street will change. But the boundaries of the street and the direction of travel will not.
In this example, the number and location of cars are state and should be passed to the template using the setState
method. The boundaries and the direction of travel are not and can be passed to the template using the assign
method.
The Page
component sometimes manages state that should be passed down to a complex component. A single Page
may manage many complex components such as Forms and ListPanels which need to update the state after making requests to the API.
The convention described below is a lightweight alternative to state management libraries such as Vuex.
State is passed down to these components as props
.
use APP\template\TemplateManager;
import('classes.handler.Handler');
class WorkflowHandler extends Handler
{
public function distribution(array $args, Request $request)
{
$templateMgr = TemplateManager::getManager($request);
$templateMgr->setState([
'formId' => 'exampleForm',
'fields' => [...],
]);
return $templateMgr->display('/workflow.tpl');
}
}
{extends file="layouts/backend.tpl"}
{block name="page"}
<pkp-form
:id="formId"
:fields="fields"
...
/>
{/block}
This leads to a problem when a field’s value changes. The Form
component can not modify that value because Vue.js enforces a strict one-way data flow.
Read this guide on organizing components to understand how props and events are used in Vue.js to manage state across multiple components.
In such cases, Page
components make use of events to manage state for these components. The component’s props are added to a components
object in the state.
use APP\template\TemplateManager;
import('classes.handler.Handler');
class WorkflowHandler extends Handler
{
public function distribution(array $args, Request $request)
{
$templateMgr = TemplateManager::getManager($request);
$templateMgr->setState([
'components' => [
'exampleForm' => [
'fields' => [...],
],
],
]);
return $templateMgr->display('/workflow.tpl');
}
}
The props are bound to the component in the template and the Page
component listens for a set
event.
{extends file="layouts/backend.tpl"}
{block name="page"}
<pkp-form
v-bind="components.exampleForm"
@set="set"
/>
{/block}
When the child component needs to update its props, it fires the set
event with the following signature.
const newData = {
fields: [...],
};
this.$emit('set', 'exampleForm', newData);
The Page
component will locate the component’s props and update them with the newData
. In order for this to work, the event must pass the object key, exampleForm
, as the second argument in the $emit
function.
The following example shows how the set
method in a Page
component updates a component’s data.
set(key, newData) {
if (!this.components[key]) {
return;
}
this.components[key] = {
...this.components[key],
...newData
};
}
It is often necessary to extend the Page
component to provide additional functionality for a page. The example below shows the SettingsPage
component, which adds or removes a navigation menu item when the announcements have been enabled or disabled.
import Page from './Page.vue';
export default {
name: 'SettingsPage',
extends: Page,
data() {
return {
announcementsLabel: '',
announcementsUrl: ''
};
},
mounted() {
pkp.eventBus.$on('form-success', (formId, context) => {
if (formId === pkp.const.FORM_ANNOUNCEMENT_SETTINGS) {
let menu = {...this.menu};
if (!context.enableAnnouncements) {
delete menu.announcements;
} else {
menu.announcements = {
name: 'Announcements',
url: 'http://example.org/announcements'
};
}
this.menu = menu;
}
});
},
destroyed() {
pkp.eventBus.$off('form-success');
}
};
Once a new page component has been created, it must be imported and registered in js/load.js
.
...
import SettingsPage from '@/components/Container/SettingsPage.vue';
window.pkp = Object.assign(PkpLoad, {
controllers: {
...
SettingsPage
}
});
Finally, the PageHandler
must assign the pageComponent
variable to the template and pass the correct state
use APP\template\TemplateManager;
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign([
'pageComponent' => 'SettingsPage',
]);
$templateMgr->setState([
'announcementLabel' => __('announcement.announcements'),
'announcementsUrl' => $request->getRouter()->url($request, null, 'management', 'settings', 'announcements'),
])
Consult the UI Library for a list of available page components.
Smarty and Vue.js template syntax conflicts in some cases. This can cause errors that need to be worked around. For example, Smarty doesn’t like self-closing tags for Vue.js components.
<pkp-form :id="formId" />
Always use a closing tag in Smarty templates.
<pkp-form :id="formId"></pkp-form>
Smarty templates are not case-sensitive, so camel-case prop and event names won’t work for Vue.js components.
<pkp-button
:isWarning="true"
@wasClicked="cancel"
>
Cancel
</pkp-button>
Use kebab-case for all prop names and :
for event names.
<pkp-button
:is-warning="true"
@clicked:button="cancel"
>
Cancel
</pkp-button>
Smarty templates use single brackets ({
and }
) for PHP variables and helper functions. This conflicts with the single brackets in Vue.js’s scoped slot syntax.
<li slot-scope="{item}">
{{ item.name }}
</li>
Use the Smarty helpers {ldelim}
and {rdelim}
instead of a single bracket.
<li slot-scope="{ldelim}item{rdelim}">
{{ item.name }}
</li>
The double bracket syntax in Vue.js templates ({{ example }}
) works without any changes.
The UI Library provides a demo, technical specification and usage guidance for each component. It also provides important documentation on utilities such as the event bus, localization, and more.
The next chapter describes how to create and use Forms.