FieldSet / FormPanel ... HowTo?

  1. #1

    FieldSet / FormPanel ... HowTo?

    Hello!
    I don't know how to port existing MVC v5.2 or WebForms example for fieldset.
    I would like that doubleclicking a grid row, pops up a window with the row editor panel.
    Clicking the save button I would like to have back the edited json record.

    Is this behavior supported?

    I prepared a sample page which I think it has several errors, first of all I don't understand if it's needed to have a <form> in page or not.

    Could you please fix my code?

    GridAndForm.cshtml
    @page
    @model ExtCookbook.Pages.GridAndFormModel
    @{
    }
    <ext-section target="Main">
        <ext-container region="Center" scrollable="true" paddingAsString="30 20 30 50">
            <content>
                <h1>GridPanel with editor form</h1>
                Doubleclick a row in order to show the editor.
                <form>
                    <ext-container id="MainContainer" model="Model.MainContainer">
                    </ext-container>
                </form>
            </content>
        </ext-container>
    </ext-section>
    GridAndForm.cshtml.cs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Ext.Net;
    using Ext.Net.Core;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.RazorPages;
    
    namespace ExtCookbook.Pages
    {
        public class GridAndFormModel : PageModel
        {
    
            public Panel MainContainer { get; set; }
    
            public void OnGet()
            {
                  ///////////////
                 // GridPanel //
                ///////////////
                var grid = new GridPanel()
                {
                    Id = "myGrid",
                    Title = "myGrid",
                    Anchor = "100%",
                    Region = RegionType.Center,
                    Columns = new List<Column>
                    {
                        new Column() {  DataIndex = "UserID", Text = "UserID", Flex = 1},
                        new Column() {  DataIndex = "UserName", Text = "User name", Flex = 1},
                        new Column() {  DataIndex = "EMail", Text = "E-Mail", Flex = 1},
                    },
                    Store = new Store
                    {
                        Id = "myGridStore",
                        ModelValue = new Model
                        {
                            Fields = new List<DataField>
                            {
                                new DataField() { Name = "UserID", Type = DataFieldType.Int },
                                new DataField() { Name = "UserName", Type = DataFieldType.String },
                                new DataField() { Name = "EMail", Type = DataFieldType.String },
                            }
                        },
                        Data = GetData()                                        
                    }
                };
                grid.Listeners.RowDblClick.Handler = "App.UserForm.setData(App.myGrid.selection.data);App.EditorWindow.show();";
    
                  ////////////
                 // Window //
                ////////////
    
                var window = new Window
                {
                    Id = "EditorWindow",
                    Title = "User",
                    IconCls = "x-md md-icon-person",
                    Width = 500,
                    Height = 500,
                    BodyPadding = 18,
                    Frame = true,
                    DefaultButton = "SaveButton"
                };
                var fp = new FieldSet()
                {
                    Id = "UserForm",
                    Anchor = "100%"
                };
                fp.FieldDefaults = new JsObject() { { "labelAlign", "top" } };
                fp.Defaults = new JsObject() { { "margin", "0 0 10 0" } };
    
                fp.Items.Add(new TextField()
                {
                    Name = "UserName",
                    FieldLabel = "User name",
                    AllowBlank = false,
                    Width = "100%"
                });
                fp.Items.Add(new TextField()
                {
                    Name = "EMail",
                    FieldLabel = "E-mail address",
                    AllowBlank = false,
                    Width = "100%"
                });
                
                var saveButton = new Button()
                {
                    Id = "SaveButton",
                    Text = "Save",
                    OnClientClick = new JsFunction("App.EditorWindow.hide();")
                };
                saveButton.DirectEvents.Click.Method = HttpMethod.POST;
                saveButton.DirectEvents.Click.Url = $"?handler=SaveButton_Click";
                saveButton.DirectEvents.Click.ExtraParams.Add("data", "App.UserForm.data", ParameterMode.Raw);
                window.Buttons.Add(saveButton);
                window.Buttons.Add(new Button()
                {
                    Id = "CancelButton",
                    Text = "Cancel",
                    OnClientClick = new JsFunction("App.EditorWindow.hide();")
                });
                window.Items.Add(fp);
    
                MainContainer = new Panel()
                {
                    Anchor = "100%"                                
                };
                MainContainer.Items.Add(grid);
                MainContainer.Items.Add(window);
            }
    
            public static List<object> GetData()
            {            
                return new List<object>
                { 
                    new Dictionary<string, object>
                    {
                        { "UserID", 0 },
                        { "UserName", "Tom" },
                        { "EMail", "tom@mail.domain" }
                    },
                    new Dictionary<string, object>
                    {
                        { "UserID", 1 },
                        { "UserName", "John" },
                        { "EMail", "john@mail.domain" }
                    }
                };
            }
    
            public IActionResult OnPostSaveButton_Click(JsObject jObj)
            {
                var data = jObj.GetValueOrDefault("data").Value;
                this.X().Toast(":D  Happy!!! :D");
                return this.Direct();
            }
        }
    }
    Thank you in advance.
  2. #2
    Hello @bbros!

    To shorten the answer length, I will point just what I changed to get your code to work:

    1. In your markup code (or an external included JavaScript file), define a handler for the grid's double click like this:

    <script type="text/javascript">
        var rowDblClickHandler = function (view, record, rowDOM, recordIdx) {
            App.EditorWindow.setTitle(record.data.UserName + " (#" + record.data.UserID + ") User Details");
            App.EditorWindow.assignedId = record.data.UserID;
            App.EditingUsername.setValue(record.data.UserName);
            App.EditingEmail.setValue(record.data.EMail);
            App.EditorWindow.show();
        }
    </script>
    You can copy-paste this block right between lines 4 and 5 in your markup code.

    2. In the grid's RowDblClick listener (line 49 in your model code), make it reference the function; this should at least make it easier for you to understand what I am doing to handle the double click and fill in the form.

    grid.Listeners.RowDblClick.Fn = "rowDblClickHandler";
    3. Now give the respective IDs to easily access your form fields from anywhere. In your model, respectively between lines 75-80 and 82-87:

    Id = "EditingUsername",
    Id = "EditingEmail",
    One ID for each field, respectively.

    4. In the Direct Events' ExtraParams (line 97 in your model code), change it to include the values from these fields on submit:

    saveButton.DirectEvents.Click.ExtraParams.Add("userId", "function() { return App.EditorWindow.assignedId; }", ParameterMode.Raw);
    saveButton.DirectEvents.Click.ExtraParams.Add("userName", "function() { return App.EditingUsername.getValue(); }", ParameterMode.Raw);
    saveButton.DirectEvents.Click.ExtraParams.Add("email", "function() { return App.EditingEmail.getValue(); }", ParameterMode.Raw);
    5. Finally, for the server-side handler just change it to receive the three arguments and "toast" them back to the client, emulating a successful processing. This means you will replace the whole OnPostSaveButton_Click() method (lines 134-139 in model code) to this:

    public IActionResult OnPostSaveButton_Click(int userId, string userName, string email)
    {
        this.X().Toast("Updated user ID #" + userId + " with username = '" + userName + "' and email = '" + email + "'.");
        return this.Direct();
    }
    Notice here we are using no form and no form panel logics at all. The fieldset here is simply a container with a nice windows forms-like outline. So go ahead and drop the <form /> tags wrapping the ext-container in the markup code. You are making AJAX requests, not full post backs.

    The FormPanel would make it easier to pass an arbitrary amount of fields (anything getting a Name config within the <form /> block), because then you'd be using its submit() handler instead of a direct event.

    The small form contained in the File Upload Field example should have all you need to know in order to explore actual form submission and Form Panel usage with Ext.NET 7.

    We are also loosen-binding the form contents with the double-clicked grid row. You can go advanced and use actual Ext JS Data Binding; but maybe that'd be more useful if you had the panel displayed all the time instead of using it as a dialog, like in the Ext.NET 5's Charts > Combination > Dashboard example.

    Hope this helps!

    edit: missing a necessary change to use the implmented function on double click:

    grid.Listeners.RowDblClick.Fn = "rowDblClickHandler";
    Line 49 of original model code would become this, effectively using the dedicated JavaScript function.
    Last edited by fabricio.murta; Apr 20, 2021 at 4:19 PM.
  3. #3
    Thank you, understood!

    I was looking into the following examples
    https://mvc5.ext.net/#/Form_FormPanel/Basic/
    https://examples5.ext.net/#/Form/FormPanel/Basic/

    and I was thinking that something like
    this.up('form').getForm().loadRecord(selected[0])
    should have done a magic for me as well, or the save button handler here

     <Click Handler="var form = this.up('form'),
                                                r = form.getForm().getRecord();
    
                                            if (r) {
                                                form.getForm().updateRecord(form.down('grid').getSelectionModel().getLastSelected());
                                            }" />
    I think script always works regardless of how many and which fields are in the dataset.

    I'll try to create something like those examples and I'll let you know!

    Thanks, bye!
  4. #4
    Hello again, @bbros!

    Yes, there's never one absolute single solution to any scenario you may think, and using the facilities to bind records to/from the form fields seems the best approach for you.

    In fact, checking the ext-formPanel component, I am missing some settings specific to it. For those, the issue #1857 was logged, so we can investigate and address why the component's specific settings didn't make it to the actual component.

    Something that might help you attain more "convenient mapping" with the direct event button would be to wrap the record as a single object. As long as you match it server-side, you can have something like:

    Direct Event's extra param:
    saveButton.DirectEvents.Click.ExtraParams.Add("user", "function() { return Ext.encode(this.up('form').getForm().getValues()); }", ParameterMode.Raw);
    An actual FormPanel wrapping the fields and window buttons:
    var formPanel = new FormPanel()
    {
        Id = "FormPanel1"
    };
    formPanel.Items.Add(fp);
    
    (...)
    
    formPanel.Buttons.Add(saveButton);
    formPanel.Buttons.Add(new Button()
    Which means, the formPanel will wrap both your fieldset (with form fields therein) and the window's buttons, just so that button.up('form') matches the implicit Ext.form.Basic instance within the Form Panel.

    Then, the model handler and class:

    public IActionResult OnPostSaveButton_Click(UserInfo user)
    {
        this.X().Toast("Updated user ID #" + user.UserId + " with username = '" + user.UserName + "' and email = '" + user.EMail + "'.");
        return this.Direct();
    }
    
    public class UserInfo
    {
        public int UserId { get; set; }
        public string UserName { get; set; }
        public string EMail { get; set; }
    }
    Hope this helps!
    Fabrício Murta
    Developer & Support Expert
  5. #5
    Hi Fabricio,
    this looks great!

    You were missing just the Row doubleclick event handler; I set it in this way:

    grid.Listeners.RowDblClick.Handler = "App.FormPanel1.setValue(App.myGrid.selection.data);App.EditorWindow.show();";
    I even kept the JsObject in the SaveButton_Click event handler in order to create a generic implementation which could be more versatile (this is what I would like to do).

    Last question: to see if what I'm coding will work, I test the js code in the browser console.
    The only thing I can't make work is this.up.
    What is the context of this?

    Thank you again.
  6. #6
    Quote Originally Posted by bbros
    You were missing just the Row doubleclick event handler; I set it in this way:
    Oops! :-)

    grid.Listeners.RowDblClick.Fn = "rowDblClickHandler";
    Line 49 of your model code would become this using the dedicated JavaScript function approach.

    Quote Originally Posted by bbros
    The only thing I can't make work is this.up.
    What is the context of this?
    Unless you point exactly where this is happening I simply cannot tell what the context or scope is. Depending on the case, it can even be passed in. If in doubt what context it is getting you should actually add a breakpoint and then inspect what it has.

    The up() method is available to several Ext JS classes, such as:
    - Ext.Component.up()
    - Ext.Widget.up()
    - Ext.dom.Element.up()
    - Ext.dom.CompositeElementLite.up()

    Although their implementation may be different in the different classes, the semantics is the same: looks on its surrounding up to the top of the component/layout hierarchy for the immediate first component (when no argument is passed) or for the specified component if a "selector" is passed. This "selector" can be the the component's xtype value, a class or another query. Then if you want to go advanced on those queries, from the links above you can get detailed documentation on it.

    Theres and "antagon" for it, the .down(); instead of looking on its surroundings, it looks within it. So if you try a button's .down() it is probably not going to find anything, whereas the ViewPort shouldn't have much to find with its .up() call.

    There are some "edgy cases" with tooltips, menus, windows, (not limited to just these three components) where they can be "floating" and not within the layout. That is, in an hypothetical example, you may set a tooltip within a grid but, as it is internally built to float "outside" the grid and be displayed on mouse over cells, the actual tooltip may be hosted way up in the page hierarchy, giving you unexpected .up() results.

    In other words, in general components are "children" of other components in the layout hierarchy. When some feature the keyword float or target it indicates it may not follow the hierarchy like other components.

    Another example would be GridPanel cell editor components. A text field there is moved from one cell to another as editing is triggered; thus the up() immediate context changes (although the grid's view, itself, a panel where it is contained and up will be the same).

    Hope this clarifies your inquiries.

    I will edit the initial answer to add the missing instruction.
    Fabrício Murta
    Developer & Support Expert

Similar Threads

  1. [CLOSED] Howto show a windows from a class
    By tMp in forum 3.x Legacy Premium Help
    Replies: 4
    Last Post: Oct 15, 2015, 5:44 PM
  2. Replies: 3
    Last Post: Mar 13, 2012, 6:58 PM
  3. [CLOSED] FormPanel and FieldSet don't work together
    By shahidmughal in forum 1.x Legacy Premium Help
    Replies: 8
    Last Post: Sep 07, 2009, 7:28 PM
  4. [Q] Howto do a postback using a splitbutton?
    By plykkegaard in forum 1.x Help
    Replies: 2
    Last Post: Apr 08, 2009, 6:15 AM
  5. [CLOSED] Howto NOWRAP a cell in the TableLayout
    By randy85253 in forum 1.x Legacy Premium Help
    Replies: 4
    Last Post: Dec 29, 2008, 1:00 PM

Posting Permissions