Refactored JS to use observer-notifier system.

git-svn-id: file:///tmp/snv/trunk@9 12951d8a-c33d-4b7c-b961-822215c816e1
master
Abhinav Sarkar 2008-12-04 15:49:57 +00:00
parent 3d33406796
commit 0aa71851ef
5 changed files with 501 additions and 41 deletions

View File

@ -1,7 +1,7 @@
move to JSON data format -- done
XBL -- done
format JS/refactor JS/Use FUEL -- done
put license
caching/db, threading, XUL templates
new UI
caching/db, threading
new UI, icons, thumbnails
JQuery
put license

View File

@ -1,9 +1,38 @@
var DiggSidebar = {
/*
Digg Sidebar - Shows Digg stories in the Firefox sidebar in real time.
Copyright (C) 2008 Abhinav Sarkar <abhinav dot sarkar at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
DiggSidebar = {
prefs: Application.extensions.get("diggsidebar@abhinavsarkar.net").prefs,
storyListRefreshEventTopic: "extension-diggsidebar-abhinavsarkar-net-storylist-refresh",
endpoint: {
container: null,
topic: null,
category: "all"
},
updateInterval: 10,
updateIntervalDecay: 0,
shownStoriesCount: 30,
categories: ['All', 'Popular', 'Upcoming', 'Hot', 'Top'],
timerId: null,
currentStories : new Array(),
storyIds: [],
playing: true,
stories: [],
fetchData: function(url, handler) {
DiggSidebar.diggIndicator.style.display = '';
@ -95,31 +124,36 @@ var DiggSidebar = {
DiggSidebar.diggIndicator.style.display = 'none';
},
populateStoryList: function(e) {
var XHR = e.target;
//Application.console.log(XHR.responseText);
var data = DiggSidebar.Utils.decodeJson(XHR.responseText);
var stories = data.stories;
populateStoryList: function() {
while (DiggSidebar.storyList.firstChild) {
DiggSidebar.storyList.removeChild(DiggSidebar.storyList.firstChild);
}
var newStories = new Array();
//Application.console.log(stories.length);
var newStoryIds = new Array();
Application.console.log(DiggSidebar.stories.length);
stories.forEach(function (story) {
newStories.push(story.id);
//Application.console.log(i + " " + story.title + " " + story.id);
var jp = new JPath(DiggSidebar.stories);
var filtedStories = DiggSidebar.stories;
if (DiggSidebar.endpoint.topic)
filtedStories = jp.$(function(story){
return story.$("topic/short_name").json == DiggSidebar.endpoint.topic;
}).json;
if (DiggSidebar.endpoint.container)
filtedStories = jp.$(function(story){
return story.$("container/short_name").json == DiggSidebar.endpoint.container;
}).json;
filtedStories.forEach(function (story, index) {
if (index < DiggSidebar.shownStoriesCount) {
var listitem = DiggSidebar.storyList.appendChild(document.createElement('richlistitem'));
listitem.id = "story_" + story.id;
listitem.setAttribute('title', story.title);
if (DiggSidebar.currentStories.indexOf(story.id) != -1) listitem.new = false;
if (DiggSidebar.storyIds.indexOf(story.id) != -1) listitem.new = false;
}
if (DiggSidebar.storyIds.indexOf(story.id) == -1) newStoryIds.push(story.id);
});
DiggSidebar.currentStories = newStories;
DiggSidebar.setUpdateInterval(stories);
DiggSidebar.storyIds = DiggSidebar.storyIds.concat(newStoryIds);
DiggSidebar.setUpdateInterval(DiggSidebar.stories.slice(-5));
DiggSidebar.diggIndicator.style.display = 'none';
},
@ -178,7 +212,20 @@ var DiggSidebar = {
DiggSidebar.fetchData("http://services.digg.com/stories" + ep.replace(/\/all/g, '') +
"?count=30" + "&type=json" +
"&appkey=" + encodeURIComponent("http://diggsidebar.googlepages.com"),
DiggSidebar.populateStoryList);
function(e) {
var newStories = DiggSidebar.Utils.decodeJson(e.target.responseText).stories.filter(
function(story) {
return !(DiggSidebar.storyIds.indexOf(story.id) != -1);
});
newStories.forEach(function(story) {
story.category = DiggSidebar.endpoint.category;
story.read = false;
});
DiggSidebar.stories = newStories.concat(DiggSidebar.stories);
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.notifyObservers(null, DiggSidebar.storyListRefreshEventTopic, null);
});
},
getDescription: function(storyId) {
@ -196,6 +243,12 @@ var DiggSidebar = {
setUpdateInterval: function(stories) {
//Adaptive update interval code START
if (stories.length == 0) {
window.clearTimeout(DiggSidebar.timerId);
var timeout = Math.round(DiggSidebar.updateInterval*(Math.pow(1.5, DiggSidebar.updateIntervalDecay)));
DiggSidebar.timerId = window.setTimeout(DiggSidebar.getStories, timeout);
return;
}
var sum = 0;
var weights = [4, 3, 2, 1]
var date = (stories[0].promote_date != null) ? 'promote_date' : 'submit_date';
@ -216,32 +269,46 @@ var DiggSidebar = {
//Application.console.log(updateInterval);
if (updateInterval > 0) {
var previousUpdateInterval = DiggSidebar.prefs.get("updateinterval").value;
DiggSidebar.prefs.get("updateinterval").value = updateInterval;
var previousUpdateInterval = DiggSidebar.updateInterval;
DiggSidebar.updateInterval = updateInterval;
var updateIntervalDecay = DiggSidebar.prefs.get("updateintervaldecay").value;
var updateIntervalDecay = DiggSidebar.updateIntervalDecay;
if (previousUpdateInterval == updateInterval)
DiggSidebar.prefs.get("updateintervaldecay").value = updateIntervalDecay + 1;
DiggSidebar.updateIntervalDecay = updateIntervalDecay + 1;
else
DiggSidebar.prefs.get("updateintervaldecay").value = 0;
DiggSidebar.updateIntervalDecay = 0;
}
window.clearTimeout(DiggSidebar.timerId);
var updateInterval = DiggSidebar.prefs.get("updateinterval").value;
var updateIntervalDecay = DiggSidebar.prefs.get("updateintervaldecay").value;
var timeout = Math.round(updateInterval*(Math.pow(1.5, updateIntervalDecay)));
var timeout = Math.round(DiggSidebar.updateInterval*(Math.pow(1.5, DiggSidebar.updateIntervalDecay)));
DiggSidebar.timerId = window.setTimeout(DiggSidebar.getStories, timeout);
//Adaptive update interval code END
},
setEndPoint: function(ep) {
DiggSidebar.prefs.get("endpoint").value = ep;
DiggSidebar.endpoint = DiggSidebar.getEndpointParts(ep);
window.clearTimeout(DiggSidebar.timerId);
DiggSidebar.getStories();
},
getEndpointParts: function(ep) {
var tmp = {
topic: null,
container: null,
category: "all"
};
parts = ep.split("/");
if (parts.length == 2)
tmp.category = parts[1];
else if (parts.length == 4) {
tmp[parts[1]] = parts[2];
tmp.category = parts[3];
}
return tmp;
},
togglePlayPause: function() {
if (DiggSidebar.playing) {
window.clearTimeout(DiggSidebar.timerId);
@ -249,10 +316,7 @@ var DiggSidebar = {
DiggSidebar.diggPlayPause.setAttribute("tooltiptext", "Click to Start autoupdate");
DiggSidebar.playing = false;
} else {
var updateInterval = DiggSidebar.prefs.get("updateinterval").value;
var updateIntervalDecay = DiggSidebar.prefs.get("updateintervaldecay").value;
var timeout = Math.round(updateInterval*(Math.pow(1.5, updateIntervalDecay)));
var timeout = Math.round(DiggSidebar.updateInterval*(Math.pow(1.5, DiggSidebar.updateIntervalDecay)));
DiggSidebar.timerId = window.setTimeout(DiggSidebar.getStories, timeout);
DiggSidebar.diggPlayPause.image = "chrome://diggsidebar/content/image/Pause.png";
DiggSidebar.diggPlayPause.setAttribute("tooltiptext", "Click to Pause autoupdate");
@ -261,6 +325,7 @@ var DiggSidebar = {
},
initialize: function(){
DiggSidebar.endpoint = DiggSidebar.getEndpointParts(DiggSidebar.prefs.get("endpoint").value);
var $ = function(id) {return document.getElementById(id)};
DiggSidebar.diggIndicator = $('diggIndicator');
DiggSidebar.topicPopup = $('topicPopup');
@ -268,8 +333,14 @@ var DiggSidebar = {
DiggSidebar.storyList = $('storyList');
DiggSidebar.diggEndPoint = $('diggEndPoint');
DiggSidebar.diggPlayPause = $('diggPlayPause');
DiggSidebar.storyListObserver = new StoryListObserver();
DiggSidebar.createMenu();
DiggSidebar.getStories();
},
destroy: function(){
DiggSidebar.storyListObserver.unregister();
}
};
@ -290,6 +361,38 @@ DiggSidebar.Utils = {
return json.decode(string);
}
}
var StoryListObserver = function() {
this.registered = false;
this.register();
}
StoryListObserver.prototype = {
observe: function(subject, topic, data) {
if (topic == window.DiggSidebar.storyListRefreshEventTopic) {
window.DiggSidebar.populateStoryList();
}
},
register: function() {
if (this.registered == false) {
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver(this, DiggSidebar.storyListRefreshEventTopic, false);
this.registered = true;
}
},
unregister: function() {
if (this.registered == true) {
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.removeObserver(this, DiggSidebar.storyListRefreshEventTopic);
this.registered = false;
}
}
}
/*TODO
preferences
toolbar button

View File

@ -6,8 +6,10 @@
<page id="sbDiggSidebar" title="&diggsidebar.title;"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="DiggSidebar.initialize()">
onload="DiggSidebar.initialize()"
onunload="DiggSidebar.destroy()">
<script src="chrome://diggsidebar/content/diggsidebar.js" type="application/x-javascript" />
<script src="chrome://diggsidebar/content/jpath.js" type="application/x-javascript" />
<hbox>
<toolbar flex="1">
<toolbaritem>

355
chrome/content/jpath.js Normal file
View File

@ -0,0 +1,355 @@
/*
JPath 1.0.3 - json equivalent to xpath
Copyright (C) 2007 Bryan English <bryan at bluelinecity dot com>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Usage:
var jpath = new JPath( myjsonobj );
var somevalue = jpath.$('book/title').json; //results in title
//or
var somevalue = jpath.query('book/title'); //results in title
Supported XPath-like Syntax:
/tagname
//tagname
tagname
* wildcard
[] predicates
operators ( >=, ==, <= )
array selection
..
*
and, or
nodename[0]
nodename[last()]
nodename[position()]
nodename[last()-1]
nodename[somenode > 3]/node
nodename[count() > 3]/node
Tested With:
Firefox 2-3, IE 6-7
Update Log:
1.0.1 - Bugfix for zero-based element selection
1.0.2 - Bugfix for IE not handling eval() and returning a function
1.0.3 - Bugfix added support for underscore and dash in query() function
Bugfix improper use of Array.concat which was flattening arrays
Added support for single equal sign in query() function
Added support for count() xpath function
Added support for and, or boolean expression in predicate blocks
Added support for global selector $$ and //
Added support for wildcard (*) selector support
*/
function JPath( json, parent )
{
this.json = json;
this._parent = parent;
}
JPath.prototype = {
/*
Property: json
Copy of current json segment to operate on
*/
json: null,
/*
Property: parent
Parent json object, null if root.
*/
parent: null,
/*
Method: $
Performs a find query on the current jpath object.
Parameters:
str - mixed, find query to perform. Can consist of a nodename or nodename path or function object or integer.
Return:
jpath - Returns the resulting jpath object after performing find query.
*/
'$': function ( str )
{
var result = null;
var working = this;
if ( this.json && str !== null )
{
switch ( typeof(str) )
{
case 'function':
result = this.f(str).json;
break;
case 'number':
result = this.json[str] || null;
break;
case 'string':
var names = str.split('/');
//foreach slash delimited node name//
for ( var i=0; i<names.length ; i++ )
{
var name = new RegExp('^' + names[i].replace(/\*/g,'.*') + '$');
var isArray = (working.json instanceof Array);
var a = new Array();
//foreach working node property//
for ( var p in working.json )
{
if ( typeof( working.json[p] ) != 'function' )
{
if ( isArray && (arguments.callee.caller != this.$$) )
{
a = a.concat( this.findAllByRegExp( name, working.json[p] ) );
}
else if ( name.test(p) )
{
a.push( working.json[p] );
}
}
}
working = new JPath( ( a.length==0 ? null : ( ( a.length == 1) ? a[0] : a ) ), working );
}
return working;
break;
}
}
return new JPath( result, this );
},
/*
Method: $$
Performs a global, recursive find query on the current jpath object.
Parameters:
str - mixed, find query to perform. Can consist of a nodename or nodename path or function object or integer.
Return:
jpath - Returns the resulting jpath object after performing find query.
*/
'$$': function( str )
{
var r = this.$(str,true).json;
var arr = new Array();
if ( r instanceof Array )
arr = arr.concat(r);
else if ( r !== null )
arr.push(r);
for ( var p in this.json )
{
if ( typeof( this.json[p] ) == 'object' )
{
arr = arr.concat( new JPath( this.json[p], this ).$$(str).json );
}
}
return new JPath( arr, this );
},
/*
Method: findAllByRegExp
Looks through a list of an object properties using a regular expression
Parameters:
re - regular expression, to use to search with
obj - object, the object to search through
Returns:
array - resulting properties
*/
findAllByRegExp: function( re, obj )
{
var a = new Array();
for ( var p in obj )
{
if ( obj instanceof Array )
{
a = a.concat( this.findAllByRegExp( re, obj[p] ) );
}
else if ( typeof( obj[p] ) != 'function' && re.test(p) )
{
a.push( obj[p] );
}
}
return a;
},
/*
Method: query (beta)
Performs a find query on the current jpath object using a single string similar to xpath. This method
is currently expirimental.
Parameters:
str - string, full xpath-like query to perform on the current object.
Return:
mixed - Returns the resulting json value after performing find query.
*/
query: function( str )
{
var re = {
" and ":" && ",
" or ":" || ",
"([\\#\\*\\@a-z\\_][\\*a-z0-9_\\-]*)(?=(?:\\s|$|\\[|\\]|\\/))" : "\$('$1').",
"\\[([0-9])+\\]" : "\$($1).",
"\\.\\." : "parent().",
"\/\/" : "$",
"(^|\\[|\\s)\\/" : "$1root().",
"\\/" : '',
"([^\\=\\>\\<\\!])\\=([^\\=])" : '$1==$2',
"\\[" : "$(function(j){ with(j){return(",
"\\]" : ");}}).",
"\\(\\.":'(',
"(\\.|\\])(?!\\$|\\p)":"$1json",
"count\\(([^\\)]+)\\)":"count('$1')"
};
//save quoted strings//
var quotes = /(\'|\")([^\1]*)\1/;
var saves = new Array();
while ( quotes.test(str) )
{
saves.push( str.match(quotes)[2] );
str = str.replace(quotes,'%'+ (saves.length-1) +'%');
}
for ( var e in re )
{
str = str.replace( new RegExp(e,'ig'), re[e] );
}
//alert('this.' + str.replace(/\%(\d+)\%/g,'saves[$1]') + ";");
return eval('this.' + str.replace(/\%(\d+)\%/g,'saves[$1]') + ";");
},
/*
Method: f
Performs the equivilant to an xpath predicate eval on the current nodeset.
Parameters:
f - function, an iterator function that is executed for every json node and is expected to return a boolean
value which determines if that particular node is selected. Alternativly you can submit a string which will be
inserted into a prepared function.
Return:
jpath - Returns the resulting jpath object after performing find query.
*/
f: function ( iterator )
{
var a = new Array();
if ( typeof(iterator) == 'string' )
{
eval('iterator = function(j){with(j){return('+ iterator +');}}');
}
for ( var p in this.json )
{
var j = new JPath(this.json[p], this);
j.index = p;
if ( iterator( j ) )
{
a.push( this.json[p] );
}
}
return new JPath( a, this );
},
/*
Method: parent
Returns the parent jpath object or itself if its the root node
Return:
jpath - Returns the parent jpath object or itself if its the root node
*/
parent: function()
{
return ( (this._parent) ? this._parent : this );
},
/*
Method: position
Returns the index position of the current node. Only valid within a function or predicate
Return:
int - array index position of this json object.
*/
position: function()
{
return this.index;
},
/*
Method: last
Returns true if this is the last node in the nodeset. Only valid within a function or predicate
Return:
booean - Returns true if this is the last node in the nodeset
*/
last: function()
{
return (this.index == (this._parent.json.length-1));
},
/*
Method: count
Returns the count of child nodes in the current node
Parameters:
string - optional node name to count, defaults to all
Return:
booean - Returns number of child nodes found
*/
count: function(n)
{
var found = this.$( n || '*').json;
return ( found ? ( found instanceof Array ? found.length : 1 ) : 0 );
},
/*
Method: root
Returns the root jpath object.
Return:
jpath - The top level, root jpath object.
*/
root: function ()
{
return ( this._parent ? this._parent.root() : this );
}
};

View File

@ -5,7 +5,7 @@
<RDF:Description RDF:about="rdf:#$55wJ23"
em:id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"
em:minVersion="3.0"
em:maxVersion="3.1.*" />
em:maxVersion="3.1b1" />
<RDF:Description RDF:about="urn:mozilla:install-manifest"
em:id="diggsidebar@abhinavsarkar.net"
em:name="Digg Sidebar"