--- /dev/null
+/**
+ * @class FeedViewer.FeedPanel
+ * @extends Ext.panel.Panel
+ *
+ * Shows a list of available feeds. Also has the ability to add/remove and load feeds.
+ *
+ * @constructor
+ * Create a new Feed Panel
+ * @param {Object} config The config object
+ */
+
+Ext.define('FeedViewer.FeedPanel', {
+ extend: 'Ext.panel.Panel',
+
+ alias: 'widget.feedpanel',
+
+ initComponent: function(){
+ Ext.apply(this, {
+ layout: 'fit',
+ title: 'Feeds',
+ animCollapse: true,
+ items: this.createView(),
+ dockedItems: this.createToolbar()
+ });
+ this.createMenu();
+ this.addEvents(
+ /**
+ * @event feedremove Fired when a feed is removed
+ * @param {FeedPanel} this
+ * @param {String} title The title of the feed
+ * @param {String} url The url of the feed
+ */
+ 'feedremove',
+
+ /**
+ * @event feedselect Fired when a feed is selected
+ * @param {FeedPanel} this
+ * @param {String} title The title of the feed
+ * @param {String} url The url of the feed
+ */
+ 'feedselect'
+ );
+
+ this.callParent(arguments);
+ },
+
+ // template method
+ afterRender: function(){
+ this.callParent(arguments);
+ var view = this.view;
+ view.getSelectionModel().select(view.store.first());
+ },
+
+ /**
+ * Create the DataView to be used for the feed list.
+ * @private
+ * @return {Ext.view.View}
+ */
+ createView: function(){
+ this.view = Ext.create('widget.dataview', {
+ store: Ext.create('Ext.data.Store', {
+ model: 'Feed',
+ data: this.feeds
+ }),
+ selModel: {
+ mode: 'SINGLE',
+ listeners: {
+ scope: this,
+ selectionchange: this.onSelectionChange
+ }
+ },
+ listeners: {
+ scope: this,
+ contextmenu: this.onContextMenu
+ },
+ trackOver: true,
+ cls: 'feed-list',
+ itemSelector: '.feed-list-item',
+ overItemCls: 'feed-list-item-hover',
+ tpl: '<tpl for="."><div class="feed-list-item">{title}</div></tpl>'
+ });
+ return this.view;
+ },
+
+ /**
+ * Creates the toolbar to be used for controlling feeds.
+ * @private
+ * @return {Ext.toolbar.Toolbar}
+ */
+ createToolbar: function(){
+ this.createActions();
+ this.toolbar = Ext.create('widget.toolbar', {
+ items: [this.addAction, this.removeAction]
+ });
+ return this.toolbar;
+ },
+
+ /**
+ * Create actions to share between toolbar and menu
+ * @private
+ */
+ createActions: function(){
+ this.addAction = Ext.create('Ext.Action', {
+ scope: this,
+ handler: this.onAddFeedClick,
+ text: 'Add feed',
+ iconCls: 'feed-add'
+ });
+
+ this.removeAction = Ext.create('Ext.Action', {
+ itemId: 'remove',
+ scope: this,
+ handler: this.onRemoveFeedClick,
+ text: 'Remove feed',
+ iconCls: 'feed-remove'
+ });
+ },
+
+ /**
+ * Create the context menu
+ * @private
+ */
+ createMenu: function(){
+ this.menu = Ext.create('widget.menu', {
+ items: [{
+ scope: this,
+ handler: this.onLoadClick,
+ text: 'Load feed',
+ iconCls: 'feed-load'
+ }, this.removeAction, '-', this.addAction],
+ listeners: {
+ hide: function(c){
+ c.activeFeed = null;
+ }
+ }
+ });
+ },
+
+ /**
+ * Used when view selection changes so we can disable toolbar buttons.
+ * @private
+ */
+ onSelectionChange: function(){
+ var selected = this.getSelectedItem();
+ this.toolbar.getComponent('remove').setDisabled(!selected);
+ this.loadFeed(selected);
+ },
+
+ /**
+ * React to the load feed menu click.
+ * @private
+ */
+ onLoadClick: function(){
+ this.loadFeed(this.menu.activeFeed);
+ },
+
+ /**
+ * Loads a feed.
+ * @private
+ * @param {Ext.data.Model} rec The feed
+ */
+ loadFeed: function(rec){
+ if (rec) {
+ this.fireEvent('feedselect', this, rec.get('title'), rec.get('url'));
+ }
+ },
+
+ /**
+ * Gets the currently selected record in the view.
+ * @private
+ * @return {Ext.data.Model} Returns the selected model. false if nothing is selected.
+ */
+ getSelectedItem: function(){
+ return this.view.getSelectionModel().getSelection()[0] || false;
+ },
+
+ /**
+ * Listens for the context menu event on the view
+ * @private
+ */
+ onContextMenu: function(view, index, el, event){
+ var menu = this.menu;
+
+ event.stopEvent();
+ menu.activeFeed = view.store.getAt(index);
+ menu.showAt(event.getXY());
+ },
+
+ /**
+ * React to a feed being removed
+ * @private
+ */
+ onRemoveFeedClick: function(){
+ var active = this.menu.activeFeed || this.getSelectedItem();
+
+
+ this.animateNode(this.view.getNode(active), 1, 0, {
+ scope: this,
+ afteranimate: function(){
+ this.view.store.remove(active);
+ }
+ });
+ this.fireEvent('feedremove', this, active.get('title'), active.get('url'));
+
+ },
+
+ /**
+ * React to a feed attempting to be added
+ * @private
+ */
+ onAddFeedClick: function(){
+ var win = Ext.create('widget.feedwindow', {
+ listeners: {
+ scope: this,
+ feedvalid: this.onFeedValid
+ }
+ });
+ win.show();
+ },
+
+ /**
+ * React to a validation on a feed passing
+ * @private
+ * @param {FeedViewer.FeedWindow} win
+ * @param {String} title The title of the feed
+ * @param {String} url The url of the feed
+ */
+ onFeedValid: function(win, title, url){
+ var view = this.view,
+ store = view.store,
+ rec;
+
+ rec = store.add({
+ url: url,
+ title: title
+ })[0];
+ this.animateNode(view.getNode(rec), 0, 1);
+ },
+
+ /**
+ * Animate a node in the view when it is added/removed
+ * @private
+ * @param {Mixed} el The element to animate
+ * @param {Number} start The start opacity
+ * @param {Number} end The end opacity
+ * @param {Object} listeners (optional) Any listeners
+ */
+ animateNode: function(el, start, end, listeners){
+ Ext.create('Ext.fx.Anim', {
+ target: Ext.get(el),
+ duration: 500,
+ from: {
+ opacity: start
+ },
+ to: {
+ opacity: end
+ },
+ listeners: listeners
+ });
+ },
+
+ // Inherit docs
+ onDestroy: function(){
+ this.callParent(arguments);
+ this.menu.destroy();
+ }
+});