Skip to main content

ReportDesignerClientSideEventsBuilder.CustomizeSaveAsDialog(String) Method

Sets the name of the JavaScript function or the entire code that will handle the Web Report Designer‘s CustomizeSaveAsDialog client-side event.

Namespace: DevExpress.AspNetCore.Reporting.ReportDesigner

Assembly: DevExpress.AspNetCore.Reporting.v24.1.dll

NuGet Package: DevExpress.AspNetCore.Reporting


public ReportDesignerClientSideEventsBuilder CustomizeSaveAsDialog(
    string callback


Name Type Description
callback String

The name of a JavaScript function or the entire JavaScript function code used to handle the CustomizeSaveAsDialog event.


Type Description

A ReportDesignerClientSideEventsBuilder that can be used to further configure the Report Designer Client Side Events.


The End-User Report Designer invokes the Save As dialog when the user executes the Save As… command to save a report with a new name.

To customize the Save As dialog, handle the ASPxClientReportDesigner.CustomizeSaveAsDialog event and call the e.Customize method.

View Example: How to Customize the Save As and Open Dialogs in the Web End-User Report Designer

Customized Dialog

Report files in this example are arranged in folders in the root Reports folder. Folder names correspond to the report’s Category. The customized dialog displays report names and categories.

Custom Save As Dialog

HTML Template

The Save As dialog is defined in an HTML template. To create a custom template, do the following:

  • Find the element related to the Save As dialog content on the page.
  • Determine the template that the Knockout framework uses to render the element.
  • Determine the template id.
  • Find the template with the specified id in the devexpress-reporting npm package.
  • Include the template in the application’s page with a different template id.
  • Modify the template.

To find the Save As dialog template id in the Chrome browser, right-click the Save As dialog and select Inspect to run the Chrome Dev Tools. In the Elements panel, look for the ko bindings - the “ko template“ or “data-bind = “{ template: …” strings.

Select the “ko template“ code block, switch to the Console and run the following method:


The method obtains information on the Knockout binding. Inspect the object returned by this method and find the property that contains the template id.

The template id is dxrd-savereport-dialog-content.

Open the node_modules\devexpress-reporting\dist\html\template.html file in your application’s node_modules folder.

You can also run the following command in any folder to create the node_modules folder and install the devexpress-reporting package:

npm i devexpress-reporting

Search the template.html file for the dxrd-savereport-dialog-content string. Copy the dxrd-savereport-dialog-content template to the application’s page and change its id to save-as.

You can customize the template as needed or review the code sample in this topic for the customized Save As dialog template.


The dialog model defines the properties used in the dialog template and binds them to Knockout observables. The model specifies the following functions:

  • to set the current report URL
  • to get the current report URL
  • to update the model’s properties when the dialog is displayed. The updateCategories JavaScript function is used.

The updateCategories function calls the client-side DevExpress.Reporting.Designer.ReportStorageWeb.getUrls method to obtain report names and categories. This method uses the ReportStorageWebExtension.GetUrls method of a server-side report storage to get a dictionary that contains report names and categories. The code processes the dictionary and fills the categories data array.

The model defines the dialog buttons and their actions. The Save button’s action calls the method and the Cancel button’s action calls the e.Popup.cancel method.

CustomizeSaveAsDialog Event

The dialog HTML template and dialog model are passed to the e.Customize(template, model) method to modify the Report Designer’s Save As dialog. This method is available in the CustomizeSaveAsDialog event handler.

The customizeSaveAsDialog function is the ASPxClientReportDesigner.CustomizeSaveAsDialog event handler. The function uses the ASPxClientReportDesignerCustomizeSaveAsDialogEventArgs.Popup property to specify the dialog’s width, height, and title. The function defines variables used in the dialog model and defines the dialog model. Finally, the function calls the e.Customize method to modify the dialog based on the specified model and template.

Code Sample

<script type="text/javascript" id="script">
    function addReport(url, category, categoryArray, categoryName, reportName, value, koCategory) {
        if(category.length === 0) {
                key: categoryName, items: [
                        text: value.Key, displayName: reportName,
                        onClick: function () { url(value.Key); koCategory && koCategory(categoryName); }
        } else {
                text: value.Key, displayName: reportName,
                onClick: function () { url(value.Key); koCategory && koCategory(categoryName); }

    function updateCategories(url, categories, koCategory) {
        DevExpress.Reporting.Designer.ReportStorageWeb.getUrls().done(function(result) {
            var categoryArray = [{ key: "none", items: [] }];
            for(var i = 0; i < result.length; i++) {
                var parts = result[i].Value.split('\\');
                var folder = parts[0];
                var reportName = parts[1];
                if(parts.length === 1) {
                    reportName = parts[0];
                    folder = "none";
                } else if (parts.length > 2) {
                    reportName = parts.pop();
                    folder = parts.join('\\');
                var category = categoryArray.filter(function(item) { return item.key === folder; });
                addReport(url, category, categoryArray, folder, reportName, result[i], koCategory);

    function customizeSaveAsDialog(s, e) {
        e.Popup.title = "Save";
        var categories = ko.observableArray([]);
        var koUrl = ko.observable("");
        var koInput = ko.observable("");
        koUrl.subscribe(function(newVal) {
            newVal = newVal.replace('/', '\\');
            var paths = newVal.split('\\');
            var fileName = paths.pop();
            if(fileName !== koInput())
            var catName = paths.join('\\');
            if(catName !== koCategory())
        var koCategory = ko.observable("");
        updateCategories(koUrl, categories);

        var model = {
            categories: categories,
            categoryName: koCategory,
            reportUrl: koUrl,
            inputValue: koInput,
            setUrl: function(url) {
            getUrl: function() {
                return koUrl();
            onShow: function(tab) {
                updateCategories(koUrl, categories, koCategory);
            popupButtons: [
                    toolbar: 'bottom', location: 'after', widget: 'dxButton', options: {
                        text: 'Save', onClick: function() {
                    toolbar: 'bottom', location: 'after', widget: 'dxButton', options: {
                        text: 'Cancel', onClick: function() {
        e.Customize("save-as", model)
    .dxrd-reportdialog-content .reportdialog-item.dx-texteditor:not(.dx-multiline):not(.dx-textarea) {
        height: 36px;
        margin-bottom: 10px;
<script type="text/html" id="save-as">
    <div class="dxrd-reportdialog-content">
        <div style="margin-bottom: 10px;" data-bind="dxTextBox: { height: 36, value: inputValue, valueChangeEvent: 'keyup', 
             onValueChanged: function (e) { reportUrl(categoryName() ? (categoryName() + '\\' + e.value) : e.value);}, 
             placeholder: 'Enter a report name to save...', showClearButton: true }"></div>
        <div style="margin-bottom: 10px;" data-bind="dxSelectBox: {
                                    height: 36,
                                    dataSource: categories,
                                    value: categoryName,
                                    keyExpr: 'key',
                                    valueExpr: 'key',
                                    displayExpr: 'key',
                                    acceptCustomValue: true,
                                    placeholder: 'Select a category...',
                                    onCustomItemCreating: function(data) {
                                        if(!data.text) {
                                            data.customItem = null;
                                        categories.push({key: data.text, items: [] })
                                        reportUrl(data.text + '\\' + inputValue());
                                        data.customItem = {
                                            key: data.text,
                                            items: []
        <div class="dx-default-border-style dxd-border-secondary" data-bind="dxList: {
                    dataSource: categories,
                    height: '260px',
                    grouped: true,
                    displayExpr: 'displayName',
                    keyExpr: 'text',
                    collapsibleGroups: true,

         .ClientSideEvents(configure => {
See Also