Symbian.org/Forums.js
author ivanl
Mon, 10 Aug 2009 11:26:10 +0100
changeset 17 5dc2963cd75f
parent 10 07ac2f6a36a9
child 18 b73e6caf0031
permissions -rw-r--r--
1.0rc14 Fixed bbcode rendering

// ////////////////////////////////////////////////////////////////////////////
// Symbian Foundation Example Code
//
// This software is in the public domain. No copyright is claimed, and you 
// may use it for any purpose without license from the Symbian Foundation.
// No warranty for any purpose is expressed or implied by the authors or
// the Symbian Foundation. 
// ////////////////////////////////////////////////////////////////////////////

// Forums

// Forums have the following structure:
//
// Forum group list
//   Forum list
//     Thread list
//       Message list

// All four views are based on customised RssReader. We customise two aspects:
// - Parsing XML - data is not RSS
// - Handling item selection (e.g. creating a new view for a newly selected forum)

// /////////////////////////////////////////////////////////////////////////////
// Forum groups

// response parser for forum groups
function forumGroupsResponseParser(broker, responseStatus, xmlDoc) {
    if (responseStatus == 200 && xmlDoc != null) {
        // node ref for iterating
        var node;

		// for compatibility with rss
		var lastModified = new Date();
		
        // init result items array
        var items = [];

		var elements = xmlDoc.getElementsByTagName("group");
		for (var i = 0; i < elements.length; i++) {
			var groupid = elements[i].getAttribute("id");
			var grouptitle = elements[i].getAttribute("title");
            items.push({ id: groupid, title: grouptitle});
		}

        // update was completed successfully
        return { status: "ok", lastModified: lastModified, items: items };
    } else {
        // update failed
        return { status: "error" };
    }
}

// FeedPresenter implementation for forum groups
function ForumGroupsFeedPresenter(rssreader){
	if (rssreader) {
		this.init(rssreader);
	}
}

// ForumGroupsFeedPresenter is a subclass of ButtonFeedPresenter
ForumGroupsFeedPresenter.prototype = new ButtonFeedPresenter(null);

// ForumGroupsFeedPresenter "Constructor"
ForumGroupsFeedPresenter.prototype.init = function(rssreader) {
	ButtonFeedPresenter.prototype.init.call(this, rssreader);
}

// Handle the click on a specific item
ForumGroupsFeedPresenter.prototype.feedClicked = function(event){
	var buttonid = event.source.id;
	
	if (buttonid == "latestPosts") {
		// show latest posts
		var url = forumFeedURL;
		var latestPostsView = new RssReader("Latest posts", url, new LatestPostsFeedPresenter(null), this.rssreader, null);
		latestPostsView.show();
	}
	else {
		// show forum group
		var groupid = this.items[buttonid].id;
		var grouptitle = this.items[buttonid].title;
		
		var url = forumsListUrl + groupid;
		var forumListView = new RssReader(grouptitle, url, new ForumsListFeedPresenter(null), this.rssreader, forumListResponseParser);
		forumListView.show();
	}
}

// Create and add controls to be shown before items list.
ForumGroupsFeedPresenter.prototype.addPreambleItems = function(){
	var feedItemControl = new NavigationButton("latestPosts", "blueright.gif", "Latest posts");
    var self = this;
	feedItemControl.addEventListener("ActionPerformed", function(event) { self.feedClicked(event); });
	this.rssreader.addControl(feedItemControl);
}


// ///////////////////////////////////////////////////////////////////////////
// List of forums in a group

// response parser for forum list - in a group
function forumListResponseParser(broker, responseStatus, xmlDoc) {
    if (responseStatus == 200 && xmlDoc != null) {
        // node ref for iterating
        var node;

		// for compatibility with rss
		var lastModified = new Date();
		
        // init result items array
        var items = [];

		// extract items for all group elements
		var elements = xmlDoc.getElementsByTagName("group");
		for (var i = 0; i < elements.length; i++) {
			var forumid = elements[i].getAttribute("id");
			var forumtitle = elements[i].getAttribute("title");
            items.push({ id: forumid, title: forumtitle});
		}

        // update was completed successfully
        return { status: "ok", lastModified: lastModified, items: items };
    } else {
        // update failed
        return { status: "error" };
    }
}

// FeedPresenter implementation for forum groups
function ForumsListFeedPresenter(rssreader){
	if (rssreader) {
		this.init(rssreader);
	}
}

// ForumsListFeedPresenter is a subclass of ButtonFeedPresenter
ForumsListFeedPresenter.prototype = new ButtonFeedPresenter(null);

// ForumsListFeedPresenter constructor
ForumsListFeedPresenter.prototype.init = function(rssreader) {
	ButtonFeedPresenter.prototype.init.call(this, rssreader);
}


// forum has been selected, create a reader showing threads in the forum
ForumsListFeedPresenter.prototype.feedClicked = function(event){
	var buttonid = event.source.id;
	if (buttonid == "latestPosts") {
		// show latest posts
		var url = forumFeedURL + "&forumids=";
		// append requested forum ids
		for( var i = 0; i < this.items.length; i++) {
			url += this.items[i].id + ",";
		}
		
		var latestPostsView = new RssReader(
			"Latest posts in " + this.rssreader.feedName, 
			url, 
			new LatestPostsFeedPresenter(null), 
			this.rssreader, 
			null);
		latestPostsView.show();
	}
	else {
		var forumid = this.items[buttonid].id;
		var forumtitle = this.items[buttonid].title;
		
		var url = forumFeedURL + forumsForumSpecQuery + forumid;
		var forumListView = new RssReader(forumtitle, url, new ThreadListFeedPresenter(null), this.rssreader, null);
		forumListView.show();
	}
}

// Create and add controls to be shown before items list.
ForumsListFeedPresenter.prototype.addPreambleItems = function(){
	var feedItemControl = new NavigationButton("latestPosts", "blueright.gif", "Latest posts in " + this.rssreader.feedName);
    var self = this;
	feedItemControl.addEventListener("ActionPerformed", function(event) { self.feedClicked(event); });
	this.rssreader.addControl(feedItemControl);
}



// ///////////////////////////////////////////////////////////////////////////
// List of threads in a forum

// response parser for thread list is the usual rss parser

// FeedPresenter implementation for forum groups
function ThreadListFeedPresenter(rssreader){
	if (rssreader) {
		this.init(rssreader);
	}
}

// ThreadListFeedPresenter is a subclass of ButtonFeedPresenter
ThreadListFeedPresenter.prototype = new ButtonFeedPresenter(null);

// ThreadListFeedPresenter constructor
ThreadListFeedPresenter.prototype.init = function(rssreader) {
	ButtonFeedPresenter.prototype.init.call(this, rssreader);
}


// Handle the click on a specific item
ThreadListFeedPresenter.prototype.feedClicked = function(event){
	var buttonid = event.source.id;
	
	if (buttonid == "newThread") {
		// extract forum id from rssreader.feedURL
		var ind = this.rssreader.feedURL.indexOf(forumsForumSpecQuery);
		var forumid = this.rssreader.feedURL.substring( ind + forumsForumSpecQuery.length);
		var postForm = new ForumPostForm(this.rssreader, forumid);
		postForm.show();
	}
	else {
		var weburl = this.items[buttonid].url;
		
		// extract thread id from url. looking for t=xxx
		var ind1 = weburl.indexOf("?t=");
		if (ind1 == -1) {
			ind1 = weburl.indexOf("&t=");
		}
		if (ind1 != -1) {
			var threadid = "";
			var ind2 = weburl.indexOf("&", ind1);
			if (ind2 == -1) {
				threadid = weburl.substring(ind1 + 3); // ?t=
			}
			else {
				threadid = weburl.substring(ind1 + 3, ind2); // ?t=
			}
			var url = forumThreadUrl + threadid;
			var title = this.items[buttonid].title;
			if (title.length > 30) {
				title = title.substring(0, 30) + "...";
			}
			var threadView = new RssReader(title, url, new ThreadFeedPresenter(null), this.rssreader, threadResponseParser);
			threadView.show();
		}
	}
}

// Create and add controls to be shown before items list.
ThreadListFeedPresenter.prototype.addPreambleItems = function(){
	var feedItemControl = new NavigationButton("newThread", "blueright.gif", "Post a new thread");
    var self = this;
	feedItemControl.addEventListener("ActionPerformed", function(event) { self.feedClicked(event); });
	this.rssreader.addControl(feedItemControl);
}

// ///////////////////////////////////////////////////////////////////////////
// List of messages in a thread

// response parser for thread list
function threadResponseParser(broker, responseStatus, xmlDoc) {
    if (responseStatus == 200 && xmlDoc != null) {
        // node ref for iterating
        var node;

		// for compatibility with rss
		var lastModified = new Date();
		
        // init result items array
        var items = [];

		// iterate over message elements
		var elements = xmlDoc.getElementsByTagName("message");
		for (var i = 0; i < elements.length; i++) {
			var postid;
			var threadid;
			var username;
			var title;
			var dateline;
			var pagetext;
			var isdeleted;
			
			// extract info about the post
			node = elements[i].firstChild;
			while (node != null) {
				if ( node.nodeName == "postid" ) postid=getTextOfNode(node);
				else if ( node.nodeName == "threadid" ) threadid=getTextOfNode(node);
				else if ( node.nodeName == "username" ) username=getTextOfNode(node);
				else if ( node.nodeName == "title" ) title=getTextOfNode(node);
				else if ( node.nodeName == "dateline" ) dateline=getTextOfNode(node);
				else if ( node.nodeName == "pagetext" ) pagetext=getTextOfNode(node);
				else if ( node.nodeName == "isdeleted" ) isdeleted=getTextOfNode(node);
				node = node.nextSibling;
			}
			if ( isdeleted == 1 ) continue;
			
			items.push({
				postid: postid,
				threadid: threadid,
				username: username,
				title: title,
				dateline: dateline,
				pagetext: pagetext
			});
		}

        // update was completed successfully
        return { status: "ok", lastModified: lastModified, items: items };
    } else {
        // update failed
        return { status: "error" };
    }
}

// FeedPresenter implementation for forum groups
function ThreadFeedPresenter(rssreader){
	if (rssreader) {
		this.init(rssreader);
	}
}

// ThreadFeedPresenter is a subclass of HtmlFeedPresenter
ThreadFeedPresenter.prototype = new HtmlFeedPresenter(null);

// ThreadFeedPresenter constructor
ThreadFeedPresenter.prototype.init = function(rssreader) {
	HtmlFeedPresenter.prototype.init.call(this, rssreader);
}


// Handle the click on a specific item
ThreadFeedPresenter.prototype.feedClicked = function(event){
	// do nothing
}

// Create a control that represents this item and add it to
// parent rss reader
ThreadFeedPresenter.prototype.show = function(item) {
	// get a feed item control from the pool or create one and
	// place it in the pool if there aren't enough feed item controls
	var feedItemControl = new ContentPanel(null, null, null, true);

	// initialize feed item control
	var title = item.title;
	if ( !title || title.length == 0 ) { 
		title = "Re:";
		item.title = title;
	}
	feedItemControl.setCaption(bbcode2html(title));
	feedItemControl.setContent(this.getContentHTMLForFeedItem(item));
	feedItemControl.setExpanded(true);
	
	// add the feed item control to the main view
	this.rssreader.feedItemControls.push(feedItemControl);
	this.rssreader.addControl(feedItemControl);
}

// Generate HTML content from the feed item
ThreadFeedPresenter.prototype.getContentHTMLForFeedItem = function (item){
	var buf = "";
	
	// item date
	if (item.dateline != null) {
		var date = new Date();
		date.setTime(item.dateline*1000);
		buf += "<div class=\"FeedItemDate\">" ;
		if ( item.username != null ) {
			buf += item.username + ", ";
		}
		buf += date + "</div>";
	}
	
	// item description
	if (item.pagetext != null) {
		var text = bbcode2html(item.pagetext);
		text = text.replace(/\r\n/g, "<br>");
		buf += "<div class=\"FeedItemDescription\">" + text + "</div>";
        buf += "<div class=\"FeedItemLink\">";
		buf += "<a href=\"JavaScript:void(0)\" onclick=\"showReplyForm(" 
				+ item.threadid+ "," + item.postid + ", '" + item.title
				+ "'); return false;\">";
		buf += "<strong>Reply to this post<strong></a>"
		buf += "</div>";
	}
	
	return buf;
}

// Show the reply-to-post form
function showReplyForm(threadid, postid, title) {
	var replyForm = new ForumReplyForm(uiManager.currentView, threadid, postid, title);
	replyForm.show();
}


// ///////////////////////////////////////////////////////////////////////////
// Latest posts - same as ThreadListFeedPresenter, only has no preamble items
// because it doesn't show one thread (so we can't post to latest items)...

// FeedPresenter implementation for latest posts
function LatestPostsFeedPresenter(rssreader){
	if (rssreader) {
		this.init(rssreader);
	}
}

LatestPostsFeedPresenter.prototype = new ThreadListFeedPresenter(null);

