Skip to content

Instantly share code, notes, and snippets.

@rpominov
Created November 16, 2012 21:29
Show Gist options
  • Save rpominov/4091094 to your computer and use it in GitHub Desktop.
Save rpominov/4091094 to your computer and use it in GitHub Desktop.
Thoughts on javascript modules.

Зачем?

  • Порядок в коде
  • Упрощение работы с DOM
  • Упрощение инициализации
  • Упрощение и стандартизация общения между модулями
  • Модуль — независимая сущность, со всеми вытекающими

Принципы

Модуль — это объект, точнее класс. Но модуль ≠ js-класс и объект модуля ≠ js-объект (хотя ...)

Модуль независим, его границы очевидны, а не висят в воздухе как негласная договоренность. Четко видно где модули общаются.

Ниша: среднее количество js — backbone/knockout рано, но что-то уже нужно.

Состав

  • Элементы
    • Статические
    • Динамические
  • События
    • DOM-события на элементах модуля
    • DOM-события на внешних элементах — "глобальные"
    • События модулей
  • Инициализация
    • При загрузке
    • Ленивая
  • Настройки
    • Дефолтные
    • В data-атрибутах у корневого элемента
    • При создании объекта модуля вручную

В билдере

M.defaultSettings()
M.tree()
M.events()
M.globalEvents()
M.modulesEvents()
M.methods()
M.init()

В объекте модуля

@root
@find()
@%element_name%
@%dynamic_element_name%()
@updateTree()

@on()
@off()
@fire()

@%method_name%()

@settings
@setOption()

Чего не будет

  • создания jquery-плагина из модуля
  • модули с множественными корнями
  • пометка метода как tree-updater

CSS-селектор → имя атрибута

  • divdiv
  • @buttonbutton
  • .buttonbutton
  • @button abuttonA
  • @my-buttonmyButton
  • input[type=text]inputTypeText

Split to words (delimetr is all not letters and not digits characters) then join words in mixedCase notation.

module 'AjaxValidation', (M) ->
M.tree """
form
"""
M.events """
ajax:error root showErrors
ajax:beforeSend root hideErrors
"""
M.methods
showErrors: (__1, __2, errors) ->
for error in errors
input = @find '[name="' + error.field + '"]'
# ...
hideErrors: ->
# ...
M.init 'lazy'
# Overview
# ------------------------------
# define module
module 'SpaceShipControl.Stupid', (M) ->
privateMethod = -> '^___^'
# Inheritance
M.extend 'another.module'
# set some settings
M.settings '$plugin', true # create JQuery plugin
M.settings
'auto-init': '@stupid' # initialize module on all @stupid elements when page loaded
# define dom elements that module use
M.elements '@foo', '@bar'
# define module methods
M.method 'hello', (e) ->
@foo.html 'hello ' + @sum(1, 2) + privateMethod()
@fire 'hello-said' # fire event on module instance
M.method 'sum', (a, b) ->
a + b
# bind to DOM event
M.event 'click', '@bar', 'hello'
# DOM
# ------------------------------
module 'treeExample', (M) ->
M.tree """
@root
@foo
@bar
@baz
"""
# ...
module 'treeExample', (M) ->
M.tree """
%root
@foo
@baz
"""
# ...
Foo = module (m) ->
m.elements
submit: '[type=submit]'
agree: '[name=agree]'
m.elements ['.delete a', 'buttom.close']
m.element 'img@photo'
m.method 'foo', ->
@submit
@agree
@deleteA
@buttonClose
@imgPhoto
# ...
M.tree """
ul.buttons
li .button /button /dynamic
"""
M.events """
click button addButton
"""
M.methods
addButton: ->
@root.append '<li><span class="button">add ' + @count() + '</span></li>'
count: ->
@button().length
# Initialisation
# ------------------------------
M.init 'load' # $ -> ..., $(document).on 'html-inserted', -> ...
M.init 'lazy' # listen for module events on document, create instances on any event fired
M.init 'none' # (default)
M.init 'create' # ??? create module elemnts on DOM
# Events
# ------------------------------
# think about selector dublication
# DOM events
M.event 'click buttonClose', 'close'
M.events
"click foo": 'bar'
"change foo": -> # ...
"click .button", 'bar' # automatic selector or attr identification?
# `this` in handler — module instance or DOM element?
# Module events
moduleInstance.on 'abc', (event_parametrs) -> # here this == moduleInstance
moduleInstance.off 'abc' [, -> # ...]
moduleInstance.fire 'abc' [, event_parametrs...]
M.modulesEvents """
event-name anotherModule myHandler
"""
# Modules interaction
# ------------------------------
module 'A', (M) ->
M.tree """
@A
@button
"""
M.events """
click button fireFoo
"""
M.methods
hide: -> @root.hide()
fireFoo: -> @fire 'foo'
M.init 'load'
module 'B', (M) ->
M.modulesEvents """
foo A hideA
"""
M.methods
hideA: (a) -> a.hide()
M.init 'lazy'
# VS
class A
constructor: (@root) ->
@root.on 'click', '@button', =>
@root.trigger 'foo'
hide: -> @root.hide()
$ ->
for el in $ '@A'
$(el).data 'A', new A $(el)
$(document).on 'foo', '@A', ->
$(this).data('A').hide()
# Lambda module
# ------------------------------
foo = module (M) ->
M.init 'lazy'
M.root '@spoiler'
M.click '@button', -> @find('@content').toggle()
# Global events
# ------------------------------
module (x) ->
x.root '@scroll'
x.init 'lazy'
x.globalEvent 'window', 'scroll', ->
@root.html $(document).scrollTop()
module (x) ->
# ...
x.initializer ->
# Can't lazy init in this case
$(window).scroll # ...
# Work with 3rd party custom event generator
# ------------------------------
module (m) ->
m.root '@scroll'
m.init 'load'
m.initializer ->
ScrollUtil.scroll (scrollTop) =>
@root.html scrollTop
# VS
$ ->
root = $ '@scroll'
ScrollUtil.scroll (scrollTop) ->
root.html scrollTop
# Page logic
# ------------------------------
page "controller#action", ->
a = module (m) ->
# ...
page "posts#index posts#show", ->
# ...
class EqualColumnHeight
constructor: (@container) ->
@update()
setTimeout @update, 100 # после того как инициализировались другие плагины
@container.on 'height-changed', @update # другие плагины могут генерировать это событие
update: =>
elements = @container.find '@equal'
elements.css('height': 'auto', 'min-height': 0)
max = Math.max ($(c).outerHeight() for c in elements when not $(c).data 'ignore')...
for c in elements
$(c).css (if $(c).data 'ignore' then 'height' else 'min-height'), max
initialize class: EqualColumnHeight, elements: '@equal-height', name: 'EqualColumnHeight'
# VS
module 'EqualColumnHeight', (M) ->
M.tree """
@equal-height
@equal
"""
M.events """
height-changed root update
"""
M.methods
initializer: ->
@update()
setTimeout (=> @update()), 100
update: ->
@equal.css('height': 'auto', 'min-height': 0)
max = Math.max ($(c).outerHeight() for c in @equal when not $(c).data 'ignore')...
for c in @equal
$(c).css (if $(c).data 'ignore' then 'height' else 'min-height'), max
M.init 'load+'
module 'utils.spoiler', (M) ->
M.tree """
@spoiler
@button
@content
"""
M.init 'lazy'
M.click '@button', -> @content.toggle()
# VS
module 'utils.spoiler', (M) ->
M.elements '@button', '@content'
M.root '@spoiler'
M.settings 'init', 'lazy'
M.method 'toggle', -> @content.toggle()
M.event 'click', '@button', 'toggle'
# VS
module 'utils.spoiler', (M) ->
M.tree """
@spoiler
@button
@content
"""
M.events """
click button toggle
"""
M.methods
toggle: -> @content.toggle()
M.init 'lazy'
# VS
$(document).on 'click', '@spoiler @button', ->
$(this).parents('@spoiler').find('@content').toggle()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment