【问题标题】:Click menu item to highlight in Backbone View单击菜单项以在主干视图中突出显示
【发布时间】:2012-10-16 05:39:12
【问题描述】:

对于初学者来说这是一个简单的问题,但到目前为止我还没有看到适合我需要的解决方案。基本上我有一个简单的菜单,上面有ulli。有两个要求:

Req1:当点击一个时,li 将获得新的类.active

要求2:菜单项是动态的,这意味着我应该能够添加或删除任何菜单项(通过使用其他按钮)。

2 种方法可以做到这一点:

方法一:将每个MenuView作为MenuItem进行遍历

我有一个类似这样的 MenuView

el:  $('li'),

events: {
  "click" : "highlight"
},

highlight: function(e) {
  thisParent = $(e.target).parent();
  thisParent.siblings('.active').removeClass('active');
  thisParent.addClass('active');
},

专业版:简单。这就是我现在所拥有的。

Con:对html结构的依赖。如果它改为div,而不是多层怎么办。

方法 2:MenuCollection 的一个视图

创建一个 MenuItemCollection 并为该集合使用 MenuView。 MenuView 的el 将是ul(而不是li)。 HTML 看起来像这样,带有单独的 id:

<ul>
    <li id="leftmenu-one">one</li>
    <li id="leftmenu-two">two</li>
    <li id="leftmenu-three">three</li>
</ul>

然后当检测到点击事件时,做两件事:

2a.删除ul li中的所有.active

2b..active 类添加到e.target DOM

专业:解耦html设计

缺点:更多的代码。

问题:我想大多数人会说方法1不好。但是有没有更好的方法3、4、5...?如何处理新增菜单项?

【问题讨论】:

  • 您的代码有点过于依赖 HTML 结构。您不应该在 Backbone 中进行那么多 DOM 遍历,因为每个视图都包含它自己的 HTML 引用。与其遍历 DOM,不如隔离正确的 View,然后让它处理较小规模的遍历。一个很好的方法是使用 Backbone 事件。
  • @CoryDanielson 是的,我知道并且正在检查其他创造性的替代方案。某些原因您在下面的答案已被删除。可以转发吗?我很想保留它以供以后学习。
  • 好的,我取消删除它。我的和其他答案之间的唯一区别是我没有 active 作为模型的一部分,因为我真的不认为这是可以保存在任何数据库中的业务数据。它会是一个浪费的专栏.. 它更多的是 UI/视图。因此,我没有 change:active,而是抛出了一个自定义事件。
  • 我也有 Req2 在那里(菜单项是动态的,这意味着我应该能够添加或删除任何菜单项(通过使用其他按钮)。)
  • 反过来。每个 menuItemView 都在 menuView 中。内存不是问题,一切都是参考

标签: javascript jquery html css backbone.js


【解决方案1】:

创建菜单项模型

    var MenuItem = Backbone.Model.extend({
        title: 'Default Title',
        isSelected: false
    });

和项目集合,将监听任何模型选择更改事件

   var MenuItemCollection = Backbone.Collection.extend({
        model: MenuItem,

        initialize: function() {
            this.on('change:isSelected', this.onSelectedChanged, this);
        },

        onSelectedChanged: function(model) {
            this.each(function(model) {
                if (model.get('isSelected') === true && !model.hasChanged('isSelected')) {
                    model.set({isSelected: false});
                }
            });
        }
    });

之后为菜单项创建一个视图

   var MenuItemView = Backbone.View.extend({
        tagName:  'li',
        events: {
          'click' : 'highlight'
        },

        initialize: function() {
            _.bindAll(this);
            this.model.on('change:isSelected', this.onSelectedChanged);
        },

        render: function() {
            this.$el.text(this.model.get('title'));
            return this;
        },

        onSelectedChanged: function() {
            if (this.model.get('isSelected') === true) {
                this.$el.addClass('active');
            }
            else {
                this.$el.removeClass('active');
            }
        },

        highlight: function() {
            this.model.set({isSelected: true});
        }
    });

和菜单本身一样

   var MenuView = Backbone.View.extend({
        tagName:  'ul',

        initialize: function() {
            _.bindAll(this);
        },

        render: function() {
            this.collection.each(function(model) {
                var item = new MenuItemView({model: model});
                this.$el.append(item.render().el);
            }, this);

            return this;
        }
    });

http://jsfiddle.net/Kf3SS/http://jsfiddle.net/Kf3SS/使用 cmets 的完整工作 js 摆弄

【讨论】:

  • 这很聪明。所以让我确保我理解你的逻辑:看起来这个想法是利用模型的change 事件,对吧? click 事件将模型设置为active 触发change 事件以使用hasChanged() 作为逻辑设置旧模型的isSelected:false
  • 是的,在这种情况下,将选择逻辑(实际上是菜单项的业务逻辑的一部分)从视图中继到模型/集合是合理的,让视图有机会相应地重绘自身模型状态。另一个优点是您将保留与项目计数或任何其他“onclick”项目逻辑无关的此类行为。尽管通常在这种情况下,主干实现在简单性上不如纯 jquery 实现。
  • 太棒了!我喜欢这种方法。使用 Marionette Collection View 和 collectionEvents 会更好。
【解决方案2】:

JSFiddle Demo w/ code comments

在您的视图中存在一些您最想避免的 DOM 遍历......因为这个示例非常小,不会破坏用户体验,但它是一个最佳实践和所有这些好东西。

我认为这对您来说应该是一个很好的起点/示例。

变量设置

var MenuView
,   MenuItemView
,   MenuItemModel
,   menu          //MenuView Instance
,   menuItem      //MenuItemView Instance
;

MenuItemModel

