I stared wonder if there is a better way to handle this and here is a way (I think) that could help. Use a state machine concept. We can create some instance variables to save state and relaying on them build all widget logic (it is crappy) like that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# no bueno | |
class DateWidget | |
constructor: -> | |
@firstLevel = true | |
@fromDate = null | |
@toDate = null | |
fillFromDate: -> | |
if @firstLevel | |
#initialize fromDate and toDate day selects | |
else if @fromDate | |
#do some | |
else if @toDate | |
#do something else |
The real state machine is something where you define available states, and events which changes the state. Pretty good lib to handle this is:
https://github.com/jakesgordon/javascript-state-machine . It allows You to define for one event many routes, depending on your actual state, which suits this state diagram. Also there are callback on enter/leave state and before/after fire event. To event you can pass message(an argument), so you can treat it as normal method, which do some extra stuff.
First of all is the pen and paper, you need to draw simple graph for your control. Circles are the states and lines are the events, and that's all. When you properly design your graph, save this to code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
initial: 'firstLevel' | |
events: [ | |
{name: 'fillFrom', from: 'firstLevel', to: 'fromFilled'}, | |
{name: 'fillFrom', from: 'toFilled', to: 'fromToFilled'}, | |
{name: 'fillTo', from: 'fromFilled', to: 'fromToFilled'}, | |
{name: 'cleanFrom', from: 'fromFilled', to: 'firstLevel'}, | |
{name: 'cleanFrom', from: 'fromToFilled', to: 'toFilled'}, | |
{name: 'cleanTo', from: 'toFilled', to: 'firstLevel'} | |
{name: 'cleanTo', from: 'fromToFilled', to: 'fromFilled'} | |
] | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class views.search_filters.DateFilter extends views.AbstractRenderedComponent | |
constructor: -> | |
@createStateMachine() | |
@configure() | |
.. | |
configure: -> | |
@configureFromToItems() | |
@element.find('.group-change').on 'click', (event) => @showMonthSelector() | |
@element.find('.month-selector span').on 'click', (event) => @selectMonth($(event.currentTarget)) | |
configureFromToItems: -> | |
@element.find('.from-to').find('.notify-item .remove').on 'click', (event) => | |
event.stopPropagation() | |
item = $(event.currentTarget).closest('.notify-item') | |
if item.attr('data-bind') == 'date_from' | |
@stateMachine.cleanFrom() | |
else | |
@stateMachine.cleanTo() | |
@element.find('.from-to').find('.notify-item').on 'click', (event) => | |
@showDaySelector($(event.currentTarget)) | |
@daysSlider = new views.search_filters.RangeWindowSlider @element.find('.from-to .range'), | |
onChange: (days) => | |
@stateMachine.slideDate(days) | |
configureItem: (item) -> | |
item.on 'click', (event) => | |
if _.isEmpty $(event.currentTarget).attr('data-value') | |
@showMonthSelector() | |
else | |
@showDaySelector($(event.currentTarget)) | |
... | |
selectDay: (date, daySelector) -> | |
... | |
if @stateMachine.current == 'firstLevel' | |
@stateMachine.fillFrom new Date(date) | |
else | |
currentItem = daySelector.closest('.notify-item') | |
if currentItem.attr('data-bind') == 'date_from' | |
@stateMachine.fillFrom new Date(date) | |
else | |
@stateMachine.fillTo new Date(date) | |
createStateMachine: -> | |
@stateMachine = StateMachine.create | |
initial: 'firstLevel' | |
events: [ | |
{name: 'fillFrom', from: 'firstLevel', to: 'fromFilled'}, | |
{name: 'fillFrom', from: 'toFilled', to: 'fromToFilled'}, | |
{name: 'fillTo', from: 'fromFilled', to: 'fromToFilled'}, | |
{name: 'cleanFrom', from: 'fromFilled', to: 'firstLevel'}, | |
{name: 'cleanFrom', from: 'fromToFilled', to: 'toFilled'}, | |
{name: 'cleanTo', from: 'toFilled', to: 'firstLevel'} | |
{name: 'cleanTo', from: 'fromToFilled', to: 'fromFilled'} | |
{name: 'slideDate', from: 'fromFilled', to: 'fromToFilled'} | |
{name: 'slideDate', from: 'toFilled', to: 'fromToFilled'} | |
{name: 'slideDate', from: 'fromToFilled', to: 'fromToFilled'} | |
#on self | |
{name: 'fillFrom', from: 'fromToFilled', to: 'fromToFilled'}, | |
{name: 'fillTo', from: 'fromToFilled', to: 'fromToFilled'}, | |
{name: 'fillFrom', from: 'fromFilled', to: 'fromFilled'}, | |
{name: 'fillTo', from: 'toFilled', to: 'toFilled'}, | |
] | |
callbacks: | |
#callbacks for states | |
onfirstLevel: (event, from, to) => | |
@cleanItems() if from != 'none' | |
@resetMonthSelector() | |
@element.find('.items').show() | |
@element.find('.from-to').hide() | |
item = @addItem() | |
@setValueOnItem(item) | |
@activateMonthSelecting() | |
onleavefirstLevel: => | |
@cleanItems() | |
@showFromAndTo() | |
@showSlider() | |
@deactivateMonthSelecting() | |
onfromFilled: => | |
@daysSlider.activate() | |
@daysSlider.reset() | |
@setValueOnItem @element.find('.from-to [data-bind="date_to"]') | |
@toDate = null | |
ontoFilled: => | |
@daysSlider.deactivate() | |
@daysSlider.reset() | |
@setValueOnItem @element.find('.from-to [data-bind="date_from"]') | |
@fromDate = null | |
onfromToFilled: (event, from, to) => | |
@daysSlider.activate() | |
#callbacks for events | |
onafterfillFrom: (event, fromState, toState, date) => | |
@fromDate = date | |
@setValueOnItem @fromItem, @fromDate, $.datepicker.formatDate('d MM yy', date) | |
switch fromState | |
when 'fromToFilled' then @stateMachine.slideDate(@daysSlider.currentValue) | |
when 'toFilled' then @stateMachine.cleanTo() | |
onafterfillTo: (event, fromState, toState, date) => | |
@toDate = date | |
@setValueOnItem @toItem, @toDate, $.datepicker.formatDate('d MM yy', date) | |
if toState == 'fromToFilled' | |
@daysSlider.setValue(@calculateDaysBeetwen(@fromDate, @toDate)) | |
onafterslideDate: (event, from, to, days) => | |
date = new Date(@fromDate.getTime() + days*views.search_filters.DateFilter.DAY) | |
@toDate = date | |
@setValueOnItem @toItem, @toDate, $.datepicker.formatDate('d MM yy', date) | |
onchangestate: (event, from, to) => | |
console.log("event: #{event}, from: #{from}, to: #{to}") | |
... |
Further? OOP design of this example is probably not best. Operation explicitly on view should be in one class and flow in other.