User:Efege:FeedViewer3 (Legacy)
This version of our Learning Center is unmaintained.
This article may be out-of-date or contain incorrect information.
Please visit the new Sencha Learning Center for up-to-date material.
From Sencha - Learn
Since I want to learn how Ext applications should be organized, and Jack is offering the Feed Viewer 3 prototype as "a good reference implementation" with "code broken up into logical classes", I began to write some documentation for this app. This helps me understand how all the pieces fit together, and probably others will be interested too.
This is a full quote of Jack's announcement (06-11-2007):
The FeedViewer example app has been rewritten from scratch for Ext 2.0. It is intended to be a good reference implementation of 2.0 and unlike the other examples which were written with speed of development in mind, FeedViewer 3 features its code broken up into logical classes. This makes the code much more organized and easier to maintain.
Some new things in FeedViewer 3 are reading pane placement, post summaries, context menus (tabs, grid and tree context menus), combobox (in "Add feed" window) and some significant performance improvements. It's starting to look pretty decent, so I have thrown a dev copy up. It is checked into the examples folder of the Ext 2.0 branch in SVN.
The application code consists of 4 classes (FeedGrid, FeedPanel, FeedWindow, and MainPanel), a singleton object (FeedViewer), an anonymous function invoked by Ext.onReady(), and a bit of auxiliary markup in the HTML file. There is also a CSS file, feed-viewer.css. On the server side, there is a PHP script, feed-proxy.php, acting as an HTTP proxy for getting the feeds.
Contents |
Class MainPanel
The main panel of the application, containing a FeedGrid and a preview (or reading) pane. This preview pane can be positioned at the bottom or the right, or it can be hidden.
MainPanel extends Ext.TabPanel with the following methods:
- loadFeed: loads a feed in the panel (invokes the loadFeed() method of the panel's FeedGrid).
- onContextMenu: displays (and if needed, creates) a context menu for the tab. Items in this menu: Close tab, Close other tabs.
- movePreview: hides or shows (bottom or right) the reading pane.
- openTab: displays a feed item in a new tab.
- openAll: displays all feed items, each in a new tab.
The MainPanel constructor creates the toolbar for the FeedGrid, which has 3 buttons:
Button text Button handler ------------------------------------------- OpenAll MainPanel.openAll Reading Pane MainPanel.movePreview Summary FeedGrid.togglePreview
Class FeedGrid
A grid whose rows represent feed items. FeedGrid extends Ext.grid.GridPanel with the following methods:
- onContextClick: adds a CSS class to highlight the current row (feed item), and displays (and if needed, creates) a context menu for the row. Items in the menu: View in new tab, Go to post, Refresh.
- onContextHide: removes the added CSS class from the row
- loadFeed: invokes the load() method of the grid's store.
- togglePreview: shows or hides the summaries in the feed grid
- applyRowClass: used for GridPanel's config option of getRowClass to determine the css class of the current row
- formatDate: renderer for the date column
- formatTitle: renderer for the title column
This is an overview of the code:
// constructor FeedGrid = function(viewer, config) { this.viewer = viewer; /* the MainPanel instance */ Ext.apply(this, config); this.store = /* HttpProxy & XmlReader */; this.columns = /* title, author, date */; FeedGrid.superclass.constructor.call(this, /* options object: region, id, loadMask, sm, viewConfig */); } // class extension Ext.extend(FeedGrid, Ext.grid.GridPanel, { // methods listed above }
Class FeedPanel
The panel used to store and manage the list of available feeds. This list is, in fact, a tree. You can add or remove nodes from it, using either the panel's toolbar or the context menu.
FeedPanel extends Ext.TreePanel with the following methods:
- onContextMenu: displays (and if needed, creates) a context menu for the current node in the feed panel. Also adds a CSS class to highlight the node. The context menu has 3 items:
| Menu item | Handler |
|---|---|
| Load Feed | FeedPanel.ctxNode.select |
| Remove | FeedPanel.removeFeed |
| Add Feed | FeedPanel.showWindow |
- onContextHide: removes the added CSS class from the node
- showWindow: displays (and if needed, creates) a FeedWindow instance, which allows to add a new feed.
- selectFeed: selects a tree node given an argument of url, this is also the id of the treenode.
- removeFeed: removes a node from the feed panel.
- addFeed: adds a node to the feed panel.
- afterRender: calls the superclass (TreePanel)'s afterRender and adds behavior to preventDefault when a contextmenu event happens - (don't show the browsers context menu)
This panel has a toolbar with 2 buttons:
Button text Handler ------------------------------------------- Add Feed FeedPanel.showWindow Remove FeedPanel.removeFeed
Overview of the code:
// constructor FeedPanel = function() { FeedPanel.superclass.constructor.call(this, /* options: id, region, title, split, width, minSize, maxSize, collapsible, margins, cmargins, rootVisible, lines, autoScroll, root, collapseFirst, tbar */); this.feeds = /* new Ext.tree.TreeNode() */; this.getSelectionModel().on(/* event listeners */); }; // class extension Ext.extend(FeedPanel, Ext.tree.TreePanel, { // methods };
Class FeedWindow
The modal dialog window for adding feeds. Includes an editable combobox with feed URLs.
FeedWindow extends Ext.Window with the following methods:
- show: overrides the show() method inherited from Ext.Window (?).
- onAdd: masks the FeedWindow and processes the feed URL, invoking Ext.Ajax.request().
- markInvalid: called on failure of either the Ajax request or the feed validation, displays an "invalid feed" message.
- validateFeed: called on success of the Ajax request, validates the returned XML; depending on the result, fires the validfeed event or calls markInvalid().
Object FeedViewer
A singleton object with only one method and one property:
- method getTemplate (defined on app initialization, since it depends on the HTML markup being available)
- property LinkInterceptor: an object with a "render" method (it's an event handler for mousedown and click that uses delegation; used for Panels that display feed items or full posts)
// *** TEST IF THIS WORKS INSTEAD OF ORIGINAL CODE *** FeedViewer = (function(){ return { // This is a custom event handler passed to preview panels so link open in a new windw LinkInterceptor : { render: function(p){ p.body.on({ 'mousedown': function(e, t){ // try to intercept the easy way t.target = '_blank'; }, 'click': function(e, t){ // if they tab + enter a link, need to do it old fashioned way if(String(t.target).toLowerCase() != '_blank'){ e.stopEvent(); window.open(t.href); } }, delegate:'a' }); } }, // Initialization init : function() { Ext.QuickTips.init(); var tpl = Ext.Template.from('preview-tpl', { compiled:true, getBody : function(v, all){ return v || all.description; } }); FeedViewer.getTemplate = function(){ return tpl; } var feeds = new FeedPanel(); var mainPanel = new MainPanel(); feeds.on('feedselect', function(feed){ mainPanel.loadFeed(feed); }); var viewport = new Ext.Viewport({ layout:'border', items:[ new Ext.BoxComponent({ // raw element region:'north', el: 'header', height:32 }), feeds, mainPanel ] }); // add some default feeds feeds.addFeed({ url:'http://extjs.com/news/archive/feed', text: 'ExtJS.com News' }, false, true); feeds.addFeed({ url:'http://extjs.com/forum/external.php?type=RSS2', text: 'ExtJS.com Forums' }, true); feeds.addFeed({ url:'http://feeds.feedburner.com/ajaxian', text: 'Ajaxian' }, true); } } })(); Ext.onReady(FeedViewer.init, FeedViewer);
Application initialization
The anonymous function invoked by Ext.onReady() does the following:
- initializes Ext.QuickTips (usual step when initializing an Ext application)
- creates an Ext.Template instance for feed items (from HTML markup), and defines the FeedViewer.getTemplate() method
- creates an instance of FeedPanel and one of MainPanel
- adds an event listener that links FeedPanel with MainPanel (selecting a feed in the left panel will load that feed in the main panel)
- sets up the application layout, i.e. an instance of Ext.Viewport with 3 items: an Ext.BoxComponent for the north region (header), and the instances of FeedPanel and MainPanel crated above
- adds some default feeds to the FeedPanel, making one of them the active feed (i.e. it's loaded in the main panel)
Issues
This is a developer's demo based on an alpha version of Ext 2.0, which has not even been released to the general public, so it's probably too early to report bugs or issues. But just in case, let's build a list of the problems we find while playing with the demo.
- Modal mask does not resize with window. If I enlarge the browser window while the "Add Feed" window is open, the mask keeps covering only the original viewport area, allowing interaction with the "new", uncovered area. This changes after the mask has been applied at least once to the larger area, since apparently the mask is reused.
- Custom context menus covered by browser's default context menu. I observed this using FF 1.5 on Linux, both on the FeedPanel and on the MainPanel tab. Missing call to stopEvent or preventDefault?
- When the active feed is removed from the FeedPanel, the MainPanel keeps displaying it. Should it be replaced by the next or previous feed in the tree?
- The menu displayed by the "Reading pane" split button is aligned to the left and its width is less than the button's width, so when you click on the arrow you must move the mouse to the left in order to reach the menu. I'd like that the only movement needed was downwards. This requires that either the menu is at least as wide as the split button, or the menu is aligned to the right.
- Custom context menus in the FeedGrid don't block scrolling, nor are hidden by a scroll of the grid, and so this produces out-of-context context menus (Posted to the General Discussion forum)




