Title: Sample Mojo code for webOS News Reader application Post by: Ken Young on April 21, 2009, 11:40:07 PM
Here's the code from the tutorial being presented in the book Palm webOS by Mitch Allen, published by O'Reilly (https://oreilly.com/catalog/9780596801816/). I'll keep it up to date as additional chapters are released and the code evolves. I'm also working on writing up a few articles that will help explain the concepts being presented in the book along with the code. Work has been pretty busy lately, however, so it may take some time to get through the whole chapter (it was pretty packed with info). I'll try to update the site with something new daily, though, so check back often! This was the application file structure at the end of Chapter 2. It has since evolved quite a bit since then. I'll take a screenshot of the updated structure and post it when I get a chance. (https://www.weboshelp.net/images/webos_application_file_structure.png) appinfo.json
Code:
{ "title": "News", "type": "web", "main": "index.html", "id": "com.palm.app.news", "version": "1.0", "icon": "icon.png" } index.html
Code:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "https://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="https://www.w3.org/1999/xhtml" xml:lang="en"> <head> <title>News</title> <script src="/usr/palm/frameworks/mojo/mojo.js" type="text/javascript" x-mojo-version="1"></script> <script src="app/assistants/stage-assistant.js" type="text/javascript"></script> <script src="app/assistants/storyView-assistant.js" type="text/javascript"></script> <!-- application stylesheet should come in after the one loaded by the framework --> <link href="stylesheets/News.css" media="screen" rel="stylesheet" type="text/css" /> </head> <body> </body> </html> storyView-scene.html
Code:
<div id='storyViewScene'> <div class="palm-page-header multi-line"> <div id="test" class="palm-page-header-wrapper"> <div id="storyViewTitle" class="title left">#{title}</div> </div> </div> <div id="storyViewSummary" class="itemFull">#{text}</div> </div> <div x-mojo-element="Button" id="previousStory"></div> <div x-mojo-element="Button" id="nextStory"></div> storyView-assistant.js
Code:
// StoryViewAssistant(storyFeed, storyIndex) // // Passed a story element, displays that element in a full scene view and offers options // for next story (right command menu button), previous story (left command menu button) // and to launch story URL in the browser (view menu). function StoryViewAssistant(storyFeed, storyIndex) { // Save the passed arguments for use in the scene. // this.storyFeed = storyFeed; this.storyIndex = storyIndex; } StoryViewAssistant.prototype.setup = function() { // Hide Previous Button if first story, and Next Button if last one if (this.storyIndex > 0) { this.controller.setupWidget("previousStory", this.attributes = { disabledProperty: 'disabled' }, this.model = { buttonLabel : "Previous", buttonClass: '', disabled: false }); this.controller.listen('previousStory', Mojo.Event.tap, this.previousStory.bindAsEventListener(this)); } else { $('previousStory').hide(); } if (this.storyIndex < this.storyFeed.stories.length-1) { this.controller.setupWidget("nextStory", this.attributes = { disabledProperty: 'disabled' }, this.model = { buttonLabel : "Next", buttonClass: '', disabled: false }); this.controller.listen('nextStory', Mojo.Event.tap, this.nextStory.bindAsEventListener(this)); } else { $('nextStory').hide(); } }; StoryViewAssistant.prototype.previousStory = function(event) { Mojo.Controller.stageController.swapScene("storyView", this.storyFeed, this.storyIndex-1); }; StoryViewAssistant.prototype.nextStory = function(event) { Mojo.Controller.stageController.swapScene("storyView", this.storyFeed, this.storyIndex+1); }; StoryViewAssistant.prototype.activate = function(event) { $("storyViewTitle").innerHTML = this.storyFeed.stories[this.storyIndex].title; $("storyViewSummary").innerHTML = this.storyFeed.stories[this.storyIndex].text; if (this.storyFeed.stories[this.storyIndex].unReadStyle === unReadFormatting) { this.storyFeed.numUnRead--; this.storyFeed.stories[this.storyIndex].unReadStyle = ""; } }; StoryViewAssistant.prototype.deactivate = function(event) { }; StoryViewAssistant.prototype.cleanup = function(event) { }; storyList-scene.html
Code:
<div class='palm-header'> <div class='palm-header-center'><span id='feedTitle'></span> </div> </div> <div class="palm-header-spacer"></div> <div class="palm-list"> <div id="storyListWgt" x-mojo-element="List"></div> </div> storyList-assistant.js
Code:
// // StoryListAssistant - Displays the feed's stories in a list, user taps display the // selected story in the storyView scene. // // Arguments: // selectedFeed Feed to be displayed // function StoryListAssistant(selectedFeedIndex) { this.feed = feedList[selectedFeedIndex]; this.feedIndex = selectedFeedIndex; } StoryListAssistant.prototype.setup = function() { // Setup story list with standard news list templates. // this.controller.setupWidget("storyListWgt", this.storyAttr = { itemTemplate: "storyList/storyRowTemplate", listTemplate: "storyList/storyListTemplate", swipeToDelete: false, renderLimit: 40, reorderable: false }, this.storyModel = { items: this.feed.stories } ); this.controller.listen("storyListWgt", Mojo.Event.listTap, this.readStory.bindAsEventListener(this)); }; StoryListAssistant.prototype.activate = function() { // Set title into header $("feedTitle").innerHTML=this.feed.title; // Update story list model in case unReadCount has changed this.controller.modelChanged(this.storyModel); }; // readStory - handler when user taps on displayed story, push that story to // the storyView scene StoryListAssistant.prototype.readStory = function(event) { Mojo.Controller.stageController.pushScene("storyView", this.feed, event.index); }; StoryListAssistant.prototype.deactivate = function(event) { }; StoryListAssistant.prototype.cleanup = function(event) { }; storyListTemplate.html
Code:
<div class="palm-list">#{listElements}</div>
storyRowTemplate.html
Code:
<div class="palm-row" x-mojo-tap-highlight="momentary"> <div id="storyTitle" class="listTitle truncating-text #{unReadStyle}">#{title}</div> <div id="storyText" class="listText truncating-text">#{text}</div> </div> stage-assistant.js
Code:
// Globals - used throughout the News application // var feedList = []; // News feed list // Feedlist entry is: // feedList[x].title String Title entered by user // feedList[x].url String Feed source URL in unescaped form // feedList[x].type String Feed type: either rdf (rss1), rss (rss2) or atom // feedList[x].numUnRead Integer How many stories are still unread // feedList[x].stories Array Each entry is a complete story: // feedList[x].stories[y].title String Story title or headline // feedList[x].stories[y].text String Story text // feedList[x].stories[y].unReadStyle String Style to apply when unRead // feedList[x].stories[y].url String Story url // // Push default feeds onto list; these will get overwritten by what's stored in the database but these will be used otherwise feedList.push({title:"Huffington Post", url:"https://feeds.huffingtonpost.com/huffingtonpost/raw_feed", type:"atom", acFreq:0, numUnRead:0, stories:[]}); feedList.push({title:"Google", url:"https://news.google.com/?output=atom", type:"atom", acFreq:0, numUnRead:0, stories:[]}); feedList.push({title:"BBC News", url:"https://newsrss.bbc.co.uk/rss/newsonline_world_edition/front_page/rss.xml", type:"rss", acFreq:0, numUnRead:0, stories:[]}); feedList.push({title:"New York Times", url:"https://www.nytimes.com/services/xml/rss/nyt/HomePage.xml", type:"rss", acFreq:0, numUnRead:0, stories:[]}); feedList.push({title:"MSNBC", url:"https://rss.msnbc.msn.com/id/3032091/device/rss/rss.xml", type:"rss", acFreq:0, numUnRead:0, stories:[]}); feedList.push({title:"National Public Radio", url:"https://www.npr.org/rss/rss.php?id=1004", type:"rss", acFreq:0, numUnRead:0, stories:[]}); feedList.push({title:"Slashdot", url:"https://rss.slashdot.org/Slashdot/slashdot", type:"rdf", acFreq:0, numUnRead:0, stories:[]}); feedList.push({title:"Engadget", url:"https://www.engadget.com/rss.xml", type:"rss", acFreq:0, numUnRead:0, stories:[]}); feedList.push({title:"The Daily Dish", url:"https://feeds.feedburner.com/andrewsullivan/rApM?format=xml", type:"rss", acFreq:0, numUnRead:0, stories:[]}); feedList.push({title:"Guardian UK", url:"https://feeds.guardian.co.uk/theguardian/rss", type:"rss", acFreq:0, numUnRead:0, stories:[]}); feedList.push({title:"Yahoo Sports", url:"https://sports.yahoo.com/top/rss.xml", type:"rss", acFreq:0, numUnRead:0, stories:[]}); feedList.push({title:"ESPN", url:"https://sports-ak.espn.go.com/espn/rss/news", type:"rss", acFreq:0, numUnRead:0, stories:[]}); feedList.push({title:"Ars Technica", url:"https://feeds.arstechnica.com/arstechnica/BAaf", type:"rss", acFreq:0, numUnRead:0, stories:[]}); var curFeedIndex = 0; // Tracks current index for feedlist updates var curFeedSource = feedList[0]; // Constants var unReadFormatting = "unReadStyle"; // Prepended to story titles when unread function StageAssistant () { } StageAssistant.prototype.setup = function() { this.controller.pushScene("storyList", curFeedIndex); }; function StageAssistant () { } StageAssistant.prototype.setup = function() { this.controller.pushScene("storyView"); }; Title: Re: Sample Mojo code for webOS News Reader application Post by: Ken Young on April 21, 2009, 11:40:40 PM
feedList-scene.html
Code:
<div class='palm-header'> <div class='palm-header-center'>News</div> </div> <div class="palm-header-spacer"></div> <div class="palm-list"> <div id="feedListWgt" x-mojo-element="List"></div> </div> <!-- Adding text field within a drawer and group box --> <div id='feedDrawer' x-mojo-element="Drawer"> <div class="palm-group"> <div class="palm-group-title"> <!-- Title and error status for invalid feeds --> <span id="add-feed-title" x-mojo-loc="">Add News Feed Source</span> </div> <div class="palm-group unlabeled"> <div class="palm-list"> <div class='palm-row first'> <div class="palm-row-wrapper textfield-group" x-mojo-focus-highlight="true"> <div class="title"> <div class="label">URL</div> <div id="newFeedURL" x-mojo-element="TextField" align="left"></div> </div> </div> </div> <div class='palm-row last'> <div class="palm-row-wrapper textfield-group" x-mojo-focus-highlight="true"> <div class="title"> <div class="label">Title</div> <div id="newFeedName" x-mojo-element="TextField" align="left"></div> </div> </div> </div> </div> </div> <div x-mojo-element="Button" id="okButton"></div> </div> </div> feedList-assistant.js
Code:
FeedListAssistant.prototype.setup = function() { // Setup the feed list // this.controller.setupWidget("feedListWgt", this.feedWgtAttr = { itemTemplate:"feedList/feedRowTemplate", listTemplate:"feedList/feedListTemplate", swipeToDelete:true, renderLimit: 40, addItemLabel:"Add...", reorderable:true }, this.feedWgtModel = {items: feedList}); // Setup Drawer for add Feed; closed to start // this.controller.setupWidget('feedDrawer', {property:'myOpenProperty'}, this.feedDrawerModel={myOpenProperty:false}); // Setup text field for the new feed's URL // this.controller.setupWidget( "newFeedURL", this.urlAttributes = { property: "value", hintText: "RSS or ATOM feed", focus: true, limitResize: true, textReplacement: false, enterSubmits: false }, this.urlModel = {value : ""}); // Setup text field for the new feed's name // this.controller.setupWidget( "newFeedName", this.nameAttributes = { property: "value", hintText: "Optional", limitResize: true, textReplacement: false, enterSubmits: false }, this.nameModel = {value : ""}); // Setup button and event handler // this.controller.setupWidget("okButton", this.attributes = { }, this.model = { buttonLabel: "Add Feed", buttonClass: "addFeedButton", disabled: false }); this.controller.listen('okButton', Mojo.Event.tap, this.checkIt. bindAsEventListener(this)); // Setup event handlers list selection, add feed, delete feed and reorder feed list // this.controller.listen('feedListWgt', Mojo.Event.listTap, this.showFeed.bindAsEventListener(this)); this.controller.listen('feedListWgt', Mojo.Event.listDelete, this.listDeleteHandler.bindAsEventListener(this)); this.controller.listen('feedListWgt', Mojo.Event.listReorder, this.listReorderHandler.bindAsEventListener(this)); // Set feedsInitialized to false so that activate knows that it's being // launched or pushed from the background // this.feedsInitialized = false; // Start the Ajax Request sequence // this.updateFeedList(); }; // feedRequest - function called to setup and make a feed request // // Uses prototype's Ajax.Request and sets up callback routines: // onSuccess feedRequestSuccess // onFailure feedRequestFailure // FeedListAssistant.prototype.feedRequest = function(curFeed) { var request = new Ajax.Request(curFeed.url, { method: 'get', evalJSON: 'false', onSuccess: this.feedRequestSuccess.bind(this), onFailure: this.feedRequestFailure.bind(this) }); }; // feedRequestFailure // // Callback routine from a failed AJAX feed request (feedRequest); // post a simple failure error message with the http status code. // FeedListAssistant.prototype.feedRequestFailure = function(transport) { // Use the Prototype template object to generate a string from the return status. // var t = new Template("Status #{status} returned from newsfeed request."); var m = t.evaluate(transport); Mojo.Log.info("........","Invalid feed - http failure (", m); }; // feedRequestSuccess // // Callback routine from a successful AJAX feed request (feedRequest); use globals: // curFeedIndex Index for the feed being updated // feedList Holds the processed feeds, will have the current // feed updated at exit; has old version of feed at entry // FeedListAssistant.prototype.feedRequestSuccess = function(transport) { var feedError = errorNone; var t = new Template($L("Status #{status} returned from newsfeed request.")); Mojo.Log.info("........","Feed Request Success: ", t.evaluate(transport)); // DEBUG - Work around if (transport.responseXML === null && transport.responseText !== null) { Mojo.Log.info("........","Request not in XML format - manually converting"); transport.responseXML = new DOMParser().parseFromString (transport.responseText, 'text/xml'); } // Process the feed, identifying the current feed index and passing in // the transport object holding the updated feed data // feedError = ProcessFeed(transport, curFeedIndex); // If successful processFeed returns errorNone if (feedError == errorNone) { // Update the feedList model object with the updated feed data in feedList this.feedWgtModel.items = feedList; this.controller.modelChanged(this.feedWgtModel); } else { // There was a feed process error if (feedError == errorUnsupportedFeedType) { Mojo.Log.info("........","Feed ", this.nameModel.value, " is not a supported feed type. (#", errorUnsupportedFeedType, ")."); } } // Increment the index. If this is NOT the last feed then // update the feedsource and request to retrieve the next feed // curFeedIndex++; if(curFeedIndex < feedList.length) { curFeedSource = feedList[curFeedIndex]; this.feedRequest(curFeedSource); } else { // Otherwise, this update is done. Reset index to 0 for next update; // if the timer is null, the set the interval to feedUpdateInterval // curFeedIndex = 0; this.feedsInitialized = true; } }; // updateFeedList (FeedListAssistant) - called to cycle through the feeds either on activate or // after the interval timer has fired. This is split from feedRequest // so that the latter can be called on each feed. This is called once per // update cycle. // FeedListAssistant.prototype.updateFeedList = function() { // request fresh copies of all stories curFeedIndex = 0; curFeedSource = feedList[curFeedIndex]; this.feedRequest(curFeedSource); }; // listDeleteHandler - triggered by deleting a feed from // the list and updates the feedList to reflect the deletion // FeedListAssistant.prototype.listDeleteHandler = function(event) { var deleteIndex = event.index; feedList.splice(deleteIndex, 1); this.feedWgtModel.items = feedList; }; // listReorderHandler- triggered re-ordering feed list and // updates the feedList to reflect the changed order // FeedListAssistant.prototype.listReorderHandler = function(event) { var fromIndex = event.fromIndex; var toIndex = event.toIndex; feedList.splice(fromIndex, 1); feedList.splice(toIndex, 0, event.item); this.feedWgtModel.items = feedList; }; // addNewFeed - triggered by "Add..." item in feed list and invokes the AddDialog // Assistant defined above. // FeedListAssistant.prototype.addNewFeed = function() { this.feedDrawerModel.myOpenProperty = !this.feedDrawerModel.myOpenProperty; this.controller.modelChanged(this.feedDrawerModel, this); }; feedListTemplate.html
Code:
<div class="palm-list">#{listElements}</div>
feedRowTemplate.html
Code:
div class="palm-row" x-mojo-tap-highlight="momentary"> <div class="palm-row-wrapper"> <div class="icon right"><div class="unReadCount">#{numUnRead}</div></div> <div class="title truncating-text">#{title}</div> </div> </div> news.css
Code:
/* News CSS - App overrides of palm scene and widget styles. */ .palm-page-header-wrapper { padding-top: 5px; padding-left: 12px; padding-right: 10px; font-weight: bold; } .itemFull { vertical-align: top; font-size: 12pt; font-weight: normal; text-align: left; padding-right: 5px; padding-left: 5px; } .listTitle { padding-top: 10px; padding-right: 5px; padding-left: 5px; font-size: 14pt; text-align: left; font-weight: normal; } .listText { vertical-align: bottom; padding-bottom: 10px; padding-right: 10px; padding-left: 5px; font-size: 10pt; height: 20px; font-weight: normal; text-align: left; overflow-x: hidden; overflow-y: hidden; } .unReadStyle { font-weight: bold; } .unReadCount { background: url('/'/../images/unread-background.png'') no-repeat ; width: 40px; height: 50px; position: relative; margin-top: 5px; top: 6px; padding-top: 3px; vertical-align: middle; text-align: center; color: white; } .addFeedButton { width: 263px; } sources.json
Code:
[
{ "source": "app\/controllers\/stage-assistant.js" }, { "source": "app\/controllers\/storyList-assistant.js", "scenes": "storyList" }, { "source": "app\/controllers\/storyView-assistant.js", "scenes": "storyView" } ] |
advertisement: