2018-11-28 17:40:54 +00:00
( function ( Modules ) {
"use strict" ;
Modules . TemplateFolderForm = function () {
this . start = function ( templateFolderForm ) {
this . $form = $ ( templateFolderForm );
2018-12-05 13:48:07 +00:00
// remove the hidden unknown button - if you've got JS enabled then the action you want to do is implied by
// which field is visible.
this . $form . find ( 'button[value=unknown]' ). remove ();
2019-02-13 17:47:24 +00:00
this . $liveRegionCounter = this . $form . find ( '.selection-counter' );
2018-11-28 17:40:54 +00:00
2019-02-15 11:03:22 +00:00
this . $liveRegionCounter . before ( this . nothingSelectedButtons );
this . $liveRegionCounter . before ( this . itemsSelectedButtons );
2018-11-28 17:40:54 +00:00
// all the diff states that we want to show or hide
2018-12-04 14:48:39 +00:00
this . states = [
2020-09-22 11:45:55 +01:00
{
key : 'nothing-selected-buttons' ,
$el : this . $form . find ( '#nothing_selected' ),
cancellable : false
},
{
key : 'items-selected-buttons' ,
$el : this . $form . find ( '#items_selected' ),
cancellable : false
},
{
key : 'move-to-existing-folder' ,
$el : this . $form . find ( '#move_to_folder_radios' ),
cancellable : true ,
setFocus : () => $ ( '#move_to_folder_radios' ). focus (),
action : 'move to folder' ,
description : 'Press move to confirm or cancel to close'
},
{
key : 'move-to-new-folder' ,
$el : this . $form . find ( '#move_to_new_folder_form' ),
cancellable : true ,
setFocus : () => $ ( '#move_to_new_folder_form' ). focus (),
action : 'move to new folder' ,
description : 'Press add to new folder to confirm name or cancel to close'
},
{
key : 'add-new-folder' ,
$el : this . $form . find ( '#add_new_folder_form' ),
cancellable : true ,
setFocus : () => $ ( '#add_new_folder_form' ). focus (),
action : 'new folder' ,
description : 'Press add new folder to confirm name or cancel to close'
},
{
key : 'add-new-template' ,
$el : this . $form . find ( '#add_new_template_form' ),
cancellable : true ,
setFocus : () => $ ( '#add_new_template_form' ). focus (),
action : 'new template' ,
description : 'Press continue to confirm selection or cancel to close'
}
2018-12-04 14:48:39 +00:00
];
2018-11-29 17:39:58 +00:00
2018-12-10 16:53:12 +00:00
// cancel/clear buttons only relevant if JS enabled, so
2018-12-04 14:48:39 +00:00
this . states . filter ( state => state . cancellable ). forEach (( x ) => this . addCancelButton ( x ));
2018-12-10 16:53:12 +00:00
this . states . filter ( state => state . key === 'items-selected-buttons' ). forEach ( x => this . addClearButton ( x ));
2018-11-30 12:02:51 +00:00
2020-09-22 11:45:55 +01:00
// make elements focusabled
this . states . filter ( state => state . setFocus ). forEach ( x => x . $el . attr ( 'tabindex' , '0' ));
this . addDescriptionsToStates ();
2019-01-17 10:52:13 +00:00
// activate stickiness of elements in each state
this . activateStickyElements ();
2018-11-29 16:20:44 +00:00
// first off show the new template / new folder buttons
2019-03-20 14:04:06 +00:00
this . _lastState = this . $form . data ( 'prev-state' );
if ( this . _lastState === undefined ) {
2019-02-13 16:43:38 +00:00
this . selectActionButtons ();
2018-12-19 15:24:48 +00:00
} else {
2019-03-20 14:04:06 +00:00
this . currentState = this . _lastState ;
2018-12-19 15:24:48 +00:00
this . render ();
2018-12-05 12:01:02 +00:00
}
2018-11-29 16:20:44 +00:00
2020-02-06 10:50:57 +00:00
this . $form . on ( 'click' , 'button.govuk-button--secondary' , ( event ) => this . actionButtonClicked ( event ));
2018-11-29 16:20:44 +00:00
this . $form . on ( 'change' , 'input[type=checkbox]' , () => this . templateFolderCheckboxChanged ());
};
2020-09-22 11:45:55 +01:00
this . addDescriptionsToStates = function () {
let id , description ;
2019-01-29 15:53:59 +00:00
2020-09-22 11:45:55 +01:00
$ . each ( this . states . filter ( state => 'description' in state ), ( idx , state ) => {
id = ` ${ state . key } __description` ;
description = `<p class="govuk-visually-hidden" id=" ${ id } "> ${ state . description } </p>` ;
state . $el
. prepend ( description )
. attr ( 'aria-describedby' , id );
});
2019-01-29 15:53:59 +00:00
};
2019-01-17 10:52:13 +00:00
this . activateStickyElements = function () {
2019-01-25 16:47:49 +00:00
var oldClass = 'js-will-stick-at-bottom-when-scrolling' ;
2019-01-17 10:52:13 +00:00
var newClass = 'js-stick-at-bottom-when-scrolling' ;
this . states . forEach ( state => {
state . $el
2019-01-25 16:47:49 +00:00
. find ( '.' + oldClass )
2019-01-17 10:52:13 +00:00
. removeClass ( oldClass )
. addClass ( newClass );
});
};
2018-12-04 14:48:39 +00:00
this . addCancelButton = function ( state ) {
2019-02-01 16:13:04 +00:00
let selector = `[value= ${ state . key } ]` ;
2019-02-15 16:10:01 +00:00
let $cancel = this . makeButton ( 'Cancel' , {
'onclick' : () => {
2018-11-30 12:02:51 +00:00
2019-02-15 16:10:01 +00:00
// clear existing data
state . $el . find ( 'input:radio' ). prop ( 'checked' , false );
state . $el . find ( 'input:text' ). val ( '' );
2018-12-10 16:53:12 +00:00
2019-02-15 16:10:01 +00:00
// go back to action buttons
this . selectActionButtons ( selector );
},
'cancelSelector' : selector ,
2020-09-21 20:27:47 +01:00
'nonvisualText' : state . action
2019-02-15 16:10:01 +00:00
});
2018-11-30 12:02:51 +00:00
2018-12-10 17:07:30 +00:00
state . $el . find ( '[type=submit]' ). after ( $cancel );
2018-11-30 12:02:51 +00:00
};
2018-12-10 16:53:12 +00:00
this . addClearButton = function ( state ) {
2019-02-13 16:43:38 +00:00
let selector = 'button[value=add-new-template]' ;
2019-02-15 16:10:01 +00:00
let $clear = this . makeButton ( 'Clear' , {
'onclick' : () => {
2019-02-13 16:43:38 +00:00
2019-02-15 16:10:01 +00:00
// uncheck all templates and folders
this . $form . find ( 'input:checkbox' ). prop ( 'checked' , false );
2018-12-10 16:53:12 +00:00
2019-02-15 16:10:01 +00:00
// go back to action buttons
this . selectActionButtons ( selector );
},
'nonvisualText' : "selection"
2018-12-10 16:53:12 +00:00
});
state . $el . find ( '.template-list-selected-counter' ). append ( $clear );
};
2019-02-15 16:10:01 +00:00
this . makeButton = ( text , opts ) => {
let $btn = $ ( '<a href=""></a>' )
. html ( text )
2019-12-09 19:09:24 +00:00
. addClass ( 'govuk-link govuk-link--no-visited-state js-cancel' )
2019-02-15 16:10:01 +00:00
// isn't set if cancelSelector is undefined
. data ( 'target' , opts . cancelSelector || undefined )
. attr ( 'tabindex' , '0' )
. on ( 'click keydown' , event => {
// space, enter or no keyCode (must be mouse input)
if ([ 13 , 32 , undefined ]. indexOf ( event . keyCode ) > - 1 ) {
event . preventDefault ();
if ( opts . hasOwnProperty ( 'onclick' )) { opts . onclick (); }
}
});
if ( opts . hasOwnProperty ( 'nonvisualText' )) {
2019-10-21 15:18:25 +01:00
$btn . append ( `<span class="govuk-visually-hidden"> ${ opts . nonvisualText } </span>` );
2018-12-10 17:32:36 +00:00
}
2019-02-15 16:10:01 +00:00
return $btn ;
};
2018-12-10 16:53:12 +00:00
2019-02-01 16:13:04 +00:00
this . selectActionButtons = function ( targetSelector ) {
2018-12-05 12:01:02 +00:00
// If we want to show one of the grey choose actions state, we can pretend we're in the choose actions state,
// and then pretend a checkbox was clicked to work out whether to show zero or non-zero options.
// This calls a render at the end
this . currentState = 'nothing-selected-buttons' ;
this . templateFolderCheckboxChanged ();
2019-02-01 16:13:04 +00:00
if ( targetSelector ) {
2020-09-22 11:45:55 +01:00
$ ( targetSelector ). focus ();
2019-02-01 16:13:04 +00:00
}
2018-12-05 12:01:02 +00:00
};
2019-03-20 14:04:06 +00:00
// method that checks the state against the last one, used prior to render() to see if needed
this . stateChanged = function () {
let changed = this . currentState !== this . _lastState ;
this . _lastState = this . currentState ;
return changed ;
};
2020-08-11 15:44:17 +01:00
this . $singleNotificationChannel = ( document . querySelector ( 'div[id=add_new_template_form]' )). getAttribute ( "data-channel" );
this . $singleChannelService = ( document . querySelector ( 'div[id=add_new_template_form]' )). getAttribute ( "data-service" );
2020-08-10 18:14:36 +01:00
2018-11-29 17:39:58 +00:00
this . actionButtonClicked = function ( event ) {
event . preventDefault ();
this . currentState = $ ( event . currentTarget ). val ();
2020-08-11 15:44:17 +01:00
if ( event . currentTarget . value === 'add-new-template' && this . $singleNotificationChannel ) {
window . location = "/services/" + this . $singleChannelService + "/templates/add-" + this . $singleNotificationChannel ;
2020-08-10 18:14:36 +01:00
} else {
if ( this . stateChanged ()) {
this . render ();
2020-08-11 15:44:17 +01:00
}
}
2018-11-29 17:39:58 +00:00
};
2019-02-13 17:47:24 +00:00
this . selectionStatus = {
'default' : 'Nothing selected' ,
2020-09-18 11:51:56 +01:00
'selected' : numSelected => {
const getString = key => {
if ( numSelected [ key ] === 0 ) {
return '' ;
} else if ( numSelected [ key ] === 1 ) {
return `1 ${ key . substring ( 0 , key . length - 1 ) } ` ;
} else {
return ` ${ numSelected [ key ] } ${ key } ` ;
}
};
const results = [];
if ( numSelected . templates > 0 ) {
results . push ( getString ( 'templates' ));
}
if ( numSelected . folders > 0 ) {
results . push ( getString ( 'folders' ));
}
return results . join ( ', ' ) + ' selected' ;
},
2019-02-13 17:47:24 +00:00
'update' : numSelected => {
2020-09-18 11:51:56 +01:00
let message = ( numSelected . total > 0 ) ? this . selectionStatus . selected ( numSelected ) : this . selectionStatus . default ;
2019-02-13 17:47:24 +00:00
2019-02-15 13:49:49 +00:00
$ ( '.template-list-selected-counter__count' ). html ( message );
this . $liveRegionCounter . html ( message );
2019-02-13 17:47:24 +00:00
}
};
2018-11-29 17:39:58 +00:00
this . templateFolderCheckboxChanged = function () {
2018-11-29 16:20:44 +00:00
let numSelected = this . countSelectedCheckboxes ();
2020-09-18 11:51:56 +01:00
if ( this . currentState === 'nothing-selected-buttons' && numSelected . total !== 0 ) {
2018-11-30 16:29:00 +00:00
// user has just selected first item
2018-12-04 14:47:05 +00:00
this . currentState = 'items-selected-buttons' ;
2020-09-18 11:51:56 +01:00
} else if ( this . currentState === 'items-selected-buttons' && numSelected . total === 0 ) {
2018-11-30 16:29:00 +00:00
// user has just deselected last item
2018-12-04 14:47:05 +00:00
this . currentState = 'nothing-selected-buttons' ;
2018-11-29 16:20:44 +00:00
}
2018-11-28 17:40:54 +00:00
2019-03-20 14:04:06 +00:00
if ( this . stateChanged ()) {
this . render ();
}
2018-12-10 16:53:12 +00:00
2019-02-13 17:47:24 +00:00
this . selectionStatus . update ( numSelected );
2018-12-10 16:53:12 +00:00
2019-01-03 10:50:05 +00:00
$ ( '.template-list-selected-counter' ). toggle ( this . hasCheckboxes ());
};
this . hasCheckboxes = function () {
return !! this . $form . find ( 'input:checkbox' ). length ;
2018-11-28 17:40:54 +00:00
};
this . countSelectedCheckboxes = function () {
2020-09-18 11:51:56 +01:00
const allSelected = this . $form . find ( 'input:checkbox:checked' );
const templates = allSelected . filter (( idx , el ) => $ ( el ). siblings ( '.template-list-template' ). length > 0 ). length ;
const folders = allSelected . filter (( idx , el ) => $ ( el ). siblings ( '.template-list-folder' ). length > 0 ). length ;
const results = {
'templates' : templates ,
'folders' : folders ,
'total' : allSelected . length
};
return results ;
2018-11-28 17:40:54 +00:00
};
this . render = function () {
2019-01-29 15:53:59 +00:00
let mode = 'default' ;
let currentStateObj = this . states . filter ( state => { return ( state . key === this . currentState ); })[ 0 ];
2020-09-22 11:45:55 +01:00
let scrollTop ;
2019-01-30 19:12:14 +00:00
2018-11-29 17:39:58 +00:00
// detach everything, unless they are the currentState
2018-12-04 14:48:39 +00:00
this . states . forEach (
2019-02-15 11:03:22 +00:00
state => ( state . key === this . currentState ? this . $liveRegionCounter . before ( state . $el ) : state . $el . detach ())
2018-11-29 17:39:58 +00:00
);
2018-12-13 14:25:22 +00:00
2019-01-08 17:30:46 +00:00
// use dialog mode for states which contain more than one form control
2019-02-04 15:17:27 +00:00
if ([ 'move-to-existing-folder' , 'add-new-template' ]. indexOf ( this . currentState ) !== - 1 ) {
2019-01-30 19:12:14 +00:00
mode = 'dialog' ;
2019-01-30 11:45:24 +00:00
}
2019-01-30 19:12:14 +00:00
GOVUK . stickAtBottomWhenScrolling . setMode ( mode );
2019-01-30 11:45:24 +00:00
// make sticky JS recalculate its cache of the element's position
GOVUK . stickAtBottomWhenScrolling . recalculate ();
2019-02-08 15:22:13 +00:00
2020-09-22 11:45:55 +01:00
if ( currentStateObj && ( 'setFocus' in currentStateObj )) {
scrollTop = $ ( window ). scrollTop ();
currentStateObj . setFocus ();
$ ( window ). scrollTop ( scrollTop );
}
2018-11-28 17:40:54 +00:00
};
2019-01-08 15:57:55 +00:00
this . nothingSelectedButtons = $ ( `
2019-01-25 16:47:49 +00:00
<div id="nothing_selected">
<div class="js-stick-at-bottom-when-scrolling">
2020-09-20 21:20:51 +01:00
<button class="govuk-button govuk-button--secondary govuk-!-margin-right-3 govuk-!-margin-bottom-1" value="add-new-template" aria-expanded="false">
New template
</button>
<button class="govuk-button govuk-button--secondary govuk-!-margin-bottom-1" value="add-new-folder" aria-expanded="false">New folder</button>
2019-02-15 13:49:49 +00:00
<div class="template-list-selected-counter">
<span class="template-list-selected-counter__count" aria-hidden="true">
${ this . selectionStatus . default }
</span>
2019-01-25 16:47:49 +00:00
</div>
2018-12-10 16:53:12 +00:00
</div>
2018-11-30 16:29:00 +00:00
</div>
2019-01-08 15:57:55 +00:00
` ). get ( 0 );
2018-11-28 17:40:54 +00:00
2019-01-08 15:57:55 +00:00
this . itemsSelectedButtons = $ ( `
2019-01-25 16:47:49 +00:00
<div id="items_selected">
<div class="js-stick-at-bottom-when-scrolling">
2020-09-20 21:20:51 +01:00
<button class="govuk-button govuk-button--secondary govuk-!-margin-right-3 govuk-!-margin-bottom-1" value="move-to-existing-folder" aria-expanded="false">
2020-08-05 11:15:38 +01:00
Move<span class="govuk-visually-hidden"> selection to folder</span>
</button>
2020-09-20 21:20:51 +01:00
<button class="govuk-button govuk-button--secondary govuk-!-margin-bottom-1" value="move-to-new-folder" aria-expanded="false">Add to new folder</button>
2019-02-15 11:03:22 +00:00
<div class="template-list-selected-counter" aria-hidden="true">
2019-02-15 13:49:49 +00:00
<span class="template-list-selected-counter__count" aria-hidden="true">
${ this . selectionStatus . selected ( 1 ) }
</span>
2019-01-25 16:47:49 +00:00
</div>
2018-12-10 16:53:12 +00:00
</div>
2018-11-30 16:29:00 +00:00
</div>
2019-01-08 15:57:55 +00:00
` ). get ( 0 );
2018-11-28 17:40:54 +00:00
};
})( window . GOVUK . Modules );