Skip to content

Commit

Permalink
list instant mode
Browse files Browse the repository at this point in the history
  • Loading branch information
barbuza committed Feb 8, 2016
1 parent 7218520 commit d43b87c
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/lib
/node_modules

/.idea
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kebakaran",
"version": "0.1.9",
"version": "0.1.10",
"description": "high level utilities for firebase interaction",
"main": "index.js",
"scripts": {
Expand Down
75 changes: 58 additions & 17 deletions src/FirebaseList.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,36 @@ import EventEmitter from 'eventemitter3';

import FirebaseStruct from './FirebaseStruct';

const noValue = Symbol();
const NO_VALUE = Symbol();

export default class FirebaseList extends EventEmitter {

keys = [];
items = {};
values = {};

constructor(ref, getFields, idField = 'id') {
constructor(ref, getFields, idField = 'id', instant = false) {
super();

this.ref = ref;
this.getFields = getFields;
this.idField = idField;
this.instant = instant;
this.hasInitialData = !instant;

this.subscribe();
}

subscribe() {
this.onChildAdded = ::this.onChildAdded;
this.onChildRemoved = ::this.onChildRemoved;
this.ref.on('child_added', this.onChildAdded);
this.ref.on('child_removed', this.onChildRemoved);
if (this.instant) {
this.onValue = ::this.onValue;
this.ref.on('value', this.onValue);
} else {
this.onChildAdded = ::this.onChildAdded;
this.onChildRemoved = ::this.onChildRemoved;
this.ref.on('child_added', this.onChildAdded);
this.ref.on('child_removed', this.onChildRemoved);
}
}

on(name, listener, context) {
Expand All @@ -34,39 +41,68 @@ export default class FirebaseList extends EventEmitter {
}
}

onValue(snapshot) {
const newKeys = [];
snapshot.forEach(itemSnapshot => {
newKeys.push(itemSnapshot.key());
});
for (const key of newKeys) {
if (this.keys.indexOf(key) === -1) {
this.addChild(key);
}
}
for (const key of this.keys) {
if (newKeys.indexOf(key) === -1) {
this.removeChild(key);
}
}
this.keys = newKeys;
this.hasInitialData = true;
this.flush();
}

onChildAdded(c) {
const key = c.key();
this.addChild(key);
}

onChildRemoved(c) {
const key = c.key();
this.removeChild(key);
this.flush();
}

addChild(key) {
const item = new FirebaseStruct(this.getFields, key);

this.values[key] = noValue;
this.values[key] = NO_VALUE;
this.keys.push(key);
this.items[key] = item;

item.on('value', value => {
this.onValue(key, value);
this.onItemValue(key, value);
});
}

onChildRemoved(c) {
const key = c.key();

removeChild(key) {
this.items[key].close();

delete this.items[key];
delete this.values[key];
this.keys.splice(this.keys.indexOf(key), 1);

this.flush();
}

onValue(key, item) {
onItemValue(key, item) {
this.values[key] = item;
this.flush();
}

hasData() {
if (!this.hasInitialData) {
return false;
}
for (const key of this.keys) {
if (this.values[key] === noValue) {
if (this.values[key] === NO_VALUE) {
return false;
}
}
Expand All @@ -89,8 +125,13 @@ export default class FirebaseList extends EventEmitter {
}

this.off('value');
this.ref.off('child_added', this.onChildAdded);
this.ref.off('child_removed', this.onChildRemoved);

if (this.instant) {
this.ref.off('value', this.onValue);
} else {
this.ref.off('child_added', this.onChildAdded);
this.ref.off('child_removed', this.onChildRemoved);
}
}

}
8 changes: 4 additions & 4 deletions src/FirebaseStream.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ function snapshotValue(snapshotOrValue) {
return snapshotOrValue;
}

export default class FirebaseStream {
const NO_VALUE = Symbol();

static noValue = Symbol();
export default class FirebaseStream {

constructor(ref) {
this.ref = ref;
this.sentValue = this.constructor.noValue;
this.currentValue = this.constructor.noValue;
this.sentValue = NO_VALUE;
this.currentValue = NO_VALUE;
this.resolve = null;
this.update = ::this.update;
this.ref.on('value', this.update);
Expand Down
6 changes: 4 additions & 2 deletions tests/Firebase-tape.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import Firebase from 'firebase';

import { FirebaseList } from '../src';

test.skip('Firebase', t => {
test.skip('FirebaseList', t => {
t.plan(2);

const list = new FirebaseList(new Firebase('https://kebakaran-test.firebaseio.com/list'), key => ({
name: new Firebase(`https://kebakaran-test.firebaseio.com/names/${key}`),
count: new Firebase(`https://kebakaran-test.firebaseio.com/counts/${key}`),
}));
}), 'id');

let step = 1;

Expand All @@ -32,6 +32,8 @@ test.skip('Firebase', t => {
]);

list.close();
Firebase.goOffline();

t.end();
}
});
Expand Down
52 changes: 52 additions & 0 deletions tests/FirebaseList-tape.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,55 @@ test('FirebaseList', t => {

t.end();
});

test('FirebaseList instant', t => {
t.plan(11);

const listRef = new RefMock();
const nameRefs = {};
nameRefs.foo = new RefMock();
nameRefs.bar = new RefMock();

const list = new FirebaseList(listRef, key => ({
name: nameRefs[key],
}), 'id', true);

let step = 1;

list.on('value', value => {
if (step === 1) {
t.deepEqual(value, [{ name: 'foo', id: 'foo' }, { name: 'bar', id: 'bar' }]);
step = 2;
} else if (step === 2) {
t.deepEqual(value, [{ name: 'bar', id: 'bar' }]);
step = 3;
} else if (step === 3) {
t.deepEqual(value, []);
step = 4;
}
});

t.equal(list.listeners('value').length, 1);
t.equal(listRef.listeners('child_added').length, 0);
t.equal(listRef.listeners('child_removed').length, 0);
t.equal(listRef.listeners('value').length, 1);
t.equal(nameRefs.foo.listeners('value').length, 0);
t.equal(nameRefs.bar.listeners('value').length, 0);

listRef.emitValue({
foo: true,
bar: true,
});

t.equal(nameRefs.foo.listeners('value').length, 1);
t.equal(nameRefs.bar.listeners('value').length, 1);

nameRefs.foo.emitValue('foo');
nameRefs.bar.emitValue('bar');

listRef.emitValue({
bar: true,
});

listRef.emitValue({});
});
36 changes: 23 additions & 13 deletions tests/RefMock.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
import EventEmitter from 'eventemitter3';

export default class RefMock extends EventEmitter {
class SnapshotMock {

constructor(val, key) {
this._val = val;
this._key = key;
}

on(name, listener) {
super.on(name, listener);
return listener;
val() {
return this._val;
}

key() {
return this._key;
}

forEach(...args) {
Object.keys(this._val).map(val => new SnapshotMock(undefined, val)).forEach(...args);
}

}

export default class RefMock extends EventEmitter {

emitValue(val) {
this.emit('value', {
val: () => val,
});
this.emit('value', new SnapshotMock(val));
}

emitChildAdded(key) {
this.emit('child_added', {
key: () => key,
});
this.emit('child_added', new SnapshotMock(undefined, key));
}

emitChildRemoved(key) {
this.emit('child_removed', {
key: () => key,
});
this.emit('child_removed', new SnapshotMock(undefined, key));
}

}

0 comments on commit d43b87c

Please sign in to comment.