Ext JS - Learning Center

User:Efege:FeedViewer3

From Learn About the Ext JavaScript Library

Jump to: navigation, search
WARNING: these are only personal notes, and not part of the official Ext documentation. Feel free to improve this page.

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.

The MainPanel, with the reading pane on the right (and a bit of self-reference)
The MainPanel, with the reading pane on the right (and a bit of self-reference)

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 FeedGrid with its own toolbar
A FeedGrid with its own toolbar

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

A FeedPanel with its 3 default feeds
A FeedPanel with its 3 default feeds

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.

A FeedWindow, with its expanded combobox and the modal mask behind
A FeedWindow, with its expanded combobox and the modal mask behind

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)
  • This page was last modified 21:40, 19 September 2007.
  • This page has been accessed 7,710 times.