mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
Merge pull request #959 from silverstripe-droptables/alternating-button-rc
NEW Extend the ssui.button with alternate appearances.
This commit is contained in:
commit
87d538c7ee
@ -1,18 +1,115 @@
|
||||
(function($) {
|
||||
|
||||
/**
|
||||
* Allows icon definition via HTML5 data attrs for easier handling in PHP
|
||||
* Allows icon definition via HTML5 data attrs for easier handling in PHP.
|
||||
*
|
||||
* Adds an alternative appearance so we can toggle back and forth between them
|
||||
* and register event handlers to add custom styling and behaviour. Example use
|
||||
* is in the CMS with the saving buttons - depending on the page's state one of
|
||||
* them will either say "Save draft" or "Saved", and will have different colour.
|
||||
*/
|
||||
$.widget('ssui.button', $.ui.button, {
|
||||
options: {
|
||||
alternate: {
|
||||
icon: null,
|
||||
text: null
|
||||
},
|
||||
showingAlternate: false
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch between the alternate appearances.
|
||||
*/
|
||||
toggleAlternate: function() {
|
||||
if (this._trigger('ontogglealternate')===false) return;
|
||||
|
||||
// Only switch to alternate if it has been enabled through options.
|
||||
if (!this.options.alternate.icon && !this.options.alternate.text) return;
|
||||
|
||||
this.options.showingAlternate = !this.options.showingAlternate;
|
||||
this.refresh();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adjust the appearance to fit with the current settings.
|
||||
*/
|
||||
_refreshAlternate: function() {
|
||||
this._trigger('beforerefreshalternate');
|
||||
|
||||
// Only switch to alternate if it has been enabled through options.
|
||||
if (!this.options.alternate.icon && !this.options.alternate.text) return;
|
||||
|
||||
if (this.options.showingAlternate) {
|
||||
this.element.find('.ui-button-icon-primary').hide();
|
||||
this.element.find('.ui-button-text').hide();
|
||||
this.element.find('.ui-button-icon-alternate').show();
|
||||
this.element.find('.ui-button-text-alternate').show();
|
||||
}
|
||||
else {
|
||||
this.element.find('.ui-button-icon-primary').show();
|
||||
this.element.find('.ui-button-text').show();
|
||||
this.element.find('.ui-button-icon-alternate').hide();
|
||||
this.element.find('.ui-button-text-alternate').hide();
|
||||
}
|
||||
|
||||
this._trigger('afterrefreshalternate');
|
||||
},
|
||||
|
||||
/**
|
||||
* Construct button - pulls in options from data attributes.
|
||||
* Injects new elements for alternate appearance (if requested via options).
|
||||
*/
|
||||
_resetButton: function() {
|
||||
var iconPrimary = this.element.data('iconPrimary') ? this.element.data('iconPrimary') : this.element.data('icon'),
|
||||
iconSecondary = this.element.data('iconSecondary');
|
||||
var iconPrimary = this.element.data('icon-primary'),
|
||||
iconSecondary = this.element.data('icon-secondary');
|
||||
|
||||
if (!iconPrimary) iconPrimary = this.element.data('icon');
|
||||
|
||||
// TODO Move prefix out of this method, without requriing it for every icon definition in a data attr
|
||||
if(iconPrimary) this.options.icons.primary = 'btn-icon-' + iconPrimary;
|
||||
if(iconSecondary) this.options.icons.secondary = 'btn-icon-' + iconSecondary;
|
||||
|
||||
$.ui.button.prototype._resetButton.call(this);
|
||||
}
|
||||
|
||||
// Pull options from data attributes. Overriden by explicit options given on widget creation.
|
||||
if (!this.options.alternate.text) {
|
||||
this.options.alternate.text = this.element.data('text-alternate');
|
||||
}
|
||||
if (!this.options.alternate.icon) {
|
||||
this.options.alternate.icon = this.element.data('icon-alternate');
|
||||
}
|
||||
if (!this.options.showingAlternate) {
|
||||
this.options.showingAlternate = this.element.hasClass('ss-ui-alternate');
|
||||
}
|
||||
|
||||
// Create missing elements.
|
||||
if (this.options.alternate.text) {
|
||||
this.buttonElement.append(
|
||||
"<span class='ui-button-text-alternate ui-button-text'>" + this.options.alternate.text + "</span>"
|
||||
);
|
||||
}
|
||||
if (this.options.alternate.icon) {
|
||||
this.buttonElement.append(
|
||||
"<span class='ui-button-icon-alternate ui-button-icon-primary ui-icon btn-icon-"
|
||||
+ this.options.alternate.icon + "'></span>"
|
||||
);
|
||||
}
|
||||
|
||||
this._refreshAlternate();
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
$.ui.button.prototype.refresh.call(this);
|
||||
|
||||
this._refreshAlternate();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.element.find('.ui-button-text-alternate').remove();
|
||||
this.element.find('.ui-button-icon-alternate').remove();
|
||||
|
||||
$.ui.button.prototype.destroy.call( this );
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
|
160
docs/en/howto/cms-alternating-button.md
Normal file
160
docs/en/howto/cms-alternating-button.md
Normal file
@ -0,0 +1,160 @@
|
||||
# How to implement an alternating button #
|
||||
|
||||
## Introduction ##
|
||||
|
||||
*Save* and *Save & publish* buttons alternate their appearance to reflect the state of the underlying `SiteTree` object.
|
||||
This is based on a `ssui.button` extension available in `ssui.core.js`.
|
||||
|
||||
The button can be configured via the data attributes in the backend, or through jQuery UI initialisation options. The
|
||||
state can be toggled from the backend (again through data attributes), and can also be easily toggled or set on the
|
||||
frontend.
|
||||
|
||||
This how-to will walk you through creation of a "Clean-up" button with two appearances:
|
||||
|
||||
* active: "Clean-up now" green constructive button if the actions can be performed
|
||||
* netural: "Cleaned" default button if the action does not need to be done
|
||||
|
||||
The controller code that goes with this example is listed in [Extend CMS Interface](../reference/extend-cms-interface).
|
||||
|
||||
## Backend support ##
|
||||
|
||||
First create and configure the action button with alternate state on a page type. The button comes with the default
|
||||
state already, so you just need to add the alternate state using two data additional attributes:
|
||||
|
||||
* `data-icon-alternate`: icon to be shown when the button is in the alternate state
|
||||
* `data-text-alternate`: likewise for text.
|
||||
|
||||
Here is the configuration code for the button:
|
||||
|
||||
:::php
|
||||
public function getCMSActions() {
|
||||
$fields = parent::getCMSActions();
|
||||
|
||||
$fields->fieldByName('MajorActions')->push(
|
||||
$cleanupAction = FormAction::create('cleanup', 'Cleaned')
|
||||
// Set up an icon for the neutral state that will use the default text.
|
||||
->setAttribute('data-icon', 'accept')
|
||||
// Initialise the alternate constructive state.
|
||||
->setAttribute('data-icon-alternate', 'addpage')
|
||||
->setAttribute('data-text-alternate', 'Clean-up now')
|
||||
);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
You can control the state of the button from the backend by applying `ss-ui-alternate` class to the `FormAction`. To
|
||||
simplify our example, let's assume the button state is controlled on the backend only, but you'd usually be better off
|
||||
adjusting the state in the frontend to give the user the benefit of immediate feedback. This technique might still be
|
||||
used for initialisation though.
|
||||
|
||||
Here we initialise the button based on the backend check, and assume that the button will only update after page reload
|
||||
(or on CMS action).
|
||||
|
||||
:::php
|
||||
public function getCMSActions() {
|
||||
// ...
|
||||
if ($this->needsCleaning()) {
|
||||
// Will initialise the button into alternate state.
|
||||
$cleanupAction->addExtraClass('ss-ui-alternate');
|
||||
}
|
||||
// ...
|
||||
}
|
||||
|
||||
## Frontend support ##
|
||||
|
||||
As with the *Save* and *Save & publish* buttons, you might want to add some scripted reactions to user actions on the
|
||||
frontend. You can affect the state of the button through the jQuery UI calls.
|
||||
|
||||
First of all, you can toggle the state of the button - execute this code in the browser's console to see how it works.
|
||||
|
||||
:::js
|
||||
jQuery('.cms-edit-form .Actions #Form_EditForm_action_cleanup').button('toggleAlternate');
|
||||
|
||||
Another, more useful, scenario is to check the current state.
|
||||
|
||||
:::js
|
||||
jQuery('.cms-edit-form .Actions #Form_EditForm_action_cleanup').button('option', 'showingAlternate');
|
||||
|
||||
You can also force the button into a specific state by using UI options.
|
||||
|
||||
:::js
|
||||
jQuery('.cms-edit-form .Actions #Form_EditForm_action_cleanup').button({showingAlternate: true});
|
||||
|
||||
This will allow you to react to user actions in the CMS and give immediate feedback. Here is an example taken from the
|
||||
CMS core that tracks the changes to the input fields and reacts by enabling the *Save* and *Save & publish* buttons
|
||||
(changetracker will automatically add `changed` class to the form if a modification is detected).
|
||||
|
||||
:::js
|
||||
/**
|
||||
* Enable save buttons upon detecting changes to content.
|
||||
* "changed" class is added by jQuery.changetracker.
|
||||
*/
|
||||
$('.cms-edit-form .changed').entwine({
|
||||
// This will execute when the class is added to the element.
|
||||
onmatch: function(e) {
|
||||
var form = this.closest('.cms-edit-form');
|
||||
form.find('#Form_EditForm_action_save').button({showingAlternate: true});
|
||||
form.find('#Form_EditForm_action_publish').button({showingAlternate: true});
|
||||
this._super(e);
|
||||
},
|
||||
// Entwine requires us to define this, even if we don't use it.
|
||||
onunmatch: function(e) {
|
||||
this._super(e);
|
||||
}
|
||||
});
|
||||
|
||||
## Frontend hooks ##
|
||||
|
||||
`ssui.button` defines several additional events so that you can extend the code with your own behaviours. For example
|
||||
this is used in the CMS to style the buttons. Three events are available:
|
||||
|
||||
* `ontogglealternate`: invoked when the `toggleAlternate` is called. Return `false` to prevent the toggling.
|
||||
* `beforerefreshalternate`: invoked before the alternate-specific rendering takes place, including the button
|
||||
initialisation.
|
||||
* `afterrefreshalternate`: invoked after the rendering has been done, including on init. Good place to add styling
|
||||
extras.
|
||||
|
||||
Continuing our example let's add a "constructive" style to our *Clean-up* button. First you need to be able to add
|
||||
custom JS code into the CMS. You can do this by adding a new source file, here
|
||||
`mysite/javascript/CMSMain.CustomActionsExtension.js`, and requiring it from the config.
|
||||
|
||||
:::ss
|
||||
LeftAndMain::require_javascript('mysite/javascript/CMSMain.CustomActionsExtension.js');
|
||||
|
||||
You can now add the styling in response to `afterrefreshalternate` event. Let's use entwine to avoid accidental memory
|
||||
leaks. The only complex part here is how the entwine handle is constructed. `onbuttonafterrefreshalternate` can be
|
||||
disassembled into:
|
||||
|
||||
* `on` signifies the entiwne event handler
|
||||
* `button` is jQuery UI widget name
|
||||
* `afterrefreshalternate`: the event from ssui.button to react to.
|
||||
|
||||
Here is the entire handler put together. You don't need to add any separate initialisation code, this will handle all
|
||||
cases.
|
||||
|
||||
:::js
|
||||
(function($) {
|
||||
|
||||
$.entwine('mysite', function($){
|
||||
$('.cms-edit-form .Actions #Form_EditForm_action_cleanup').entwine({
|
||||
/**
|
||||
* onafterrefreshalternate is SS-specific jQuery UI hook that is executed
|
||||
* every time the button is rendered (including on initialisation).
|
||||
*/
|
||||
onbuttonafterrefreshalternate: function() {
|
||||
if (this.button('option', 'showingAlternate')) {
|
||||
this.addClass('ss-ui-action-constructive');
|
||||
}
|
||||
else {
|
||||
this.removeClass('ss-ui-action-constructive');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}(jQuery));
|
||||
|
||||
## Summary ##
|
||||
|
||||
The code presented gives you a fully functioning alternating button, similar to the defaults that come with the the CMS.
|
||||
These alternating buttons can be used to give user the advantage of visual feedback upon his actions.
|
Loading…
x
Reference in New Issue
Block a user