Commit 7a175f1a authored by Thomas Marrinan's avatar Thomas Marrinan

observing application state for changes


Former-commit-id: 1261c4b2ee4e309110d53c714e829f7ca4c12b03
parent eddf9226
......@@ -17,6 +17,7 @@
</script>
<script type="text/javascript" src="lib/websocket.io.js"></script>
<script type="text/javascript" src="lib/observe.poly.js"></script>
<script type="text/javascript" src="lib/Class.js"></script>
<script type="text/javascript" src="lib/SAGE2_App.js"></script>
<script type="text/javascript" src="lib/SAGE2_runtime.js"></script>
......@@ -356,9 +357,18 @@
}, false);
js.addEventListener('load', function(event) {
var app = new window[data.application];
Object.observe(app.state, function(changes) {
var updatedState = JSON.parse(JSON.stringify(app.state));
console.log(data.id + ": ");
console.log(updatedState);
});
app.init(data.id, data.width, data.height, data.url, date);
app.load(data.data, date);
app.draw(date);
applications[data.id] = app;
if(data.animation === true) wsio.emit('finishedRenderingAppFrame', {id: data.id});
}, false);
......@@ -370,9 +380,18 @@
// load existing app
else {
var app = new window[data.application];
Object.observe(app.state, function(changes) {
var updatedState = JSON.parse(JSON.stringify(app.state));
console.log(data.id + ": ");
console.log(updatedState);
});
app.init(data.id, data.width, data.height, data.url, date);
app.load(data.data, date);
app.draw(date);
applications[data.id] = app;
if(data.animation === true) wsio.emit('finishedRenderingAppFrame', {id: data.id});
}
......
......@@ -14,6 +14,7 @@ var SAGE2_App = Class.extend( {
this.element = null;
this.resrcPath = null;
this.resizeEvents = "never";
this.state = {};
this.startDate = null;
this.prevDate = null;
......
/*
Tested against Chromium build with Object.observe and acts EXACTLY the same,
though Chromium build is MUCH faster
Trying to stay as close to the spec as possible,
this is a work in progress, feel free to comment/update
Specification:
http://wiki.ecmascript.org/doku.php?id=harmony:observe
Built using parts of:
https://github.com/tvcutsem/harmony-reflect/blob/master/examples/observer.js
Limits so far;
Built using polling... Will update again with polling/getter&setters to make things better at some point
TODO:
Add support for Object.prototype.watch -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch
*/
"use strict";
if(!Object.observe){
(function(extend, global){
var isCallable = (function(toString){
var s = toString.call(toString),
u = typeof u;
return typeof global.alert === "object" ?
function(f){
return s === toString.call(f) || (!!f && typeof f.toString == u && typeof f.valueOf == u && /^\s*\bfunction\b/.test("" + f));
}:
function(f){
return s === toString.call(f);
}
;
})(extend.prototype.toString);
// isNode & isElement from http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object
//Returns true if it is a DOM node
function isNode(o){
return (
typeof Node === "object" ? o instanceof Node :
o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName==="string"
);
}
//Returns true if it is a DOM element
function isElement(o){
return (
typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string"
);
}
var _isImmediateSupported = (function(){
return !!global.setImmediate;
})();
var _doCheckCallback = (function(){
if(_isImmediateSupported){
return function(f){
return setImmediate(f);
};
}else{
return function(f){
return setTimeout(f, 10);
};
}
})();
var _clearCheckCallback = (function(){
if(_isImmediateSupported){
return function(id){
clearImmediate(id);
};
}else{
return function(id){
clearTimeout(id);
};
}
})();
var isNumeric=function(n){
return !isNaN(parseFloat(n)) && isFinite(n);
};
var sameValue = function(x, y){
if(x===y){
return x !== 0 || 1 / x === 1 / y;
}
return x !== x && y !== y;
};
var isAccessorDescriptor = function(desc){
if (typeof(desc) === 'undefined'){
return false;
}
return ('get' in desc || 'set' in desc);
};
var isDataDescriptor = function(desc){
if (typeof(desc) === 'undefined'){
return false;
}
return ('value' in desc || 'writable' in desc);
};
var validateArguments = function(O, callback, accept){
if(typeof(O)!=='object'){
// Throw Error
throw new TypeError("Object.observeObject called on non-object");
}
if(isCallable(callback)===false){
// Throw Error
throw new TypeError("Object.observeObject: Expecting function");
}
if(Object.isFrozen(callback)===true){
// Throw Error
throw new TypeError("Object.observeObject: Expecting unfrozen function");
}
if (accept !== undefined) {
if (!Array.isArray(accept)) {
throw new TypeError("Object.observeObject: Expecting acceptList in the form of an array");
}
}
};
var Observer = (function(){
var wraped = [];
var Observer = function(O, callback, accept){
validateArguments(O, callback, accept);
if (!accept) {
accept = ["add", "update", "delete", "reconfigure", "setPrototype", "preventExtensions"];
}
Object.getNotifier(O).addListener(callback, accept);
if(wraped.indexOf(O)===-1){
wraped.push(O);
}else{
Object.getNotifier(O)._checkPropertyListing();
}
};
Observer.prototype.deliverChangeRecords = function(O){
Object.getNotifier(O).deliverChangeRecords();
};
wraped.lastScanned = 0;
var f = (function(wrapped){
return function(){
var i = 0, l = wrapped.length, startTime = new Date(), takingTooLong=false;
for(i=wrapped.lastScanned; (i<l)&&(!takingTooLong); i++){
Object.getNotifier(wrapped[i])._checkPropertyListing();
takingTooLong=((new Date())-startTime)>100; // make sure we don't take more than 100 milliseconds to scan all objects
}
wrapped.lastScanned=i<l?i:0; // reset wrapped so we can make sure that we pick things back up
_doCheckCallback(f);
};
})(wraped);
_doCheckCallback(f);
return Observer;
})();
var Notifier = function(watching){
var _listeners = [], _acceptLists = [], _updates = [], _updater = false, properties = [], values = [];
var self = this;
Object.defineProperty(self, '_watching', {
enumerable: true,
get: (function(watched){
return function(){
return watched;
};
})(watching)
});
var wrapProperty = function(object, prop){
var propType = typeof(object[prop]), descriptor = Object.getOwnPropertyDescriptor(object, prop);
if((prop==='getNotifier')||isAccessorDescriptor(descriptor)||(!descriptor.enumerable)){
return false;
}
if((object instanceof Array)&&isNumeric(prop)){
var idx = properties.length;
properties[idx] = prop;
values[idx] = object[prop];
return true;
}
(function(idx, prop){
properties[idx] = prop;
values[idx] = object[prop];
Object.defineProperty(object, prop, {
get: function(){
return values[idx];
},
set: function(value){
if(!sameValue(values[idx], value)){
Object.getNotifier(object).queueUpdate(object, prop, 'update', values[idx]);
values[idx] = value;
}
}
});
})(properties.length, prop);
return true;
};
self._checkPropertyListing = function(dontQueueUpdates){
var object = self._watching, keys = Object.keys(object), i=0, l=keys.length;
var newKeys = [], oldKeys = properties.slice(0), updates = [];
var prop, queueUpdates = !dontQueueUpdates, propType, value, idx, aLength;
if(object instanceof Array){
aLength = properties.length;
}
for(i=0; i<l; i++){
prop = keys[i];
value = object[prop];
propType = typeof(value);
if((idx = properties.indexOf(prop))===-1){
if(wrapProperty(object, prop)&&queueUpdates){
self.queueUpdate(object, prop, 'add', null, object[prop]);
}
}else{
if((object instanceof Array)&&(isNumeric(prop))){
if(values[idx] !== value){
if(queueUpdates){
self.queueUpdate(object, prop, 'update', values[idx], value);
}
values[idx] = value;
}
}
oldKeys.splice(oldKeys.indexOf(prop), 1);
}
}
if(object instanceof Array && object.length !== aLength){
if(queueUpdates){
self.queueUpdate(object, 'length', 'update', aLength, object);
}
}
if(queueUpdates){
l = oldKeys.length;
for(i=0; i<l; i++){
idx = properties.indexOf(oldKeys[i]);
self.queueUpdate(object, oldKeys[i], 'delete', values[idx]);
properties.splice(idx,1);
values.splice(idx,1);
};
}
};
self.addListener = function(callback, accept){
var idx = _listeners.indexOf(callback);
if(idx===-1){
_listeners.push(callback);
_acceptLists.push(accept);
}
else {
_acceptLists[idx] = accept;
}
};
self.removeListener = function(callback){
var idx = _listeners.indexOf(callback);
if(idx>-1){
_listeners.splice(idx, 1);
_acceptLists.splice(idx, 1);
}
};
self.listeners = function(){
return _listeners;
};
self.queueUpdate = function(what, prop, type, was){
this.queueUpdates([{
type: type,
object: what,
name: prop,
oldValue: was
}]);
};
self.queueUpdates = function(updates){
var self = this, i = 0, l = updates.length||0, update;
for(i=0; i<l; i++){
update = updates[i];
_updates.push(update);
}
if(_updater){
_clearCheckCallback(_updater);
}
_updater = _doCheckCallback(function(){
_updater = false;
self.deliverChangeRecords();
});
};
self.deliverChangeRecords = function(){
var i = 0, l = _listeners.length,
//keepRunning = true, removed as it seems the actual implementation doesn't do this
// In response to BUG #5
retval;
for(i=0; i<l; i++){
if(_listeners[i]){
var currentUpdates;
if (_acceptLists[i]) {
currentUpdates = [];
for (var j = 0, updatesLength = _updates.length; j < updatesLength; j++) {
if (_acceptLists[i].indexOf(_updates[j].type) !== -1) {
currentUpdates.push(_updates[j]);
}
}
}
else {
currentUpdates = _updates;
}
if (currentUpdates.length) {
if(_listeners[i]===console.log){
console.log(currentUpdates);
}else{
_listeners[i](currentUpdates);
}
}
}
}
/*
for(i=0; i<l&&keepRunning; i++){
if(typeof(_listeners[i])==='function'){
if(_listeners[i]===console.log){
console.log(_updates);
}else{
retval = _listeners[i](_updates);
if(typeof(retval) === 'boolean'){
keepRunning = retval;
}
}
}
}
*/
_updates=[];
};
self.notify = function(changeRecord) {
if (typeof changeRecord !== "object" || typeof changeRecord.type !== "string") {
throw new TypeError("Invalid changeRecord with non-string 'type' property");
}
changeRecord.object = watching;
self.queueUpdates([changeRecord]);
};
self._checkPropertyListing(true);
};
var _notifiers=[], _indexes=[];
extend.getNotifier = function(O){
var idx = _indexes.indexOf(O), notifier = idx>-1?_notifiers[idx]:false;
if(!notifier){
idx = _indexes.length;
_indexes[idx] = O;
notifier = _notifiers[idx] = new Notifier(O);
}
return notifier;
};
extend.observe = function(O, callback, accept){
// For Bug 4, can't observe DOM elements tested against canry implementation and matches
if(!isElement(O)){
return new Observer(O, callback, accept);
}
};
extend.unobserve = function(O, callback){
validateArguments(O, callback);
var idx = _indexes.indexOf(O),
notifier = idx>-1?_notifiers[idx]:false;
if (!notifier){
return;
}
notifier.removeListener(callback);
if (notifier.listeners().length === 0){
_indexes.splice(idx, 1);
_notifiers.splice(idx, 1);
}
};
})(Object, this);
}
......@@ -10,16 +10,16 @@
var pdf_viewer = SAGE2_App.extend( {
construct: function() {
arguments.callee.superClass.construct.call(this);
this.resizeEvents = "onfinish";
this.src = null;
this.canvas = null;
this.ctx = null;
this.loaded = null;
this.loaded = false;
this.pdfDoc = null;
this.pageNum = 1;
this.numPagesShown = 1
},
init: function(id, width, height, resrc, date) {
......@@ -32,25 +32,35 @@ var pdf_viewer = SAGE2_App.extend( {
this.canvas.height = this.element.height;
this.ctx = this.canvas.getContext("2d");
this.loaded = false;
// *** DO NOT OVERWRITE this.state, ALWAYS EDIT ITS PROPERTIES
// this.state = {srcPDF: null, pageNum: null, numPagesShown: null}; // BAD
this.state.srcPDF = null;
this.state.pageNum = null;
this.state.numPagesShown = null;
},
load: function(state, date) {
// load new document
if(state.src !== undefined && state.src !== null) {
var _this = this;
this.loaded = false;
PDFJS.getDocument(state.src).then(function getPdfHelloWorld(_pdfDoc) {
_this.pdfDoc = _pdfDoc;
_this.pageNum = state.page;
_this.numPagesShown = state.numPagesShown;
_this.loaded = true;
_this.pdfDoc = _pdfDoc;
_this.state.srcPDF = state.src;
_this.state.pageNum = state.page;
_this.state.numPagesShown = state.numPagesShown;
_this.draw(date);
});
}
// load new state of same document
else {
this.pageNum = state.page;
this.numPagesShown = state.numPagesShown;
this.state.pageNum = state.page;
this.state.numPagesShown = state.numPagesShown;
this.draw(date);
}
},
......@@ -60,10 +70,10 @@ var pdf_viewer = SAGE2_App.extend( {
arguments.callee.superClass.preDraw.call(this, date);
// application specific 'draw'
if(!this.loaded) return;
if(this.loaded === false) return;
var _this = this;
this.pdfDoc.getPage(this.pageNum).then(function(page) {
this.pdfDoc.getPage(this.state.pageNum).then(function(page) {
// set the scale to match the canvas
var viewport = page.getViewport(_this.canvas.width / page.getViewport(1.0).width);
viewport.height = _this.canvas.height;
......@@ -93,14 +103,14 @@ var pdf_viewer = SAGE2_App.extend( {
if(eventType === "specialKey"){
if(data.code === 37 && data.state === "up"){ // Left Arrow
if(this.pageNum <= 1) return;
this.pageNum = this.pageNum - 1;
if(this.state.pageNum <= 1) return;
this.state.pageNum = this.state.pageNum - 1;
this.draw(date);
}
if(data.code === 39 && data.state === "up"){ // Right Arrow
if(this.pageNum >= this.pdfDoc.numPages) return;
this.pageNum = this.pageNum + 1;
if(this.state.pageNum >= this.pdfDoc.numPages) return;
this.state.pageNum = this.state.pageNum + 1;
this.draw(date);
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment