Na semana passada, eu fiz uma reescrita e limpeza bem completa do Minical, meu plugin jQuery datepicker. Eu o abri originalmente apenas para adicionar um pequeno recurso, mas rapidamente me desviei de como a base de código era ruim e quão melhor eu poderia escrevê-la agora (nota: isso acontece com tudo que eu já codifiquei ou projetei, e é completamente normal )
Então, acabei reescrevendo muito mais do que caberia em uma postagem de blog – mas aqui está um resumo de como reescrevi um método específico.
showCalendar: (e) ->
mc = if e then $(e.target).data("minical") else @
$other_cals = $("[id^='minical_calendar']").not(mc.$cal)
$other_cals.data("minical").hideCalendar() if $other_cals.length
return true if mc.$cal.is(":visible") or mc.$el.is(":disabled")
offset = if mc.align_to_trigger then mc.$trigger[mc.offset_method]() else mc.$el[mc.offset_method]()
height = if mc.align_to_trigger then mc.$trigger.outerHeight() else mc.$el.outerHeight()
position =
left: "#{offset.left + mc.offset.x}px",
top: "#{height + offset.top + mc.offset.y}px"
mc.render().css(position).show()
overlap = mc.$cal.width() + mc.$cal[mc.offset_method]().left - $(window).width()
if overlap > 0
mc.$cal.css("left", offset.left - overlap - 10)
mc.attachCalendarKeyEvents()
Esse era o showCalendar
método. É denso e desconcertante – claramente escrito por meu irmão gêmeo malvado de 2011, enquanto ele gargalhava malevolamente das profundezas de sua ilha-fortaleza vulcânica. showCalendar
tem uma tonelada de responsabilidades:
- use condicionalmente um
this
ou o armazenamento de dados do destino do evento para suathis
referência (porque às vezes é um manipulador) - ocultar outros calendários na página
- resgate se o calendário já estiver sendo exibido ou a entrada de texto estiver desativada
- posicionar-se
- reconstruir o elemento do calendário
- ajustar para sobreposição
- anexar eventos
Suspiro. Para piorar a situação, ele estava sendo chamado como um manipulador de eventos …
@$el.on("focus.minical click.minical", @showCalendar)
… diretamente, de dentro de uma função de manipulador keydown …
preventKeystroke: (e) ->
mc = @
if mc.$cal.is(":visible") then return true
key = e.which
keys =
9: -> true # tab
13: -> # enter
mc.showCalendar()
false
… e para reposicionar o calendário no redimensionamento da janela.
if @move_on_resize
$(window).resize(() ->
$cal = $(".minical:visible")
$cal.length && $cal.hide().data("minical").showCalendar()
)
São muitos lugares! Há implicações de desempenho aqui (reconstruir todo o calendário sempre que a janela é redimensionada é grosseiro) e o código é simplesmente confuso. Vamos consertar todas as coisas!
Em primeiro lugar, o posicionamento do calendário deve ser um método próprio. Vamos abstrair isso e referenciá-lo diretamente e, enquanto estamos nisso, vamos condensar o código de ajuste de sobreposição lá também. Ainda é prolixo, mas ei, o posicionamento é complicado. Pelo menos está em sua pequena área agora.
positionCalendar: ->
offset = if @align_to_trigger then @$trigger[@offset_method]() else @$el[@offset_method]()
height = if @align_to_trigger then @$trigger.outerHeight() else @$el.outerHeight()
position =
left: "#{offset.left + @offset.x}px",
top: "#{height + offset.top + @offset.y}px"
@$cal.css(position)
overlap = @$cal.width() + @$cal[@offset_method]().left - $(window).width()
if overlap > 0
@$cal.css("left", offset.left - overlap - 10)
@$cal
E faremos referência a ISSO em nosso evento de redimensionamento e nosso método showCalendar.
showCalendar: (e) ->
mc = if e then $(e.target).data("minical") else @
$other_cals = $("[id^='minical_calendar']").not(mc.$cal)
$other_cals.data("minical").hideCalendar() if $other_cals.length
return true if mc.$cal.is(":visible") or mc.$el.is(":disabled")
mc.render()
@positionCalendar().show()
mc.attachCalendarKeyEvents()
if @move_on_resize
$(window).on('resize.minical', $.proxy(@positionCalendar, @))
Ok, isso já está muito melhor. showCalendar
é muito mais curto e está sendo chamado uma vez a menos. Mas essa redefinição condicional de this
poderia ser feita de algumas maneiras diferentes que são muito mais claras. Fará muito mais sentido separar a funcionalidade do manipulador de eventos em seu próprio método. Faremos isso definindo um evento que chama showCalendar no contexto adequado.show.minical
@$cal.on("show.minical", $.proxy(@showCalendar, @))
Agora, todas as outras chamadas para showCalendar podem, em vez disso, acionar o evento no elemento e, por sua vez, showCalendar
só está sendo chamado em um único lugar.
Além disso, se fizermos o mesmo para o evento, podemos reduzir a funcionalidade “ocultar outros calendários” a uma linha, portanto, essas linhas de localização / dados / método …hide.minical
$other_cals = $("[id^='minical_calendar']").not(mc.$cal)
$other_cals.data("minical").hideCalendar() if $other_cals.length
… se tornar um único gatilho de evento.
$(".minical").not(@$cal).trigger('hide.minical')
Ei, nosso showCalendar
método é quase sensato agora:
showCalendar: ->
$(".minical").not(@$cal).trigger('hide.minical')
return if @$cal.is(":visible") or @$el.is(":disabled")
@render()
@positionCalendar().show()
@attachCalendarKeyEvents()
No entanto, ainda há um grande problema: o calendário é refeito incondicionalmente toda vez que é exibido. Poderíamos adicionar lógica para showCalendar
combater isso, mas não acho que showCalendar deva se preocupar se o calendário precisa ser redesenhado ou não. Afinal, é apenas responsável por mostrar o calendário.
Para corrigir isso, acabei reescrevendo o render
método e várias outras associações de eventos. Isso está fora do escopo desta postagem do blog, mas basta dizer que acabei com um novo highlightDay
método que é responsável por saber quando redesenhar:
highlightDay: (date) ->
# try and find the day to highlight
$td = @$cal.find(".#{date_tools.getDayClass(date)}")
# bail if the day is illegal
return if $td.hasClass("minical_disabled")
return if @to and date > @to
return if @from and date < @from
# rerender the proper month and call itself again if the day isn't found
if !$td.length
@render(date)
@highlightDay(date)
return
# highlight the day
klass = "minical_highlighted"
@$cal.find(".#{klass}").removeClass(klass)
$td.addClass(klass)
Então aqui está a encarnação atual de showCalendar
. ( selected_day
é uma variável interna registrando qual dia foi escolhido e escrito na entrada.)
showCalendar: (e) ->
$(".minical").not(@$cal).trigger('hide.minical')
return if @$cal.is(":visible") or @$el.is(":disabled")
@highlightDay(@selected_day)
@positionCalendar().show()
@attachCalendarEvents()
e.preventDefault()
showCalendar
agora tem menos da metade do tamanho original. Ele não precisa saber se ou quando o calendário está sendo redesenhado, ou mesmo se o dia em que deveria ser exibido é legal. É apenas responsável por destacar o elemento selecionado (se houver) e posicionar o elemento do calendário. Além disso, a highlightDay
implementação garante que o calendário seja renderizado apenas quando for necessário alternar meses. E há apenas uma condicional. Alívio!
Você pode verificar mais sobre o Minical no site do Minical Github . No geral, o processo de reescrita resultou em uma reescrita de cerca de 60% do plugin, com um aumento drástico na legibilidade. Tenho certeza de que o meu futuro não será tão louco no presente como o presente estava no passado.
(postado originalmente no Hashrocket Blog )