const LanguageSearchWidget = require( './widgets/LanguageSearchWidget.js' ), Metrics = require( './Metrics.js' ), Model = require( 'ext.templateDataGenerator.data' ).Model, ParamImportWidget = require( './widgets/ParamImportWidget.js' ), ParamSelectWidget = require( './widgets/ParamSelectWidget.js' ), ParamWidget = require( './widgets/ParamWidget.js' ); /** * TemplateData Dialog * * @class * @extends OO.ui.ProcessDialog * * @constructor * @param {Object} config Dialog configuration object * * @external LanguageResultWidget */ function Dialog( config ) { // Parent constructor Dialog.super.call( this, config ); this.model = null; this.modified = false; this.language = null; this.availableLanguages = []; this.selectedParamKey = ''; this.propInputs = {}; this.propFieldLayout = {}; this.isSetup = false; this.mapsCache = undefined; this.descriptionChanged = false; this.paramsReordered = false; this.paramPropertyChangeTracking = {}; // Initialize this.$element.addClass( 'tdg-templateDataDialog' ); } /* Inheritance */ OO.inheritClass( Dialog, OO.ui.ProcessDialog ); /* Static properties */ Dialog.static.name = 'TemplateDataDialog'; Dialog.static.title = mw.msg( 'templatedata-modal-title' ); Dialog.static.size = 'large'; Dialog.static.actions = [ { action: 'apply', label: mw.msg( 'templatedata-modal-button-apply' ), flags: [ 'primary', 'progressive' ], modes: 'list' }, { action: 'done', label: mw.msg( 'templatedata-modal-button-done' ), flags: [ 'primary', 'progressive' ], modes: [ 'edit', 'maps' ] }, { action: 'add', label: mw.msg( 'templatedata-modal-button-addparam' ), icon: 'add', flags: [ 'progressive' ], modes: 'list' }, { action: 'delete', label: mw.msg( 'templatedata-modal-button-delparam' ), modes: 'edit', flags: 'destructive' }, { action: 'cancel', label: mw.msg( 'templatedata-modal-button-cancel' ), modes: 'maps', flags: 'destructive' }, { label: mw.msg( 'templatedata-modal-button-cancel' ), flags: [ 'safe', 'close' ], modes: [ 'list', 'error' ] }, { action: 'back', label: mw.msg( 'templatedata-modal-button-back' ), flags: [ 'safe', 'back' ], modes: [ 'language', 'add' ] } ]; /** * Initialize window contents. * * The first time the window is opened, #initialize is called so that changes to the window that * will persist between openings can be made. See #getSetupProcess for a way to make changes each * time the window opens. * * @throws {Error} If not attached to a manager * @chainable */ Dialog.prototype.initialize = function () { // Parent method Dialog.super.prototype.initialize.call( this ); this.$spinner = $( '
' ).text( mw.msg( 'templatedata-modal-errormsg', '|', '=', '}}' ) ) ); } else if ( changed && this.model.getAllParamNames().indexOf( value ) !== -1 ) { // We're changing the name. Make sure it doesn't conflict. $errors = $errors.add( $( '
' ).text( mw.msg( 'templatedata-modal-errormsg-duplicate-name' ) ) ); } } propInput.$element.toggleClass( 'tdg-editscreen-input-error', !!$errors.length ); // Check if there is a dependent input to activate const dependentField = prop.textValue; if ( dependentField && this.propFieldLayout[ dependentField ] ) { // The textValue property depends on this property // toggle its view this.propFieldLayout[ dependentField ].toggle( !!value ); this.propInputs[ dependentField ].setValue( this.model.getParamProperty( this.selectedParamKey, dependentField ) ); } // Validate // FIXME: Don't read model information from the DOM // eslint-disable-next-line no-jquery/no-global-selector const anyInputError = !!$( '.tdg-templateDataDialog-paramInput.tdg-editscreen-input-error' ).length; // Disable the 'done' button if there are any errors in the inputs this.actions.setAbilities( { done: !anyInputError } ); if ( $errors.length ) { this.toggleNoticeMessage( 'edit', true, 'error', $errors ); } else { this.toggleNoticeMessage( 'edit', false ); this.model.setParamProperty( this.selectedParamKey, propName, value, this.language ); } // If we're changing the aliases and the name has an error, poke its change // handler in case that error was because of a duplicate name with its own // aliases. // FIXME: Don't read model information from the DOM // eslint-disable-next-line no-jquery/no-class-state if ( propName === 'aliases' && this.propInputs.name.$element.hasClass( 'tdg-editscreen-input-error' ) ) { this.onParamPropertyInputChange( 'name', this.propInputs.name.getValue() ); } this.trackPropertyChange( propName ); }; Dialog.prototype.toggleSuggestedValues = function ( type ) { const suggestedValuesAllowedTypes = [ 'content', 'line', 'number', 'string', 'unbalanced-wikitext', 'unknown' ]; // Don't show the suggested values field when the feature flag is // disabled, or for inapplicable types. this.propFieldLayout.suggestedvalues.toggle( suggestedValuesAllowedTypes.indexOf( type ) !== -1 ); }; /** * Set the parameter details in the detail panel. * * @param {string} paramKey */ Dialog.prototype.getParameterDetails = function ( paramKey ) { const paramData = this.model.getParamData( paramKey ); const allProps = Model.static.getAllProperties( true ); this.stopParameterInputTracking(); for ( const prop in this.propInputs ) { this.changeParamPropertyInput( paramKey, prop, paramData[ prop ], this.language ); // Show/hide dependents if ( allProps[ prop ].textValue ) { this.propFieldLayout[ allProps[ prop ].textValue ].toggle( !!paramData[ prop ] ); } } // Update suggested values field visibility this.toggleSuggestedValues( paramData.type || allProps.type.default ); let status; // This accepts one of the three booleans only if the other two are false if ( paramData.deprecated ) { status = !paramData.required && !paramData.suggested && 'deprecated'; } else if ( paramData.required ) { status = !paramData.deprecated && !paramData.suggested && 'required'; } else if ( paramData.suggested ) { status = !paramData.deprecated && !paramData.required && 'suggested'; } else { status = 'optional'; } // Status is false at this point when more than one was set to true this.propFieldLayout.status.toggle( status ); this.propFieldLayout.deprecated.toggle( !status ); this.propFieldLayout.required.toggle( !status ); this.propFieldLayout.suggested.toggle( !status ); if ( !status ) { // No unambiguous status found, can't use the dropdown this.propInputs.status.getMenu().disconnect( this ); } else { this.changeParamPropertyInput( paramKey, 'status', status ); this.propInputs.status.getMenu().connect( this, { choose: function ( item ) { const selected = item.getData(); // Forward selection from the dropdown to the hidden checkboxes, these get saved this.propInputs.deprecated.setSelected( selected === 'deprecated' ); this.propInputs.required.setSelected( selected === 'required' ); this.propInputs.suggested.setSelected( selected === 'suggested' ); } } ); } this.startParameterInputTracking( paramData ); }; Dialog.prototype.stopParameterInputTracking = function () { this.paramPropertyChangeTracking = {}; }; /** * Temporary metrics to understand how properties are edited, see T260343. * * @param {Object} paramValues parameter property values at dialog open time */ Dialog.prototype.startParameterInputTracking = function ( paramValues ) { this.paramPropertyChangeTracking = {}; for ( const prop in this.propInputs ) { // Set to true, unless one of the exceptions applies. this.paramPropertyChangeTracking[ prop ] = !( // Setting type when we already have a specific type. ( prop === 'type' && paramValues[ prop ] !== undefined && paramValues[ prop ] !== 'unknown' ) || // Setting priority but already required, suggested, or deprecated. ( ( prop === 'required' || prop === 'suggested' || prop === 'deprecated' ) && ( paramValues.required || paramValues.suggested || paramValues.deprecated ) ) || // Fields ignored by tracking. ( prop === 'name' || prop === 'aliases' || prop === 'autovalue' || prop === 'deprecatedValue' ) ); } }; Dialog.prototype.trackPropertyChange = function ( property ) { const eventKey = ( property === 'required' || property === 'suggested' || property === 'deprecated' ) ? 'parameter-priority-change' : 'parameter-' + property + '-change'; if ( this.paramPropertyChangeTracking[ property ] ) { Metrics.logEvent( eventKey ); } this.paramPropertyChangeTracking[ property ] = false; // These properties form a conceptual group; suppress additional events. if ( property === 'required' || property === 'suggested' || property === 'deprecated' ) { this.paramPropertyChangeTracking.required = this.paramPropertyChangeTracking.suggested = this.paramPropertyChangeTracking.deprecated = false; } }; /** * Reset contents on reload */ Dialog.prototype.reset = function () { this.language = null; this.availableLanguages = []; if ( this.paramSelect ) { this.paramSelect.clearItems(); this.selectedParamKey = ''; } if ( this.languageDropdownWidget ) { this.languageDropdownWidget.getMenu().clearItems(); } }; /** * Empty and repopulate the parameter select widget. */ Dialog.prototype.repopulateParamSelectWidget = function () { if ( !this.isSetup ) { return; } const paramList = this.model.getParams(), paramOrder = this.model.getTemplateParamOrder(); this.paramSelect.clearItems(); // Update all param descriptions in the param select widget for ( const i in paramOrder ) { const paramKey = paramList[ paramOrder[ i ] ]; if ( paramKey && !paramKey.deleted ) { this.addParamToSelectWidget( paramOrder[ i ] ); } } // Check if there are potential parameters to add // from the template source code const missingParams = this.model.getMissingParams(); this.paramImport .toggle( !!missingParams.length ) .buildParamLabel( missingParams ); }; /** * Change parameter property * * @param {string} paramKey Parameter key * @param {string} propName Property name * @param {Mixed} [value] Property value * @param {string} [lang] Language */ Dialog.prototype.changeParamPropertyInput = function ( paramKey, propName, value, lang ) { const prop = Model.static.getAllProperties( true )[ propName ]; let propInput = this.propInputs[ propName ]; switch ( prop.type ) { case 'select': propInput = propInput.getMenu(); propInput.selectItem( propInput.findItemFromData( value || prop.default ) ); break; case 'boolean': propInput.setSelected( !!value ); break; case 'array': value = value || []; propInput.setValue( value.map( // TagMultiselectWidget accepts nothing but strings or objects with a .data property ( v ) => v && v.data ? v : String( v ) ) ); break; default: if ( typeof value === 'object' ) { value = value[ lang || this.language ]; } propInput.setValue( value || '' ); } }; /** * Add parameter to the list * * @param {string} paramKey Parameter key in the model */ Dialog.prototype.addParamToSelectWidget = function ( paramKey ) { const data = this.model.getParamData( paramKey ); this.paramSelect.addItems( [ new ParamWidget( { key: paramKey, label: this.model.getParamValue( paramKey, 'label', this.language ), aliases: data.aliases, description: this.model.getParamValue( paramKey, 'description', this.language ) } ) // Forward keyboard-triggered events from the OptionWidget to the SelectWidget .connect( this.paramSelect, { choose: [ 'emit', 'choose' ] } ) ] ); }; /** * Create the information page about individual parameters * * @return {jQuery} Editable details page for the parameter */ Dialog.prototype.createParamDetails = function () { const paramProperties = Model.static.getAllProperties( true ); // Fieldset const paramFieldset = new OO.ui.FieldsetLayout(); for ( const propName in paramProperties ) { const prop = paramProperties[ propName ]; const config = {}; let propInput; // Create the property inputs switch ( prop.type ) { case 'select': { propInput = new OO.ui.DropdownWidget( config ); const items = []; for ( const i in prop.children ) { items.push( new OO.ui.MenuOptionWidget( { data: prop.children[ i ], // The following messages are used here: // * templatedata-doc-param-status-optional // * templatedata-doc-param-status-deprecated // * templatedata-doc-param-status-required // * templatedata-doc-param-status-suggested // * templatedata-doc-param-type-boolean, templatedata-doc-param-type-content, // * templatedata-doc-param-type-date, templatedata-doc-param-type-line, // * templatedata-doc-param-type-number, templatedata-doc-param-type-string, // * templatedata-doc-param-type-unbalanced-wikitext, templatedata-doc-param-type-unknown, // * templatedata-doc-param-type-url, templatedata-doc-param-type-wiki-file-name, // * templatedata-doc-param-type-wiki-page-name, templatedata-doc-param-type-wiki-template-name, // * templatedata-doc-param-type-wiki-user-name label: mw.msg( 'templatedata-doc-param-' + propName + '-' + prop.children[ i ] ) } ) ); } propInput.getMenu().addItems( items ); break; } case 'boolean': propInput = new OO.ui.CheckboxInputWidget( config ); break; case 'array': config.allowArbitrary = true; config.placeholder = mw.msg( 'templatedata-modal-placeholder-multiselect' ); propInput = new OO.ui.TagMultiselectWidget( config ); break; default: config.autosize = true; if ( !prop.multiline ) { config.rows = 1; config.allowLinebreaks = false; } propInput = new OO.ui.MultilineTextInputWidget( config ); break; } this.propInputs[ propName ] = propInput; // The following classes are used here: // * tdg-templateDataDialog-paramInput tdg-templateDataDialog-paramList-aliases // * tdg-templateDataDialog-paramInput tdg-templateDataDialog-paramList-autovalue // * tdg-templateDataDialog-paramInput tdg-templateDataDialog-paramList-default // * tdg-templateDataDialog-paramInput tdg-templateDataDialog-paramList-deprecated // * tdg-templateDataDialog-paramInput tdg-templateDataDialog-paramList-deprecatedValue // * tdg-templateDataDialog-paramInput tdg-templateDataDialog-paramList-description // * tdg-templateDataDialog-paramInput tdg-templateDataDialog-paramList-example // * tdg-templateDataDialog-paramInput tdg-templateDataDialog-paramList-label // * tdg-templateDataDialog-paramInput tdg-templateDataDialog-paramList-name // * tdg-templateDataDialog-paramInput tdg-templateDataDialog-paramList-required // * tdg-templateDataDialog-paramInput tdg-templateDataDialog-paramList-suggested // * tdg-templateDataDialog-paramInput tdg-templateDataDialog-paramList-suggestedvalues // * tdg-templateDataDialog-paramInput tdg-templateDataDialog-paramList-type propInput.$element .addClass( 'tdg-templateDataDialog-paramInput tdg-templateDataDialog-paramList-' + propName ); this.propFieldLayout[ propName ] = new OO.ui.FieldLayout( propInput, { align: 'left', // The following messages are used here: // * templatedata-modal-table-param-aliases // * templatedata-modal-table-param-autovalue // * templatedata-modal-table-param-default // * templatedata-modal-table-param-deprecated // * templatedata-modal-table-param-deprecatedValue // * templatedata-modal-table-param-description // * templatedata-modal-table-param-example // * templatedata-modal-table-param-label // * templatedata-modal-table-param-name // * templatedata-modal-table-param-required // * templatedata-modal-table-param-suggested // * templatedata-modal-table-param-suggestedvalues // * templatedata-modal-table-param-type label: mw.msg( 'templatedata-modal-table-param-' + propName ) } ); // Event if ( propInput instanceof OO.ui.DropdownWidget ) { propInput.getMenu().connect( this, { choose: [ 'onParamPropertyInputChange', propName ] } ); } else { propInput.connect( this, { change: [ 'onParamPropertyInputChange', propName ] } ); } // Append to parameter section paramFieldset.$element.append( this.propFieldLayout[ propName ].$element ); } return paramFieldset.$element; }; /** * Update the labels for parameter property inputs that include language, so * they show the currently used language. */ Dialog.prototype.updateParamDetailsLanguage = function () { const languageProps = Model.static.getPropertiesWithLanguage(); for ( let i = 0; i < languageProps.length; i++ ) { const prop = languageProps[ i ]; // The following messages are used here: // * templatedata-modal-table-param-aliases // * templatedata-modal-table-param-autovalue // * templatedata-modal-table-param-default // * templatedata-modal-table-param-deprecated // * templatedata-modal-table-param-deprecatedValue // * templatedata-modal-table-param-description // * templatedata-modal-table-param-example // * templatedata-modal-table-param-label // * templatedata-modal-table-param-name // * templatedata-modal-table-param-required // * templatedata-modal-table-param-suggested // * templatedata-modal-table-param-suggestedvalues // * templatedata-modal-table-param-type const label = mw.msg( 'templatedata-modal-table-param-' + prop, this.language ); this.propFieldLayout[ prop ].setLabel( label ); this.propInputs[ prop ] .$input.attr( { lang: mw.language.bcp47( this.language ), dir: 'auto' } ); } }; /** * Override getBodyHeight to create a tall dialog relative to the screen. * * @return {number} Body height */ Dialog.prototype.getBodyHeight = function () { return window.innerHeight - 200; }; /** * Show or hide the notice message in the dialog with a set message. * * Hides all other notices messages when called, not just the one specified. * * @param {string} [type='list'] Which notice label to show: 'list', 'edit' or 'global' * @param {boolean} [isShowing=false] Show or hide the message * @param {string} [noticeMessageType='notice'] Message type: 'notice', 'error', 'warning', 'success' * @param {jQuery|string|OO.ui.HtmlSnippet|Function|null} [noticeMessageLabel] The message to display */ Dialog.prototype.toggleNoticeMessage = function ( type, isShowing, noticeMessageType, noticeMessageLabel ) { // Hide all this.noticeMessage.toggle( false ); this.paramEditNoticeMessage.toggle( false ); this.paramListNoticeMessage.toggle( false ); if ( noticeMessageLabel ) { // See which error to display let noticeReference; if ( type === 'global' ) { noticeReference = this.noticeMessage; } else if ( type === 'edit' ) { noticeReference = this.paramEditNoticeMessage; } else { noticeReference = this.paramListNoticeMessage; } // FIXME: Don't read model information from the DOM // eslint-disable-next-line no-jquery/no-sizzle isShowing = isShowing || !noticeReference.$element.is( ':visible' ); noticeReference.setLabel( noticeMessageLabel ); noticeReference.setType( noticeMessageType ); noticeReference.toggle( isShowing ); } }; /** * Import parameters from the source code. */ Dialog.prototype.importParametersFromTemplateCode = function () { const response = this.model.importSourceCodeParameters(); // Repopulate the list this.repopulateParamSelectWidget(); let $message = $( [] ), state = 'success'; if ( response.imported.length === 0 ) { $message = $( '
' ).text( mw.msg( 'templatedata-modal-errormsg-import-noparams' ) ); state = 'error'; } else { $message = $message.add( $( '
' ).text( mw.msg( 'templatedata-modal-notice-import-numparams', response.imported.length, response.imported.join( mw.msg( 'comma-separator' ) ) ) ) ); } this.toggleNoticeMessage( 'list', true, state, $message ); }; /** * Get a process for setting up a window for use. * * @param {Object} data Dialog opening data * @param {Model} data.model * @param {OO.ui.Element} data.editNoticeMessage * @return {OO.ui.Process} Setup process */ Dialog.prototype.getSetupProcess = function ( data ) { return Dialog.super.prototype.getSetupProcess.call( this, data ) .next( () => { this.isSetup = false; this.reset(); // The dialog must be supplied with a reference to a model this.model = data.model; this.modified = false; // Hide the panels and display a spinner this.$spinner.show(); this.panels.$element.hide(); this.toggleNoticeMessage( 'global', false ); this.toggleNoticeMessage( 'list', false ); // Start with parameter list this.switchPanels(); // Events this.model.connect( this, { 'change-description': 'onModelChangeDescription', 'change-map': 'onModelChangeMapInfo', 'change-paramOrder': 'onModelChangeParamOrder', 'change-property': 'onModelChangeProperty', change: 'onModelChange' } ); // Setup the dialog this.setupDetailsFromModel(); this.newLanguageSearch.addResults(); // Bring in the editNoticeMessage from the main page this.listParamsPanel.$element.prepend( data.editNoticeMessage.$element ); this.availableLanguages = this.model.getExistingLanguageCodes().slice(); const defaultLanguage = this.model.getDefaultLanguage(); if ( this.availableLanguages.indexOf( defaultLanguage ) === -1 ) { this.availableLanguages.unshift( defaultLanguage ); } const items = this.availableLanguages.map( ( lang ) => new OO.ui.MenuOptionWidget( { data: lang, label: $.uls.data.getAutonym( lang ) } ) ); this.languageDropdownWidget.getMenu() .addItems( items ) // Trigger the initial language choice .selectItemByData( defaultLanguage ); this.isSetup = true; this.repopulateParamSelectWidget(); // Show the panel this.$spinner.hide(); this.panels.$element.show(); this.actions.setAbilities( { apply: false } ); } ); }; /** * Set up the list of parameters from the model. This should happen * after initialization of the model. */ Dialog.prototype.setupDetailsFromModel = function () { // Set up description this.descriptionInput.setValue( this.model.getTemplateDescription( this.language ) ); // set up maps this.populateMapsItems( this.model.getMapInfo() ); this.mapsCache = OO.copy( this.model.getMapInfo() ); this.onMapsGroupSelect(); if ( this.model.getMapInfo() !== undefined ) { const firstMapItem = Object.keys( this.model.getMapInfo() )[ 0 ]; this.templateMapsInput.setValue( this.stringifyObject( this.model.getMapInfo()[ firstMapItem ] ) ); } else { this.templateMapsInput.setValue( '' ); this.templateMapsInput.setDisabled( true ); } // Set up format const format = this.model.getTemplateFormat(); if ( format === 'inline' || format === 'block' || format === null ) { this.templateFormatSelectWidget.selectItemByData( format ); this.templateFormatInputWidget.setDisabled( true ); } else { this.templateFormatSelectWidget.selectItemByData( 'custom' ); this.templateFormatInputWidget.setValue( this.formatToDisplay( format ) ); this.templateFormatInputWidget.setDisabled( false ); } // Repopulate the parameter list this.repopulateParamSelectWidget(); Metrics.logEvent( this.model.getOriginalTemplateDataObject() ? 'dialog-open-edit' : 'dialog-open-create' ); }; /** * Switch between stack layout panels * * @param {OO.ui.PanelLayout} [panel] Panel to switch to, defaults to the first panel */ Dialog.prototype.switchPanels = function ( panel ) { panel = panel || this.listParamsPanel; this.panels.setItem( panel ); this.listParamsPanel.$element.toggle( panel === this.listParamsPanel ); this.editParamPanel.$element.toggle( panel === this.editParamPanel ); this.languagePanel.$element.toggle( panel === this.languagePanel ); this.addParamPanel.$element.toggle( panel === this.addParamPanel ); this.editMapsPanel.$element.toggle( panel === this.editMapsPanel ); switch ( panel ) { case this.listParamsPanel: this.actions.setMode( 'list' ); // Reset message this.toggleNoticeMessage( 'list', false ); // Deselect parameter this.paramSelect.selectItem( null ); // Repopulate the list to account for any changes if ( this.model ) { this.repopulateParamSelectWidget(); } break; case this.editParamPanel: this.actions.setMode( 'edit' ); // Deselect parameter this.paramSelect.selectItem( null ); this.editParamPanel.focus(); break; case this.addParamPanel: this.actions.setMode( 'add' ); this.newParamInput.focus(); break; case this.editMapsPanel: this.actions.setMode( 'maps' ); this.templateMapsInput.adjustSize( true ).focus(); break; case this.languagePanel: this.actions.setMode( 'language' ); this.newLanguageSearch.query.focus(); break; } }; /** * Get a process for taking action. * * @param {string} [action] Symbolic name of action * @return {OO.ui.Process} Action process */ Dialog.prototype.getActionProcess = function ( action ) { if ( action === 'add' ) { return new OO.ui.Process( () => { this.switchPanels( this.addParamPanel ); } ); } if ( action === 'done' ) { return new OO.ui.Process( () => { // setMapInfo with the value and keep the done button active this.model.setMapInfo( this.mapsCache ); this.model.originalMaps = OO.copy( this.mapsCache ); this.switchPanels(); } ); } if ( action === 'back' ) { return new OO.ui.Process( () => { this.switchPanels(); } ); } if ( action === 'maps' ) { return new OO.ui.Process( () => { this.switchPanels( this.editMapsPanel ); } ); } if ( action === 'cancel' ) { return new OO.ui.Process( () => { this.mapsCache = OO.copy( this.model.getOriginalMapsInfo() ); this.model.restoreOriginalMaps(); this.populateMapsItems( this.mapsCache ); this.onCancelAddingMap(); this.switchPanels(); } ); } if ( action === 'delete' ) { return new OO.ui.Process( () => { this.model.deleteParam( this.selectedParamKey ); this.switchPanels(); } ); } if ( action === 'apply' ) { return new OO.ui.Process( () => { Metrics.logEvent( this.model.getOriginalTemplateDataObject() ? 'save-page-edit' : 'save-page-create' ); this.emit( 'apply', this.model.outputTemplateData() ); this.close( { action: action } ); } ); } if ( !action && this.modified ) { return new OO.ui.Process( () => OO.ui.confirm( mw.msg( 'templatedata-modal-confirmcancel' ) ) .then( ( result ) => { if ( result ) { this.close(); } else { return $.Deferred().resolve().promise(); } } ) ); } // Fallback to parent handler return Dialog.super.prototype.getActionProcess.call( this, action ); }; module.exports = Dialog;