mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
UNFINISHED Processing multiple PJAX responses on CMS JavaScript, introducing data-pjax-fragment attribute to identify reloadable template parts
This commit is contained in:
parent
473eda43cb
commit
5178954311
@ -352,7 +352,6 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
|||||||
if($this->request->isAjax()) {
|
if($this->request->isAjax()) {
|
||||||
$this->response->addHeader('X-ControllerURL', $url);
|
$this->response->addHeader('X-ControllerURL', $url);
|
||||||
if($header = $this->request->getHeader('X-Pjax')) $this->response->addHeader('X-Pjax', $header);
|
if($header = $this->request->getHeader('X-Pjax')) $this->response->addHeader('X-Pjax', $header);
|
||||||
if($header = $this->request->getHeader('X-Pjax-Selector')) $this->response->addHeader('X-Pjax-Selector', $header);
|
|
||||||
return ''; // Actual response will be re-requested by client
|
return ''; // Actual response will be re-requested by client
|
||||||
} else {
|
} else {
|
||||||
parent::redirect($url, $code);
|
parent::redirect($url, $code);
|
||||||
@ -437,6 +436,9 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
|||||||
'Content' => function() use(&$controller) {
|
'Content' => function() use(&$controller) {
|
||||||
return $controller->renderWith($controller->getTemplatesWithSuffix('_Content'));
|
return $controller->renderWith($controller->getTemplatesWithSuffix('_Content'));
|
||||||
},
|
},
|
||||||
|
'Breadcrumbs' => function() use (&$controller) {
|
||||||
|
return $controller->renderWith('CMSBreadcrumbs');
|
||||||
|
},
|
||||||
'default' => function() use(&$controller) {
|
'default' => function() use(&$controller) {
|
||||||
return $controller->renderWith($controller->getViewer('show'));
|
return $controller->renderWith($controller->getViewer('show'));
|
||||||
}
|
}
|
||||||
@ -951,6 +953,7 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
|||||||
$form->addExtraClass('cms-edit-form');
|
$form->addExtraClass('cms-edit-form');
|
||||||
$form->loadDataFrom($record);
|
$form->loadDataFrom($record);
|
||||||
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
|
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
|
||||||
|
$form->setAttribute('data-pjax-fragment', 'CurrentForm');
|
||||||
|
|
||||||
// Set this if you want to split up tabs into a separate header row
|
// Set this if you want to split up tabs into a separate header row
|
||||||
// if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
|
// if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
|
||||||
@ -1015,6 +1018,7 @@ class LeftAndMain extends Controller implements PermissionProvider {
|
|||||||
$form->addExtraClass('cms-edit-form');
|
$form->addExtraClass('cms-edit-form');
|
||||||
$form->addExtraClass('root-form');
|
$form->addExtraClass('root-form');
|
||||||
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
|
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
|
||||||
|
$form->setAttribute('data-pjax-fragment', 'CurrentForm');
|
||||||
|
|
||||||
return $form;
|
return $form;
|
||||||
}
|
}
|
||||||
|
@ -151,6 +151,7 @@ abstract class ModelAdmin extends LeftAndMain {
|
|||||||
$form->addExtraClass('cms-edit-form cms-panel-padded center');
|
$form->addExtraClass('cms-edit-form cms-panel-padded center');
|
||||||
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
|
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
|
||||||
$form->setFormAction(Controller::join_links($this->Link($this->modelClass), 'EditForm'));
|
$form->setFormAction(Controller::join_links($this->Link($this->modelClass), 'EditForm'));
|
||||||
|
$form->setAttribute('data-pjax-fragment', 'CurrentForm');
|
||||||
|
|
||||||
$this->extend('updateEditForm', $form);
|
$this->extend('updateEditForm', $form);
|
||||||
|
|
||||||
|
@ -160,6 +160,7 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
|
|||||||
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
|
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
|
||||||
if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
|
if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
|
||||||
$form->addExtraClass('center ss-tabset cms-tabset ' . $this->BaseCSSClasses());
|
$form->addExtraClass('center ss-tabset cms-tabset ' . $this->BaseCSSClasses());
|
||||||
|
$form->setAttribute('data-pjax-fragment', 'CurrentForm');
|
||||||
|
|
||||||
$this->extend('updateEditForm', $form);
|
$this->extend('updateEditForm', $form);
|
||||||
|
|
||||||
|
@ -86,8 +86,7 @@
|
|||||||
// sending back different `X-Pjax` headers and content
|
// sending back different `X-Pjax` headers and content
|
||||||
jQuery.ajax(jQuery.extend({
|
jQuery.ajax(jQuery.extend({
|
||||||
headers: {
|
headers: {
|
||||||
"X-Pjax" : "CurrentForm",
|
"X-Pjax" : "CurrentForm,Breadcrumbs"
|
||||||
'X-Pjax-Selector': '.cms-edit-form'
|
|
||||||
},
|
},
|
||||||
url: form.attr('action'),
|
url: form.attr('action'),
|
||||||
data: formData,
|
data: formData,
|
||||||
|
@ -40,10 +40,7 @@ jQuery.noConflict();
|
|||||||
// Normalize trailing slashes in URL to work around routing weirdnesses in SS_HTTPRequest.
|
// Normalize trailing slashes in URL to work around routing weirdnesses in SS_HTTPRequest.
|
||||||
var isSame = (url && History.getPageUrl().replace(/\/+$/, '') == url.replace(/\/+$/, ''));
|
var isSame = (url && History.getPageUrl().replace(/\/+$/, '') == url.replace(/\/+$/, ''));
|
||||||
if(url && !isSame) {
|
if(url && !isSame) {
|
||||||
opts = {
|
opts = {pjax: xhr.getResponseHeader('X-Pjax') ? xhr.getResponseHeader('X-Pjax') : settings.headers['X-Pjax']};
|
||||||
pjax: xhr.getResponseHeader('X-Pjax') ? xhr.getResponseHeader('X-Pjax') : settings.headers['X-Pjax'],
|
|
||||||
selector: xhr.getResponseHeader('X-Pjax-Selector') ? xhr.getResponseHeader('X-Pjax-Selector') : settings.headers['X-Pjax-Selector']
|
|
||||||
};
|
|
||||||
window.History.pushState(opts, '', url);
|
window.History.pushState(opts, '', url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,30 +202,24 @@ jQuery.noConflict();
|
|||||||
* if the URL is loaded without ajax.
|
* if the URL is loaded without ajax.
|
||||||
*/
|
*/
|
||||||
handleStateChange: function() {
|
handleStateChange: function() {
|
||||||
var self = this, h = window.History, state = h.getState();
|
|
||||||
|
|
||||||
// Don't allow parallel loading to avoid edge cases
|
// Don't allow parallel loading to avoid edge cases
|
||||||
if(this.getCurrentXHR()) this.getCurrentXHR().abort();
|
if(this.getCurrentXHR()) this.getCurrentXHR().abort();
|
||||||
|
|
||||||
var selector = state.data.selector || '.cms-content', contentEl = $(selector);
|
var self = this, h = window.History, state = h.getState(),
|
||||||
|
fragments = state.data.pjax || 'Content', headers = {},
|
||||||
|
reduceFn = function(fragment) {return '[data-pjax-fragment="' + fragment + '"]';},
|
||||||
|
contentEls = $($.map(fragments.split(','), reduceFn).join(','));
|
||||||
|
|
||||||
this.trigger('beforestatechange', {
|
this.trigger('beforestatechange', {
|
||||||
state: state, element: contentEl
|
state: state, element: contentEls
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set Pjax headers, which can declare a preference for the returned view.
|
// Set Pjax headers, which can declare a preference for the returned view.
|
||||||
// The actually returned view isn't always decided upon when the request
|
// The actually returned view isn't always decided upon when the request
|
||||||
// is fired, so the server might decide to change it based on its own logic.
|
// is fired, so the server might decide to change it based on its own logic.
|
||||||
var headers = {};
|
headers['X-Pjax'] = fragments;
|
||||||
if(state.data.pjax) {
|
|
||||||
headers['X-Pjax'] = state.data.pjax;
|
|
||||||
} else {
|
|
||||||
// Standard Pjax behaviour is to replace right content area
|
|
||||||
headers["X-Pjax"] = 'Content';
|
|
||||||
}
|
|
||||||
headers['X-Pjax-Selector'] = selector;
|
|
||||||
|
|
||||||
contentEl.addClass('loading');
|
contentEls.addClass('loading');
|
||||||
var xhr = $.ajax({
|
var xhr = $.ajax({
|
||||||
headers: headers,
|
headers: headers,
|
||||||
url: state.url,
|
url: state.url,
|
||||||
@ -241,44 +232,58 @@ jQuery.noConflict();
|
|||||||
var title = xhr.getResponseHeader('X-Title');
|
var title = xhr.getResponseHeader('X-Title');
|
||||||
if(title) document.title = title;
|
if(title) document.title = title;
|
||||||
|
|
||||||
// Update panels
|
// Remove loading indication from old content els (regardless of which are replaced)
|
||||||
var newContentEl = $(data);
|
contentEls.removeClass('loading');
|
||||||
if(newContentEl.find('.cms-container').length) {
|
|
||||||
throw 'Content loaded via ajax is not allowed to contain tags matching the ".cms-container" selector to avoid infinite loops';
|
var newFragments = {};
|
||||||
|
if(xhr.getResponseHeader('Content-Type') == 'text/json') {
|
||||||
|
newFragments = data;
|
||||||
|
} else {
|
||||||
|
// Fall back to replacing the first fragment only if HTML is returned
|
||||||
|
newFragments[fragments.split(',').pop()] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set loading state and store element state
|
$.each(newFragments, function(newFragment, html) {
|
||||||
newContentEl.addClass('loading');
|
var contentEl = $('[data-pjax-fragment=' + newFragment + ']'), newContentEl = $(html);
|
||||||
var origStyle = contentEl.attr('style');
|
|
||||||
var layoutClasses = ['east', 'west', 'center', 'north', 'south'];
|
|
||||||
var elemClasses = contentEl.attr('class');
|
|
||||||
|
|
||||||
var origLayoutClasses = [];
|
// Update panels
|
||||||
if(elemClasses) {
|
if(newContentEl.find('.cms-container').length) {
|
||||||
origLayoutClasses = $.grep(
|
throw 'Content loaded via ajax is not allowed to contain tags matching the ".cms-container" selector to avoid infinite loops';
|
||||||
elemClasses.split(' '),
|
}
|
||||||
function(val) { return ($.inArray(val, layoutClasses) >= 0);}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
newContentEl
|
// Set loading state and store element state
|
||||||
.removeClass(layoutClasses.join(' '))
|
newContentEl.addClass('loading');
|
||||||
.addClass(origLayoutClasses.join(' '));
|
var origStyle = contentEl.attr('style');
|
||||||
if(origStyle) newContentEl.attr('style', origStyle);
|
var layoutClasses = ['east', 'west', 'center', 'north', 'south'];
|
||||||
newContentEl.css('visibility', 'hidden');
|
var elemClasses = contentEl.attr('class');
|
||||||
|
|
||||||
// Allow injection of inline styles, as they're not allowed in the document body.
|
var origLayoutClasses = [];
|
||||||
// Not handling this through jQuery.ondemand to avoid parsing the DOM twice.
|
if(elemClasses) {
|
||||||
var styles = newContentEl.find('style').detach();
|
origLayoutClasses = $.grep(
|
||||||
if(styles.length) $(document).find('head').append(styles);
|
elemClasses.split(' '),
|
||||||
|
function(val) { return ($.inArray(val, layoutClasses) >= 0);}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Replace panel completely (we need to override the "layout" attribute, so can't replace the child instead)
|
newContentEl
|
||||||
contentEl.replaceWith(newContentEl);
|
.removeClass(layoutClasses.join(' '))
|
||||||
|
.addClass(origLayoutClasses.join(' '));
|
||||||
|
if(origStyle) newContentEl.attr('style', origStyle);
|
||||||
|
newContentEl.css('visibility', 'hidden');
|
||||||
|
|
||||||
// Unset loading and restore element state (to avoid breaking existing panel visibility, e.g. with preview expanded)
|
// Allow injection of inline styles, as they're not allowed in the document body.
|
||||||
self.redraw();
|
// Not handling this through jQuery.ondemand to avoid parsing the DOM twice.
|
||||||
newContentEl.css('visibility', 'visible');
|
var styles = newContentEl.find('style').detach();
|
||||||
newContentEl.removeClass('loading');
|
if(styles.length) $(document).find('head').append(styles);
|
||||||
|
|
||||||
|
// Replace panel completely (we need to override the "layout" attribute, so can't replace the child instead)
|
||||||
|
contentEl.replaceWith(newContentEl);
|
||||||
|
|
||||||
|
// Unset loading and restore element state (to avoid breaking existing panel visibility, e.g. with preview expanded)
|
||||||
|
self.redraw();
|
||||||
|
newContentEl.css('visibility', 'visible');
|
||||||
|
newContentEl.removeClass('loading');
|
||||||
|
});
|
||||||
|
|
||||||
self.trigger('afterstatechange', {data: data, status: status, xhr: xhr, element: newContentEl});
|
self.trigger('afterstatechange', {data: data, status: status, xhr: xhr, element: newContentEl});
|
||||||
},
|
},
|
||||||
@ -290,6 +295,7 @@ jQuery.noConflict();
|
|||||||
|
|
||||||
this.setCurrentXHR(xhr);
|
this.setCurrentXHR(xhr);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function: refresh
|
* Function: refresh
|
||||||
*
|
*
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div class="breadcrumbs-wrapper">
|
<div class="breadcrumbs-wrapper" data-pjax-fragment="Breadcrumbs">
|
||||||
<% loop Breadcrumbs %>
|
<% loop Breadcrumbs %>
|
||||||
<% if Last %>
|
<% if Last %>
|
||||||
<span class="cms-panel-link crumb">$Title.XML</span>
|
<span class="cms-panel-link crumb">$Title.XML</span>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div class="cms-content center $BaseCSSClasses" data-layout-type="border">
|
<div class="cms-content center $BaseCSSClasses" data-layout-type="border" data-pjax-fragment="Content">
|
||||||
|
|
||||||
$Tools
|
$Tools
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div class="cms-content center $BaseCSSClasses" data-layout-type="border">
|
<div class="cms-content center $BaseCSSClasses" data-layout-type="border" data-pjax-fragment="Content">
|
||||||
|
|
||||||
<div class="cms-content-header north">
|
<div class="cms-content-header north">
|
||||||
<div>
|
<div>
|
||||||
|
@ -145,7 +145,7 @@ Selectors used in these files should mirrow the "scope" set by its filename,
|
|||||||
so don't place a rule applying to all form buttons inside `ModelAdmin.js`.
|
so don't place a rule applying to all form buttons inside `ModelAdmin.js`.
|
||||||
|
|
||||||
The CMS relies heavily on Ajax-loading of interfaces, so each interface and the JavaScript
|
The CMS relies heavily on Ajax-loading of interfaces, so each interface and the JavaScript
|
||||||
driving it have to assume its underlying DOM structure is appended via Ajax callback
|
driving it have to assume its underlying DOM structure is appended via an Ajax callback
|
||||||
rather than being available when the browser window first loads.
|
rather than being available when the browser window first loads.
|
||||||
jQuery.entwine is effectively an advanced version of [jQuery.live](http://api.jquery.com/live/)
|
jQuery.entwine is effectively an advanced version of [jQuery.live](http://api.jquery.com/live/)
|
||||||
and [jQuery.delegate](http://api.jquery.com/delegate/), so takes care of dynamic event binding.
|
and [jQuery.delegate](http://api.jquery.com/delegate/), so takes care of dynamic event binding.
|
||||||
@ -167,9 +167,6 @@ a CMS developer needs to fire a navigation event rather than invoking the Ajax c
|
|||||||
The main point of contact here is `$('.cms-container').loadPanel(<url>, <title>, <data>)`
|
The main point of contact here is `$('.cms-container').loadPanel(<url>, <title>, <data>)`
|
||||||
in `LeftAndMain.js`. The `data` object can contain additional state which is required
|
in `LeftAndMain.js`. The `data` object can contain additional state which is required
|
||||||
in case the same navigation event is fired again (e.g. when the user pressed the back button).
|
in case the same navigation event is fired again (e.g. when the user pressed the back button).
|
||||||
Most commonly, the (optional) `data.selector` property declares which DOM element to replace
|
|
||||||
with the newly loaded HTML (it defaults to `.cms-content`). This is handy to only replace
|
|
||||||
e.g. an edit form, but leave the search panel in the same "content area" unchanged.
|
|
||||||
|
|
||||||
No callbacks are allowed in this style of Ajax loading, as all state needs
|
No callbacks are allowed in this style of Ajax loading, as all state needs
|
||||||
to be "repeatable". Any logic required to be exected after the Ajax call
|
to be "repeatable". Any logic required to be exected after the Ajax call
|
||||||
@ -177,16 +174,79 @@ should be placed in jQuery.entinwe `onmatch()` rules which apply to the newly cr
|
|||||||
See `$('.cms-container').handleStateChange()` in `LeftAndMain.js` for details.
|
See `$('.cms-container').handleStateChange()` in `LeftAndMain.js` for details.
|
||||||
|
|
||||||
Alternatively, form-related Ajax calls can be invoked through their own wrappers,
|
Alternatively, form-related Ajax calls can be invoked through their own wrappers,
|
||||||
which don't cause history events and hence allow callbacks: `$('.cms-content').submitForm()`.
|
which don't cause history events and hence allow callbacks: `$('.cms-container').submitForm()`.
|
||||||
|
|
||||||
|
## PJAX: Partial template replacement through Ajax
|
||||||
|
|
||||||
|
Many user interactions can change more than one area in the CMS.
|
||||||
|
For example, editing a page title in the CMS form changes it in the page tree
|
||||||
|
as well as the breadcrumbs. In order to avoid unnecessary processing,
|
||||||
|
we often want to update these sections independently from their neighbouring content.
|
||||||
|
|
||||||
|
In order for this to work, the CMS templates declare certain sections as "PJAX fragments"
|
||||||
|
through a `data-pjax-fragment` attribute. These names correlate to specific
|
||||||
|
rendering logic in the PHP controllers, through the `[api:PjaxResponseNegotiator]` class.
|
||||||
|
|
||||||
Within the PHP logic, the `[api:PjaxResponseNegotiator]` class determines which view is rendered.
|
|
||||||
Through a custom `X-Pjax` HTTP header, the client can declare which view he's expecting,
|
Through a custom `X-Pjax` HTTP header, the client can declare which view he's expecting,
|
||||||
through identifiers like `CurrentForm` or `Content` (see `[api:LeftAndMain->getResponseNegotiator()]`).
|
through identifiers like `CurrentForm` or `Content` (see `[api:LeftAndMain->getResponseNegotiator()]`).
|
||||||
These identifiers are passed to `loadPanel()` via the `pjax` data option.
|
These identifiers are passed to `loadPanel()` via the `pjax` data option.
|
||||||
|
The HTTP response is a JSON object literal, with template replacements keyed by their Pjax fragment.
|
||||||
|
Through PHP callbacks, we ensure that only the required template parts are actually executed and rendered.
|
||||||
|
When the same URL is loaded without Ajax (and hence without `X-Pjax` headers),
|
||||||
|
it should behave like a normal full page template, but using the same controller logic.
|
||||||
|
|
||||||
|
Example: Create a bare-bones CMS subclass which shows breadcrumbs (a built-in method),
|
||||||
|
as well as info on the current record. A single link updates both sections independently
|
||||||
|
in a single Ajax request.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
// mysite/code/MyAdmin.php
|
||||||
|
class MyAdmin extends LeftAndMain {
|
||||||
|
static $url_segment = 'myadmin';
|
||||||
|
public function getResponseNegotiator() {
|
||||||
|
$negotiator = parent::getResponseNegotiator();
|
||||||
|
$controller = $this;
|
||||||
|
// Register a new callback
|
||||||
|
$negotiator->setCallback('MyRecordInfo', function() use(&$controller) {
|
||||||
|
return $controller->MyRecordInfo();
|
||||||
|
});
|
||||||
|
return $negotiator;
|
||||||
|
}
|
||||||
|
public function MyRecordInfo() {
|
||||||
|
return $this->renderWith('MyRecordInfo');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:::js
|
||||||
|
// MyAdmin.ss
|
||||||
|
<% include CMSBreadcrumbs %>
|
||||||
|
<div>Static content (not affected by update)</div>
|
||||||
|
<% include MyRecordInfo %>
|
||||||
|
<a href="admin/myadmin" class="cms-panel-link" data-pjax-target="MyRecordInfo,Breadcrumbs">
|
||||||
|
Update record info
|
||||||
|
</a>
|
||||||
|
|
||||||
|
:::ss
|
||||||
|
// MyRecordInfo.ss
|
||||||
|
<div data-pjax-fragment="MyRecordInfo">
|
||||||
|
Current Record: $currentPage.Title
|
||||||
|
</div>
|
||||||
|
|
||||||
|
A click on the link will cause the following (abbreviated) ajax HTTP request:
|
||||||
|
|
||||||
|
GET /admin/myadmin HTTP/1.1
|
||||||
|
X-Pjax:Content
|
||||||
|
X-Requested-With:XMLHttpRequest
|
||||||
|
|
||||||
|
... and result in the following response:
|
||||||
|
|
||||||
|
{"MyRecordInfo": "<div...", "CMSBreadcrumbs": "<div..."}
|
||||||
|
|
||||||
Keep in mind that the returned view isn't always decided upon when the Ajax request
|
Keep in mind that the returned view isn't always decided upon when the Ajax request
|
||||||
is fired, so the server might decide to change it based on its own logic,
|
is fired, so the server might decide to change it based on its own logic,
|
||||||
sending back different `X-Pjax` headers and content.
|
sending back different `X-Pjax` headers and content.
|
||||||
|
|
||||||
|
|
||||||
## Ajax Redirects
|
## Ajax Redirects
|
||||||
|
|
||||||
Sometimes, a server response represents a new URL state, e.g. when submitting an "add record" form,
|
Sometimes, a server response represents a new URL state, e.g. when submitting an "add record" form,
|
||||||
@ -226,8 +286,7 @@ Built-in headers are:
|
|||||||
Some links should do more than load a new page in the browser window.
|
Some links should do more than load a new page in the browser window.
|
||||||
To avoid repetition, we've written some helpers for various use cases:
|
To avoid repetition, we've written some helpers for various use cases:
|
||||||
|
|
||||||
* Load into a panel: `<a href="..." class="cms-panel-link" data-target-panel=".cms-content">`
|
* Load into a PJAX panel: `<a href="..." class="cms-panel-link" data-pjax-target="Content">`
|
||||||
* Load via ajax, and show response status message: `<a href="..." class="cms-link-ajax">`
|
|
||||||
* Load URL as an iframe into a popup/dialog: `<a href="..." class="ss-ui-dialog-link">`
|
* Load URL as an iframe into a popup/dialog: `<a href="..." class="ss-ui-dialog-link">`
|
||||||
|
|
||||||
## Buttons
|
## Buttons
|
||||||
|
@ -326,6 +326,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
|
|||||||
// TODO Allow customization, e.g. to display an edit form alongside a search form from the CMS controller
|
// TODO Allow customization, e.g. to display an edit form alongside a search form from the CMS controller
|
||||||
$form->setTemplate('LeftAndMain_EditForm');
|
$form->setTemplate('LeftAndMain_EditForm');
|
||||||
$form->addExtraClass('cms-content cms-edit-form center ss-tabset');
|
$form->addExtraClass('cms-content cms-edit-form center ss-tabset');
|
||||||
|
$form->setAttribute('data-pjax-fragment', 'CurrentForm Content');
|
||||||
if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
|
if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
|
||||||
// TODO Link back to controller action (and edited root record) rather than index,
|
// TODO Link back to controller action (and edited root record) rather than index,
|
||||||
// which requires more URL knowledge than the current link to this field gives us.
|
// which requires more URL knowledge than the current link to this field gives us.
|
||||||
|
@ -33,7 +33,7 @@ class GridFieldLevelup implements GridField_HTMLProvider{
|
|||||||
//$controller = $gridField->getForm()->Controller();
|
//$controller = $gridField->getForm()->Controller();
|
||||||
$forTemplate = new ArrayData(array(
|
$forTemplate = new ArrayData(array(
|
||||||
'UpLink' => sprintf(
|
'UpLink' => sprintf(
|
||||||
'<a class="cms-panel-link list-parent-link" href="?ParentID=%d&view=list" data-target-panel="#Form_ListViewForm" data-pjax="ListViewForm">%s</a>',
|
'<a class="cms-panel-link list-parent-link" href="?ParentID=%d&view=list" data-pjax-target="ListViewForm,Breadcrumbs">%s</a>',
|
||||||
$parentID,
|
$parentID,
|
||||||
_t('GridField.LEVELUP', 'Level up' )
|
_t('GridField.LEVELUP', 'Level up' )
|
||||||
),
|
),
|
||||||
|
Loading…
Reference in New Issue
Block a user