Bug: Prevent invalid UI states in markdown editor

When rapidly transitioning between various states, the markdown
editor could wind up with a weird mixture of UI elements showing as
some of transitions were dropped because they followed too quickly
on the heels of another transition.

This change fixes the problem by changing how the BaseController's
`hide` and `show` functions work. They now set a "target" state on
the element and, during each step of the animation, check whether
the target has changed. If so, the transition begins to work in
reverse to return to the prior state.
This commit is contained in:
Andrew Miner
2015-05-13 09:12:44 -07:00
parent 7a05862be2
commit a545628e04
2 changed files with 71 additions and 47 deletions
+65 -40
View File
@@ -41,25 +41,8 @@ module.exports = class BaseController extends backbone.View
hide: ($el)-> hide: ($el)->
$el ?= @$el $el ?= @$el
$el.data 'target-visibility', 'hidden'
if not $el.hasClass 'hideable' _.delay (=> @_adjustTargetVisibility $el), Duration.snap
$el.addClass 'hideable'
_.defer => @hide $el
return
if not ($el.hasClass('hiding') or $el.hasClass('hidden'))
logger.verbose => "#{this} is hiding \"#{$el.selector}\""
$el.addClass 'hiding'
@trigger Event.animate.hide.start, this, $el
@_onAnimationComplete $el, Duration.normal, => @hide $el
return
if $el.hasClass('hiding') and not $el.hasClass('hidden')
$el.removeClass 'hiding'
$el.addClass 'hidden'
logger.verbose => "#{this} is finished hiding #{$el.selector}"
@trigger Event.animate.hide.finish, this, $el
refresh: -> refresh: ->
logger.verbose => "#{this} refreshing" logger.verbose => "#{this} refreshing"
@@ -89,27 +72,8 @@ module.exports = class BaseController extends backbone.View
show: ($el)-> show: ($el)->
$el ?= @$el $el ?= @$el
$el.data 'target-visibility', 'visible'
if not $el.hasClass 'hideable' _.delay (=> @_adjustTargetVisibility $el), Duration.snap
$el.addClass 'hideable'
_.defer => @show $el
return
if $el.hasClass 'hidden'
logger.verbose => "#{this} is showing \"#{$el.selector}\""
$el.removeClass 'hidden'
$el.addClass 'hiding'
@trigger Event.animate.show.start, this, $el
_.defer => @show $el
return
if $el.hasClass 'hiding'
$el.removeClass 'hiding'
@_onAnimationComplete $el, Duration.normal, => @show $el
return
logger.verbose => "#{this} is finished showing #{$el.selector}"
@trigger Event.animate.show.finish, this, $el
unrender: -> unrender: ->
@undelegateEvents() @undelegateEvents()
@@ -205,6 +169,67 @@ module.exports = class BaseController extends backbone.View
# Private Methods ############################################################################## # Private Methods ##############################################################################
_adjustTargetVisibility: ($el)->
priorTarget = $el.data 'prior-target'
target = $el.data 'target-visibility'
target ?= 'visible'
$el.data 'prior-target', target
return if priorTarget is target and $el.data 'animating'
current = 'visible'
current = 'hiding' if $el.hasClass 'hiding'
current = 'hidden' if $el.hasClass 'hidden'
prior = $el.data 'prior-visibility'
$el.data 'prior-visibility', current
if target is current and current isnt prior
$el.removeData 'prior-visibility'
$el.removeData 'prior-target'
if target is 'visible'
@trigger Event.animate.show.finish, this, $el
logger.verbose => "#{this} is finished showing #{$el.selector}"
else if target is 'hidden'
@trigger Event.animate.hide.finish, this, $el
logger.verbose => "#{this} is finished hiding #{$el.selector}"
return if current is target
if not $el.hasClass 'hideable'
$el.addClass 'hideable'
_.defer => @_adjustTargetVisibility $el
if target is 'hidden'
if current is 'visible'
logger.verbose => "#{this} is hiding \"#{$el.selector}\""
$el.addClass 'hiding'
@trigger Event.animate.hide.start, this, $el
$el.data 'animating', true
@_onAnimationComplete $el, Duration.normal, =>
$el.removeData 'animating'
@_adjustTargetVisibility $el
else if current is 'hiding'
$el.removeClass 'hiding'
$el.addClass 'hidden'
_.defer => @_adjustTargetVisibility $el
else if target is 'visible'
if current is 'hidden'
logger.verbose => "#{this} is showing \"#{$el.selector}\""
$el.removeClass 'hidden'
$el.addClass 'hiding'
@trigger Event.animate.show.start, this, $el
_.defer => @_adjustTargetVisibility $el
else if current is 'hiding'
$el.removeClass 'hiding'
$el.data 'animating', true
@_onAnimationComplete $el, Duration.normal, =>
$el.removeData 'animating'
@_adjustTargetVisibility $el
_loadTemplate: (templateName)-> _loadTemplate: (templateName)->
if templateName? if templateName?
@_template = views[templateName] @_template = views[templateName]
@@ -82,6 +82,7 @@ module.exports = class MarkdownSectionController extends BaseController
onSaveClicked: (event)-> onSaveClicked: (event)->
event.preventDefault() event.preventDefault()
@model = @$textarea.val()
@state = State.waiting @state = State.waiting
@_endEditing() @_endEditing()
.then => .then =>
@@ -94,8 +95,7 @@ module.exports = class MarkdownSectionController extends BaseController
@state = State.viewing @state = State.viewing
onTextChanged: (event)-> onTextChanged: (event)->
event.preventDefault() @tryRefresh()
@model = @$textarea.val()
# Property Methods ############################################################################# # Property Methods #############################################################################
@@ -139,10 +139,9 @@ module.exports = class MarkdownSectionController extends BaseController
super super
onWillChangeModel: (oldModel, newModel)-> onWillChangeModel: (oldModel, newModel)->
result = super oldModel, newModel _.defer => @_resetToDefaultState()
@$textarea.val newModel @$textarea.val newModel
super oldModel, newModel
return result
# Backbone.View Overrides ###################################################################### # Backbone.View Overrides ######################################################################
@@ -173,14 +172,14 @@ module.exports = class MarkdownSectionController extends BaseController
return result return result
_updateSizer: -> _updateSizer: ->
text = @model text = @$textarea.val()
if @model? if @model?
text = text.replace /\n/g, '<br>' text = text.replace /\n/g, '<br>'
@$sizer.html text @$sizer.html text
_updatePreview: -> _updatePreview: ->
text = @model text = @$textarea.val()
if @model? if @model?
text = @_convertWikiLinks text text = @_convertWikiLinks text
text = @_convertImageLinks text text = @_convertImageLinks text