cssmagic / action

Easy and lazy solution for click-event-binding.
95 stars 32 forks source link

My Story / 我的故事 #11

Open cssmagic opened 9 years ago

cssmagic commented 9 years ago

Video / 视频

Slides / 幻灯片

此分享的幻灯片实验性地采用了纯 JS 代码的形式。


// MY STORY ABOUT CLICK-EVENT-BINDING

// @CSSMAGIC

// BACKGROUND

// ONE DAY...

// REQUIREMENT: LUCKY DRAW?

var btn = document.getElementById('lucky-draw')

btn.onclick = function () {
    BX.luckyDraw()
}

// TRACKING?

btn.onclick = function () {
    BX.luckyDraw()
    BX.track('lucky-draw')
}

// BUT...

btn.onclick = function () {
    BX.luckyDraw()
}

// some code...

btn.onclick = function () {
    BX.track('lucky-draw')
}

btn.addEventListener('click', function () {
    BX.luckyDraw()
}, false)

// some code...

btn.addEventListener('click', function () {
    BX.track('lucky-draw')
}, false)

// A/B TEST?

// HALF OF USERS HAVE NO BTN?

var btn = document.getElementById('lucky-draw')

// A FIFTY-FIFTY CHANCE:
btn === null

if (btn) {
    btn.addEventListener('click', function () {
        BX.luckyDraw()
    }, false)
}

// some code...

if (btn) {
    btn.addEventListener('click', function () {
        BX.track('lucky-draw')
    }, false)
}

// "USE JQUERY, YOU IDIOT!"

var $btn = $('#lucky-draw')
$btn.on('click', function () {
    BX.luckyDraw()
})

// some code...

$btn.on('click', function () {
    BX.track('lucky-draw')
})

// SO FAR SO GOOD!

// BUT...

// SPA?

// HTML LAZY LOAD?

// HTML RENDERED BY JS?

$('.tabs > .surprise').on('click', function () {
    var htmlTab = [
        '<div>',
            '<button id="lucky-draw">Lucky Draw</button>',
        '</div>'
    ].join('')
    $('.tab-panels > .surprise').html(htmlTab)

    // EVENT-BINDING HERE...
})

// WHAT A MESS...

// EVENT DELEGATION?

$('body').on('click', '#lucky-draw', function () {
    BX.luckyDraw()
})

// WHAT IF MORE AND MORE BTNS?

$body = $('body')
$body.on('click', '#lucky-draw', function () {
    BX.luckyDraw()
})

$body.on('click', '#some-btn', function () {
    // do something...
})
$body.on('click', '#another-btn', function () {
    // do something else...
})
// ...

// NEED TO IMPROVE THIS!

// GROUP ALL THESE EVENT DELEGATION?

// FIRST, THE SAME CLASS NAME.

var btn1 = '<button class="action" id="lucky-draw">Lucky Draw</button>'
var btn2 = '<button class="action" id="some-action">Button</button>'
var link1 = '<a href="#" class="action" id="another-action">Link</a>'
var link2 = '<a href="#" class="action" id="another-action-2">Link</a>'

// THEN, ONLY ONE LISTENER.

$body.on('click', '.action', function () {

    // WHEN CLICK ANY '.action', WE COME HERE.

})

$body.on('click', '.action', function () {

    // DISPATCH ACTION
    // USE `id` TO IDENTIFY EACH BTN...

    switch (this.id) {
        case 'lucky-draw':
            BX.luckyDraw()
            break
        case 'some-btn':
            // do something...
            break
        // ...
    }

})

// `ID`?

// WHY NOT HTML5 DATASET?

// USE A CUSTOM DATA ATTRIBUTE -- 'data-action'!

var btn1 = '<button class="action" data-action="lucky-draw">Lucky Draw</button>'
var btn2 = '<button class="action" data-action="some-action">Button</button>'
var link1 = '<a href="#" class="action" data-action="another-action">Link</a>'
var link2 = '<a href="#" class="action" data-action="another-action-2">Link</a>'

// WAIT, CLASS NAMES ARE UNNECESSARY!

// SIMPLIFIED!

var btn1 = '<button data-action="lucky-draw">Lucky Draw</button>'
var btn2 = '<button data-action="some-action">Button</button>'
var link1 = '<a href="#" data-action="another-action">Link</a>'
var link2 = '<a href="#" data-action="another-action-2">Link</a>'

$body.on('click', '[data-action]', function () {
    var actionName = $(this).data('action')

    switch (actionName) {
        case 'lucky-draw':
            BX.luckyDraw()
            break
        case 'some-btn':
            // do something...
            break
        // ...
    }
})

// NO UGLY 'switch'!

var actionList = {
    'lucky-draw': function () {
        BX.luckyDraw()
    },
    'some-btn': function () {
        // do something...
    }
    // ...
}

$body.on('click', '[data-action]', function () {
    var actionName = $(this).data('action')
    var action = actionList[actionName]

    if ($.isFunction(action)) action()
})

// DEFINE MORE ACTION LATER?

// HTML
$body.append('<a href="#" data-action="more">Link</a>')

// JS
$.extend(actionList, {
    'more': function () {
        // ...
    }
})

// MAYBE THIS FITS MORE PEOPLE...

// OPEN-SOURCE IT!

// `ACTION`
// EASY AND LAZY SOLUTION FOR CLICK-EVENT-BINDING.

// GITHUB: CSSMAGIC/ACTION

// API

// DEFINE ACTIONS
action.add({
    'my-action': function () {
        // ...
    }
    // ...
})

// TRIGGER ACTION MANUALLY
action.trigger('my-action')

// WHO'S USING IT?

// CMUI USES `ACTION` AS A CORE SERVICE.
// GITHUB: CMUI/CMUI

// EVERYONE USING CMUI IS USING `ACTION`:

// - M.BAIXING.COM
// - M.VICHY.COM.CN
// - M.UEMALL.COM

// USE CASE: CMUI

CMUI.dialog = {
    template: [
        '<div class="dialog">',
            '<a href="#" data-action="close-dialog">×</a>',
            '<h2><%= data.title %></h2>',
            '<div class="content"><%- data.html %></div>',
        '</div>'
    ].join(''),

    init: function () {
        action.add({
            'close-dialog': function () {
                $(this).closest('.dialog').hide()
            }
        })
    },
    open: function (config) {
        var html = render(this.template, config)
        $(html).appendTo('body').show()
    }
}

// NO EVENT-BINDING CODE NEEDED!

// Q & A

// THX!

  • 幻灯片内的所有代码均为示意代码。
  • 关于 Action 的实际实现,请参阅文档和源码。
litson commented 9 years ago

很高兴,其实我一直也会这么写,并且会为actionList里的每一个属性(action)重新指定上下文及event; 类似于:

actionList[actionName] && actionList[actionName].call(this,event);

好处是: 我们会有一些场景需要操作触发该action的元素; 坏处是: trigger的时候,[context]就会出错,而且代码阅读起来会比较混乱;

请问有没有更好的方式解决这个问题?比如trigger的时候自动绑定正确的上下文?

cssmagic commented 9 years ago

@litson 谢谢留言。

trigger的时候,[context]就会出错,而且代码阅读起来会比较混乱; 请问有没有更好的方式解决这个问题?比如trigger的时候自动绑定正确的上下文?

Action 目前的设计可以参考这里(#19),最后一节讨论了 .trigger() 方法的使用问题。

anhulife commented 9 years ago

个人觉得Backbone里面的event map也比较方便

events: {
  'click [data-action="my-action"]': 'doMyAction'
}

而且上下文是自动绑定

cssmagic commented 9 years ago

@anhulife 谢谢评论。

Backbone 的这种机制自然是极好的,但不是每个项目都会使用或适合使用 Backbone。Action 专注做这一件事,而且它要解决的问题十分通用,使得它适用于大多数项目。而且 Action 的体积十分小巧,minify + gzip 后只有 500 多字节,对任何项目来说几乎都是零负担。

关于 “上下文自动绑定”,Action 也是这样设计的。参见 #19。