[CLOSED] Toolbar and events on form fields

  1. #1

    [CLOSED] Toolbar and events on form fields

    Hello support team,
    I use the following toolbar configuration based on the scenario suggested in post 284071:

    @{
        Layout = null;
        var X = Html.X();
    
        Toolbar toolbar = new Toolbar()
        {
            Border = false,
            Dock = Dock.Top,
            EnableOverflow = true,
            OverflowHandler = OverflowHandler.Menu
        };
    
        toolbar.Plugins.Add(new BoxReorderer());
        toolbar.LayoutConfig.Add(new HBoxLayoutConfig() { Align = HBoxAlign.Stretch });
    
        ButtonGroup grpSave = new ButtonGroup { Title = "Save" };
    
        grpSave.LayoutConfig.Add(new HBoxLayoutConfig() { Align = HBoxAlign.Stretch });
    
        grpSave.Items.Add(new Button { Icon = Icon.Disk, IconAlign = IconAlign.Top, Text = "Save" });
        grpSave.Items.Add(new Button { Icon = Icon.DiskEdit, IconAlign = IconAlign.Top, Text = "Save+New" });
        grpSave.Items.Add(new Button { Icon = Icon.DiskError, IconAlign = IconAlign.Top, Text = "Save+Close" });
    
        ButtonGroup grpOutput = new ButtonGroup { Title = "Output" };
    
        grpOutput.Items.Add(new Button { Icon = Icon.Printer, IconAlign = IconAlign.Top, Text = "Print" });
        grpOutput.Items.Add(new Button { Icon = Icon.Layout, IconAlign = IconAlign.Top, Text = "Preview" });
    
        toolbar.Items.Add(grpSave);
        toolbar.Items.Add(grpOutput);
        toolbar.Items.Add(new ToolbarFill());
    }
    
    @(X.ResourceManager())
    
    @(
        X.Window().Width(400).Height(200)
            .DockedItems(toolbar)
            .Items(
                X.FormPanel().Border(false).PaddingSpec("5 0 0 5")
                    .Items(
                        X.TextField().FieldLabel("Label 1").Listeners(l => l.Blur.Handler = "console.log('BLUR_1')").AutoFocus(true),
                        X.TextField().FieldLabel("Label 2").Listeners(l => l.Blur.Handler = "console.log('BLUR_2')")
                    )
            )
    )
    Mentioned design works perfectly with one problem - clicking the button in the Save group or on the ToolbarFill, it does not trigger a blur event on the input fields. Please try the following:

    1. Code unchanged - blur event is triggered only after clicking on buttons in the Output group.

    2. Comment out grpSave.LayoutConfig - the blur event is fired on both toolbar groups but still not on ToolbarFill.

    3. Put grpSave.LayoutConfig back into the game and comment out the BoxReorderer plugin - everything works as expected.

    I definitely need LayoutConfig settings for toolbar groups, so in the worst case I can sacrifice the plug-in to make it work.

    But why is it behaves this way and is there any work-around to preserve all the features and still be able to capture blur events on the input fields?

    Thank you for your assistance.

    Kind regards
    Dan
    Last edited by fabricio.murta; Aug 26, 2019 at 7:18 PM.
  2. #2
    Hello Dan!

    Thanks for the clean and objective test case, it really helps us seeing what you mean!

    The toolbar behaves like this once you enable the plug in because it treats everywhere you click within each "box" in the box-layout container as a potential drag spot.

    Notice how you get the drag effect in the toolbar when you don't get the blur event -- by clicking and dragging around the very buttons. Yet, you won't get to be able to move the button group (when clicking and dragging the button) while you get the blur event to trigger. It is easy to reproduce the click+drag on buttons in the first (grpSave) group, and to get the blur event to trigger while no drag effect by dragging around buttons in the second (grpOutput) group.

    Now we really have a rather inconsistent behavior as it triggers or not depending on the layout config. We will have to give it a better look to understand why the drag handler takes precedence if you specify custom layout options, or what exactly affects that. I'm thinking a lot of things, but nothing good will come unless a deeper look up is undergone.

    Another inconsistent behavior is that trying some times to drag a same button will result on either focus (incurring the blur event in the field if it was focused), or drag. Insisting on the same focused button sometimes fails (keeping the focus) and succeeds (dragging the whole button group).

    We'll post back when we have a better prospect whether there is an easy workaround (keeping the visual layout), or we'll need to file a bug and fix it.
    Fabrício Murta
    Developer & Support Expert
  3. #3
    Hi Fabrício,
    thank you for your prompt reply. Indeed, there are more inconsistencies in the way events and dragging are handled. I look forward to the results of your investigation.

    Kind regards
    Dan
  4. #4
    Hello again, Dan!

    While I'm not sure I should add this to the actual code, this might be just what you need to get going with this feature.

    Check out this little override in your test case:

    Ext.define('forumthread_62698', {
        override: 'Ext.ux.BoxReorderer',
        clickValidator: function (e) {
            var cmp = Ext.get(e.getTarget()).component;
            return !!(cmp && cmp.reorderable !== false);
        }
    });
    This alone wouldn't do, but if you then just add the Reorderable = false setting to each of the buttons within the button groups, you would be able to precisely control which inner components trigger or not the "drag" process from the BoxReorderer plug in.

    This plug in is a rather simple one, that can get as complicated as one wants, and maybe giving freedom via extensions and overrides would be the best in this case -- at least for now. If we address it to cover your case well, it could then break other cases that requires it the way it is now.

    The inconsistency between triggering or not the blur (plus being able to drag or not) though, is of great concern nevertheless, and this override might alone be enough to fix it (always triggering the drag event when clicks are within the plugin's domain).

    Let us know if the override above (plus setting the reorderable config option to buttons) satisfies your needs and works properly. If not, give some directions on reproducing what's not fit yet, and we'll try our best to address those potential edge cases.

    This override might give you a good hang also allowing you even to "generalize" to your needs and even cover edge cases you encounter without the need of further posts here. For instance if you change the return test to !!(cmp && cmp.xtype !== "button"), it should disable drag-handling for every button within the "domain" of the BoxReorderer plug in (would also include buttons outside button groups!).

    Ideally, maybe, a more complex drag handling with "threshold" should be a solution that could make it to the Ext.NET code. If the hold+and+drag event is farther than N pixels, then do the drag, else forward the click as if it were a simple click/tap event. But even like this other issues might arise, like a button's "hold" visual effect that may be affected.

    Well, hope this helps!

    edit: I can't reproduce that inconsistency on clicks in the simple example (linked above), so maybe it wouldn't be the case of going the path of assuming this is buggy, or faulty: just a simple implementation that can't handle it without changes in the special use case you pointed.
    Last edited by fabricio.murta; Aug 22, 2019 at 9:34 PM.
  5. #5
    Hi Fabrício,
    as it is usual in your case, the solution provided works perfectly. However, I have one small problem: while the test case is running like a charm (Ext JS 6.5.1.345 / Ext.NET 4.4.1), if I put the same override in our framework (Ext JS 6.7.0.161 / Ext.NET 4.8.2), clickValidator is not triggered at all, and therefore the override is doing nothing. Probably a version problem ... but that's something I need to fix myself.

    Thank you for your valuable help, your assistance is always highly appreciated.

    Kind regards
    Dan

    Edit: Since no newer version than Ext.NET 5 is available, we have compiled this version with our framework. Unfortunately, it crashes on the missing BoxContainerDD.js file (first error message we receive) and - therefore? - cannot find the Ext.ux.TabScrollerMenu class (second error message we receive).

    Edit: The problem is not in the version. There are two toolbars using BoxReorderer in our framework. While one toolbar triggers clickValidator, the other (more complex) does not. So there still must be “something” that causes trouble. But that's already my fight...
    Last edited by NewLink; Aug 23, 2019 at 10:46 AM.
  6. #6
    Hi Fabrício,
    to find out why clickValidator override is running on one toolbar while not on the other, I tried to debug the createInterceptor code:

    createInterceptor: function(origFn, newFn, scope, returnValue) {
        if (!Ext.isFunction(newFn)) {
            return origFn;
        } else {
            returnValue = Ext.isDefined(returnValue) ? returnValue : null;
            return function () {
                var me = this,
                    args = arguments;
                return (newFn.apply(scope || me || global, args) !== false) ? origFn.apply(me || global, args) : returnValue;
            };
        }
    }
    The problem is that for the first toolbar newFn is different than for the second.

    1. Toolbar which triggers clickValidator:
    newFn - my override
    origFn - ExtJS function

    clickValidator: function(e) {
        var target = e.getTarget();
        return (this.isValidHandleChild(target) && (this.id === this.handleElId || this.DDMInstance.handleWasClicked(target, this.id)));
    }
    2. Toolbar which does not trigger clickValidator:
    newFn - Ext.NET function

    clickValidator:function(e){var cmp=this.getDragCmp(e);return!!(cmp&&cmp.reorderable!==false);}
    orgFn - ExtJS function as in the first case

    Does it make any sense to you?

    Thank you for your help.

    Kind regards
    Dan
  7. #7
    Hello Dan!

    Maybe you have an issue with the way you are reusing code to create the toolbars? That's just a wild guess, but if I create the toolbars via separate calls, so that every component, as well as the plugin instance, is created separately, I get both toolbars to trigger the override.

    Here, I reworked your original test sample to create two panels, each with its own toolbar, and I get the "toast" coming from interaction with either one:

    @{
        Layout = null;
        var X = Html.X();
    }
    
    @functions {
        public static Toolbar getToolbar(string id)
        {
            Toolbar toolbar = new Toolbar()
            {
                ID = id,
                Border = false,
                Dock = Dock.Top,
                EnableOverflow = true,
                OverflowHandler = OverflowHandler.Menu
            };
    
            toolbar.Plugins.Add(new BoxReorderer());
            toolbar.LayoutConfig.Add(new HBoxLayoutConfig() { Align = HBoxAlign.Stretch });
    
            ButtonGroup grpSave = new ButtonGroup { Title = "Save" };
    
            grpSave.LayoutConfig.Add(new HBoxLayoutConfig() { Align = HBoxAlign.Stretch });
    
            grpSave.Items.Add(new Button { Icon = Icon.Disk, IconAlign = IconAlign.Top, Text = "Save" });
            grpSave.Items.Add(new Button { Icon = Icon.DiskEdit, IconAlign = IconAlign.Top, Text = "Save+New", Reorderable = false });
            grpSave.Items.Add(new Button { Icon = Icon.DiskError, IconAlign = IconAlign.Top, Text = "Save+Close", Reorderable = false });
    
            ButtonGroup grpOutput = new ButtonGroup { Title = "Output" };
    
            grpOutput.Items.Add(new Button { Icon = Icon.Printer, IconAlign = IconAlign.Top, Text = "Print", Reorderable = false });
            grpOutput.Items.Add(new Button { Icon = Icon.Layout, IconAlign = IconAlign.Top, Text = "Preview", Reorderable = false });
    
            toolbar.Items.Add(grpSave);
            toolbar.Items.Add(grpOutput);
            toolbar.Items.Add(new ToolbarFill());
    
            return toolbar;
        }
    }
    
    @(X.ResourceManager())
    <script type="text/javascript">
        var count = 0;
    
        Ext.define('forumthread_62698', {
            override: 'Ext.ux.BoxReorderer',
            clickValidator: function (e) {
                //var cmp = this.getDragCmp(e);
                var cmp = Ext.get(e.getTarget()).component;
    
                // If cmp is null, this expression MUST be coerced to boolean so that createInterceptor
                // is able to test it against false
                Ext.toast("Called override from toolbar: " + this.cmp.id);
                return !!(cmp && cmp.xtype !== "button");
            }
        });
    </script>
    @(
                X.Panel().Width(600).Height(400).Title("Pan1")
                    .DockedItems(getToolbar("tb1"))
                    .Items(
                        X.FormPanel().Border(false).PaddingSpec("5 0 0 5")
                            .Items(
                                X.TextField().FieldLabel("Label 1").Listeners(l => l.Blur.Handler = "Ext.toast('BLUR_1 - Event hit count: ' + ++count)").AutoFocus(true),
                                X.TextField().FieldLabel("Label 2").Listeners(l => l.Blur.Handler = "Ext.toast('BLUR_2 - Event hit count: ' + ++count)")
                            )
                    )
    )
    
    @(
                X.Panel().Width(600).Height(400).Title("Pan2")
                    .DockedItems(getToolbar("tb2"))
                    .Items(
                        X.FormPanel().Border(false).PaddingSpec("5 0 0 5")
                            .Items(
                                X.TextField().FieldLabel("Label 1").Listeners(l => l.Blur.Handler = "Ext.toast('BLUR_1 - Event hit count: ' + ++count)").AutoFocus(true),
                                X.TextField().FieldLabel("Label 2").Listeners(l => l.Blur.Handler = "Ext.toast('BLUR_2 - Event hit count: ' + ++count)")
                            )
                    )
    )
    The ID passed to the toolbar function is not really important, I used it just to ensure I know which toolbar the override was called from.

    So either you have something there that's (as strange as it may sound) biasing the toolbars' interaction, or I just did something else that simply does not trigger the exact scenario you're facing. For me, it works fine, maybe you can use this to try to isolate the problem?

    Hope this helps!
    Fabrício Murta
    Developer & Support Expert
  8. #8
    Hi Fabrício,
    thank you for all your efforts. After various tests, I found out that I had to put the Ext.ux.BoxReorderer override on several places.

    In our framework, there is a System toolbar, which serves to all applications opened within the framework (Help, About, Logout ...) and then the Application toolbar generated for each application based on its configuration.

    So far, we have pasted all overrides into Base.js, which is file registered once the frame is created (which has worked well so far). However, this override only works for the System toolbar.

    In order for the Application toolbars to work, I also had to paste this override into the scripts registered with each partial view that the application is created from.

    Moreover, we need to do this in the script which is directly generated with partial view. For example, we have the generic Nelis.View class, which the ListView and DetailView classes are inherited from:

    Ext.define ("Nelis.DetailView", {
        extend: "Nelis.View"
    }
    Putting an override in the general Nelis.View class does not work. For this to work, I need to put the override directly into Nelis.DetailView.

    I may not fully understand how this override works or how the createInterceptor is acting, but this concept leads to the desired behavior.

    Thanks for your valuable help, the issue may be closed.

    Kind regards
    Dan
  9. #9
    Hello Dan!

    Glad you could work it out in the end, and yes, the override should take place in every independent page and should be run right after the Ext.NET base scripts (not the "init script", the script with the code specific to the page), in order to be in effect.

    In case you have a central place to call the X.ResourceManager() bit, you could just Ext.Net.MVC.MvcResourceManager.AddBeforeInitScript("Ext.toast('my override here');"); to add the override "programmatically". This could help you at least put all overrides in a "center/public" class, and even control whether to call that script or not (use something to "mark" the resource manager instance as one using such toolbars), and lay the overrides as strings in a single place in your project.

    That's just an idea, given the number of places you needed to stick your override "copies" in. The advantage of the solution you adopted, I believe, is that you will ensure you only have the override where you really need it (you could instead just append this command and keep the override in a central place in your project for easier maintainability, perhaps).

    Hope this helps, and marking thread as closed.
    Fabrício Murta
    Developer & Support Expert

Similar Threads

  1. Grouping form fields
    By atze187 in forum 2.x Help
    Replies: 10
    Last Post: Sep 09, 2014, 8:15 AM
  2. [CLOSED] Mouse events interacting with Ext.Menu fields
    By FVNoel in forum 2.x Premium Help
    Replies: 1
    Last Post: May 06, 2013, 9:27 AM
  3. [CLOSED] paging toolbar events
    By speedstepmem3 in forum 1.x Premium Help
    Replies: 3
    Last Post: Mar 05, 2013, 4:52 AM
  4. Replies: 1
    Last Post: Aug 02, 2011, 12:59 PM
  5. Replies: 6
    Last Post: May 19, 2011, 9:36 AM

Posting Permissions