MenuItemModel = Backbone.Model.extend({
    'defaults': {
        'url': undefined
    ,   'text': undefined
    }
});

菜单视图(

MenuView = Backbone.View.extend({
    'tagName': 'ul'
,   'id': 'MenuView'
,   'menuItems': []
,   'activeButton': undefined
,   'initialize': function ( menuObjJSON, parent ) {
        var menuItem;

        for ( menuItemIndex in menuObjJSON ) {
            this.addMenuItem( new MenuItemModel(menuObjJSON[menuItemIndex]) )
        }
        this.render(parent);
    }
,   'render': function ( parent ) {
        $(parent).append(this.$el);
    }
,   'addMenuItem': function ( model ) {
    var menuItem = new MenuItemView({
            'model': model, 
            'parentElement': this.$el
        });
        menuItem.on('changeActive', this.setActiveButton, this);
        this.menuItems.push( menuItem );
        return menuItem;
}
,   'removeMenuItem': function ( identifier ) {
        var i, menuItem, length = this.menuItems.length, menuItemsCopy;
        for (i = 0; i < length; i++) {
            if ( this.menuItems[i] ) {
                menuItemView = this.menuItems[i];
                if ( menuItemView.model.get('text').toLowerCase() === identifier.toLowerCase() 
                     || menuItemView.model.get('url').toLowerCase() === identifier.toLowerCase() ) 
                {
                    menuItemView.destroy();
                    debugger;
                    menuItemsEnd = this.menuItems.slice(i+1, length);
                    this.menuItems = [].concat(this.menuItems.slice(0,i), menuItemsEnd);
                    return true;
                }
            }
        }
        return false; //if menu item not found
    }
,   'setActiveButton': function ( activeMenuItem ) {
        if ( this.activeButton ) {
            this.activeButton.removeHighlight();
        }
        this.activeButton = activeMenuItem;    
    }
});

MenuItemView (

  • )
    MenuItemView = Backbone.View.extend({
        'tagName': 'li'
    ,   'className': 'menuItem'
    ,   'events': { 
            'click a': 'highlight'
        }
    ,    'initialize': function ( options ) {
            this.render(options.model, options.parentElement);
        }
    ,   'render': function ( model, parentElement ) {
            this.$el.append("<a href='" + model.get('url')+ "'>" + model.get('text') + "</a>");
            parentElement.append(this.$el);
        }
    ,   'highlight': function ( event ) {
            if ( !this.$el.hasClass('active') ) {
                this.trigger('changeActive', this);
                this.$el.addClass('active');
            }
        }
    ,   'removeHighlight': function () {
            this.$el.removeClass('active');
        }
    ,   'destroy': function () {
            this.unbind('click a');
            this.remove(); //unbind from DOM, remove DOM events
        }
    });
    

    菜单实例化

    menu = new MenuView([
        {'url': '#home',    'text': 'Home'}
    ,   {'url': '#catpics', 'text': 'Cat Pics'}
    ,   {'url': '#dogpics', 'text': 'Dog Pics'}
    ,   {'url': '#about',   'text': 'About Us'}
    ], $('body'));
    

    添加新的菜单项。 (不是最实际的例子,而是一个起点)

    setTimeout(function(){
        menu.addMenuItem({'url': '#contact', 'text': 'Contact Us'});
    }, 1000);
    
    setTimeout(function(){
        menu.addMenuItem({'url': '#Login', 'text': 'Log In'});
    }, 2000);
    
    setTimeout(function(){
        menu.addMenuItem({'url': '#W3bm4573r', 'text': 'W3bm4573r'});
    }, 3000);
    
    setTimeout(function(){
        menu.addMenuItem({'url': '#something', 'text': 'Something'});
    }, 4000);
    

    删除菜单项(不是最实际的例子,而是一个起点)

    setTimeout(function(){
        menu.removeMenuItem('#contact');
    }, 5000);
    
    setTimeout(function(){
        menu.removeMenuItem('about us');
    }, 5000);
    
    setTimeout(function(){
        menu.removeMenuItem('#SOMETHING');
    }, 5000);
    
  • 【讨论】:

    • 对不起,我很难理解这里的click 事件逻辑。所以当click 发生时,会调用highlight 来添加active 类。但是changeActive 事件如何最终触发旧的活动菜单项的removeHighlight?我想我不明白setActiveButton 中的部分。谢谢。
    • MenuView 拥有每个 MenuItemView 并监听每个的 changeActive 事件。当他们触发 changeActive 事件时,他们将this(视图本身)作为参数传递。当 MenuView 看到这个事件时,它告诉this.activeButton 移除高亮,然后将新视图(触发事件的那个)保存到 this.activeButton
    • menuItem: this.trigger('changeActive', this); .......... menu: menuItem.on('changeActive', this.setActiveButton, this); and 'setActiveButton': function ( activeMenuItem ) {} .......... this 来自 menuItem进入 setActiveButton 作为 activeMenuItem。菜单移除旧菜单上的高亮,然后覆盖this.activeButton中的新菜单
    • aaronhardy.com/javascript/…(JavaScript 架构:Backbone.js 事件)Aaron Hardy //////////////////addyosmani.github.com/backbone-fundamentals/#thebasics-events(开发 Backbone.js应用程序 - 事件)Addy Osmani
    • 查看demo的控制台日志,可能会有所帮助。
    【解决方案3】:

    我希望这可能对您有所帮助。使用这个 jquery

    $("ul li").click(function() {
    $("ul li").removeClass("active");
    $(this).addClass("active");
    });
    

    Demo Link

    【讨论】:

    • 对,我可以单独使用 jQuery,但整个想法是将菜单抽象为 View 对象。
    • 简而言之,@HP。希望活动项是基于活动视图,而不是基于点击事件。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多