Some of the JavaScript code has been broken to accommodate the book layout, and may not execute as broken. It’s best to retrieve the code from the oreilly.com link above.
news app assistants app-assistant.js dashboard-assistant.js feedList-assistant.js preferences-assistant.js stage-assistant.js storyList-assistant.js storyView-assistant.js models cookie.js feeds.js views dashboard dashboard-scene.html item-info.html feedList feedRowTemplate.html feedListTemplate.html addFeed-dialog.html feedList-scene.html preferences preferences-scene.html storyList storyList-scene.html storyListTemplate.html storyRowTemplate.html storyView storyView-scene.html appinfo.json framework_config.json icon.png images cal-selector-header-gray.png dashboard-icon-news.png details-closed-arrow.png details-open-arrow.png feedlist-newitem.png filter-search-light-bg.png header-icon-news.png icon-rssfeed.png info-icon.png list-icon-rssfeed.png menu-icon-back.png menu-icon-forward.png menu-icon-web.png news-icon.png palm-drawer-background-2.png url-icon.png index.html resources es_us appinfo.json strings.json views feedList addFeed-dialog.html feedList-scene.html preferences preferences-scene.html sources.json stylesheets News.css
/* AppAssistant - NEWS Copyright 2009 Palm, Inc. All rights reserved. Responsible for app startup, handling launch points and updating news feeds. Major components: - setup; app startup including preferences, initial load of feed data from the Depot and setting alarms for periodic feed updates - handleLaunch; launch entry point for initial launch, feed update alarm, dashboard or banner tap - handleCommand; handles app menu selections Data structures: - globals; set of persistant data used throughout app - Feeds Model; handles all feedlist updates, db handling and default data - Cookies Model; handles saving and restoring preferences App architecture: - AppAssistant; handles startup, feed list management and app menu management - FeedListAssistant; handles feedList navigation, search, feature feed - StoryListAssistant; handles single feed navigation - StoryViewAssistant; handles single story navigation - PreferencesAssistant; handles preferences display and changes - DashboardAssistant; displays latest new story and new story count */ // --------------------------------------------------------------- // GLOBALS // --------------------------------------------------------------- // News namespace News = {}; // Constants News.unreadStory = "unReadStyle"; News.versionString = "1.0"; News.MainStageName = "newsStage"; News.DashboardStageName = "newsDashboard"; News.errorNone = "0"; // No error, success News.invalidFeedError = "1"; // Not RSS2, RDF (RSS1), or ATOM // Global Data Structures // Persistent Globals - will be saved across app launches News.featureFeedEnable = true; // Enables feed rotation News.featureStoryInterval = 5000; // Feature Interval (in ms) News.notificationEnable = true; // Enables notifcations News.feedUpdateBackgroundEnable = false; // Enable device wakeup News.feedUpdateInterval = "00:15:00"; // Feed update interval // Session Globals - not saved across app launches News.feedListChanged = false; // Triggers update to Depot db News.feedListUpdateInProgress = false; // Feed update is in progress News.featureStoryTimer = null; // Timer for story rotations News.dbUpdate = ""; // Default is no update News.wakeupTaskId = 0; // Id for wakeup tasks // Setup App Menu for all scenes; all menu actions handled in // AppAssistant.handleCommand() News.MenuAttr = {omitDefaultItems: true}; News.MenuModel = { visible: true, items: [ {label: $L("About News..."), command: "do-aboutNews"}, Mojo.Menu.editItem, {label: $L("Update All Feeds"), checkEnabled: true, command: "do-feedUpdate"}, {label: $L("Preferences..."), command: "do-newsPrefs"}, Mojo.Menu.helpItem ] }; function AppAssistant (appController) { } // ------------------------------------------------------- // setup - all startup actions: // - Setup globals with preferences // - Set up application menu; used in every scene // - Open Depot and use contents for feedList // - Initiate alarm for first feed update AppAssistant.prototype.setup = function() { // initialize the feeds model this.feeds = new Feeds(); this.feeds.loadFeedDb(); // load preferences and globals from saved cookie News.Cookie.initialize(); // Set up first timeout alarm this.setWakeup(); }; // ------------------------------------------------------- // handleLaunch - called by the framework when the application is asked to launch // - First launch; create card stage and first first scene // - Update; after alarm fires to update feeds // - Notification; after user taps banner or dashboard // AppAssistant.prototype.handleLaunch = function (launchParams) { Mojo.Log.info("ReLaunch"); var cardStageController = this.controller.getStageController(News.MainStageName); var appController = Mojo.Controller.getAppController(); if (!launchParams) { // FIRST LAUNCH // Look for an existing main stage by name. if (cardStageController) { // If it exists, just bring it to the front by focusing its window. Mojo.Log.info("Main Stage Exists"); cardStageController.popScenesTo("feedList"); cardStageController.activate(); } else { // Create a callback function to set up the new main stage // once it is done loading. It is passed the new stage controller // as the first parameter. var pushMainScene = function(stageController) { stageController.pushScene("feedList", this.feeds); }; Mojo.Log.info("Create Main Stage"); var stageArguments = {name: News.MainStageName, lightweight: true}; this.controller.createStageWithCallback(stageArguments, pushMainScene.bind(this), "card"); } } else { Mojo.Log.info("com.palm.app.news -- Wakeup Call", launchParams.action); switch (launchParams.action) { // UPDATE FEEDS case "feedUpdate" : // Set next wakeup alarm this.setWakeup(); // Update the feed list Mojo.Log.info("Update FeedList"); this.feeds.updateFeedList(); break; // NOTIFICATION case "notification" : Mojo.Log.info("com.palm.app.news -- Notification Tap"); if (cardStageController) { // If it exists, find the appropriate story list and activate it. Mojo.Log.info("Main Stage Exists"); cardStageController.popScenesTo("feedList"); cardStageController.pushScene("storyList", this.feeds.list, launchParams.index); cardStageController.activate(); } else { // Create a callback function to set up a new main stage, // push the feedList scene and then the appropriate story list var pushMainScene2 = function(stageController) { stageController.pushScene("feedList", this.feeds); stageController.pushScene("storyList", this.feeds.list, launchParams.index); }; Mojo.Log.info("Create Main Stage"); var stageArguments2 = {name: News.MainStageName, lightweight: true}; this.controller.createStageWithCallback(stageArguments2, pushMainScene2.bind(this), "card"); } break; } } }; // ----------------------------------------- // handleCommand - called to handle app menu selections // AppAssistant.prototype.handleCommand = function(event) { var stageController = this.controller.getActiveStageController(); var currentScene = stageController.activeScene(); if (event.type == Mojo.Event.commandEnable) { if (News.feedListUpdateInProgress && (event.command == "do-feedUpdate")) { event.preventDefault(); } } else { if(event.type == Mojo.Event.command) { switch(event.command) { case "do-aboutNews": currentScene.showAlertDialog({ onChoose: function(value) {}, title: $L("News — v#{version}").interpolate ({version: News.versionString}), message: $L("Copyright 2008-2009, Palm Inc."), choices:[ {label:$L("OK"), value:""} ] }); break; case "do-newsPrefs": stageController.pushScene("preferences"); break; case "do-feedUpdate": this.feeds.updateFeedList(); break; } } } }; // ------------------------------------------------------------------------ // setWakeup - called to setup the wakeup alarm for background feed updates // if preferences are not set for a manual update (value of "00:00:00") AppAssistant.prototype.setWakeup = function() { if (News.feedUpdateInterval !== "00:00:00") { this.wakeupRequest = new Mojo.Service.Request("palm://com.palm.power/timeout", { method: "set", parameters: { "key": "com.palm.app.news.update", "in": News.feedUpdateInterval, "wakeup": News.feedUpdateBackgroundEnable, "uri": "palm://com.palm.applicationManager/open", "params": { "id": "com.palm.app.news", "params": {"action": "feedUpdate"} } }, onSuccess: function(response){ Mojo.Log.info("Alarm Set Success", response.returnValue); News.wakeupTaskId = Object.toJSON(response.taskId); }, onFailure: function(response){ Mojo.Log.info("Alarm Set Failure", response.returnValue, response.errorText); } }); Mojo.Log.info("Set Update Timeout"); } };
/* Dashboard Assistant - NEWS Copyright 2009 Palm, Inc. All rights reserved. Responsible for posting that last feed with new stories, including the new story count and the latest story headline. Arguments: - feedlist; News feed list - selectedFeedIndex; target feed Other than posting the new story, the dashboard will call the News apps handleLaunch with a "notification" action when the dashboard is tapped, and the dashboard window will be closed. */ function DashboardAssistant(feedlist, selectedFeedIndex) { this.list = feedlist; this.index = selectedFeedIndex; this.title = this.list[this.index].title; this.message = this.list[this.index].stories[0].title; this.count = this.list[this.index].newStoryCount; } DashboardAssistant.prototype.setup = function() { this.displayDashboard(this.title, this.message, this.count); this.switchHandler = this.launchMain.bindAsEventListener(this); this.controller.listen("dashboardinfo", Mojo.Event.tap, this.switchHandler); this.stageDocument = this.controller.stageController.document; this.activateStageHandler = this.activateStage.bindAsEventListener(this); Mojo.Event.listen(this.stageDocument, Mojo.Event.stageActivate, this.activateStageHandler); this.deactivateStageHandler = this.deactivateStage.bindAsEventListener(this); Mojo.Event.listen(this.stageDocument, Mojo.Event.stageDeactivate, this.deactivateStageHandler); }; DashboardAssistant.prototype.cleanup = function() { // Release event listeners this.controller.stopListening("dashboardinfo", Mojo.Event.tap, this.switchHandler); Mojo.Event.stopListening(this.stageDocument, Mojo.Event.stageActivate, this.activateStageHandler); Mojo.Event.stopListening(this.stageDocument, Mojo.Event.stageDeactivate, this.deactivateStageHandler); }; DashboardAssistant.prototype.activateStage = function() { Mojo.Log.info("Dashboard stage Activation"); this.storyIndex = 0; this.showStory(); }; DashboardAssistant.prototype.deactivateStage = function() { Mojo.Log.info("Dashboard stage Deactivation"); this.stopShowStory(); }; // Update scene contents, using render to insert the object into an HTML template DashboardAssistant.prototype.displayDashboard = function(title, message, count) { var info = {title: title, message: message, count: count}; var renderedInfo = Mojo.View.render({ object: info, template: "dashboard/item-info" }); var infoElement = this.controller.get("dashboardinfo"); infoElement.innerHTML = renderedInfo; }; DashboardAssistant.prototype.launchMain = function() { Mojo.Log.info("Tap to Dashboard"); var appController = Mojo.Controller.getAppController(); appController.assistant.handleLaunch({action:"notification", index:this.index}); this.controller.window.close(); }; // showStory - rotates stories shown in dashboard panel, every 3 seconds. DashboardAssistant.prototype.showStory = function() { Mojo.Log.info("Dashboard Story Rotation", this.timer, this.storyIndex); this.interval = 3000; // If timer is null, just restart the timer and use the most recent story // or the last one displayed; if (!this.timer) { this.timer = this.controller.window.setInterval(this.showStory.bind(this), this.interval); } // Else, get next story in list and update the story in the dashboard display else { // replace with test for unread story this.storyIndex = this.storyIndex+1; if(this.storyIndex >= this.list[this.index].stories.length) { this.storyIndex = 0; } this.message = this.list[this.index].stories[this.storyIndex].title; this.displayDashboard(this.title, this.message, this.count); } }; DashboardAssistant.prototype.stopShowStory = function() { if (this.timer) { this.controller.window.clearInterval(this.timer); this.timer = undefined; } }; // Update dashboard scene contents - external method DashboardAssistant.prototype.updateDashboard = function(selectedFeedIndex) { this.index = selectedFeedIndex; this.title = this.list[this.index].title; this.message = this.list[this.index].stories[0].title; this.count = this.list[this.index].newStoryCount; this.displayDashboard(this.title, this.message, this.count); };
/* FeedListAssistant - NEWS Copyright 2009 Palm, Inc. All rights reserved. Main scene for News app. Includes AddDialog-assistant for handling feed entry and then feedlist-assistant and supporting functions. Major components: - AddDialogAssisant; Scene assistant for add feed dialog and handlers - FeedListAssistant; manages feedlists - List Handlers - delete, reorder and add feeds - Feature Feed - functions for rotating and showing feature stories - Search - functions for searching across the entire feedlist database Arguments: - feeds; Feeds object */ // ------------------------------------------------------------------------ // AddDialogAssistant - simple controller for adding new feeds to the list // when the "Add..." is selected on the feedlist. The dialog will // allow the user to enter the feed's url and optionally a name. When // the "Ok" button is tapped, the new feed will be loaded. If no errors // are encountered, the dialog will close otherwise the error will be // posted and the user encouraged to try again. // function AddDialogAssistant(sceneAssistant, feeds, index) { this.feeds = feeds; this.sceneAssistant = sceneAssistant; // If an index is provided then this is an edit feed, not add feed // so provide the existing title, url and modify the dialog title if (index !== undefined) { this.title = this.feeds.list[index].title; this.url = this.feeds.list[index].url; this.feedIndex = index; this.dialogTitle = $L("Edit News Feed"); } else { this.title = ""; this.url = ""; this.feedIndex = null; this.dialogTitle = $L("Add News Feed Source"); } } AddDialogAssistant.prototype.setup = function(widget) { this.widget = widget; // Set the dialog title to either Edit or Add Feed var addFeedTitleElement = this.sceneAssistant.controller.get("add-feed-title"); addFeedTitleElement.innerHTML = this.dialogTitle; // Setup text field for the new feed's URL this.sceneAssistant.controller.setupWidget( "newFeedURL", { hintText: $L("RSS or ATOM feed URL"), autoFocus: true, autoReplace: false, textCase: Mojo.Widget.steModeLowerCase, enterSubmits: false }, this.urlModel = {value : this.url}); // Setup text field for the new feed's name this.sceneAssistant.controller.setupWidget( "newFeedName", { hintText: $L("Title (Optional)"), autoReplace: false, textCase: Mojo.Widget.steModeTitleCase, enterSubmits: false }, this.nameModel = {value : this.title}); // Setup OK & Cancel buttons // OK button is an activity button which will be active // while processing and adding feed. Cancel will just // close the scene this.okButtonModel = {label: $L("OK"), disabled: false}; this.sceneAssistant.controller.setupWidget("okButton", {type: Mojo.Widget.activityButton}, this.okButtonModel); this.okButtonActive = false; this.okButton = this.sceneAssistant.controller.get("okButton"); this.checkFeedHandler = this.checkFeed.bindAsEventListener(this); this.sceneAssistant.controller.listen("okButton", Mojo.Event.tap, this.checkFeedHandler); this.cancelButtonModel = {label: $L("Cancel"), disabled: false}; this.sceneAssistant.controller.setupWidget("cancelButton", {type: Mojo.Widget.defaultButton}, this.cancelButtonModel); this.sceneAssistant.controller.listen("cancelButton", Mojo.Event.tap, this.widget.mojo.close); }; // checkFeed - called when OK button is clicked implying a valid feed URL // has been entered. AddDialogAssistant.prototype.checkFeed = function() { if (this.okButtonActive === true) { // Shouldn't happen, but log event if it does and exit Mojo.Log.info("Multiple Check Feed requests"); return; } // Check entered URL and name to confirm a valid and supported feedlist Mojo.Log.info("New Feed URL Request: ", this.urlModel.value); // Check for "http://" on front or other prefix; assume any string of // 1 to 5 alpha characters followed by ":" is ok, else prepend "http://" var url = this.urlModel.value; if (/^[a-z]{1,5}:/.test(url) === false) { // Strip any leading slashes url = url.replace(/^/{1,2}/,""); url = "http://"+url; } // Update the entered URL & model this.urlModel.value = url; this.sceneAssistant.controller.modelChanged(this.urlModel); // If the url is the same, then assume that it's just a title change, // update the feed title and close the dialog. Otherwise update the feed. if (this.feedIndex && this.feeds.list[this.feedIndex].url == this.urlModel.value) { this.feeds.list[this.feedIndex].title = this.nameModel.value; this.sceneAssistant.feedWgtModel.items = this.feeds.list; this.sceneAssistant.controller.modelChanged( this.sceneAssistant.feedWgtModel); this.widget.mojo.close(); } else { this.okButton.mojo.activate(); this.okButtonActive = true; this.okButtonModel.label = "Updating Feed"; this.okButtonModel.disabled = true; this.sceneAssistant.controller.modelChanged(this.okButtonModel); var request = new Ajax.Request(url, { method: "get", evalJSON: "false", onSuccess: this.checkSuccess.bind(this), onFailure: this.checkFailure.bind(this) }); } }; // checkSuccess - Ajax request success AddDialogAssistant.prototype.checkSuccess = function(transport) { Mojo.Log.info("Valid URL - HTTP Status", transport.status); // DEBUG - Work around due occasional Ajax XML error in response. 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"); } var feedError = News.errorNone; // If a new feed, push the entered feed data on to the feedlist and // call processFeed to evaluate it. if (this.feedIndex === null) { this.feeds.list.push({title:this.nameModel.value, url:this.urlModel.value, type:"", value:false, numUnRead:0, stories:[]}); // processFeed - index defaults to last entry feedError = this.feeds.processFeed(transport); } else { this.feeds.list[this.feedIndex] = {title:this.nameModel.value, url:this.urlModel.value, type:"", value:false, numUnRead:0, stories:[]}; feedError = this.feeds.processFeed(transport, this.feedIndex); } // If successful processFeed returns errorNone if (feedError === News.errorNone) { // update the widget, save the DB and exit this.sceneAssistant.feedWgtModel.items = this.feeds.list; this.sceneAssistant.controller.modelChanged(this.sceneAssistant.feedWgtModel); this.feeds.storeFeedDb(); this.widget.mojo.close(); } else { // Feed can't be processed - remove it but keep the dialog open this.feeds.list.pop(); if (feedError == News.invalidFeedError) { Mojo.Log.warn("Feed ", this.urlModel.value, " isn't a supported feed type."); var addFeedTitleElement = this.controller.get("add-feed-title"); addFeedTitleElement.innerHTML = $L("Invalid Feed Type - Please Retry"); } this.okButton.mojo.deactivate(); this.okButtonActive = false; this.okButtonModel.buttonLabel = "OK"; this.okButtonModel.disabled = false; this.sceneAssistant.controller.modelChanged(this.okButtonModel); } }; // checkFailure - Ajax request failure AddDialogAssistant.prototype.checkFailure = function(transport) { // Log error and put message in status area Mojo.Log.info("Invalid URL - HTTP Status", transport.status); var addFeedTitleElement = this.controller.get("add-feed-title"); addFeedTitleElement.innerHTML = $L("Invalid Feed Type - Please Retry"); }; // cleanup - remove listeners AddDialogAssistant.prototype.cleanup = function() { this.sceneAssistant.controller.stopListening("okButton", Mojo.Event.tap, this.checkFeedHandler); this.sceneAssistant.controller.stopListening("cancelButton", Mojo.Event.tap, this.widget.mojo.close); }; // --------------------------------------------------------------------------- // // FeedListAssistant - main scene handler for news feedlists // function FeedListAssistant(feeds) { this.feeds = feeds; this.appController = Mojo.Controller.getAppController(); this.stageController = this.appController.getStageController(News.MainStageName); } FeedListAssistant.prototype.setup = function() { // Setup App Menu this.controller.setupWidget(Mojo.Menu.appMenu, News.MenuAttr, News.MenuModel); // Setup the search filterlist and handlers; this.controller.setupWidget("startSearchField", { itemTemplate: "storyList/storyRowTemplate", listTemplate: "storyList/storyListTemplate", filterFunction: this.searchList.bind(this), renderLimit: 70, delay: 350 }, this.searchFieldModel = { disabled: false }); this.viewSearchStoryHandler = this.viewSearchStory.bindAsEventListener(this); this.controller.listen("startSearchField", Mojo.Event.listTap, this.viewSearchStoryHandler); this.searchFilterHandler = this.searchFilter.bindAsEventListener(this); this.controller.listen("startSearchField", Mojo.Event.filter, this.searchFilterHandler, true); // Setup header, drawer, scroller and handler for feature feeds this.featureDrawerHandler = this.toggleFeatureDrawer.bindAsEventListener(this); this.controller.listen("featureDrawer", Mojo.Event.tap, this.featureDrawerHandler); this.controller.setupWidget("featureFeedDrawer", {}, this.featureFeedDrawer = {open:News.featureFeedEnable}); this.featureScrollerModel = { scrollbars: false, mode: "vertical" }; this.controller.setupWidget("featureScroller", this.featureScrollerModel); this.readFeatureStoryHandler = this.readFeatureStory.bindAsEventListener(this); this.controller.listen("featureStoryDiv", Mojo.Event.tap, this.readFeatureStoryHandler); // If feature story is enabled, then set the icon to open if (this.featureFeedDrawer.open === true) { this.controller.get("featureDrawer").className = "featureFeed-open"; } else { this.controller.get("featureDrawer").className = "featureFeed-close"; } // Setup the feed list, but it's empty this.controller.setupWidget("feedListWgt", { itemTemplate:"feedList/feedRowTemplate", listTemplate:"feedList/feedListTemplate", addItemLabel:$L("Add..."), swipeToDelete:true, renderLimit: 40, reorderable:true }, this.feedWgtModel = {items: this.feeds.list}); // Setup event handlers: list selection, add, delete and reorder feed entry this.showFeedHandler = this.showFeed.bindAsEventListener(this); this.controller.listen("feedListWgt", Mojo.Event.listTap, this.showFeedHandler); this.addNewFeedHandler = this.addNewFeed.bindAsEventListener(this); this.controller.listen("feedListWgt", Mojo.Event.listAdd, this.addNewFeedHandler); this.listDeleteFeedHandler = this.listDeleteFeed.bindAsEventListener(this); this.controller.listen("feedListWgt", Mojo.Event.listDelete, this.listDeleteFeedHandler); this.listReorderFeedHandler = this.listReorderFeed.bindAsEventListener(this); this.controller.listen("feedListWgt", Mojo.Event.listReorder, this.listReorderFeedHandler); // Setup spinner for feedlist updates this.controller.setupWidget("feedSpinner", {property: "value"}); // Setup listeners for minimize/maximize events this.activateWindowHandler = this.activateWindow.bindAsEventListener(this); Mojo.Event.listen(this.controller.stageController.document, Mojo.Event.activate, this.activateWindowHandler); this.deactivateWindowHandler = this.deactivateWindow.bindAsEventListener(this); Mojo.Event.listen(this.controller.stageController.document, Mojo.Event.deactivate, this.deactivateWindowHandler); // Setup up feature story index to first story of the first feed this.featureIndexFeed = 0; this.featureIndexStory = 0; }; // activate - handle portrait/landscape orientation, feature feed layout and rotation FeedListAssistant.prototype.activate = function() { // Set Orientation to free to allow rotation if (this.controller.stageController.setWindowOrientation) { this.controller.stageController.setWindowOrientation("free"); } if (News.feedListChanged === true) { this.feedWgtModel.items = this.feeds.list; this.controller.modelChanged(this.feedWgtModel, this); this.controller.modelChanged(this.searchFieldModel, this); // Don't update the database here; it's slow enough that it lags the UI; // wait for a feature story update to mask the update effect } // If there's some stories in the feed list, then start // the story rotation even if the featureFeed is disabled as we'll use // the rotation timer to update the DB if(this.feeds.list[this.featureIndexFeed].stories.length > 0) { var splashScreenElement = this.controller.get("splashScreen"); splashScreenElement.hide(); this.showFeatureStory(); } }; // deactivate - always turn off feature timer FeedListAssistant.prototype.deactivate = function() { Mojo.Log.info("FeedList deactivating"); this.clearTimers(); }; // cleanup - always turn off timers, and save this.feeds.list contents FeedListAssistant.prototype.cleanup = function() { Mojo.Log.info("FeedList cleaning up"); // Save the feed list on close, as a precaution; shouldn't be needed; //don't wait for results this.feeds.storeFeedDb(); // Clear feature story timer and activity indicators this.clearTimers(); // Remove event listeners this.controller.stopListening("startSearchField", Mojo.Event.listTap, this.viewSearchStoryHandler); this.controller.stopListening("startSearchField", Mojo.Event.filter, this.searchFilterHandler, true); this.controller.stopListening("featureDrawer", Mojo.Event.tap, this.featureDrawerHandler); this.controller.stopListening("feedListWgt", Mojo.Event.listTap, this.showFeedHandler); this.controller.stopListening("feedListWgt", Mojo.Event.listAdd, this.addNewFeedHandler); this.controller.stopListening("feedListWgt", Mojo.Event.listDelete, this.listDeleteFeedHandler); this.controller.stopListening("feedListWgt", Mojo.Event.listReorder, this.listReorderFeedHandler); Mojo.Event.stopListening(this.controller.stageController.document, Mojo.Event.activate, this.activateWindowHandler); Mojo.Event.stopListening(this.controller.stageController.document, Mojo.Event.deactivate, this.deactivateWindowHandler); }; FeedListAssistant.prototype.activateWindow = function() { Mojo.Log.info("Activate Window"); this.feedWgtModel.items = this.feeds.list; this.controller.modelChanged(this.feedWgtModel); // If stories exist in the this.featureIndexFeed, start the rotation // if not started if ((this.feeds.list[this.featureIndexFeed].stories.length > 0) && (News.featureStoryTimer === null)) { var splashScreenElement = this.controller.get("splashScreen"); splashScreenElement.hide(); this.showFeatureStory(); } }; FeedListAssistant.prototype.deactivateWindow = function() { Mojo.Log.info("Deactivate Window"); this.clearTimers(); }; // ------------------------------------------------------------------------ // List functions for Delete, Reorder and Add // // listDeleteFeed - triggered by deleting a feed from the list and updates // the feedlist to reflect the deletion // FeedListAssistant.prototype.listDeleteFeed = function(event) { Mojo.Log.info("News deleting ", event.item.title,"."); var deleteIndex = this.feeds.list.indexOf(event.item); this.feeds.list.splice(deleteIndex, 1); News.feedListChanged = true; // Adjust the feature story index if needed: // - feed that falls before feature story feed is deleted // - feature story feed itself is deleted (default back to first feed) if (deleteIndex == this.featureIndexFeed) { this.featureIndexFeed = 0; this.featureIndexStory = 0; } else { if (deleteIndex < this.featureIndexFeed) { this.featureIndexFeed--; } } }; // listReorderFeed - triggered re-ordering feed list and updates the // feedlist to reflect the changed order FeedListAssistant.prototype.listReorderFeed = function(event) { Mojo.Log.info("com.palm.app.news - News moving ", event.item.title,"."); var fromIndex = this.feeds.list.indexOf(event.item); var toIndex = event.toIndex; this.feeds.list.splice(fromIndex, 1); this.feeds.list.splice(toIndex, 0, event.item); News.feedListChanged = true; // Adjust the feature story index if needed: // - feed that falls after featureIndexFeed is moved before it // - feed before is moved after // - the feature story feed itself is moved if (fromIndex > this.featureIndexFeed && toIndex <= this.featureIndexFeed) { this.featureIndexFeed++; } else { if (fromIndex < this.featureIndexFeed && toIndex > this.featureIndexFeed) { this.featureIndexFeed--; } else { if (fromIndex == this.featureIndexFeed) { this.featureIndexFeed = toIndex; } } } }; // addNewFeed - triggered by "Add..." item in feed list FeedListAssistant.prototype.addNewFeed = function() { this.controller.showDialog({ template: "feedList/addFeed-dialog", assistant: new AddDialogAssistant(this, this.feeds) }); }; // ------------------------------------------------------------------------------ // clearTimers - clears timers used in this scene when exiting the scene FeedListAssistant.prototype.clearTimers = function() { if(News.featureStoryTimer !== null) { this.controller.window.clearInterval(News.featureStoryTimer); News.featureStoryTimer = null; } // Clean up any active update spinners for (var i=0; i<this.feeds.list.length; i++) { this.feeds.list[i].value = false; } this.controller.modelChanged(this.feedWgtModel); }; // --------------------------------------------------------------------- // considerForNotification - called by the framework when a notification // is issued; look for notifications of feed updates and update the // feedWgtModel to reflect changes, update the feed's spinner model FeedListAssistant.prototype.considerForNotification = function(params){ if (params && (params.type == "update")) { this.feedWgtModel.items = this.feeds.list; this.feeds.list[params.feedIndex].value = params.update; this.controller.modelChanged(this.feedWgtModel); // If stories exist, start the rotation if not started if ((this.feeds.list[this.featureIndexFeed].stories.length > 0) && (News.featureStoryTimer === null)) { var splashScreenElement = this.controller.get("splashScreen"); splashScreenElement.hide(); this.showFeatureStory(); } } return undefined; }; // ----------------------------------------------------------------------------- // Feature story functions // // showFeatureStory - simply rotate the stories within the // featured feed, which the user can set in their preferences. FeedListAssistant.prototype.showFeatureStory = function() { // If timer is null, either initial story or restarting. Start with // previous story.. if (News.featureStoryTimer === null) { News.featureStoryTimer = this.controller.window.setInterval(this.showFeatureStory.bind(this), News.featureStoryInterval); } else { this.featureIndexStory = this.featureIndexStory+1; if(this.featureIndexStory >= this.feeds.list[this.featureIndexFeed].stories.length) { this.featureIndexStory = 0; this.featureIndexFeed = this.featureIndexFeed+1; if (this.featureIndexFeed >= this.feeds.list.length) { this.featureIndexFeed = 0; } } } var summary = this.feeds.list[this.featureIndexFeed]. stories[this.featureIndexStory].text.replace(/(<([^>]+)>)/ig,""); summary = summary.replace(/http:S+/ig,""); var featureStoryTitleElement = this.controller.get("featureStoryTitle"); featureStoryTitleElement.innerHTML = unescape(this.feeds.list[this.featureIndexFeed]. stories[this.featureIndexStory].title); var featureStoryElement = this.controller.get("featureStory"); featureStorySummaryElement.innerHTML = summary; // Because this is periodic and not tied to a screen transition, use // this to update the db when changes have been made if (News.feedListChanged === true) { this.feeds.storeFeedDb(); News.feedListChanged = false; } }; // readFeatureStory - handler when user taps on feature story; will push storyView // with the current feature story. FeedListAssistant.prototype.readFeatureStory = function() { this.stageController.pushScene("storyView", this.feeds.list[this.featureIndexFeed], this.featureIndexStory); }; // toggleFeatureDrawer - handles taps to the featureFeed drawer. Toggle // drawer and icon class to reflect drawer state. FeedListAssistant.prototype.toggleFeatureDrawer = function(event) { var featureDrawer = this.controller.get("featureDrawer"); if (this.featureFeedDrawer.open === true) { this.featureFeedDrawer.open = false; News.featureFeedEnable = false; featureDrawer.className = "featureFeed-close"; } else { this.featureFeedDrawer.open = true; News.featureFeedEnable = true; featureDrawer.className = "featureFeed-open"; } this.controller.modelChanged(this.featureFeedDrawer); News.Cookie.storeCookie(); // Update News saved preferences }; // --------------------------------------------------------------------- // Search Functions // // searchFilter - triggered by entry into search field. First entry will // hide the main feedList scene - clearing the entry will restore the scene. // FeedListAssistant.prototype.searchFilter = function(event) { Mojo.Log.info("Got search filter: ", event.filterString); var feedListMainElement = this.controller.get("feedListMain"); if (event.filterString !== "") { // Hide rest of feedList scene to make room for search results feedListMainElement.hide(); } else { // Restore scene when search string is null feedListMainElement.show(); } }; // viewSearchStory - triggered by tapping on an entry in the search results // list will push the storyView scene with the tapped story. // FeedListAssistant.prototype.viewSearchStory = function(event) { var searchList = {title: $L("Search for: ")+this.filter, stories: this.entireList}; var storyIndex = this.entireList.indexOf(event.item); Mojo.Log.info("Search display selected story with title = ", searchList.title, "; Story index - ", storyIndex); this.stageController.pushScene("storyView", searchList, storyIndex); }; // searchList - filter function called from search field widget to update the // results list. This function will build results list by matching the // filterstring to the story titles and text content, and then return the // subset of the list based on offset and size requested by the widget. // FeedListAssistant.prototype.searchList = function(filterString, listWidget, offset, count) { var subset = []; var totalSubsetSize = 0; this.filter = filterString; // If search string is null, return empty list, else build results if (filterString !== "") { // Search database for stories with the search string; push matches var items = []; // Comparison function for matching strings in next for loop var hasString = function(query, s) { if(s.text.toUpperCase().indexOf(query.toUpperCase())>=0) { return true; } if(s.title.toUpperCase().indexOf(query.toUpperCase())>=0) { return true; } return false; }; for (var i=0; i<this.feeds.list.length; i++) { for (var j=0; j<this.feeds.list[i].stories.length; j++) { if(hasString(filterString, this.feeds.list[i].stories[j])) { var sty = this.feeds.list[i].stories[j]; items.push(sty); } } } this.entireList = items; Mojo.Log.info("Search list asked for items: filter=", filterString, " offset=", offset, " limit=", count); // Cut down results to just the window asked for by the widget var cursor = 0; while (true) { if (cursor >= this.entireList.length) { break; } if (subset.length < count && totalSubsetSize >= offset) { subset.push(this.entireList[cursor]); } totalSubsetSize++; cursor++; } } // Update List listWidget.mojo.noticeUpdatedItems(offset, subset); // Update filter field count of items found listWidget.mojo.setLength(totalSubsetSize); listWidget.mojo.setCount(totalSubsetSize); }; // ------------------------------------------------------------------------------ // Show feed and popup menu handler // // showFeed - triggered by tapping a feed in the this.feeds.list. // Detects taps on the unReadCount icon; anywhere else, // the scene for the list view is pushed. If the icon is tapped, // put up a submenu for the feedlist options FeedListAssistant.prototype.showFeed = function(event) { var target = event.originalEvent.target.id; if (target !== "info") { this.stageController.pushScene("storyList", this.feeds.list, event.index); } else { var myEvent = event; var findPlace = myEvent.originalEvent.target; this.popupIndex = event.index; this.controller.popupSubmenu({ onChoose: this.popupHandler, placeNear: findPlace, items: [ {label: $L("All Unread"), command: "feed-unread"}, {label: $L("All Read"), command: "feed-read"}, {label: $L("Edit Feed"), command: "feed-edit"}, {label: $L("New Card"), command: "feed-card"} ] }); } }; // popupHandler - choose function for feedPopup FeedListAssistant.prototype.popupHandler = function(command) { var popupFeed=this.feeds.list[this.popupIndex]; switch(command) { case "feed-unread": Mojo.Log.info("Popup - unread for feed:", popupFeed.title); for (var i=0; i<popupFeed.stories.length; i++ ) { popupFeed.stories[i].unreadStyle = News.unreadStory; } popupFeed.numUnRead = popupFeed.stories.length; this.controller.modelChanged(this.feedWgtModel); break; case "feed-read": Mojo.Log.info("Popup - read for feed:", popupFeed.title); for (var j=0; j<popupFeed.stories.length; j++ ) { popupFeed.stories[j].unreadStyle = ""; } popupFeed.numUnRead = 0; this.controller.modelChanged(this.feedWgtModel); break; case "feed-edit": Mojo.Log.info("Popup edit for feed:", popupFeed.title); this.controller.showDialog({ template: "feedList/addFeed-dialog", assistant: new AddDialogAssistant(this, this.feeds, this.popupIndex) }); break; case "feed-card": Mojo.Log.info("Popup tear off feed to new card:", popupFeed.title); var newCardStage = "newsCard"+this.popupIndex; var cardStage = this.appController.getStageController(newCardStage); var feedList = this.feeds.list; var feedIndex = this.popupIndex; if(cardStage) { Mojo.Log.info("Existing Card Stage"); cardStage.popScenesTo(); cardStage.pushScene("storyList", this.feeds.list, feedIndex); cardStage.activate(); } else { Mojo.Log.info("New Card Stage"); var pushStoryCard = function(stageController){ stageController.pushScene("storyList", feedList, feedIndex); }; this.appController.createStageWithCallback({ name: newCardStage, lightweight: true }, pushStoryCard, "card"); } break; } };
/* Preferences - NEWS Copyright 2009 Palm, Inc. All rights reserved. Preferences - Handles preferences scene, where the user can: - select the featured feed rotation interval - select the interval for feed updates - enable or disable background feed notifications - enable or disable device wakeup App Menu is disabled in this scene. */ function PreferencesAssistant() { } PreferencesAssistant.prototype.setup = function() { // Setup Integer Picker to pick feature feed rotation interval this.controller.setupWidget("featureFeedDelay", { label: $L("Rotation (in seconds)"), modelProperty: "value", min: 1, max: 20 }, this.featureDelayModel = { value : News.featureStoryInterval/1000 }); this.changeFeatureDelayHandler = this.changeFeatureDelay.bindAsEventListener(this); this.controller.listen("featureFeedDelay", Mojo.Event.propertyChange, this.changeFeatureDelayHandler); // Setup list selector for UPDATE INTERVAL this.controller.setupWidget("feedCheckIntervalList", { label: $L("Interval"), choices: [ {label: $L("Manual Updates"), value: "00:00:00"}, {label: $L("5 Minutes"), value: "00:05:00"}, {label: $L("15 Minutes"), value: "00:15:00"}, {label: $L("1 Hour"), value: "01:00:00"}, {label: $L("4 Hours"), value: "04:00:00"}, {label: $L("1 Day"), value: "23:59:59"} ] }, this.feedIntervalModel = { value : News.feedUpdateInterval }); this.changeFeedIntervalHandler = this.changeFeedInterval.bindAsEventListener(this); this.controller.listen("feedCheckIntervalList", Mojo.Event.propertyChange, this.changeFeedIntervalHandler); // Toggle for enabling notifications for new stories during feed updates this.controller.setupWidget("notificationToggle", {}, this.notificationToggleModel = { value: News.notificationEnable }); this.changeNotificationHandler = this.changeNotification.bindAsEventListener(this); this.controller.listen("notificationToggle", Mojo.Event.propertyChange, this.changeNotificationHandler); // Toggle for enabling feed updates while the device is asleep this.controller.setupWidget("bgUpdateToggle", {}, this.bgUpdateToggleModel = { value: News.feedUpdateBackgroundEnable }); this.changeBgUpdateHandler = this.changeBgUpdate.bindAsEventListener(this); this.controller.listen("bgUpdateToggle", Mojo.Event.propertyChange, this.changeBgUpdate); }; // Deactivate - save News preferences and globals PreferencesAssistant.prototype.deactivate = function() { News.Cookie.storeCookie(); }; // Cleanup - remove listeners PreferencesAssistant.prototype.cleanup = function() { this.controller.stopListening("featureFeedDelay", Mojo.Event.propertyChange, this.changeFeatureDelayHandler); this.controller.stopListening("feedCheckIntervalList", Mojo.Event.propertyChange, this.changeFeedIntervalHandler); this.controller.stopListening("notificationToggle", Mojo.Event.propertyChange, this.changeNotificationHandler); this.controller.stopListening("bgUpdateToggle", Mojo.Event.propertyChange, this.changeBgUpdate); }; // changeFeatureDelay - Handle changes to the feature feed interval PreferencesAssistant.prototype.changeFeatureDelay = function(event) { Mojo.Log.info("Preferences Feature Delay Handler; value = ", this.featureDelayModel.value); // Interval is in milliseconds News.featureStoryInterval = this.featureDelayModel.value*1000; // If timer is active, restart with new value if(News.featureStoryTimer !== null) { this.controller.window.clearInterval(News.featureStoryTimer); News.featureStoryTimer = null; } }; // changeFeedInterval - Handle changes to the feed update interva; PreferencesAssistant.prototype.changeFeedInterval = function(event) { Mojo.Log.info("Preferences Feed Interval Handler; value = ", this.feedIntervalModel.value); News.feedUpdateInterval = this.feedIntervalModel.value; }; // changeNotification - disables/enables notifications PreferencesAssistant.prototype.changeNotification = function(event) { Mojo.Log.info("Preferences Notification Toggle Handler; value = ", this.notificationToggleModel.value); News.notificationEnable = this.notificationToggleModel.value; }; // changeBgUpdate - disables/enables background wakeups PreferencesAssistant.prototype.changeBgUpdate = function(event) { Mojo.Log.info("Preferences Background Update Toggle Handler; value = ", this.bgUpdateToggleModel.value); News.feedUpdateBackgroundEnable = this.bgUpdateToggleModel.value; };
/* StoryListAssistant - NEWS Copyright 2009 Palm, Inc. All rights reserved. Displays the feed's stories in a list, user taps display the selected story in the storyView scene. Major components: - Setup view menu to move to next or previous feed - Search filter; perform keyword search within feed list - Story View; push story scene when a story is tapped - Update; handle notifications if feedlist has been updated Arguments: - feedlist; Feeds.list array of all feeds - selectedFeedIndex; Feed to be displayed */ function StoryListAssistant(feedlist, selectedFeedIndex) { this.feedlist = feedlist; this.feed = feedlist[selectedFeedIndex]; this.feedIndex = selectedFeedIndex; Mojo.Log.info("StoryList entry = ", this.feedIndex); Mojo.Log.info("StoryList feed = " + Object.toJSON(this.feed)); } StoryListAssistant.prototype.setup = function() { this.stageController = this.controller.stageController; // Setup scene header with feed title and next/previous feed buttons. If // this is the first feed, suppress Previous menu; if last, suppress Next menu var feedMenuPrev = {}; var feedMenuNext = {}; if (this.feedIndex > 0) { feedMenuPrev = { icon: "back", command: "do-feedPrevious" }; } else { // Push empty menu to force menu bar to draw on left (label is the force) feedMenuPrev = {icon: "", command: "", label: " "}; } if (this.feedIndex < this.feedlist.length-1) { feedMenuNext = { iconPath: "images/menu-icon-forward.png", command: "do-feedNext" }; } else { // Push empty menu to force menu bar to draw on right (label is the force) feedMenuNext = {icon: "", command: "", label: " "}; } this.feedMenuModel = { visible: true, items: [{ items: [ feedMenuPrev, { label: this.feed.title, width: 200 }, feedMenuNext ] }] }; this.controller.setupWidget(Mojo.Menu.viewMenu, { spacerHeight: 0, menuClass:"no-fade" }, this.feedMenuModel); // Setup App Menu this.controller.setupWidget(Mojo.Menu.appMenu, News.MenuAttr, News.MenuModel); // Setup the search filterlist and handlers; this.controller.setupWidget("storyListSearch", { itemTemplate: "storyList/storyRowTemplate", listTemplate: "storyList/storyListTemplate", filterFunction: this.searchList.bind(this), renderLimit: 70, delay: 350 }, this.searchFieldModel = { disabled: false }); this.viewSearchStoryHandler = this.viewSearchStory.bindAsEventListener(this); this.controller.listen("storyListSearch", Mojo.Event.listTap, this.viewSearchStoryHandler); this.searchFilterHandler = this.searchFilter.bindAsEventListener(this); this.controller.listen("storyListSearch", Mojo.Event.filter, this.searchFilterHandler, true); // Setup story list with standard news list templates. this.controller.setupWidget("storyListWgt", { itemTemplate: "storyList/storyRowTemplate", listTemplate: "storyList/storyListTemplate", swipeToDelete: false, renderLimit: 40, reorderable: false }, this.storyModel = { items: this.feed.stories } ); this.readStoryHandler = this.readStory.bindAsEventListener(this); this.controller.listen("storyListWgt", Mojo.Event.listTap, this.readStoryHandler); }; StoryListAssistant.prototype.activate = function() { // Update list models in case unReadCount has changed this.controller.modelChanged(this.storyModel); }; StoryListAssistant.prototype.cleanup = function() { // Remove event listeners this.controller.stopListening("storyListSearch", Mojo.Event.listTap, this.viewSearchStoryHandler); this.controller.stopListening("storyListSearch", Mojo.Event.filter, this.searchFilterHandler, true); this.controller.stopListening("storyListWgt", Mojo.Event.listTap, this.readStoryHandler); }; // readStory - when user taps on displayed story, push storyView scene StoryListAssistant.prototype.readStory = function(event) { Mojo.Log.info("Display selected story = ", event.item.title, "; Story index = ", event.index); this.stageController.pushScene("storyView", this.feed, event.index); }; // handleCommand - handle next and previous commands StoryListAssistant.prototype.handleCommand = function(event) { if(event.type == Mojo.Event.command) { switch(event.command) { case "do-feedNext": this.nextFeed(); break; case "do-feedPrevious": this.previousFeed(); break; } } }; // nextFeed - Called when the user taps the next menu item StoryListAssistant.prototype.nextFeed = function(event) { this.stageController.swapScene( { transition: Mojo.Transition.crossFade, name: "storyList" }, this.feedlist, this.feedIndex+1); }; // previousFeed - Called when the user taps the previous menu item StoryListAssistant.prototype.previousFeed = function(event) { this.stageController.swapScene( { transition: Mojo.Transition.crossFade, name: "storyList" }, this.feedlist, this.feedIndex-1); }; // searchFilter - triggered by entry into search field. First entry will // hide the main storyList scene and clearing the entry will restore the scene. StoryListAssistant.prototype.searchFilter = function(event) { var storyListSceneElement = this.controller.get("storyListScene"); if (event.filterString !== "") { // Hide rest of storyList scene to make room for search results storyListSceneElement.hide(); } else { // Restore scene when search string is null storyListSceneElement.show(); } }; // viewSearchStory - triggered by tapping on an entry in the search results list. StoryListAssistant.prototype.viewSearchStory = function(event) { var searchList = {title: $L("Search for: #{filter}").interpolate({filter: this.filter}), stories: this.entireList}; var storyIndex = this.entireList.indexOf(event.item); this.stageController.pushScene("storyView", searchList, storyIndex); }; // searchList - filter function called from search field widget to update // results list. This function will build results list by matching the // filterstring to story titles and text content, and return the subset // of list based on offset and size requested by the widget.t. StoryListAssistant.prototype.searchList = function(filterString, listWidget, offset, count) { var subset = []; var totalSubsetSize = 0; this.filter = filterString; // If search string is null, then return empty list, else build results list if (filterString !== "") { // Search database for stories with the search string // and push on to the items array var items = []; // Comparison function for matching strings in next for loop var hasString = function(query, s) { if(s.text.toUpperCase().indexOf(query.toUpperCase())>=0) { return true; } if(s.title.toUpperCase().indexOf(query.toUpperCase())>=0) { return true; } return false; }; for (var j=0; j<this.feed.stories.length; j++) { if(hasString(filterString, this.feed.stories[j])) { var sty = this.feed.stories[j]; items.push(sty); } } this.entireList = items; Mojo.Log.info("Search list asked for items: filter=", filterString, " offset=", offset, " limit=", count); // Cut down the list results to just the window asked for by the widget var cursor = 0; while (true) { if (cursor >= this.entireList.length) { break; } if (subset.length < count && totalSubsetSize >= offset) { subset.push(this.entireList[cursor]); } totalSubsetSize++; cursor++; } } // Update List listWidget.mojo.noticeUpdatedItems(offset, subset); // Update filter field count of items found listWidget.mojo.setLength(totalSubsetSize); listWidget.mojo.setCount(totalSubsetSize); }; // considerForNotification - called when a notification is issued; if this // feed has been changed, then update it. StoryListAssistant.prototype.considerForNotification = function(params){ if (params && (params.type == "update")) { if ((params.feedIndex == this.feedIndex) && (params.update === false)) { this.storyModel.items = this.feed.stories; this.controller.modelChanged(this.storyModel); } } return undefined; };
/* StoryViewAssistant - NEWS Copyright 2009 Palm, Inc. All rights reserved. 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) or share story via email or messaging. Major components: - StoryView; display story in main scene - Next/Previous; command menu options to go to next or previous story - Web; command menu option to display original story in browser - Share; command menu option to share story by messaging or email Arguments: - storyFeed; Selected feed from which the stories are being viewed - storyIndex; Index of selected story to be put into the view */ function StoryViewAssistant(storyFeed, storyIndex) { this.storyFeed = storyFeed; this.storyIndex = storyIndex; } // setup - set up menus StoryViewAssistant.prototype.setup = function() { this.stageController = this.controller.stageController; this.storyMenuModel = { items: [ {iconPath: "images/url-icon.png", command: "do-webStory"}, {}, {items: []}, {}, {icon: "send", command: "do-shareStory"} ]}; if (this.storyIndex > 0) { this.storyMenuModel.items[2].items.push({ icon: "back", command: "do-viewPrevious" }); } else { this.storyMenuModel.items[2].items.push({ icon: "", command: "", label: " " }); } if (this.storyIndex < this.storyFeed.stories.length-1) { this.storyMenuModel.items[2].items.push({ icon: "forward", command: "do-viewNext"} ); } else { this.storyMenuModel.items[2].items.push({ icon: "", command: "", label: " " }); } this.controller.setupWidget(Mojo.Menu.commandMenu, undefined, this.storyMenuModel); // Setup App Menu this.controller.setupWidget(Mojo.Menu.appMenu, News.MenuAttr, News.MenuModel); // Update story title in header and summary var storyViewTitleElement = this.controller.get("storyViewTitle"); var storyViewSummaryElement = this.controller.get("storyViewSummary"); storyViewTitleElement.innerHTML = this.storyFeed.stories[this.storyIndex].title; storyViewSummaryElement.innerHTML = this.storyFeed.stories[this.storyIndex].text; }; // activate - display selected story StoryViewAssistant.prototype.activate = function(event) { Mojo.Log.info("Story View Activated"); // Update unreadStyle string and unReadCount in case it's changed if (this.storyFeed.stories[this.storyIndex].unreadStyle == News.unreadStory) { this.storyFeed.numUnRead--; this.storyFeed.stories[this.storyIndex].unreadStyle = ""; News.feedListChanged = true; } }; // --------------------------------------------------------------------- // Handlers to go to next and previous stories, display web view // or share via messaging or email. StoryViewAssistant.prototype.handleCommand = function(event) { if(event.type == Mojo.Event.command) { switch(event.command) { case "do-viewNext": this.stageController.swapScene( { transition: Mojo.Transition.crossFade, name: "storyView" }, this.storyFeed, this.storyIndex+1); break; case "do-viewPrevious": this.stageController.swapScene( { transition: Mojo.Transition.crossFade, name: "storyView" }, this.storyFeed, this.storyIndex-1); break; case "do-shareStory": var myEvent = event; var findPlace = myEvent.originalEvent.target; this.controller.popupSubmenu({ onChoose: this.shareHandler, placeNear: findPlace, items: [ {label: $L("Email"), command: "do-emailStory"}, {label: $L("SMS/IM"), command: "do-messageStory"} ] }); break; case "do-webStory": this.controller.serviceRequest( "palm://com.palm.applicationManager", { method: "open", parameters: { id: "com.palm.app.browser", params: { target: this.storyFeed.stories[this.storyIndex].url } } }); break; } } }; // shareHandler - choose function for share submenu StoryViewAssistant.prototype.shareHandler = function(command) { switch(command) { case "do-emailStory": this.controller.serviceRequest( "palm://com.palm.applicationManager", { method: "open", parameters: { id: "com.palm.app.email", params: { summary: $L("Check out this News story..."), text: this.storyFeed.stories[this.storyIndex].url } } }); break; case "do-messageStory": this.controller.serviceRequest( "palm://com.palm.applicationManager", { method: "open", parameters: { id: "com.palm.app.messaging", params: { messageText: $L("Check this out: ") +this.storyFeed.stories[this.storyIndex].url } } }); break; } };
/* Cookie - NEWS Copyright 2009 Palm, Inc. All rights reserved. Handler for cookieData, a stored version of News preferences. Will load or create cookieData, migrate preferences and update cookieData when called. Functions: initialize - loads or creates newsCookie; updates preferences withcontents of stored cookieData and migrates any preferences due version changes store - updates stored cookieData with current global preferences */ News.Cookie = ({ initialize: function() { // Update globals with preferences or create it. this.cookieData = new Mojo.Model.Cookie("comPalmAppNewsPrefs"); var oldNewsPrefs = this.cookieData.get(); if (oldNewsPrefs) { // If current version, just update globals & prefs if (oldNewsPrefs.newsVersionString == News.versionString) { News.featureFeedEnable = oldNewsPrefs.featureFeedEnable; News.featureStoryInterval = oldNewsPrefs.featureStoryInterval; News.feedUpdateInterval = oldNewsPrefs.feedUpdateInterval; News.versionString = oldNewsPrefs.newsVersionString; News.notificationEnable = oldNewsPrefs.notificationEnable; News.feedUpdateBackgroundEnable = oldNewsPrefs.feedUpdateBackgroundEnable; } else { // migrate old preferences here on updates of News app } } this.storeCookie(); }, // store - function to update stored cookie with global values storeCookie: function() { this.cookieData.put( { featureFeedEnable: News.featureFeedEnable, feedUpdateInterval: News.feedUpdateInterval, featureStoryInterval: News.featureStoryInterval, newsVersionString: News.versionString, notificationEnable: News.notificationEnable, feedUpdateBackgroundEnable: News.feedUpdateBackgroundEnable }); } });
/* Feeds - NEWS Copyright 2009 Palm, Inc. All rights reserved. The primary data model for the News app. Feeds includes the primary data structure for the newsfeeds, which are structured as a list of lists: Feeds.list entry is: list[x].title String Title entered by user list[x].url String Feed source URL in unescaped form list[x].type String Feed type: either rdf, rss, or atom list[x].value Boolean Spinner model for feed update indicator list[x].numUnRead Integer How many stories are still unread list[x].newStoryCount Integer For each update, how many new stories list[x].stories Array Each entry is a complete story list.stories entry is: stories[y].title String Story title or headline stories[y].text String Story text stories[y].summary String Story text, stripped of markup stories[y].unreadStyle String Null when Read stories[y].url String Story url Methods: initialize(test) - create default and test feed lists getDefaultList() - returns the default feed list as an array getTestList() - returns both the default and test feed lists as a single array loadFeedDb() - loads feed database depot, or creates default feed list if no existing depot processFeed(transport, index) - function to process incoming feeds that are XML encoded in an Ajax object and stores them in the Feeds.list. Supports RSS, RDF and Atom feed formats. storeFeedDb() - writes contents of Feeds.list array to feed database depot updateFeedList(index) - updates entire feed list starting with this.feedIndex. */ var Feeds = Class.create ({ // Default Feeds.list defaultList: [ { title:"Huffington Post", url:"http://feeds.huffingtonpost.com/huffingtonpost/raw_feed", type:"atom", value:false, numUnRead:0, newStoryCount:0, stories:[] },{ title:"Google", url:"http://news.google.com/?output=atom", type:"atom", value:false, numUnRead:0, newStoryCount:0, stories:[] },{ title:"BBC News", url:"http://newsrss.bbc.co.uk/rss/newsonline_world_edition/ front_page/rss.xml", type:"rss", value:false, numUnRead:0, newStoryCount:0, stories:[] },{ title:"New York Times", url:"http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml", type:"rss", value:false, numUnRead:0, newStoryCount:0, stories:[] },{ title:"MSNBC", url:"http://rss.msnbc.msn.com/id/3032091/device/rss/rss.xml", type:"rss", value:false, numUnRead:0, newStoryCount:0, stories:[] },{ title:"National Public Radio", url:"http://www.npr.org/rss/rss.php?id=1004", type:"rss", value:false, numUnRead:0, newStoryCount:0, stories:[] },{ title:"Slashdot", url:"http://rss.slashdot.org/Slashdot/slashdot", type:"rdf", value:false, numUnRead:0, newStoryCount:0, stories:[] },{ title:"Engadget", url:"http://www.engadget.com/rss.xml", type:"rss", value:false, numUnRead:0, newStoryCount:0, stories:[] },{ title:"The Daily Dish", url:"http://feeds.feedburner.com/andrewsullivan/rApM?format=xml", type:"rss", value:false, numUnRead:0, newStoryCount:0, stories:[] },{ title:"Guardian UK", url:"http://feeds.guardian.co.uk/theguardian/rss", type:"rss", value:false, numUnRead:0, newStoryCount:0, stories:[] },{ title:"Yahoo Sports", url:"http://sports.yahoo.com/top/rss.xml", type:"rss", value:false, numUnRead:0, newStoryCount:0, stories:[] },{ title:"ESPN", url:"http://sports-ak.espn.go.com/espn/rss/news", type:"rss", value:false, numUnRead:0, newStoryCount:0, stories:[] },{ title:"Ars Technica", url:"http://feeds.arstechnica.com/arstechnica/index?format=xml", type:"rss", value:false, numUnRead:0, newStoryCount:0, stories:[] },{ title:"Nick Carr", url:"http://feeds.feedburner.com/roughtype/unGc", type:"rss", value:false, numUnRead:0, newStoryCount:0, stories:[] } ], // Additional test feeds testList: [ { title:"Hacker News", url:"http://news.ycombinator.com/rss", type:"rss", value:false, numUnRead:0, stories:[] },{ title:"Ken Rosenthal", url:"http://feeds.feedburner.com/foxsports/rss/rosenthal", type:"rss", value:false, numUnRead:0, stories:[] },{ title:"George Packer", url:"http://www.newyorker.com/online/blogs/georgepacker/rss.xml", type:"rss", value:false, numUnRead:0, stories:[] },{ title:"Palm Open Source", url:"http://www.palmopensource.com/tmp/news.rdf", type:"rdf", value:false, numUnRead:0, stories:[] },{ title:"Baseball Prospectus", url:"http://www.baseballprospectus.com/rss/feed.xml", type:"rss", value:false, numUnRead:0, stories:[] },{ title:"The Page", url:"http://feedproxy.google.com/time/thepage?format=xml", type:"rss", value:false, numUnRead:0, stories:[] },{ title:"Salon", url:"http://feeds.salon.com/salon/index", type:"rss", value:false, numUnRead:0, stories:[] },{ title:"Slate", url:"http://feedproxy.google.com/slate?format=xml", type:"rss", value:false, numUnRead:0, stories:[] },{ title:"SoSH", url:"http://sonsofsamhorn.net/index.php?act=rssout&id=1", type:"rss", value:false, numUnRead:0, stories:[] },{ title:"Talking Points Memo", url:"http://feeds.feedburner.com/talking-points-memo", type:"atom", value:false, numUnRead:0, stories:[] },{ title:"Whatever", url:"http://scalzi.com/whatever/?feed=rss2", type:"rss", value:false, numUnRead:0, stories:[] },{ title:"Baseball America", url:"http://www.baseballamerica.com/today/rss/rss.xml", type:"rss", value:false, numUnRead:0, stories:[] },{ title:"Test RDF Feed", url:"http://foobar.blogalia.com/rdf.xml", type:"rdf", value:false, numUnRead:0, stories:[] },{ title:"Daily Kos", url:"http://feeds.dailykos.com/dailykos/index.html", type:"rss", value:false, numUnRead:0, stories:[] } ], // initialize - Assign default data to the feedlist initialize: function(test) { this.feedIndex = 0; if (!test) { this.list = this.getDefaultList(); } else { this.list = this.getTestList(); } }, // getDefaultList - returns the default feed list as an array getDefaultList: function() { var returnList = []; for (var i=0; i<this.defaultList.length; i++) { returnList[i] = this.defaultList[i]; } return returnList; }, // getTestList - returns the default and tests feeds in one array getTestList: function() { var returnList = []; var defaultLength = this.defaultList.length; for (var i=0; i<defaultLength; i++) { returnList[i] = this.defaultList[i]; } for (var j=0; j<this.testList.length; j++) { returnList[j+defaultLength] = this.testList[j]; } return returnList; }, // loadFeedDb - loads feed db depot, or creates it with default list // if it doesn't already exist loadFeedDb: function() { // Open the database to get the most recent feed list // DEBUG - replace is true to recreate db every time; false for release this.db = new Mojo.Depot( {name:"feedDB", version:1, estimatedSize: 100000, replace: false}, this.loadFeedDbOpenOk.bind(this), function(result) { Mojo.Log.warn("Can't open feed database: ", result); } ); }, // dbOpenOK - Callback for successful db request in setup. Get stored db or // fallback to using default list loadFeedDbOpenOk: function() { Mojo.Log.info("Database opened OK"); this.db.simpleGet("feedList", this.loadFeedDbGetSuccess.bind(this), this.loadFeedDbUseDefault.bind(this)); }, // loadFeedDbGetSuccess - successful retrieval of db. Call // useDefaultList if the feedlist empty or null or initiate an update // to the list by calling updateFeedList. loadFeedDbGetSuccess: function(fl) { Mojo.Log.info("Database Retrieved OK"); if (fl === null) { Mojo.Log.warn("Retrieved empty or null list from DB"); this.loadFeedDbUseDefault(); } else { Mojo.Log.info("Retrieved feedlist from DB"); this.list = fl; // If update, then convert from older versions this.updateFeedList(); } }, // loadFeedDbUseDefault() - Callback for failed DB retrieval meaning no list loadFeedDbUseDefault: function() { // Couldn't get the list of feeds. Maybe its never been set up, so // initialize it here to the default list and then initiate an update // with this feed list Mojo.Log.warn("Database has no feed list. Will use default."); this.list = this.getDefaultList(); this.updateFeedList(); }, // processFeed (transport, index) - process incoming feeds that // are XML encoded in an Ajax object and stores them in Feeds.list. // Supports RSS, RDF and Atom feed formats. processFeed: function(transport, index) { // Used to hold feed list as it's processed from the Ajax request var listItems = []; // Variable to hold feed type tags var feedType = transport.responseXML.getElementsByTagName("rss"); if (index === undefined) { // Default index is at end of the list index = this.list.length-1; } // Determine whether RSS 2, RDF (RSS 1) or ATOM feed if (feedType.length > 0) { this.list[index].type = "rss"; } else { feedType = transport.responseXML.getElementsByTagName("RDF"); if (feedType.length > 0) { this.list[index].type = "RDF"; } else { feedType = transport.responseXML.getElementsByTagName("feed"); if (feedType.length > 0) { this.list[index].type = "atom"; } else { // If none of those then it can't be processed, set an error code // in the result, log the error and return Mojo.Log.warn("Unsupported feed format in feed ", this.list[index].url); return News.invalidFeedError; } } } // Process feeds; retain title, text content and url switch(this.list[index].type) { case "atom": // Temp object to hold incoming XML object var atomEntries = transport.responseXML.getElementsByTagName("entry"); for (var i=0; i<atomEntries.length; i++) { listItems[i] = { title: unescape(atomEntries[i].getElementsByTagName("title"). item(0).textContent), text: atomEntries[i].getElementsByTagName("content"). item(0).textContent, unreadStyle: News.unreadStory, url: atomEntries[i].getElementsByTagName("link"). item(0).getAttribute("href") }; // Strip HTML from text for summary and shorten to 100 characters listItems[i].summary = listItems[i].text.replace(/(<([^>]+)>)/ig,""); listItems[i].summary = listItems[i].summary.replace(/http:S+/ig,""); listItems[i].summary = listItems[i].summary.replace(/#[a-z]+/ig,"{"); listItems[i].summary = listItems[i].summary.replace(/({([^}]+)})/ig,""); listItems[i].summary = listItems[i].summary.replace(/digg_url .../,""); listItems[i].summary = unescape(listItems[i].summary); listItems[i].summary = listItems[i].summary.substring(0,101); } break; case "rss": // Temp object to hold incoming XML object var rssItems = transport.responseXML.getElementsByTagName("item"); for (i=0; i<rssItems.length; i++) { listItems[i] = { title: unescape(rssItems[i].getElementsByTagName("title"). item(0).textContent), text: rssItems[i].getElementsByTagName("description"). item(0).textContent, unreadStyle: News.unreadStory, url: rssItems[i].getElementsByTagName("link"). item(0).textContent }; // Strip HTML from text for summary and shorten to 100 characters listItems[i].summary = listItems[i].text.replace(/(<([^>]+)>)/ig,""); listItems[i].summary = listItems[i].summary.replace(/http:S+/ig,""); listItems[i].summary = listItems[i].summary.replace(/#[a-z]+/ig,"{"); listItems[i].summary = listItems[i].summary.replace(/({([^}]+)})/ig,""); listItems[i].summary = listItems[i].summary.replace(/digg_url .../,""); listItems[i].summary = unescape(listItems[i].summary); listItems[i].summary = listItems[i].summary.substring(0,101); } break; case "RDF": // Temp object to hold incoming XML object var rdfItems = transport.responseXML.getElementsByTagName("item"); for (i=0; i<rdfItems.length; i++) { listItems[i] = { title: unescape(rdfItems[i].getElementsByTagName("title"). item(0).textContent), text: rdfItems[i].getElementsByTagName("description"). item(0).textContent, unreadStyle: News.unreadStory, url: rdfItems[i].getElementsByTagName("link"). item(0).textContent }; // Strip HTML from text for summary and shorten to 100 characters listItems[i].summary = listItems[i].text.replace(/(<([^>]+)>)/ig,""); listItems[i].summary = listItems[i].summary.replace(/http:S+/ig,""); listItems[i].summary = listItems[i].summary.replace(/#[a-z]+/ig,"{"); listItems[i].summary = listItems[i].summary.replace(/({([^}]+)})/ig,""); listItems[i].summary = listItems[i].summary.replace(/digg_url .../,""); listItems[i].summary = unescape(listItems[i].summary); listItems[i].summary = listItems[i].summary.substring(0,101); } break; } // Update read items by comparing new stories with stories last // in the feed. For all old stories, use the old unreadStyle value, // otherwise set unreadStyle to News.unreadStory. // Count number of unread stories and store value. // Determine if any new stories when URLs don't match a previously // downloaded story. // var numUnRead = 0; var newStoryCount = 0; var newStory = true; for (i = 0; i < listItems.length; i++) { var unreadStyle = News.unreadStory; var j; for (j=0; j<this.list[index].stories.length; j++ ) { if(listItems[i].url == this.list[index].stories[j].url) { unreadStyle = this.list[index].stories[j].unreadStyle; newStory = false; } } if(unreadStyle == News.unreadStory) { numUnRead++; } if (newStory) { newStoryCount++; } listItems[i].unreadStyle = unreadStyle; } // Save updated feed in global feedlist this.list[index].stories = listItems; this.list[index].numUnRead = numUnRead; this.list[index].newStoryCount = newStoryCount; // If new feed, the user may not have entered a name; if so, set the // name to the feed title if (this.list[index].title === "") { // Will return multiple hits, but the first is the feed name var titleNodes = transport.responseXML.getElementsByTagName("title"); this.list[index].title = titleNodes[0].textContent; } return News.errorNone; }, // storeFeedDb() - writes contents of Feeds.list array to feed database depot storeFeedDb: function() { Mojo.Log.info("FeedList save started"); this.db.simpleAdd("feedList", this.list, function() {Mojo.Log.info("FeedList saved OK");}, this.storeFeedDBFailure); }, // storeFeedDbFailure(transaction, result) - handles save failure, usually an // out of memory error storeFeedDbFailure: function(result) { Mojo.Log.warn("Database save error: ", result); }, // updateFeedList(index) - called to cycle through feeds. This is called // once per update cycle. updateFeedList: function(index) { News.feedListUpdateInProgress = true; // request fresh copies of all stories this.currentFeed = this.list[this.feedIndex]; this.updateFeedRequest(this.currentFeed); }, // feedRequest - function called to setup and make a feed request updateFeedRequest: function(currentFeed) { Mojo.Log.info("URL Request: ", currentFeed.url); // Notify the chain that there is an update in progress Mojo.Controller.getAppController().sendToNotificationChain({ type: "update", update: true, feedIndex: this.feedIndex}); var request = new Ajax.Request(currentFeed.url, { method: "get", evalJSON: "false", onSuccess: this.updateFeedSuccess.bind(this), onFailure: this.updateFeedFailure.bind(this) }); }, // updateFeedFailure - Callback routine from a failed AJAX feed request; // post a simple failure error message with the http status code. updateFeedFailure: function(transport) { // Prototype template to generate a string from the return status. var t = new Template( $L("Status #{status} returned from newsfeed request.")); var m = t.evaluate(transport); // Post error alert and log error Mojo.Log.info("Invalid feed - http failure, check feed: ", m); // Notify the chain that this update is complete Mojo.Controller.getAppController().sendToNotificationChain({ type: "update", update: false, feedIndex: this.feedIndex}); }, // updateFeedSuccess - Successful AJAX feed request (feedRequest); // uses this.feedIndex and this.list updateFeedSuccess: function(transport) { var t = new Template($L({key: "newsfeed.status", value: "Status #{status} returned from newsfeed request."})); Mojo.Log.info("Feed Request Success: ", t.evaluate(transport)); // Work around due to occasional XML errors 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, passing in transport holding the updated feed data var feedError = this.processFeed(transport, this.feedIndex); // If successful processFeed returns News.errorNone if (feedError == News.errorNone) { var appController = Mojo.Controller.getAppController(); var stageController = appController.getStageController(News.MainStageName); var dashboardStageController = appController.getStageProxy(News.DashboardStageName); // Post a notification if new stories and application is minimized if (this.list[this.feedIndex].newStoryCount > 0) { Mojo.Log.info("New Stories: ", this.list[this.feedIndex].title," : ", this.list[this.feedIndex].newStoryCount, " New Items"); if (!stageController.isActiveAndHasScenes() && News.notificationEnable) { var bannerParams = { messageText: Mojo.Format.formatChoice( this.list[this.feedIndex].newStoryCount, $L("0##{title} : No New Items| 1##{title} : 1 New Item| 1>##{title} : #{count} New Items"), { title: this.list[this.feedIndex].title, count: this.list[this.feedIndex].newStoryCount } ) }; appController.showBanner(bannerParams, { action: "notification", index: this.feedIndex }, this.list[this.feedIndex].url); // Create or update dashboard var feedlist = this.list; var selectedFeedIndex = this.feedIndex; if(!dashboardStageController) { Mojo.Log.info("New Dashboard Stage"); var pushDashboard = function(stageController){ stageController.pushScene("dashboard", feedlist, selectedFeedIndex); }; appController.createStageWithCallback({ name: News.DashboardStageName, lightweight: true }, pushDashboard, "dashboard"); } else { Mojo.Log.info("Existing Dashboard Stage"); dashboardStageController.delegateToSceneAssistant( "updateDashboard", selectedFeedIndex); } } } } else { // There was a feed process error; unlikely, but could happen if the // feed was changed by the feed service. Log the error. if (feedError == News.invalidFeedError) { Mojo.Log.info("Feed ", this.nameModel.value, " is not a supported feed type."); } } // Notify the chain that this update is done Mojo.Controller.getAppController().sendToNotificationChain({ type: "update", update: false, feedIndex: this.feedIndex}); News.feedListChanged = true; // If NOT the last feed then update the feedsource and request next feed this.feedIndex++; if(this.feedIndex < this.list.length) { this.currentFeed = this.list[this.feedIndex]; // Notify the chain that there is a new update in progress Mojo.Controller.getAppController().sendToNotificationChain({ type: "update", update: true, feedIndex: this.feedIndex }); // Request an update for the next feed this.updateFeedRequest(this.currentFeed); } else { // Otherwise, this update is done. Reset index to 0 for next update this.feedIndex = 0; News.feedListUpdateInProgress = false; } } });
<div class="dashboard-notification-module"> <div class="palm-dashboard-icon-container"> <div class="dashboard-newitem"> <span>#{count}</span> </div> <div id="dashboard-icon" class="palm-dashboard-icon dashboard-icon-news"> </div> </div> <div class="palm-dashboard-text-container"> <div class="dashboard-title"> #{title} </div> <div id="dashboard-text" class="palm-dashboard-text">#{message}</div> </div> </div
<div id="palm-dialog-content" class="palm-dialog-content"> <div id="add-feed-title" class="palm-dialog-title"> Add Feed </div> <div class="palm-dialog-separator"></div> <div class="textfield-group" x-mojo-focus-highlight="true"> <div class="title"> <div x-mojo-element="TextField" id="newFeedURL"></div> </div> </div> <div class="textfield-group" x-mojo-focus-highlight="true"> <div class="title"> <div x-mojo-element="TextField" id="newFeedName"></div> </div> </div> <div class="palm-dialog-buttons"> <div x-mojo-element="Button" id="okButton"> <div x-mojo-element="Button" id="cancelButton"> </div> </div>
<div id="feedListScene"> <!-- Search Field --> <div id="searchFieldContainer"> <div x-mojo-element="FilterList" id="startSearchField"></div> </div> <div id="feedListMain"> <!-- Rotating Feature Story --> <div id="feedList_view_header" class="palm-header left"> Latest News <div id="featureDrawer" class="featureFeed-close"></div> </div> <div class="palm-header-spacer"></div> <div x-mojo-element="Drawer" id="featureFeedDrawer"> <div x-mojo-element="Scroller" id="featureScroller" > <div id="featureStoryDiv" class="featureScroller"> <div id="splashScreen" class="splashScreen"> <div class="update-image"></div> <div class="title">News v0.8<span>#{version}</span> <div class="palm-body-text">Copyright 2009, Palm®</div> </div> </div> <div id="featureStoryTitle" class="palm-body-title"> </div> <div id="featureStory" class="palm-body-text"> </div> </div> </div> </div> <!-- Feed List --> <div class="palm-list"> <div x-mojo-element="List" id="feedListWgt"></div> </div> </div> </div>
<div class="palm-row" x-mojo-touch-feedback="delayed"> <div class="palm-row-wrapper textfield-group"> <div class="title"> <div class="palm-dashboard-icon-container feedlist-icon-container"> <div class="dashboard-newitem feedlist-newitem"> <span class="unReadCount">#{numUnRead}</span> </div> <div id="dashboard-icon" class="palm-dashboard-icon feedlist-icon"> </div> </div> <div class="feedlist-info icon right" id="info"></div> <div x-mojo-element="Spinner" class="right" name="feedSpinner"</div> <div class="feedlist-title truncating-text">#{title}</div> <div class="feedlist-url truncating-text">#{-url}</div> </div> </div> </div>
<div class="palm-page-header"> <div class="palm-page-header-wrapper"> <div class="icon news-mini-icon"></div> <div class="title">News Preferences</div> </div> </div> <div class="palm-group"> <div class="palm-group-title"><span>Feature Feed</span></div> <div class="palm-list"> <div x-mojo-element="IntegerPicker" id="featureFeedDelay"></div> </div> </div> </div> <div class="palm-group"> <div class="palm-group-title"><span>Feed Updates</span></div> <div class="palm-list"> <div class="palm-row first"> <div class="palm-row-wrapper"> <div x-mojo-element="ListSelector" id="feedCheckIntervalList"> </div> </div> </div> <div class="palm-row"> <div class="palm-row-wrapper"> <div x-mojo-element="ToggleButton" id="notificationToggle"> </div> <div class="title left">Show Notification</div> </div> </div> <div class="palm-row last"> <div class="palm-row-wrapper"> <div x-mojo-element="ToggleButton" id="bgUpdateToggle"> </div> <div class="title left">Wake Device</div> </div> </div> </div> </div> </div>
<div class="palm-header-spacer"></div> <div id="storyListScene" class="storyListScene"> <div x-mojo-element="List" id="storyListWgt" ></div> </div> <div class="storyList-filter"> <div x-mojo-element="FilterList" id="storyListSearch" class="palm-list"></div> </div>
<div class="palm-row" x-mojo-touch-feedback="delayed"> <div class="palm-row-wrapper"> <div id="storyTitle" class="title truncating-text #{unreadStyle}"> #{title} </div> <div id="storySummary" class="news-subtitle truncating-text"> #{summary} </div> </div> </div>
<div id="storyViewScene"> <div class="palm-page-header multi-line"> <div class="palm-page-header-wrapper"> <div id="storyViewTitle" class="title left"> </div> </div> </div> <div class="palm-text-wrapper"> <div id="storyViewSummary" class="palm-body-text"> </div> </div> </div>
{ "title": "News", "type": "web", "main": "index.html", "id": "com.palm.app.news11-1", "version": "1.0.0", "vendor": "Palm", "noWindow" : "true", "icon": "icon.png", "theme": "light" }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://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> <link href="stylesheets/News.css" media="screen" rel="stylesheet" type="text/css"/> </head> <body> </body> </html>
{ "title": "Noticias", "type": "web", "main": "../../index.html", "id": "com.palm.app.news", "version": "1.0.0", "vendor": "Palm", "noWindow" : "true", "icon": "../../icon.png", "theme": "light" }
{ "#{status}" : "#{status}", "0##{title} : No New Items|1##{title} : 1 New Item|1>##{title} : #{count} New Items" : "0##{title} : No hay elementos nuevos|1##{title} : 1 elemento nuevo|1>##{title} : #{count} elementos nuevos", "1 Day" : "1 día", "1 Hour" : "1 hora", "15 Minutes" : "15 minutos", "4 Hours" : "4 horas", "5 Minutes" : "5 minutos", "About News..." : "Acerca de noticias...", "Add Feed DB save error : #{message}; can't save feed list." : "Error de base de datos al intentar agregar nueva fuente web : #{message}; no se puede guardar la lista de fuentes web.", "Add News Feed Source" : "Añadir fuente web de noticias", "Add..." : "Añadir...", "Adding a Feed" : "Añadiendo una fuente web", "All Read" : "Todas leídas", "All Unread" : "Todas las no leídas", "Can't open feed database: " : "No se puede abrir la base de datos de fuentes web: ", "Cancel" : "Cancelar", "Cancel search" : "Cancelar búsqueda", "Check out this News story..." : "Leer esta noticia...", "Check this out: " : "Mira esto: ", "Copyright 2009, Palm Inc." : "Copyright 2009, Palm Inc.", "Database save error: " : "Error al guardar en la base de datos: ", "Edit a Feed" : "Editar una fuente web", "Edit Feed" : "Editar fuente web", "Edit News Feed" : "Editar una fuente web de noticias", "Feature Feed" : "Fuente web destacada", "Featured Feed" : "Fuente web destacada", "Feature Rotation" : "Rotación de fuente web destacada", "Feed Request Success:" : "Solicitud de fuente web lograda:", "Feed Updates" : "Actualización de fuentes web", "Help..." : "Ayuda...", "Interval" : "Intervalo", "Invalid Feed - not a supported feed type" : "Fuente web no válida: no es un tipo de fuente web admitido", "Latest News" : "Últimas noticias", "Manual Updates" : "Actualizaciones manuales", "Mark Read or Unread" : "Marcar leída o no leída", "New Card" : "Tarjeta nueva", "New features" : "Nuevas características", "New Items" : "Elementos nuevos", "News Help" : "Ayuda para noticias", "News Preferences" : "Preferencias para noticias", "newsfeed.status" : "Estado #{status} devuelto desde solicitud de fuente web de noticias", "OK" : "OK", "Optional" : "Opcional", "Preferences..." : "Preferencias...", "Reload" : "Cargar nuevamente", "Rotate Every" : "Girar cada", "Rotation (in seconds)" : "Rotación (en segundos)", "RSS or ATOM feed URL" : "Fuente web RSS o ATOM URL", "Search for: #{filter}" : "Buscar: #{filter}", "Show Notification" :"Mostrar aviso", "SMS/IM" : "SMS/IM", "Status #{status} returned from newsfeed request." : "La solicitud de fuente web de noticias indicó el estado #{status}.", "Stop" : "Detener", "Title" : "Título", "Title (Optional)" : "Título (Opcional)", "Update All Feeds" : "Actualizar todas las fuentes web", "Wake Device" : "Activar dispositivo", "Will need to reload on next use." : "Se tendrá que cargar de nuevo la próxima vez que se use." }
<div id="palm-dialog-content" class="palm-dialog-content"> <div id="add-feed-title" class="palm-dialog-title"> Añadir fuente web </div> <div class="palm-dialog-separator"></div> <div class="textfield-group" x-mojo-focus-highlight="true"> <div class="title"> <div x-mojo-element="TextField" id="newFeedURL"></div> </div> </div> <div class="textfield-group" x-mojo-focus-highlight="true"> <div class="title"> <div x-mojo-element="TextField" id="newFeedName"></div> </div> </div> <div class="palm-dialog-buttons"> <div x-mojo-element="Button" id="okButton"> <div x-mojo-element="Button" id="cancelButton"> </div> </div>
<div id="feedListScene"> <!-- Search Field --> <div id="searchFieldContainer"> <div x-mojo-element="FilterList" id="startSearchField"></div> </div> <div id="feedListMain"> <!-- Rotating Feature Story --> <div id="feedList_view_header" class="palm-header left"> Últimas noticias <div id="featureDrawer" class="featureFeed-close"></div> </div> <div class="palm-header-spacer"></div> <div x-mojo-element="Drawer" id="featureFeedDrawer"> <div x-mojo-element="Scroller" id="featureScroller" > <div id="featureStoryDiv" class="featureScroller"> <div id="splashScreen" class="splashScreen"> <div class="splashImage"></div> <div class="splashText"> Noticias v0.8<span>#{version}</span> <div class="splashBody">Copyright 2009, Palm®</div> </div> </div> <div id="featureStoryTitle" class="palm-body-title"> </div> <div id="featureStory" class="palm-body-text"> </div> </div> </div> </div> <!-- Feed List --> <div class="palm-list"> <div x-mojo-element="List" id="feedListWgt"></div> </div> </div> </div>
<div class="palm-page-header"> <div class="palm-page-header-wrapper"> <div class="icon news-mini-icon"></div> <div class="title">Preferencias para noticias</div> </div> </div> <div class="palm-group"> <div class="palm-group-title"><span>Fuente web destacada</span></div> <div class="palm-list"> <div x-mojo-element="IntegerPicker" id="featureFeedDelay"></div> </div> </div> </div> <div class="palm-group"> <div class="palm-group-title"><span>Actualización de fuentes web</span></div> <div class="palm-list"> <div class="palm-row first"> <div class="palm-row-wrapper"> <div x-mojo-element="ListSelector" id="feedCheckIntervalList"> </div> </div> </div> <div class="palm-row"> <div class="palm-row-wrapper"> <div x-mojo-element="ToggleButton" id="notificationToggle"> </div> <div class="title left">Mostrar aviso</div> </div> </div> <div class="palm-row last"> <div class="palm-row-wrapper"> <div x-mojo-element="ToggleButton" id="bgUpdateToggle"></div> <div class="title left">Activar dispositivo</div> </div> </div> </div> </div> </div>
[ { "source": "app/assistants/app-assistant.js" }, { "source": "app/assistants/stage-assistant.js" }, { "source": "app/assistants/dashboard-assistant.js", "scenes": "dashboard" }, { "source": "app/assistants/feedList-assistant.js", "scenes": "feedList" }, { "source": "app/assistants/preferences-assistant.js", "scenes": "preferences" }, { "source": "app/assistants/storyList-assistant.js", "scenes": "storyList" }, { "source": "app/assistants/storyView-assistant.js", "scenes": "storyView" }, { "source" : "app/models/cookies.js" }, { "source" : "app/models/feeds.js" } ]
/* News CSS Copyright 2009 Palm, Inc. All rights reserved. App overrides of palm scene and widget styles. */ /* Contrains storyView content to width of scene */ img { max-width:280px; } /* Header Styles */ .icon.news-mini-icon { background: url(../images/header-icon-news.png) no-repeat; margin-top: 13px; margin-left: 17px; } /* FeedList Header styles for feature drawer and selection */ .featureFeed-close { float:right; margin: 8px −12px 0px 0px; height:35px; width: 35px; background: url(../images/details-open-arrow.png) no-repeat; } .featureFeed-open { float:right; margin: 8px −12px 0px 0px; height:35px; width: 35px; background: url(../images/details-closed-arrow.png) no-repeat; }.palm-drawer-container { border-width: 20px 1px 20px 1px; -webkit-border-image: url(../images/palm-drawer-background-1.png) 20 1 20 1 repeat repeat; -webkit-box-sizing: border-box; overflow: visible; } /* Feature Feed styles */ .featureScroller { height: 100px; width: 280px; margin-left: 20px; } /* feedList styles */ .palm-row-wrapper.textfield-group { margin-top: 5px; } .feedlist-title { line-height: 2.0em; } .feedlist-url { font-size: 14px; color: gray; margin-top: −20px; margin-bottom: −20px; line-height: 16px; } .feedlist-info { background: url(../images/info-icon.png) center center no-repeat; } .feedlist-icon-container { height: 54px; margin-top: 5px; } .feedlist-icon { background: url(../images/list-icon-rssfeed.png) center no-repeat; } .feedlist-newitem { line-height: 20px; height: 26px; min-width: 26px; -webkit-border-image: url(../images/feedlist-newitem.png) 4 10 4 10 stretch stretch; -webkit-box-sizing: border-box; border-width: 4px 10px 4px 10px; } .unReadCount { color: white; } /* Story List styles */ .news-subtitle { padding: 0px 14px 0px 14px; font-size: 14px; margin-top: −10px; line-height: 16px; } .palm-row-wrapper > .unReadStyle { font-weight: bold; } .storyList-filter .filter-field-container { top: 48px; left: 0px; position: fixed; width: 100%; height: 48px; border-width: 26px 23px 20px 23px; -webkit-border-image: url(../images/filter-search-light-bg.png) 26 23 20 23 repeat repeat; -webkit-box-sizing: border-box; z-index: 11002; } /* Splash Screen image */ .update-image { background: url(../images/news-icon.png) center center no-repeat; float: left; height: 58px; width: 58px; margin-left: −3px; } /* dashboard styles */ .dashboard-icon-news { background: url(../images/dashboard-icon-news.png); }
3.145.93.145