[FIXED] [#665] [4.3.0] Spotlight show and hide method callback isn't used

  1. #1

    [FIXED] [#665] [4.3.0] Spotlight show and hide method callback isn't used

    Hi,

    The show and hide methods of Ext.ux.Spotlight are documented to have a callback (and scope).

    This worked nicely in 1.x

    Just realized in 2.x and 3.x (Ext JS 4.2.2 and Ext JS 5.1) while documented, and part of the function definition, the callback related code has gone...!

    Normally I think as a workaround I could just override the method and call the callback after the show and hide methods have run. But because of animation (which is true by default and good to have) it needs to be wired in correctly.

    Using show as an example:

    1.x code:

        show : function (el, callback, scope) {
            if (this.animated) {
                this.show.defer(50, this, [el, callback, scope]);
    
                return;
            }
            
            this.el = Ext.net.getEl(el);
            
            if (!this.right) {
                this.createElements();
            }
            
            if (!this.active) {
                this.all.setDisplayed("");
                this.applyBounds(true, false);
                this.active = true;
                Ext.EventManager.onWindowResize(this.syncSize, this);
                this.applyBounds(false, this.animate, false, callback, scope);
            } else {
                this.applyBounds(false, false, false, callback, scope); // all these booleans look hideous
            }
        },
    3.x code:

        show: function(el, callback, scope) {
            var me = this;
            
            //get the target element
            me.el = Ext.get(el);
    
            //create the elements if they don't already exist
            if (!me.right) {
                me.createElements();
            }
    
            if (!me.active) {
                //if the spotlight is not active, show it
                me.all.setDisplayed('');
                me.active = true;
                Ext.on('resize', me.syncSize, me);
                me.applyBounds(me.animate, false);
            } else {
                //if the spotlight is currently active, just move it
                me.applyBounds(false, false);
            }
        },
    Only solution I can think at the moment is to take the 1.x code and apply that. Also, applyBounds in Ext JS 5.1 doesn't have callback/scope arguments either, and it looks like that code is missing. So I wonder if this was accidentally taken out (seems odd)?

    Anyway, I tried to override Ext.ux.Spotlight to add it back in, and this is what I have so far, incorporated into a working example adapted from the Examples Explorer (further notes below)

    <%@ Page Language="C#" %>
     
    <%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
     
    <!DOCTYPE html>
     
    <html>
    <head runat="server">
        <title>Spotlight - with fix</title>    
     
        <ext:ResourcePlaceHolder Mode="ScriptFiles" />
        
        <script>
            /*
             * Fixes to support callback/scope in show/hide methods
             * Unfortunately requires writing a lot more code than ideal.
             */
            Ext.ux.Spotlight.override({
                /**
                 * Show the spotlight
                 */
                show: function (el, callback, scope) {
                    var me = this;
     
                    //get the target element
                    me.el = Ext.get(el);
     
                    //create the elements if they don't already exist
                    if (!me.right) {
                        me.createElements();
                    }
     
                    if (!me.active) {
                        //if the spotlight is not active, show it
                        me.all.setDisplayed('');
                        me.active = true;
                        Ext.on('resize', me.syncSize, me);
                        me.applyBounds(me.animate, false, callback, scope); // FIX: added callback/scope
                    } else {
                        //if the spotlight is currently active, just move it
                        me.applyBounds(false, false, callback, scope); // FIX: added callback/scope
                    }
                },
     
                /**
                 * Hide the spotlight
                 */
                hide: function (callback, scope) {
                    var me = this;
     
                    Ext.un('resize', me.syncSize, me);
     
                    me.applyBounds(me.animate, true, callback, scope); // FIX: added callback/scope
                },
     
                /**
                 * Resizes the spotlight depending on the arguments
                 * @param {Boolean} animate True to animate the changing of the bounds
                 * @param {Boolean} reverse True to reverse the animation
                 */
                applyBounds: function (animate, reverse, callback, scope) {
                    var me = this,
                        box = me.el.getBox(),
                        //get the current view width and height
                        viewWidth = Ext.Element.getViewportWidth(),
                        viewHeight = Ext.Element.getViewportHeight(),
                        i = 0,
                        config = false,
                        from, to, clone;
     
                    //where the element should start (if animation)
                    from = {
                        right: {
                            x: box.right,
                            y: viewHeight,
                            width: (viewWidth - box.right),
                            height: 0
                        },
                        left: {
                            x: 0,
                            y: 0,
                            width: box.x,
                            height: 0
                        },
                        top: {
                            x: viewWidth,
                            y: 0,
                            width: 0,
                            height: box.y
                        },
                        bottom: {
                            x: 0,
                            y: (box.y + box.height),
                            width: 0,
                            height: (viewHeight - (box.y + box.height)) + 'px'
                        }
                    };
     
                    //where the element needs to finish
                    to = {
                        right: {
                            x: box.right,
                            y: box.y,
                            width: (viewWidth - box.right) + 'px',
                            height: (viewHeight - box.y) + 'px'
                        },
                        left: {
                            x: 0,
                            y: 0,
                            width: box.x + 'px',
                            height: (box.y + box.height) + 'px'
                        },
                        top: {
                            x: box.x,
                            y: 0,
                            width: (viewWidth - box.x) + 'px',
                            height: box.y + 'px'
                        },
                        bottom: {
                            x: 0,
                            y: (box.y + box.height),
                            width: (box.x + box.width) + 'px',
                            height: (viewHeight - (box.y + box.height)) + 'px'
                        }
                    };
     
                    //reverse the objects
                    if (reverse) {
                        clone = Ext.clone(from);
                        from = to;
                        to = clone;
                    }
     
                    if (animate) {
                        Ext.Array.forEach(['right', 'left', 'top', 'bottom'], function (side) {
                            me[side].setBox(from[side]);
     
                            me[side].animate({
                                duration: me.duration,
                                easing: me.easing,
                                to: to[side],
     
                                /* FIX for missing callback support */
                                listeners: {
                                    afteranimate: {
                                        fn: callback || Ext.emptyFn,
                                        scope: scope
                                    }
                                }
                            });
                        },
                        this);
                    } else {
                        Ext.Array.forEach(['right', 'left', 'top', 'bottom'], function (side) {
                            me[side].setBox(Ext.apply(from[side], to[side]));
                            me[side].repaint();
     
                            /* FIX for missing callback support */
                            if (callback) {
                                callback.apply(scope); // not sure what arguments to pass here
                            }
                        },
                        this);
                    }
                }
            });
        </script>
     
        <script>
            var updateSpot = function (cmp) {
                App.Spot.show(cmp.el);
                updateButtons(cmp);
            };
     
            var hideSpot = function () {
                if (App.Spot.active) {
                    App.Spot.hide();
                }
     
                updateButtons();
            };
     
            var getButton = function (panel) {
                return panel.dockedItems.last().items.first();
            };
     
            var updateButtons = function (cmp) {
                cmp = cmp || {};
                getButton(App.Panel1).setDisabled(cmp.id != App.Panel1.id);
                getButton(App.Panel2).setDisabled(cmp.id != App.Panel2.id);
                getButton(App.Panel3).setDisabled(cmp.id != App.Panel3.id);
            };
        </script>
    </head>
    <body>
        <form runat="server">
            <ext:ResourceManager ID="ResourceManager1" runat="server">
                <Listeners>
                    <DocumentReady Handler="updateButtons();" />
                </Listeners>
            </ext:ResourceManager>
            
            <h1>Spotlight</h1>
     
            <p>This control allows you to restrict input to a particular element by masking all other page content.</p>
            
            <ext:Spotlight ID="Spot" runat="server" Easing="EaseOut" Duration="300" />
            
            <ext:Button runat="server" Text="Start">
                <Listeners>
                    <Click Handler="updateSpot(App.Panel1);" />
                </Listeners>
            </ext:Button>
            
            <ext:Panel runat="server" Border="false" Layout="TableLayout" ShrinkWrap="Width">
                <LayoutConfig>
                    <ext:TableLayoutConfig Columns="3" />
                </LayoutConfig>
                <Items>
                    <ext:Panel ID="Panel1" runat="server" 
                        Frame="true"
                        Title="Demo Panel"
                        Width="200"
                        Height="150"
                        Html="Some panel content goes here!" 
                        PaddingSummary="10px 15px">
                        <Buttons>
                            <ext:Button ID="Button1" runat="server" Text="Next Panel">
                                <Listeners>
                                    <Click Handler="updateSpot(App.Panel2);" />
                                </Listeners>
                            </ext:Button>
                        </Buttons>
                    </ext:Panel>
                    <ext:Panel ID="Panel2" runat="server" 
                        Frame="true"
                        Title="Demo Panel"
                        Width="200"
                        Height="150"
                        Html="Some panel content goes here!" 
                        PaddingSummary="10px 15px">
                        <Buttons>
                            <ext:Button ID="Button2" runat="server" Text="Next Panel">
                                <Listeners>
                                    <Click Handler="updateSpot(App.Panel3);" />
                                </Listeners>
                            </ext:Button>
                        </Buttons>
                    </ext:Panel>
                    <ext:Panel ID="Panel3" runat="server" 
                        Frame="true"
                        Title="Demo Panel"
                        Width="200"
                        Height="150"
                        Html="Some panel content goes here!" 
                        PaddingSummary="10px 15px">
                        <Buttons>
                            <ext:Button ID="Button3" runat="server" Text="Done">
                                <Listeners>
                                    <Click Handler="hideSpot();" />
                                </Listeners>
                            </ext:Button>
                        </Buttons>
                    </ext:Panel>
                </Items>
            </ext:Panel>        
        </form>
    </body>
    </html>
    I have tested the animated version and it seems to work.

    Looks for comments that include the word FIX to see where I put my actual changes.

    Initially I noticed the animation isn't the same as 1.x. For example, compare the Spotlight animation on the Examples Explorer for 1.x and 3.x. In 1.x the four sides animate in one after the other. This does not happen in the latest version. Turns out the duration in 5.x (maybe since 4.x) of Ext JS has changed to milliseconds. So instead of 0.3 in the duration I changed it to 300 (maybe it is worth updating the Examples Explorer accordingly)?

    Note, in the updateSpot method, I ended up passing cmp.el into the show method, instead of just cmp. This might be a subtle change between Ext JS 5.0 and 5.1; not sure...

    Remaining questions/issues:
    1) Is this the best fix? Is there a better way to do this without copying so much original code to inject the fixes?
    2) Can this, or a similar (better!) fix be incorporated into Ext.NET 3.x?
    3) If incorporated into Ext.NET and/or improved, at that time, you or I can report it to Sencha perhaps?
    Last edited by fabricio.murta; Jun 08, 2017 at 12:42 PM.
  2. #2
    Hi Anup,

    Thank you for the report! Created an Issue.
    https://github.com/extnet/Ext.NET/issues/665

    Corrected the Durations in the examples.

    1) Is this the best fix? Is there a better way to do this without copying so much original code to inject the fixes?
    2) Can this, or a similar (better!) fix be incorporated into Ext.NET 3.x?
    I think your code is good. Incorporated to the SVN trunk in the revision #6288. Thank you!

    3) If incorporated into Ext.NET and/or improved, at that time, you or I can report it to Sencha perhaps?
    Probably, no bid sense in that. There is a Spotlight issue (reported by me) still opened since ExtJS 4.1.x:)

    not sure what arguments to pass here
    If you determine later that some parameters are needed to pass, please let us know and we'll add to SVN.

    By the way, you can remove this
    <ext:ResourcePlaceHolder Mode="ScriptFiles" />
    It is not required anymore. The resources are rendered to the beginning of a page's <head> by default.
  3. #3
    Thanks for your reply. I will look into reporting a bug to sencha with the workaround later this weekend hopefully.

    Just wanted to comment on your note about not needing resource placeholder anymore. That is good to know. Thanks.

    The only consideration I had about that is if people want to add links to their own stylesheets, they will end up after the script references to the Ext JS scripts. From a best practice point of view, it is considered better to have all the styles at the top as scripts can block download of other resources (unless they are async, which doesn't make sense in apps like Ext JS based apps).

    This can result in perceived download performance degradation (given the Ext JS scripts are large as well, albeit likely to come from a CDN, or local Intranet, or often the browser's cache). This is because it can result in a flash of unstyled content - or in the case of Ext.NET, partially unstyled content as the main theme will have loaded first. So it will look like a delay for the user for all the styles to be fully applied.

    However, from an Ext.NET point of view, I can understand that there isn't a better solution other than perhaps the following

    • Put Ext styles at top as is now, but Ext JavaScript at end of head (with risk of people inserting their own JS referring to Ext which will end up before the Ext scripts)
    • Detect the first script tag in the head an place the Ext ones before that one (leaving it up to developer to get it right, which may be fine)
    • Leave it as is (good in cases where custom stylesheets not needed) and let developers use the resource placeholder when needed.


    Maybe the last option, leave as is, is good as it will be least change for you and everyone who already uses it, but augment with an example in the Examples Explorer of the ResourcePlaceholder and "document" its optional need and scenarios in the example?

    (Also, I realised I missed out runat="server" when using the resource placeholder in my example!)
  4. #4
    Hi,

    Please note, I realised there as a bug in the fix - the callback was firing for every side that was animated rather than at the end. So I've modified the spotlight override to only fire the callback on the last animate. Here is the updated script:

    Ext.define('overrides.Ext.ux.Spotlight', {
        override: 'Ext.ux.Spotlight',
        
        /**
         * Show the spotlight
         */
        show: function (el, callback, scope) {
            var me = this;
            
            //get the target element
            me.el = Ext.get(el);
            
            //create the elements if they don't already exist
            if (!me.right) {
                me.createElements();
            }
            
            if (!me.active) {
                //if the spotlight is not active, show it
                me.all.setDisplayed('');
                me.active = true;
                Ext.on('resize', me.syncSize, me);
                me.applyBounds(me.animate, false, callback, scope); // FIX: added callback/scope
            } else {
                //if the spotlight is currently active, just move it
                me.applyBounds(false, false, callback, scope); // FIX: added callback/scope
            }
        },
        
        /**
         * Hide the spotlight
         */
        hide: function (callback, scope) {
            var me = this;
            
            Ext.un('resize', me.syncSize, me);
            
            me.applyBounds(me.animate, true, callback, scope); // FIX: added callback/scope
        },
        
        /**
         * Resizes the spotlight depending on the arguments
         * @param {Boolean} animate True to animate the changing of the bounds
         * @param {Boolean} reverse True to reverse the animation
         */
        applyBounds: function (animate, reverse, callback, scope) {
            var me = this,
                box = me.el.getBox(),
                //get the current view width and height
                viewWidth = Ext.Element.getViewportWidth(),
                viewHeight = Ext.Element.getViewportHeight(),
                i = 0,
                config = false,
                from, to, clone;
            
            //where the element should start (if animation)
            from = {
                right: {
                    x: box.right,
                    y: viewHeight,
                    width: (viewWidth - box.right),
                    height: 0
                },
                left: {
                    x: 0,
                    y: 0,
                    width: box.x,
                    height: 0
                },
                top: {
                    x: viewWidth,
                    y: 0,
                    width: 0,
                    height: box.y
                },
                bottom: {
                    x: 0,
                    y: (box.y + box.height),
                    width: 0,
                    height: (viewHeight - (box.y + box.height)) + 'px'
                }
            };
            
            //where the element needs to finish
            to = {
                right: {
                    x: box.right,
                    y: box.y,
                    width: (viewWidth - box.right) + 'px',
                    height: (viewHeight - box.y) + 'px'
                },
                left: {
                    x: 0,
                    y: 0,
                    width: box.x + 'px',
                    height: (box.y + box.height) + 'px'
                },
                top: {
                    x: box.x,
                    y: 0,
                    width: (viewWidth - box.x) + 'px',
                    height: box.y + 'px'
                },
                bottom: {
                    x: 0,
                    y: (box.y + box.height),
                    width: (box.x + box.width) + 'px',
                    height: (viewHeight - (box.y + box.height)) + 'px'
                }
            };
            
            //reverse the objects
            if (reverse) {
                clone = Ext.clone(from);
                from = to;
                to = clone;
            }
            
            if (animate) {
                Ext.Array.forEach(['right', 'left', 'top', 'bottom'], function (side, i, all) {
                    /* FIX for missing callback support */
                    var listeners = i < all.length - 1 ? {} : {
                        listeners: {
                            afteranimate: {
                                fn: callback || Ext.emptyFn,
                                scope: scope
                            }
                        }
                    }
                    
                    me[side].setBox(from[side]);
                    me[side].animate(Ext.apply({
                        duration: me.duration,
                        easing: me.easing,
                        to: to[side],
                    }, listeners));
                },
                this);
            } else {
                Ext.Array.forEach(['right', 'left', 'top', 'bottom'], function (side) {
                    me[side].setBox(Ext.apply(from[side], to[side]));
                    me[side].repaint();
                    
                    /* FIX for missing callback support */
                    if (callback) {
                        callback.apply(scope); // not sure what arguments to pass here
                    }
                },
                this);
            }
        }
    });
    The change I've made is in the applyBounds method in the bit inside if (animate) to define the listeners variable only if it is the last item in the array being animated, and to apply the callback at that time only.

    Apologies, but if you can update the version in SVN with this (or improve it if there is a better way) that would be appreciated!

    Also, I've created a Sencha fiddle to demonstrate the fix:
    https://fiddle.sencha.com/#fiddle/hei

    And, I've reported it to Sencha:
    http://www.sencha.com/forum/showthre...ot-implemented
  5. #5
    The changes to the fix has been committed in the revision 6295 (trunk). It goes to 3.1.0 beta. Thank you!

    Also thank you for the bug report to Sencha. I have re-opened the GiHub issue and labeled as "review" that means that the issue has been fixed in SVN and it needs to review if Sencha fixes it by their own.

    As for ResourcePlaceHolder, please start a new forum thread to keep the things about Spotlight in the current thread. I didn't expect my small comment will cause a discussion:)
  6. #6
    Thanks Daniil,

    I too was wondering about whether I should have created another thread about the resource placeholder. I will do so shortly. Thanks!
  7. #7
    Hi! I was just reviewing this forum thread and probably it shouldn't be still marked as open, as the functionality has been merged to Ext.NET for some time now.

    Something else that confused me is that the code sample provided implements the callback functionality but... does not actually use it? :)

    I've reviewed the code sample in an approach to take advantage of the callbacks in the same scenario... Unless I'm completely lost here, that should be one of the ways to use the callback with the spotlight's show() and hide() methods.

    <%@ Page Language="C#" %>
     
    <!DOCTYPE html>
     
    <html>
    <head runat="server">
        <title>Spotlight - with fix</title>    
     
        <script>
            var updateSpot = function (cmp) {
                // Pass updateButtons as a callback to the show() method.
                App.Spot.show(cmp.el, function () { updateButtons(cmp); });
            };
     
            var hideSpot = function () {
                if (App.Spot.active) {
                    // Pass updateButtons as a callback to the hide() method.
                    // Here we can just point the method, as no argument needs to be passed thru.
                    App.Spot.hide(updateButtons);
                }
            };
     
            var getButton = function (panel) {
                return panel.dockedItems.last().items.first();
            };
     
            var updateButtons = function (cmp) {
                cmp = cmp || {};
                getButton(App.Panel1).setDisabled(cmp.id != App.Panel1.id);
                getButton(App.Panel2).setDisabled(cmp.id != App.Panel2.id);
                getButton(App.Panel3).setDisabled(cmp.id != App.Panel3.id);
                App.Status.setHtml('Status: Updated at ' + Date());
            };
        </script>
    </head>
    <body>
        <form runat="server">
            <ext:ResourceManager ID="ResourceManager1" runat="server">
                <Listeners>
                    <DocumentReady Handler="updateButtons();" />
                </Listeners>
            </ext:ResourceManager>
            
            <h1>Spotlight</h1>
     
            <p>This control allows you to restrict input to a particular element by masking all other page content.</p>
            
            <ext:Spotlight ID="Spot" runat="server" Easing="EaseOut" Duration="1500">
                
            </ext:Spotlight>
            
            <ext:Button runat="server" Text="Start">
                <Listeners>
                    <Click Handler="updateSpot(App.Panel1);" />
                </Listeners>
            </ext:Button>
            
            <ext:Panel runat="server" Border="false" Layout="TableLayout" ShrinkWrap="Width" Height="200">
                <LayoutConfig>
                    <ext:TableLayoutConfig Columns="3" />
                </LayoutConfig>
                <TopBar>
                    <ext:StatusBar runat="server">
                        <Items>
                            <ext:Component ID="Status" runat="server" Html="Status: Page loaded." />
                        </Items>
                    </ext:StatusBar>
                </TopBar>
                <Items>
                    <ext:Panel ID="Panel1" runat="server" 
                        Frame="true"
                        Title="Demo Panel"
                        Width="200"
                        Height="150"
                        Html="Some panel content goes here!" 
                        PaddingSummary="10px 15px">
                        <Buttons>
                            <ext:Button ID="Button1" runat="server" Text="Next Panel">
                                <Listeners>
                                    <Click Handler="updateSpot(App.Panel2);" />
                                </Listeners>
                            </ext:Button>
                        </Buttons>
                    </ext:Panel>
                    <ext:Panel ID="Panel2" runat="server" 
                        Frame="true"
                        Title="Demo Panel"
                        Width="200"
                        Height="150"
                        Html="Some panel content goes here!" 
                        PaddingSummary="10px 15px">
                        <Buttons>
                            <ext:Button ID="Button2" runat="server" Text="Next Panel">
                                <Listeners>
                                    <Click Handler="updateSpot(App.Panel3);" />
                                </Listeners>
                            </ext:Button>
                        </Buttons>
                    </ext:Panel>
                    <ext:Panel ID="Panel3" runat="server" 
                        Frame="true"
                        Title="Demo Panel"
                        Width="200"
                        Height="150"
                        Html="Some panel content goes here!" 
                        PaddingSummary="10px 15px">
                        <Buttons>
                            <ext:Button ID="Button3" runat="server" Text="Done">
                                <Listeners>
                                    <Click Handler="hideSpot();" />
                                </Listeners>
                            </ext:Button>
                        </Buttons>
                    </ext:Panel>
                </Items>
            </ext:Panel>        
        </form>
    </body>
    </html>
    Notice the above code does not have the override as we expect it to already be in Ext.NET version, at least as of 4.2.1 (current release at the time of the writing).
    Fabrício Murta
    Developer & Support Expert
  8. #8
    Marked as fixed but, of course, we will be reviewing this every release to see whether Sencha has implemented the callback support by themselves (so we could remove the override).
    Fabrício Murta
    Developer & Support Expert

Similar Threads

  1. Show/Hide fieldset
    By JosefTrbusek in forum 2.x Help
    Replies: 2
    Last Post: Aug 03, 2012, 7:04 AM
  2. Replies: 3
    Last Post: Mar 19, 2012, 12:35 PM
  3. Replies: 0
    Last Post: Mar 03, 2011, 1:08 PM
  4. MessageBox does't show till after method
    By jarremw in forum 1.x Help
    Replies: 3
    Last Post: Jun 23, 2009, 4:08 AM

Posting Permissions