// ForumGroupsFeedPresenter "Constructor"
LatestPostsFeedPresenter.prototype.init = function(rssreader) {
	ButtonFeedPresenter.prototype.init.call(this, rssreader);
}

// LatestPostsFeedPresenter has no preamble items
LatestPostsFeedPresenter.prototype.addPreambleItems = function(){
}


// ///////////////////////////////////////////////////////////////////////////
// Utilities


// Forum posts can be be quite messy and include bbcodes, smilies etc.
// This function does the minimum by stripping bbcodes and such
function sanitize(text) {
	var prevind = 0;
	var ind = text.indexOf("[");
	if ( ind == -1 ) return text;
	var buf = "";
	while ( ind != -1 ) {
		buf += text.substring(prevind, ind);
		var ind2 = text.indexOf("]", ind);
		if ( ind2 != -1 ) {
			prevind = ind2+1;
		} else {
			break;
		}
		ind = text.indexOf("[", prevind);
	}
	if ( prevind > 0 && prevind < text.length) {
		buf += text.substring(prevind);
	}
	return buf;
}



// feeds contain bbcodes - this function should turn the bbcode markup
// to HTML
function bbcode2html(s) {
	var prevind = 0;
	var buf = "";
	var ind = s.indexOf("[");
	if ( ind == -1 ) return s;
	while ( ind != -1 ) {
		buf += s.substring(prevind, ind);
		var ind2 = s.indexOf("]", ind); // end of tag
		var fulltag = s.substring(ind+1,ind2);
		var tag = fulltag;
		var ind3 = s.indexOf("=", ind); // end of tag name, eg. [URL=http...]
		if ( ind3 != -1 && ind3 < ind2) {
			tag = s.substring(ind+1,ind3);
		} 
		var ind4 = s.indexOf("[/"+tag+"]", ind2);
		var tagContent = s.substring(ind2+1, ind4);
		buf += convertTag(tag, fulltag, tagContent);
		if ( ind4 != -1 ) {
			prevind = s.indexOf(']',ind4) + 1;
		} else {
			break;
		}
		ind = s.indexOf("[", prevind);
	}
	buf += s.substring(prevind);
	return buf;
}

function convertTag(tag, fulltag, tagContent) {
	tag = tag.toLowerCase();
	var param = null;
	var eqsign = fulltag.indexOf("=");  // onclick=\"openURL('" + item.url + "');
	if (eqsign > -1) {
		param = fulltag.substring(eqsign+1);
	}
	switch(tag) {
		case '*': return bbcode2html(tagContent);
		case 'b':case 'i':case 'u':case 's':case 'sup':case 'sub':case 'h1':case 'h2':case 'h3':case 'h4':case 'h5':case 'h6':case 'table':case 'tr':case 'th':case 'td':
		{
			return '<' + tag + '>' + bbcode2html(tagContent) + "</" + tag + ">";
		}
		case 'font': return '<font face="'+param+'">' + bbcode2html(tagContent) + '</font>';
		case 'size': return '<font size="'+param+'">' + bbcode2html(tagContent) + '</font>';
		case 'color': return '<font color="'+param+'">' + bbcode2html(tagContent) + '</font>';
		case 'left': return '<div align="left">' + bbcode2html(tagContent) + '</div>';
		case 'right': return '<div align="right">' + bbcode2html(tagContent) + '</div>';
		case 'center': return '<div align="center">' + bbcode2html(tagContent) + '</div>';
		case 'list': return tagContent; // todo
		case 'php': 
		case 'code': return '<div class=codebox><code>' + tagContent + '</code></div>';
		case 'html':{
			var escaped = tagContent.replace(/</g, "&lt;").replace(/>/g, "&gt;");
			return '<div class=codebox><code>' + escaped + '</code></div>';
		}
		case 'quote': return '<div class=codebox><b>Quote:</b><br><i>' + tagContent + '</i></div>';
		case 'url': {
			if ( eqsign > -1 ) {
				return "<div class=\"FeedItemLink\"><a href=\"JavaScript:void(0)\" onclick=\"openURL( '"
				+ param
				+ "')\" ><i>"
				+ tagContent
				+ '</i></a></div>'; 
			} else {
				return "<div class=\"FeedItemLink\"><a href=\"JavaScript:void(0)\" onclick=\"openURL( '"
				+ tagContent
				+ "')\" ><i>"
				+ tagContent
				+ '</i></a></div>'; 
			}
		}
	}
}