Skip to content

Instantly share code, notes, and snippets.

@webcss
Last active April 5, 2016 09:36
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save webcss/e4aaa7d95342d107e1ce to your computer and use it in GitHub Desktop.
Save webcss/e4aaa7d95342d107e1ce to your computer and use it in GitHub Desktop.
Mixin for mithril controllers to enable firebase livedata
/******************************************
* Firebase mixin
******************************************/
export function mixinFirebase(target) {
var OBJECT = '[object Object]', ARRAY = '[object Array]', STRING = '[object String]', FUNCTION = '[object Function]';
var type = {}.toString;
var _references = [];
function unify(key, value) {
if(type.call(value) !== OBJECT || !value) {
value = { '.value': value };
}
value._id = key;
return value;
}
function findIndex(arr, key) {
for(var i = 0, l = arr.length; i < l; i++) {
if(arr[i]._id === key) {
return i;
}
}
return -1;
}
// using the Firebase API's prevChild behavior, we
// place each element in the list after it's prev
// sibling or, if prevChild is null, at the beginning
function positionAfter(arr, prevChild) {
var idx;
if (prevChild === null) {
return 0;
} else {
idx = findIndex(arr, prevChild);
return (idx === -1) ? arr.length: idx + 1;
}
}
/**
* getData - retrieve the data at the specified firebase location once
* param @reference firebaseReference - firebase reference to write to
* param @oncomplete function - callback function with fetched data
*
*/
target.getData = function(reference, oncomplete) {
reference.once('value', function(snap) {
oncomplete.call(target, snap.val());
m.redraw();
});
};
/**
* onlivedata - listen to firebase live data changes
* param @reference firebaseReference - firebase reference to write to
* param @ondata function - callback function with updated data
* param @asObject boolean - optional, pass a truthy value to receive a
* complete snapshot.val() rather than an array
*
*/
target.onlivedata = function(reference, ondata, asObject) {
// save the reference for later removal of eventlisteners
_references.push(reference);
// add eventlistener
if (asObject) {
reference.on('value', function(snap) {
ondata.call(target, snap.val());
m.redraw();
});
} else {
var out = [];
reference.on('child_added', function(snap, prevChild) {
var pos, idx = findIndex(out, snap.key());
if (idx < 0) {
pos = positionAfter(out, prevChild);
out.splice( pos, 0, unify( snap.key(), snap.val() ) );
ondata.call(target, out);
m.redraw();
}
});
reference.on('child_changed', function(snap) {
var idx = findIndex(out, snap.key());
if (idx > -1) {
out[idx] = unify( snap.key(), snap.val() );
ondata.call(target, out);
m.redraw();
}
});
reference.on('child_removed', function(snap) {
var idx = findIndex(out, snap.key());
out.splice(idx, 1);
ondata.call(target, out);
m.redraw();
});
reference.on('child_moved', function(snapshot, prevChild) {
var data, newpos,
idx = findIndex(out, snapshot.key());
if (idx > -1) {
data = out.splice(idx, 1)[0];
newpos = positionAfter(out, prevChild);
out.splice(newpos, 0, data);
ondata.call(target, out);
m.redraw();
}
});
}
};
/**
* bindFirebase - helper function to bind an input value to a firebase location
* param @ref firebaseReference - firebase reference to write to
* param @key string - key index
* param @property string - key property to bind to
* param @attr string - HTML attribute to bind to
* return mithril m.withAttr function
*
*/
target.bindFirebase = function(reference, key, childProp, attr) {
return m.withAttr(attr, function(value) {
reference.child(key).child(childProp).set(value);
});
};
// save a reference to a possible present unload method
var _savedUnload = (target.onunload)? target.onunload : null;
target.onunload = function() {
while (_references.length) {
_references.pop().off();
}
_savedUnload && _savedUnload();
};
return target;
}
// usage:
var MyCustomers = new Firebase('https://<myfirebase>.firebaseio.com/customers')
var Customers = {};
Customers.controller = function(args) {
var scope = mixinFirebase(this);
scope.onLivedata(args.firebase, function(data) {
scope.customers = data;
});
};
Customers.view = function(ctrl) {
return m('ul', ctrl.customers.map(function(customer) {
return m('li', { key: customer._id }, customer.name );
}));
};
m.module(document.body, Customers, { firebase: MyCustomers });
@farzher
Copy link

farzher commented Mar 29, 2015

How do I add new items? It doesn't save new items to firebase.
Also how do I remove items? They just come back on reload.

Also, why does this redraw my view twice on page load? Once with 1 item, then redraws again with the rest of the items.

@webcss
Copy link
Author

webcss commented Apr 22, 2015

You add, update or remove items using the firebase reference object as described in the firebase docs. There are no dedicated methods within this mixin to help you do that, because these would only wrap firebase native methods, but I don't want to duplicate code. ;)
The multiple redraws are due to firebase's nature. Since this mixin listens for child_...-events and stores them in an array internally for better consumption with mithril, anytime a new child is added, updated or removed the array is changed, although only for the child record in question.

Thus on pageload, when the first child record arrives, it returns the array, and updates on every subsequent child received.

@kulakowka
Copy link

@webcss Looks pretty cool! Let's create a separate github project for that? You could publish it in npm.

@kulakowka
Copy link

I have created repo for this: https://github.com/kulakowka/mithril-firebase-mixin
And published it to npm with minimal changes: https://www.npmjs.com/package/mithril-firebase-mixin
PR Welcome...

@webcss
Copy link
Author

webcss commented Mar 7, 2016

@kulakowka Would be nice if you'd at least mentioned me as the initial author in your repo's readme or within your license...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment