Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make each-* routine compatible with collections #275

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions spec/rivets/functional.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,69 @@ describe('Functional', function() {
expect(el.getElementsByTagName('li')[3]).toHaveTheTextContent('last');
})

it('should unbind and remove elements bound to items removed from the collection', function () {
listItem.setAttribute('data-text', 'item.name');
rivets.bind(el, bindData);
expect(el.getElementsByTagName('li').length).toBe(2);
var collection = data.get('items').slice();
collection.shift();
data.set({items: collection});
expect(el.getElementsByTagName('li').length).toBe(collection.length);
expect(el.getElementsByTagName('li')[0]).toHaveTheTextContent('b');
});

it('should create a new element at the beginning of the container element when a new item is prepended to the collection', function () {
listItem.setAttribute('data-text', 'item.name');
rivets.bind(el, bindData);
expect(el.getElementsByTagName('li')[0]).toHaveTheTextContent('a');
var collection = data.get('items').slice();
collection.unshift({name: 'start'});
data.set({items: collection});
expect(el.getElementsByTagName('li')[0]).toHaveTheTextContent('start');
});

it('should create a new element at the end of the container element when a new item is appended to the collection', function () {
listItem.setAttribute('data-text', 'item.name');
rivets.bind(el, bindData);
var collection = data.get('items').slice();
collection.push({name: 'end'});
data.set({items: collection});
expect(el.getElementsByTagName('li').length).toBe(3);
expect(el.getElementsByTagName('li')[0]).toHaveTheTextContent('a');
expect(el.getElementsByTagName('li')[1]).toHaveTheTextContent('b');
expect(el.getElementsByTagName('li')[2]).toHaveTheTextContent('end');
});

it('should sort the bound elements according to order of items in the collection', function () {
listItem.setAttribute('data-text', 'item.name');
var collection = data.get('items').slice();
collection.push({name: 'e'}, {name: 'c'}, {name: 'd'});
data.set({items: collection});
rivets.bind(el, bindData);
expect(el.getElementsByTagName('li').length).toBe(collection.length);
expect(el.textContent).toBe('abecd');

// sort alphabetically
collection = collection.slice().sort(function (a, b) {
return a.name < b.name ? -1 : 1;
});
data.set({items: collection});
expect(el.textContent).toBe('abcde');

// reverse order
collection = collection.slice().reverse();
data.set({items: collection});
expect(el.textContent).toBe('edcba');
});

it('should replace all bound elements to match the new collection and sort them according to order of items in the collection', function () {
listItem.setAttribute('data-text', 'item.name');
rivets.bind(el, bindData);
expect(el.textContent).toBe('ab');
data.set({items: [{name: 'e'}, {name: 'p'}, {name: 'r'}]});
expect(el.getElementsByTagName('li').length).toBe(3);
expect(el.textContent).toBe('epr');
});
});
});

Expand Down
64 changes: 47 additions & 17 deletions src/binders.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -164,23 +164,45 @@ Rivets.binders['each-*'] =
routine: (el, collection) ->
modelName = @args[0]
collection = collection or []

if @iterated.length > collection.length
for i in Array @iterated.length - collection.length
view = @iterated.pop()
view.unbind()
@marker.parentNode.removeChild view.els[0]
# mirror is a live copy of the list of all `modelName` models bound to this view
iterated_mirror = (iter.models[modelName] for iter in @iterated)
# copy is just a dummy copy of mirror for keeping track of what's to be removed
iterated_copy = iterated_mirror.slice()

for model, index in collection
data = {}
data[modelName] = model

if not @iterated[index]?
for key, model of @view.models
data[key] ?= model
iter_index = iterated_mirror.indexOf model
if ~ iter_index
iterated_copy.splice iterated_copy.indexOf(model), 1
# make sure index == iter_index
if iter_index isnt index
# get the element to append this view's element after
previous = if index and @iterated.length
@iterated[index - 1].els[0]
else
@marker

# move the view to the right index
view = @iterated.splice(iter_index, 1)[0]
@iterated.splice index, 0, view
# move the view's element to the right position
@marker.parentNode.insertBefore view.els[0], previous.nextSibling

# also fix the mirror's order
iterated_mirror.splice index, 0, iterated_mirror.splice(iter_index, 1)[0]

previous = if @iterated.length
@iterated[@iterated.length - 1].els[0]
else
# it's a new model in the collection, so bind it
data = {}
data[modelName] = model

for key, view_model of @view.models
data[key] ?= view_model

previous = if index and @iterated.length
if @iterated[index - 1]
@iterated[index - 1].els[0]
else
@iterated[@iterated.length - 1].els[0]
else
@marker

Expand All @@ -196,11 +218,19 @@ Rivets.binders['each-*'] =
template = el.cloneNode true
view = new Rivets.View(template, data, options)
view.bind()
@iterated.push view
@iterated.splice index, 0, view

@marker.parentNode.insertBefore template, previous.nextSibling
else if @iterated[index].models[modelName] isnt model
@iterated[index].update data

# unbind views that no longer exist in collection
if iterated_copy.length
i = @iterated.length
while i--
view = @iterated[i]
if view and ~ iterated_copy.indexOf view.models[modelName]
@iterated.splice i, 1
view.unbind()
@marker.parentNode.removeChild view.els[0]

if el.nodeName is 'OPTION'
for binding in @view.bindings
Expand Down