Created
September 26, 2014 16:04
-
-
Save javisantana/b4dbea922d177c69bdf7 to your computer and use it in GitHub Desktop.
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
/** | |
* track changes and sync on every model | |
*/ | |
function instrumentBackbone() { | |
var events = []; | |
var eventMap = {} | |
instrumentBackbone.events = events; | |
EVENT_SET = "SET"; | |
EVENT_SYNC = "SYNC"; | |
EVENT_SAVE = "SAVE"; | |
EVENT_SAVE_SUCCESS = "SAVE_SUCESS"; | |
EVENT_FETCH = "FETCH"; | |
EVENT_FETCH_SUCCESS = "FETCH_SUCESS"; | |
EVENT_FETCH_COLLECTION = "FETCH_COLLECTION"; | |
EVENT_FETCH_COLLECTION_SUCCESS = "FETCH_COLLECTION_SUCCESS"; | |
EVENT_SYNC_COLL = "SYNC_COLL"; | |
function track(obj, ev, data, parentId) { | |
var t, o; | |
events.push(o = { | |
id: events.length, | |
time: Date.now(), | |
ev: ev, | |
obj: obj, | |
data: _.clone(data), | |
parentId: parentId | |
}); | |
eventMap[o.id] = o; | |
return o.id; | |
} | |
var coll = 0; | |
// sync | |
var sync = Backbone.sync; | |
Backbone.sync = function(method, model, options) { | |
if (model) { | |
if (!model.cid) { | |
model.cid = "col_" + coll; | |
++coll; | |
} | |
track(model.cid, model.cid.indexOf('col') != -1 ? EVENT_SYNC_COLL: EVENT_SYNC); | |
} | |
return sync.apply(Backbone, arguments); | |
} | |
// set | |
var set = Backbone.Model.prototype.set; | |
Backbone.Model.prototype.set = function(key, value, options) { | |
var attrs, attr, val; | |
if (_.isObject(key) || key == null) { | |
attrs = key; | |
options = value; | |
} else { | |
attrs = {}; | |
attrs[key] = value; | |
} | |
track(this.cid, EVENT_SET, attrs); | |
return set.apply(this, arguments) | |
} | |
// save | |
var save = Backbone.Model.prototype.save; | |
Backbone.Model.prototype.save = function(key, value, options) { | |
var attrs, current; | |
// Handle both `("key", value)` and `({key: value})` -style calls. | |
if (_.isObject(key) || key == null) { | |
attrs = key; | |
options = value; | |
} else { | |
attrs = {}; | |
attrs[key] = value; | |
} | |
options = options ? _.clone(options) : {}; | |
var i = track(this.cid, EVENT_SAVE, attrs); | |
// If we're "wait"-ing to set changed attributes, validate early. | |
if (options.wait) { | |
if (!this._validate(attrs, options)) return false; | |
current = _.clone(this.attributes); | |
} | |
// Regular saves `set` attributes before persisting to the server. | |
var silentOptions = _.extend({}, options, {silent: true}); | |
if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) { | |
return false; | |
} | |
// After a successful server-side save, the client is (optionally) | |
// updated with the server-side state. | |
var model = this; | |
var success = options.success; | |
var self = this; | |
options.success = function(resp, status, xhr) { | |
track(self.cid, EVENT_SAVE_SUCCESS, resp, i); | |
var serverAttrs = model.parse(resp, xhr); | |
if (options.wait) { | |
delete options.wait; | |
serverAttrs = _.extend(attrs || {}, serverAttrs); | |
} | |
if (!model.set(serverAttrs, options)) return false; | |
if (success) { | |
success(model, resp); | |
} else { | |
model.trigger('sync', model, resp, options); | |
} | |
}; | |
// Finish configuring and sending the Ajax request. | |
options.error = Backbone.wrapError(options.error, model, options); | |
var method = this.isNew() ? 'create' : 'update'; | |
var xhr = (this.sync || Backbone.sync).call(this, method, this, options); | |
if (options.wait) this.set(current, silentOptions); | |
return xhr; | |
}; | |
Backbone.Model.prototype.fetch = function(options) { | |
options = options ? _.clone(options) : {}; | |
var model = this; | |
var success = options.success; | |
var i = track(this.cid, EVENT_FETCH); | |
var self = this; | |
options.success = function(resp, status, xhr) { | |
track(self.cid, EVENT_FETCH_SUCCESS, resp, i); | |
if (!model.set(model.parse(resp, xhr), options)) return false; | |
if (success) success(model, resp); | |
}; | |
options.error = Backbone.wrapError(options.error, model, options); | |
return (this.sync || Backbone.sync).call(this, 'read', this, options); | |
} | |
Backbone.Collection.fetch = function(options) { | |
options = options ? _.clone(options) : {}; | |
if (options.parse === undefined) options.parse = true; | |
var collection = this; | |
var success = options.success; | |
var i = track(this, EVENT_FETCH_COLLECTION); | |
options.success = function(resp, status, xhr) { | |
track(this, EVENT_FETCH_COLLECTION_SUCCESS, resp, i); | |
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options); | |
if (success) success(collection, resp); | |
}; | |
options.error = Backbone.wrapError(options.error, collection, options); | |
return (this.sync || Backbone.sync).call(this, 'read', this, options); | |
} | |
instrumentBackbone.show = function(cid) { | |
var padding = 100; | |
var div = d3.select(document.body) | |
.append('div') | |
.style('position', 'absolute') | |
.style('top', 0) | |
.style('left', 0) | |
.style('right', 0) | |
.style('bottom', 0) | |
.style('z-index', 1000) | |
.style('background', 'rgba(0, 0, 0, 0.85)'); | |
var tooltip = div.append("div") | |
.style('width', 100) | |
.style('height', 100) | |
.style('position', 'absolute') | |
.style('background', 'rgba(255, 255, 255, 0.85)') | |
.style('color', 'black') | |
.style("opacity", 0); | |
var width = div.node().clientWidth; | |
var height = div.node().clientHeight; | |
var svg = div.append("svg:svg") | |
.attr('width', width) | |
.attr('height', height); | |
var zx = d3.scale.linear() | |
.domain([-width / 2, width / 2]) | |
.range([0, width]); | |
var zy = d3.scale.linear() | |
.domain([-height / 2, height / 2]) | |
.range([height, 0]); | |
var zoom = d3.behavior.zoom() | |
.x(zx) | |
.y(zy) | |
.scaleExtent([1, 10]) | |
var xAxis = d3.svg.axis() | |
.scale(zx) | |
.orient("bottom") | |
.tickSize(-height); | |
var yAxis = d3.svg.axis() | |
.scale(zy) | |
.orient("left") | |
.ticks(5) | |
.tickSize(-width); | |
var ev = svg.append("g").attr("transform", 'translate(0, 0)').call(zoom); | |
svg.append("g") | |
.style('fill', 'none') | |
.style('stoke', '#FFF') | |
.attr("transform", "translate(0," + height + ")") | |
.call(xAxis); | |
svg.append("g") | |
.style('fill', 'none') | |
.style('stoke', '#FFF') | |
.call(yAxis); | |
objEvents = events; | |
if (cid) { | |
var objEvents = events.filter(function(e) { | |
return cid.indexOf(e.obj) !== -1 | |
}); | |
} else { | |
cid = events.map(function(e) { return e.obj; }); | |
} | |
var e = { | |
"FETCH": 0, | |
"FETCH_SUCESS": 1, | |
"FETCH_COLLECTION": 2, | |
"FETCH_COLLECTION_SUCCESS": 3, | |
"SET": 4, | |
"SAVE": 5, | |
"SAVE_SUCESS": 6, | |
"SYNC": 7, | |
"SYNC_COLL": 8 | |
}; | |
var colors = d3.scale.category10() | |
var catx = d3.scale.linear() | |
.domain([0, cid.length]) | |
.range([0, width/Object.keys(e).length]) | |
function x(d) { | |
return catx(cid.indexOf(d.obj)) + padding*2 + e[d.ev]*100; | |
} | |
function _time(d) { return +d.time; }; | |
var y = d3.scale.linear() | |
.domain([d3.min(objEvents, _time), d3.max(objEvents, _time)]) | |
.range([padding, div.node().clientHeight - padding ]); | |
// lines | |
var line = d3.svg.line() | |
.x(function(d) { return d.x; }) | |
.y(function(d) { return d.y; }) | |
.interpolate("basis"); | |
var lines = objEvents.filter(function(e) { | |
return !!e.parentId; | |
}); | |
ev.selectAll('.line') | |
.data(lines) | |
.enter() | |
.append("path") | |
.attr("fill", "none") | |
.attr("stroke", '#FFF') | |
.attr("stroke-width", 0.2) | |
.attr('d', function(d, i) { | |
var p = eventMap[d.parentId]; | |
return line([ | |
{x: x(d), y: y(+d.time) }, | |
{x: x(p), y: y(+p.time) } | |
]); | |
}) | |
ev.selectAll('.event') | |
.data(objEvents) | |
.enter() | |
.append("svg:circle") | |
.attr("fill", function(d) { | |
console.log(d.ev); | |
return colors(e[d.ev]); | |
}) | |
.attr("r", 3) | |
.attr('cx', function(d, i) { | |
return x(d); | |
}) | |
.attr('cy', function(d, i) { | |
return y(d.time); | |
}) | |
.on("mouseover", function(d) { | |
tooltip.transition() | |
.duration(200) | |
.style("opacity", .9); | |
tooltip.html("<h1>" + d.ev + " (" + d.obj + ")<br>" + (d.time - objEvents[0].time)) | |
.style("left", (d3.event.pageX) + "px") | |
.style("top", (d3.event.pageY - 48) + "px"); | |
}) | |
.on("mouseout", function(d) { | |
tooltip.transition() | |
.duration(500) | |
.style("opacity", 0); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment