On Github EmmanuelOga / js-knockout-presentation
The first solution...
<div id="widget1" class="widget">
<input class="from" type="text" value="01/01/2013" />
<input class="to" type="text" value="02/31/2013" />
<button class="month">Month</button>
<button class="week">Week</button>
<button class="all">All</button>
<br/>
<button>Show current range</button>
</div>
// repeat for each range: all, month, week
$buttonMonth = $widget.find(".month")
$buttonMonth.on "click", (ev) ->
$buttonMonth.addClass("selected")
$buttonWeek.removeClass("selected")
$buttonAll.removeClass("selected")
$from.val(monthAgo); $to.val(max)
$buttonProfit.on "click", (ev) ->
range = $widget.find("button.selected:first").attr("class")
alert(range)
Oh no...
syncButtonsAndInputs = () ->
$buttonMonth.removeClass("selected")
$buttonWeek.removeClass("selected")
$buttonAll.removeClass("selected")
$buttonAll.addClass("selected") if $from.val() == min and $to.val() == max
$buttonMonth.addClass("selected") if $from.val() == monthAgo and $to.val() == max
$buttonWeek.addClass("selected") if $from.val() == weekAgo and $to.val() == max
$buttonMonth.on "click", (ev) ->
$from.val(monthAgo); $to.val(max); syncButtonsAndInputs();
$from.on "keyup", syncButtonsAndInputs
$to.on "keyup", syncButtonsAndInputs
But...
class DateSelector
constructor: (@from, @to) ->
@knownRanges =
week: { from: moment(@to).subtract('days', 7), to: @to },
month: { from: moment(@to).subtract('days', 30), to: @to },
all: { from: @from, to: @to }
setRange: (name) =>
@from = @knownRanges[name].from; @to = @knownRanges[name].to
inRange: (name) =>
@knownRanges[name].from == @from and @knownRanges[name].to == @this.to
currentRange: (name) =>
for key in @knownRanges
return key if @inRange(key)
dateSelector = new DateSelector("01/01/2013", "28/02/2013")
it "defaults to showing the whole range", ->
expect(dateSelector.currentRange()).toEqual "all"
it "allows changing ranges", ->
expect(dateSelector.inRange("all")).toEqual true
expect(dateSelector.inRange("week")).toEqual false
expect(dateSelector.currentRange()).toEqual "all"
dateSelector.setRange "week"
expect(dateSelector.inRange("all")).toEqual false
expect(dateSelector.inRange("week")).toEqual true
expect(dateSelector.currentRange()).toEqual "week"
ds = new DateSelector "01/01/2013", "02/31/2013"
$buttonMonth.on "click", ->
ds.setRange("month"); $from.val(ds.from); $to.val(ds.to)
if (ds.inRange("month"))
$buttonMonth.addClass("selected")
else
$buttonMonth.removeClass("selected")
$from.on "keyup", ->
ds.from = $from.val(); ds.to = $to.val(); syncButtonsAndInputs()
var ds = new DateSelector("01/01/2013", "02/31/2013");
ko.applyBindings window.document.getElementByID("widget5")
<div id="widget5" class="widget">
<input type="text" data-bind="{ value: from }"/>
<input type="text" data-bind="{ value: to }"/>
<button data-bind="{click: setMonth }, css: {selected: isMonth })">Month</button>
<button data-bind="{click: setWeek }, css: {selected: isWeek })">Week</button>
<button data-bind="{click: setAll }, css: {selected: isAll })">All</button>
<br/>
<button data-bind="{click: showRange }">Profit!</button>
</div>
...compare with the old HTML
describe 'SaveModal', ->
it 'Validates input', ->
user = new MonitorPublicApp.User
server.respond()
saveModal = new MonitorPublicApp.SaveModal(user)
saveModal.currentEntry(' ')
(expect saveModal.errorMessage()).toEqual 'Name must not be blank.'
saveModal.currentEntry('Google')
(expect saveModal.errorMessage()).toEqual 'That name is taken.'
it "Loads a report with saved comparisons and enters and saves a new one", ->
countReport = new MonitorPublicApp.CountReport
server.respond() # Respond with bookmarks
expect(countReport.title()).toEqual 'Comparisons'
expect(countReport.savedStatusMessage()).toEqual 'Save Changes'
countReport.showSaveModal() # Open modal
countReport.saveModal.currentEntry('NewEntry') # Doesn't already exist
expect(countReport.saveModal.isInvalidInput()).toBe false
countReport.saveModal.onAction() # Trigger save action in modal
server.respond()
(expect countReport.user.bookmarks().length).toEqual 3
(expect countReport.title()).toEqual 'NewEntry'
(expect countReport.savedStatusMessage()).toEqual 'Saved!'
(expect countReport.user.viewingSavedBookmark()).toBe true
Logic and presentation is separated. Is easier to create different views for the same model or add more logic.
To reuse a component, create another instance and bind it to the DOM.
Removed the need to write tons of boilerplate code.