Results 1 to 7 of 7

Thread: MVC: handling link clicks in the controller

  1. #1
    Sencha User a_arias's Avatar
    Join Date
    Aug 2011
    Location
    Florida
    Posts
    28

    Question MVC: handling link clicks in the controller

    I'm having problems with the following bits of code. I'm trying to create window location hash based navigation. I have a widget that contains a bunch of links. For some reason, I'm not able to bind events in the controller using this.control() the same way that I am for buttons.

    Is there a typo here that I'm not seeing, or am i just doing this all wrong?

    /ext-ui/app/view/Navigation.js:
    Code:
    Ext.define('FashionHelper.view.Navigation', {
        extend: 'Ext.Container',
        alias: 'widget.navigation',
        height: 70,
    
    
        autoRender: true,
        autoShow: true,
    
    
        initComponent: function(){
    
    
            this.layout = {
                type: 'vbox',
                align: 'center',
                pack: 'center',
            };
    
    
            this.items = [{
                xtype: 'container',
                width: '100%',
                height: 50,
                layout: {
                    type: 'hbox',
                    align: 'stretch'
                },
                items: [{
                    xtype: 'component',
                    flex: 1,
                    autoEl: {
                        tag: 'h1',
                        html: 'Fashion Helper'
                    },
                    action: 'locale'
                }, {
                    xtype: 'component',
                    flex: 2,
                    autoEl: {
                        tag: 'a',
                        href: '/',
                        html: 'Home'
                    },
                    action: 'home'
                }]
            }, {
                xtype: 'container',
                width: '100%',
                height: 50,
                layout: {
                    type: 'hbox',
                    align: 'stretch'
                },
                items: [{
                    xtype: 'component',
                    flex: 1,
                    autoEl: {
                        tag: 'a',
                        href: '#!/locale',
                        id: 'golocale',
                        html: 'Locale'
                    }
                }, {
                    xtype: 'component',
                    flex: 1,
                    autoEl: {
                        tag: 'a',
                        href: '#!/brand',
                        html: 'Brand'
                    }
                }, {
                    xtype: 'component',
                    flex: 1,
                    autoEl: {
                        tag: 'a',
                        href: '#!/size',
                        html: 'Size'
                    }
                }, {
                    xtype: 'component',
                    flex: 1,
                    autoEl: {
                        tag: 'a',
                        href: '#!/article',
                        html: 'Article'
                    }
                }, {
                    xtype: 'component',
                    flex: 1,
                    autoEl: {
                        tag: 'a',
                        href: '#!/gender',
                        html: 'Gender'
                    }
                }, {
                    xtype: 'component',
                    flex: 1,
                    autoEl: {
                        tag: 'a',
                        href: '#!/profile',
                        html: 'Profile'
                    }
                }]
            }];
    
    
            this.callParent(arguments);
        }
    });
    /ext-ui/app/controller/Navigation.js:
    Code:
    Ext.define('FashionHelper.controller.Navigation', {
        extend: 'Ext.app.Controller',
    
    
        views: ['Navigation'],
    
    
        init: function(){
            console.log('controller rendered');
            this.control({
                'navigation a': {
                    click: this.goLocale
                }
            });
        },
    
    
        goLocale: function(link){
            console.log('link clicked');
        }
    });
    Last edited by a_arias; 11 Nov 2011 at 12:49 PM. Reason: Correcting title

  2. #2
    Sencha User hendricd's Avatar
    Join Date
    Aug 2007
    Location
    Long Island, NY USA
    Posts
    5,966

    Default

    @a_arias--

    You need DOM Event support, but the app.Controller.control method only seeks Components (not the DOM elements you seek). Reference the Component first, then attach a delegated listener to it's 'el', intercepting all <a> click events at once and cleans up when the View is destroyed.

    Here is a direct (local to the controller) approach where you may be handling view creation yourself for some reason:

    Code:
    Ext.define('FashionHelper.controller.Navigation', {
        extend: 'Ext.app.Controller',
        views: ['Navigation'],
    
        refs : [ { ref : 'navigationView' , selector : 'navigation' } ],
    
        onLaunch : function( application ){
            console.log('controller launched');
    
            var domListen = {
                 fn          : this.goLocale,
                 element : 'el',  //indicates wait till after rendered !
                 delegate : "a[href^='#!']" , //tighten as necessary
                 scope     : this
             };
    
            //instantiate the view
            var view = this.getNavigationView({
                    autoCreate : true,
                    listeners : { click : domListen }
             }) ; 
    
            view.autoShow || view.show();
        },
    
        goLocale: function(e, target){
            
            console.log('link clicked');
        }
    });
    Alternatively, you could bind the delegated DOMListener to the view itself, and emit an event of interest (eg. 'navaction') so any controller can respond to it:

    Code:
    Ext.define('FashionHelper.view.Navigation', {
        extend: 'Ext.Container',
        alias: 'widget.navigation',
        height: 70,
        autoRender: true,
        autoShow: true,
    
        emitNavAction  : function(e, target) {
            return this.fireEvent('navaction', this, e, target);
        },
    
        initComponent: function(){
    
            this.layout = {
                type: 'vbox',
                align: 'center',
                pack: 'center'
            };
    
            this.listeners = {
                 click : {    //intercept qualified links
                     fn          : this.emitNavAction,
                     element : 'el',                   //indicates wait till after rendered !                  
                     delegate : "a[href^='#!']" ,     //tighten as necessary
                     scope     : this
                  }
            };
    
            this.items = [{
                xtype: 'container',
                width: '100%',  // <-- careful, not valid
                height: 50,
                layout: {
                    type: 'hbox',
                    align: 'stretch'
                },
                items: [ .... ]
    Then, the view controller gets simple again:
    Code:
    Ext.define('FashionHelper.controller.Navigation', {
        extend: 'Ext.app.Controller',
        views: ['Navigation'],
    
        refs : [ { ref : 'navigationView' , selector : 'navigation' } ],
    
       init : function( application ){
            console.log('controller init');
            this.control({
                 'navigation' : {
                       'navaction' : this.goLocale,
                       scope        : this
                 }
            });
        },
    
        goLocale: function(view, e, target){
            
            console.log('link clicked');
        }
    });
    This way you don't worry about render timing and your View controller AND/OR app.Application can handle such an event via the internal event-bus (I'm likin' that one!).

    Watch those extra commas
    Last edited by hendricd; 12 Nov 2011 at 2:29 PM. Reason: simplified and alternate approach
    "be dom-ready..."
    Doug Hendricks

    Maintaining ux: ManagedIFrame, MIF2 (FAQ, Wiki), ux.Media/Flash, AudioEvents, ux.Chart[Fusion,OFC,amChart], ext-basex.js/$JIT, Documentation Site.


    Got Sencha licensing questions? Find out more here.


  3. #3
    Sencha User a_arias's Avatar
    Join Date
    Aug 2011
    Location
    Florida
    Posts
    28

    Default this.getView('navigation') is null. What do?

    Hello hendricd,

    Thanks for your reply. Unfortunately your example did not work for me. I got the following error:

    Code:
    Uncaught TypeError: Cannot call method 'create' of null
    After a big of messing around with some example code provided my some helpful individuals on IRC, I kinda got it to work. In the example below I first capture all clicks on a tags and preventDefault. Then, I create another click handler specifically for the link that was clicked.

    /ext-ui/app/controller/Navigation.js:
    Code:
    Ext.define('FashionHelper.controller.Navigation', {
        extend: 'Ext.app.Controller',
    
        views: ['Navigation'],
    
        onLaunch: function(){
            console.log('controller rendered');
            
            Ext.getBody().on('click', function(evt, el, o) {
                console.dir(el);
            }, this, {
                delegate: 'a',
                preventDefault: true
            });
    
    
            Ext.get('golocale').on('click', function(evt, el, o){
                alert('woo');
            });
    
    
            // TODO: throw event for each link for this.control() to handle
    
    
        goLocale: function(link){
            console.log('locale clicked');
        }
    });
    Now, maybe I don't know any better, but I find this to be terribly inefficient. For one, I really don't want to capture all a tags. Can someone offer any suggestions that will help me improve this?

    Secondly, I was looking at the Ext.util.Observer and I think I should do something similar for my pseudo navigation link/components. I found what seems to be, at least to me, a good guide explaining how to handle events using the observable pattern.

    I guess the only thing I don't really understand about it is:

    How does this fit into the MVC architecture? What I mean is where would I store my class files and would it be possible to have them autoload? (I am using the bootstrap.js found in the examples)

  4. #4
    Sencha User a_arias's Avatar
    Join Date
    Aug 2011
    Location
    Florida
    Posts
    28

    Default I replied too soon

    Quote Originally Posted by a_arias View Post
    Hello hendricd,

    Thanks for your reply. Unfortunately your example did not work for me. I got the following error:

    Code:
    Uncaught TypeError: Cannot call method 'create' of null
    I see you you edited your reply as I was in the process of creating my reply. I will experiment with your new suggestions and let you know how it goes.

  5. #5
    Sencha User a_arias's Avatar
    Join Date
    Aug 2011
    Location
    Florida
    Posts
    28

    Talking emitNavAction and this.listeners is the way to go

    Ok, so after a bit of experimentation with your suggestion I seem to have a solution that I am happy with. Basically, as per your alternate suggestion I emit a custom event based on the id of the link that was clicked. Then, in the init method of the controller, I use this.control to catch the events and execute the appropriate handler.

    /ext-ui/app/view/Navigation.js:
    Code:
    Ext.define('FashionHelper.view.Navigation', {
        extend: 'Ext.Container',
        alias: 'widget.navigation',
        height: 70,
        autoRender: true,
        autoShow: true,
    
    
        emitNavAction: function(e, target){
            return this.fireEvent(target.id, this, e, target);
        },
        
        initComponent: function(){
    
    
            this.layout = {
                type: 'vbox',
                align: 'center',
                pack: 'center',
            };
    
    
            this.listeners = {
                click: {
                    fn: this.emitNavAction,
                    element: 'el',
                    delegate: 'a',
                    scope: this,
                    preventDefault: true
                }
            };
    
    
            this.items = [{
                xtype: 'container',
                width: '100%',
                height: 50,
                layout: {
                    type: 'hbox',
                    align: 'stretch'
                },
                items: [{
                    xtype: 'component',
                    flex: 1,
                    autoEl: {
                        tag: 'h1',
                        html: 'Fashion Helper'
                    },
                    action: 'locale'
                }, {
                    xtype: 'component',
                    flex: 2,
                    autoEl: {
                        tag: 'a',
                        href: '/',
                        html: 'Home'
                    },
                    action: 'home'
                }]
            }, {
                xtype: 'container',
                width: '100%',
                height: 50,
                layout: {
                    type: 'hbox',
                    align: 'stretch'
                },
                items: [{
                    xtype: 'component',
                    flex: 1,
                    id: 'golocale',
                    autoEl: {
                        tag: 'a',
                        href: '#!/locale',
                        html: 'Locale'
                    },
                    action: 'locale'
                }, {
                    xtype: 'component',
                    flex: 1,
                    id: 'gobrand',
                    autoEl: {
                        tag: 'a',
                        href: '#!/brand',
                        html: 'Brand'
                    }
                }, {
                    xtype: 'component',
                    flex: 1,
                    id: 'gosize',
                    autoEl: {
                        tag: 'a',
                        href: '#!/size',
                        html: 'Size'
                    }
                }, {
                    xtype: 'component',
                    flex: 1,
                    id: 'goarticle',
                    autoEl: {
                        tag: 'a',
                        href: '#!/article',
                        html: 'Article'
                    }
                }, {
                    xtype: 'component',
                    flex: 1,
                    id: 'gogender',
                    autoEl: {
                        tag: 'a',
                        href: '#!/gender',
                        html: 'Gender'
                    }
                }, {
                    xtype: 'component',
                    flex: 1,
                    id: 'goprofile',
                    autoEl: {
                        tag: 'a',
                        href: '#!/profile',
                        html: 'Profile'
                    }
                }]
            }];
    
    
            this.callParent(arguments);
        }
    });
    /ext-ui/app/controller/Navigation.js:
    Code:
    Ext.define('FashionHelper.controller.Navigation', {
        extend: 'Ext.app.Controller',
    
    
        views: ['Navigation'],
    
    
        refs : [{
            ref: 'navigationView',
            selector: 'navigation'
        }],
    
    
        init: function(application){
            console.log('controller init');
            this.control({
                'navigation': {
                    'golocale': this.goLocale,
                    'gobrand': this.goBrand,
                    'gosize': this.goSize,
                    'goarticle': this.goArticle,
                    'gogender': this.goGender,
                    'goprofile': this.goProfile,
                    scope: this
                }
            });
        },
    
    
        goLocale: function(link){
            console.log('locale clicked');
            console.dir(link);
        },
    
    
        goBrand: function(link){
            console.log('brand clicked');
        },
    
    
        goSize: function(link){
            console.log('size clicked');
        },
    
    
        goArticle: function(link){
            console.log('article clicked');
        },
    
    
        goGender: function(link){
            console.log('gender clicked');
        },
        
        goProfile: function(link){
            console.log('profile clicked');
        }
    });

  6. #6

    Default

    Thanks hendricd your suggestion worked

  7. #7
    Sencha User
    Join Date
    May 2013
    Posts
    1

    Default Doesn't work from Architect

    I am newbie to ExtJs, I am using Sencha Architect.

    I am unable to get this working, Architect doesn't allow to add code using text editor, but it gives almost all the features to achieve what you guys have mentioned . The initComponent : function() of Navigation in your edition has a lot of code to initialize the widget, but since architect doesn't allow modifying this section, but instead allows to add processNavigation() function like this:

    Code:
     initComponent: function() {
            var me = this;
    
    
            me.processNavigation(me);
            me.callParent(arguments);
        },
    
    
        processNavigation: function(config) {
    
    
        },

    The difference here is that I use the config parameter in function processNavigation instead of this. Other than everything else is same, I wonder why I am getting this error.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •