summaryrefslogtreecommitdiffstats
path: root/js/menu.js
diff options
context:
space:
mode:
authorBernhard Posselt <nukeawhale@gmail.com>2012-08-30 19:31:09 +0200
committerBernhard Posselt <nukeawhale@gmail.com>2012-09-01 00:31:38 +0200
commitbe192931ce450ea6c5310bf858517ef391a2a2b3 (patch)
tree2a2937e9fd72c3d7b1291b3db9bd4e4f4e47993b /js/menu.js
parentf19d8f0e1399a7da1beff6df5282a0d8991d1852 (diff)
merged
Diffstat (limited to 'js/menu.js')
-rw-r--r--js/menu.js683
1 files changed, 232 insertions, 451 deletions
diff --git a/js/menu.js b/js/menu.js
index 32d528156..19a869e35 100644
--- a/js/menu.js
+++ b/js/menu.js
@@ -18,79 +18,11 @@
* HOWTO
*
-We create a new instance of the menu. The first argument is its class.
-
- var menu = new News.Menu('feedlist');
-
-
-To fill it with items we use JSON. Hint: If no icons are given, default icons
-are being used. A typical JSON array would look like this:
-
- var menuStructure = [
- {
- id: 1,
- title: 'New Articles',
- type: News.MenuNodeType.New,
- children: [],
- unreadCount: 4,
- },
- {
- id: 1,
- title: 'Starred',
- type: News.MenuNodeType.Starred,
- children: [],
- unreadCount: 1,
- },
- {
- id: 1,
- title: 'hi',
- type: News.MenuNodeType.Folder,
- unreadCount: 7,
- children: [
- {
- id: 2,
- title: 'hi too',
- type: News.MenuNodeType.Feed,
- unreadCount: 4,
- children: [],
- icon: '/test/test.png'
- },
- {
- id: 2,
- title: 'hi 3',
- type: News.MenuNodeType.Feed,
- children: [],
- unreadCount: 3,
- },
- ]
- },
- {
- id: 4,
- title: 'hi 4',
- type: News.MenuNodeType.Feed,
- children: [],
- unreadCount: 1,
- },
- {
- id: 114,
- title: 'hi 3',
- type: News.MenuNodeType.Folder,
- children: [],
- unreadCount: 1,
- },
- ];
-
- menu.populateFromJSON(menuStructure, menu);
-
-
-Now that the menu is populated, we can render, append it into the place we'd
-like to and select the current selected item
-
- $('#leftcontent').append(menu.render());
- var selectedType = News.MenuNodeType.Feed;
- var selectedId = 3;
- menu.setSelected(selectedType, selectedId);
+We create a new instance of the menu. Then we need to bind it on an ul which contains
+all the items:
+ var menu = new News.Menu();
+ menu.bindOn('#feeds ul');
Updating nodes (you dont have to set all values in data):
@@ -164,16 +96,15 @@ var t = t || function(app, string){ return string; }; // mock translation for lo
'Feed': 0,
'Folder': 1,
'Starred': 2,
- 'New': 3
+ 'Subscriptions': 3
}
- // TODO: set paths for icons
- MenuNodeTypeDefaultIcon = {
- 'Feed': '',
- 'Folder': '',
- 'Starred': '',
- 'New': ''
- }
+ // map css classes to MenuNodeTypes
+ MenuNodeTypeClass = {};
+ MenuNodeTypeClass[MenuNodeType.Feed] = 'feed';
+ MenuNodeTypeClass[MenuNodeType.Folder] = 'folder';
+ MenuNodeTypeClass[MenuNodeType.Starred] = 'starred';
+ MenuNodeTypeClass[MenuNodeType.Subscriptions] = 'subscriptions';
News.MenuNodeType = MenuNodeType;
@@ -183,255 +114,248 @@ var t = t || function(app, string){ return string; }; // mock translation for lo
*#########################################################################/
/**
* This is the basic menu used to construct and maintain the menu
- * @param cls the css class of the element
+ * @param showAll if all items should be shown by default
*/
- Menu = function(cls){
- this._class = cls;
- this._children = [];
- this._parent = false;
- this._id = 0;
- this._$htmlElement = $('<ul>');
- this._$htmlElement.attr('data-id', this._id);
- this._bindDroppable(this._$htmlElement);
- this._selectedNode = undefined;
- this._showAll = false;
+ Menu = function(showAll){
+ this._showAll = showAll;
+ this._unreadCount = {
+ Feed: {},
+ Folder: {},
+ Starred: 0,
+ Subscriptions: 0
+ };
}
News.Menu = Menu;
/**
- * Attaches a MenuNode to a node and renders it in the dom
- * @param parentType the type of the parent node
- * @param parentId the id of the parent node, if 0 the top menu is used
- * @param node the MenuNode that should be created
+ *
*/
- Menu.prototype.createNode = function(parentType, parentId, node){
- var parentNode = this._findNode(parentType, parentId);
- parentNode._addChildNode(node);
- parentNode._$htmlElement.append(node.render());
+ Menu.prototype.removeNode = function(type, id){
+
}
/**
- * Recursively remove all occurences of the node from the dom and
- * from the datastructure
- * @param type the type of the node (MenuNodeType)
- * @param id the id of the node
- * @param removeDom if true, also remove the dom
- * @return the childelemnt or undefined if not found
+ * A node can only be added to a folder or to the root
*/
- Menu.prototype.removeNode = function(type, id, removeDom){
- for(var i=0; i<this._children.length; i++){
- var child = this._children[i];
- if(child._type === type && child._id === id){
- var nodeIndex = i;
- // if we have children, we need to remove their
- // html from the dom first then we need to
- if(removeDom){
- child._$htmlElement.remove();
- }
- this._children.splice(nodeIndex, 1);
- return child;
- } else {
- var child = child.removeNode(type, id);
- if(child !== undefined){
- return child;
- }
- }
- }
- return undefined;
+ Menu.prototype.addNode = function(parentId, type, id, data){
+
}
/**
- * Updates a node in the menu
- * @param type the type of the node (MenuNodeType)
- * @param id the id of the node
- * @param data the data array like {title: 'title', unreadCount: 1, icon: 'path/icon.png'}
+ *
*/
Menu.prototype.updateNode = function(type, id, data){
- var node = this._findNode(type, id);
- node.update(data);
+
}
/**
- * Creates the menu from a json structure
- * @param json the json looks like this
- [
- {
- id: 1,,
- title: 'hi',
- type: MenuNodeType.Folder,
- icon: 'url/to/jpg.png',
- children: [
- {
- id: 1,
- title: 'hi too',
- type: MenuNodeType.Feed,
- children: []
- },
- {
- ...
- }
- ]
- },
- {
- ...
- }
- ]
- * @param attachToNode used for recursion. to set the current
- * element to the node, pass the current structure
+ * Binds the menu on an existing menu
+ * @param css Selector the selector to get the element with jquery
*/
- Menu.prototype.populateFromJSON = function(json, attachToNode){
- for(var i=0; i<json.length; i++){
- var nodeInfo = json[i];
- var nodeData = {
- title: nodeInfo.title,
- icon: nodeInfo.icon,
- unreadCount: nodeInfo.unreadCount
- };
- var node = new MenuNode(nodeInfo.type, nodeInfo.id, nodeData);
- attachToNode._addChildNode(node);
- this.populateFromJSON(nodeInfo.children, node);
- }
+ Menu.prototype.bindOn = function(cssSelector){
+ var self = this;
+ this._$root = $(cssSelector);
+ this._id = this._$root.data('id');
+ this._$root.children('li').each(function(){
+ self._bindMenuItem($(this));
+ });
+ this._bindDroppable(this._$root);
}
/**
- * This function creates the html of the menu and its children
- * @return the menu of the node and its children
+ * Binds the according handlers and reads in the meta data for each node
+ * @param $listItem the jquery list element
*/
- Menu.prototype.render = function(){
- var $html = this._$htmlElement.addClass(this._class).data('id', this._id);
- for(var i=0; i<this._children.length; i++){
- var child = this._children[i];
- var childHTML = child.render();
- $html.append(childHTML);
+ Menu.prototype._bindMenuItem = function($listItem){
+ switch(this._listItemToMenuNodeType($listItem)){
+ case MenuNodeType.Feed:
+ this._bindFeed($listItem);
+ break;
+ case MenuNodeType.Folder:
+ this._bindFolder($listItem);
+ break;
+ case MenuNodeType.Starred:
+ this._bindStarred($listItem);
+ break;
+ case MenuNodeType.Subscriptions:
+ this._bindSubscriptions($listItem);
+ break;
+ default:
+ console.log('Found unknown MenuNodeType');
+ console.log($listItem);
+ break;
}
- this._rendered = true;
-
- return $html;
}
/**
- * Returns the number of elements in the menu
- * @return the number of all children
+ * Returns the MenuNodeType of a list item
+ * @param $listItem the jquery list element
+ * @return the MenuNodeType of the jquery element
*/
- Menu.prototype.getSize = function(){
- var size = this._children.length;
- for(var i=0; i<this._children.length; i++){
- size += this._children[i].getSize();
+ Menu.prototype._listItemToMenuNodeType = function($listItem){
+ if($listItem.hasClass(this._menuNodeTypeToClass(MenuNodeType.Feed))){
+ return MenuNodeType.Feed;
+ } else if($listItem.hasClass(this._menuNodeTypeToClass(MenuNodeType.Folder))){
+ return MenuNodeType.Folder;
+ } else if($listItem.hasClass(this._menuNodeTypeToClass(MenuNodeType.Starred))){
+ return MenuNodeType.Starred;
+ } else if($listItem.hasClass(this._menuNodeTypeToClass(MenuNodeType.Subscriptions))){
+ return MenuNodeType.Subscriptions;
}
- return size;
}
/**
- * Shortcut for intially setting the selected node
- * @param type the type of the node (MenuNodeType)
- * @param id the id of the node
+ * Returns the classname of the MenuNodeType
+ * @param menuNodeType the type of the menu node
+ * @return the class of the MenuNodeType
*/
- Menu.prototype.setSelected = function(type, id){
- this._setSelected(this._findNode(type, id));
+ Menu.prototype._menuNodeTypeToClass = function(menuNodeType){
+ return MenuNodeTypeClass[menuNodeType];
}
/**
- * Elements should only be set as hidden if the user clicked on a new entry
- * Then all all_read entries should be marked as hidden
- * This function is used to hide all the read ones if showAll is false,
- * otherwise shows all
+ * Binds event listeners to the folder and its subcontents
+ * @param $listItem the jquery list element
*/
- Menu.prototype.triggerHideRead = function(){
- // only trigger in the root menu
- if(this._parent === false){
- if(this._showAll){
- $(this._$htmlElement).find('.hidden').each(function(){
- $(this).removeClass('hidden');
- });
- } else {
- $(this._$htmlElement).find('.all_read').each(function(){
- if(!$(this).hasClass('hidden')){
- $(this).addClass('hidden');
- }
- });
- }
-
- }
- }
+ Menu.prototype._bindFolder = function($listItem){
+ var self = this;
+ var id = $listItem.data('id');
+ this._setUnreadCount(MenuNodeType.Folder, id,
+ this._getAndRemoveUnreadCount($listItem));
- /**
- * Sets the showAll value
- * @param showAll if true, all read folders and feeds are being shown
- * if false only unread ones are shown
- */
- Menu.prototype.setShowAll = function(showAll){
- this._showAll = showAll;
- this.triggerHideRead();
+ this._resetOpenFolders();
+
+ // bind subitems
+ $children.each(function(){
+ self._bindMenuItem($(this));
+ });
+
+ // bind click listeners
+ this._bindDroppable($listItem);
+ this._bindDroppable($listItem.children('ul'));
+
+ $listItem.children('.title').click(function(){
+ self._load(MenuNodeType.Folder, id);
+ return false;
+ });
+
+ $listItem.children('.collapsable_trigger').click(function(){
+ self._toggleCollapse($listItem);
+ });
+
+ $listItem.children('.feeds_delete').click(function(){
+ self._delete(MenuNodeType.Folder, id);
+ });
+
+ $listItem.children('.feeds_edit').click(function(){
+ self._edit(MenuNodeType.Folder, id);
+ });
+
+ $listItem.children('.feeds_markread').click(function(){
+ self._markRead(MenuNodeType.Folder, id);
+ });
}
- /**
- * Shortcut for toggling show all
- */
- Menu.prototype.toggleShowAll = function(){
- this.setShowAll(!this._showAll);
+ Menu.prototype._bindFeed = function($listItem){
+ var self = this;
+ var id = $listItem.data('id');
+ this._setUnreadCount(MenuNodeType.Feed, id,
+ this._getAndRemoveUnreadCount($listItem));
+
+ // bind click listeners
+ $listItem.children('.title').click(function(){
+ self._load(MenuNodeType.Folder, id);
+ return false;
+ });
+
+ $listItem.children('.feeds_delete').click(function(){
+ self._delete(MenuNodeType.Folder, id);
+ });
+
+ $listItem.children('.feeds_markread').click(function(){
+ self._markRead(MenuNodeType.Folder, id);
+ });
+
+ // bind draggable
+ $listItem.draggable({
+ revert: true,
+ stack: '> li',
+ zIndex: 1000,
+ axis: 'y',
+ });
}
+ Menu.prototype._bindStarred = function($listItem){
+ this._setUnreadCount(MenuNodeType.Starred, 0,
+ this._getAndRemoveUnreadCount($listItem));
- /* #### private #### */
+ // bind click listeners
+ $listItem.children('.title').click(function(){
+ self._load(MenuNodeType.Folder, id);
+ return false;
+ });
+ }
- /**
- * Adds a node to the current one
- * @param node the node which we want to add to the menu
- */
- Menu.prototype._addChildNode = function(node){
- node._parent = this;
- this._children.push(node);
+ Menu.prototype._bindSubscriptions = function($listItem){
+ this._setUnreadCount(MenuNodeType.Subscriptions, 0,
+ this._getAndRemoveUnreadCount($listItem));
+
+ // bind click listeners
+ $listItem.children('.title').click(function(){
+ self._load(MenuNodeType.Folder, id);
+ return false;
+ });
}
-
- /**
- * Recursively traverse the menu and returns the
- * Node element matching the type and id
- * @param type the type of the node (MenuNodeType)
- * @param id the id of the node, 0 returns the menu
- * @return the node element or undefined
- */
- Menu.prototype._findNode = function(type, id){
- if(id === 0){
- return this;
- }
- for(var i=0; i<this._children.length; i++){
- var child = this._children[i];
- if(child._type === type && child._id === id){
- return child;
+
+ Menu.prototype._resetOpenFolders = function(){
+ $folders = $('.folder');
+ $folders.each(function(){
+ $children = $(this).children('ul').children('li');
+ if($children.length > 0){
+ $(this).addClass('collapsable');
} else {
- var childNode = child._findNode(type, id);
- if(childNode !== undefined){
- return childNode;
- }
+ $(this).removeClass('collapsable');
}
- }
- return undefined;
+ });
}
- /**
- * Returns the root element
- * @return the root element
- */
- Menu.prototype._getRoot = function(){
- if(this._parent === false){
- return this;
- } else {
- return this._parent._getRoot();
- }
+ Menu.prototype._load = function(type, id){
+
+ }
+
+ Menu.prototype._toggleCollapse = function($listItem){
+ $listItem.toggleClass('open');
+ $listItem.children('.collapsable_trigger').toggleClass('triggered');
+ $listItem.children('ul').toggle();
+ }
+
+ Menu.prototype._getAndRemoveUnreadCount = function($listItem){
+ var $unreadCounter = $listItem.children('.unread_items_counter');
+ var unreadCount = parseInt($unreadCounter.html());
+ $unreadCounter.remove();
+ return unreadCount;
}
/**
- * Sets a node selected and removes the class from the previous node
- * @param node the node which should be set as selected
+ * Sets the unread count and handles the appropriate css
+ * classes
+ * @param unreadCount the count of unread items
*/
- Menu.prototype._setSelected = function(node){
- if(this._selectedNode !== undefined){
- this._selectedNode._$htmlElement.removeClass('selected');
+ Menu.prototype._setUnreadCount = function(type, id, unreadCount){
+ /**
+ if(unreadCount === 0){
+ this._$htmlElement.addClass('all_read');
+ }
+
+ if(this._unreadCount !== undefined && this._unreadCount === 0
+ && unreadCount > 0){
+ this._$htmlElement.removeClass('all_read hidden');
}
- node._$htmlElement.addClass('selected');
- this._selectedNode = this;
- // TODO: load feeds from server
+
+ this._unreadCount = unreadCount;
+ */
+ //this._unreadCount[type]
}
/**
@@ -439,8 +363,7 @@ var t = t || function(app, string){ return string; }; // mock translation for lo
* @param $elem the element that should be set droppable
*/
Menu.prototype._bindDroppable = function($elem){
- var root = this._getRoot();
-
+ var self = this;
$elem.droppable({
accept: '.feed',
hoverClass: 'dnd_over',
@@ -449,210 +372,68 @@ var t = t || function(app, string){ return string; }; // mock translation for lo
var $dropped = $(this);
var $dragged = $(ui.draggable);
- // fix object structure
var feedId = parseInt($dragged.data('id'));
- var feed = root.removeNode(News.MenuNodeType.Feed, feedId, false);
-
var folderId = parseInt($dropped.data('id'));
- var folder = root._findNode(News.MenuNodeType.Folder, folderId);
-
- folder._addChildNode(feed);
// to also be able to drop this on a folder entry or the top menu
// we have to check if we use a folder and append to a different
// item
- if($elem.hasClass('folder')){
+ if($dropped.hasClass(self._menuNodeTypeToClass(MenuNodeType.Folder))){
$dropped.children('ul').append($dragged[0]);
} else {
$dropped.append($dragged[0]);
}
-
- console.log('Moved elem with id ' + feedId + ' to folder with id ' + folderId);
- // TODO: notify server
+
+ self._resetOpenFolders();
+ self._moveItemToFolder(feedId, folderId);
}
});
}
- /*##########################################################################
- * MenuNode
- *#########################################################################/
- /**
- * Items which are in the menu
- * @param type the type of the node (MenuNodeType)
- * @param id the id of the node. id and type must be unique!
- * @param data the data array like {title: 'title', unreadCount: 1, icon: 'path/icon.png'}
- */
- MenuNode = function(type, id, data){
- this._type = type;
- this._id = id;
- this._$htmlElement = $('<li>');
- this._children = [];
- this.update(data);
- }
-
- MenuNode.prototype = Object.create(Menu.prototype);
- News.MenuNode = MenuNode;
-
- /**
- * Updates the given values of a node
- * @param data the data array like {title: 'title', unreadCount: 1, icon: 'path/icon.png'}
- */
- MenuNode.prototype.update = function(data){
- if(data.title !== undefined){
- this._title = data.title;
- this._$htmlElement.children('.title').html(data.title);
- }
-
- if(data.icon !== undefined){
- this._icon = data.icon;
- var iconCss = 'url("' + data.icon + '")';
- this._$htmlElement.css('background-image', iconCss);
- } else {
- // if undefined, we check for default icons
- this._icon = MenuNodeTypeDefaultIcon[this._type];
- }
-
- if(data.unreadCount !== undefined){
- this._setUnreadCount(data.unreadCount);
- }
+ Menu.prototype._moveItemToFolder = function(feedId, folderId){
+ // TODO
}
/**
- * This function creates the html of the node and its children
- * @return the html of the node and its children
+ * Elements should only be set as hidden if the user clicked on a new entry
+ * Then all all_read entries should be marked as hidden
+ * This function is used to hide all the read ones if showAll is false,
+ * otherwise shows all
*/
- MenuNode.prototype.render = function(){
- var self = this;
- var $elem = this._$htmlElement;
-
- var $title = $('<a>').addClass('title').html(this._title).attr('href', '#');
- $title.attr('title', t('news', 'Load feed'));
- $title.click(function(){
- self._click();
- });
-
- // buttons
- var $deleteButton = $('<button>').addClass('svg action feeds_delete');
- $deleteButton.attr('title', t('news', 'Delete'));
- $deleteButton.click(function(){
- self._deleteClick();
- });
-
- var $expandButton = $('<button>').addClass('action collapsable');
- $expandButton.attr('title', t('news', 'Expand/Collapse'));
- $expandButton.click(function(){
- self._expandClick();
- });
-
- var $editButton = $('<button>').addClass('svg action feeds_edit');
- $editButton.attr('title', t('news', 'Edit'));
- $editButton.click(function(){
- self._editClick();
- });
-
- // set the type class
- switch(this._type){
- case MenuNodeType.Feed:
- $elem.append($title);
- $elem.append($deleteButton);
- $elem.addClass('feed');
- // bind dragging
- $elem.draggable({
- revert: true,
- stack: '> li',
- zIndex: 1000,
- axis: 'y',
+ Menu.prototype.triggerHideRead = function(){
+ // only trigger in the root menu
+ if(this._parent === false){
+ if(this._showAll){
+ $(this._$htmlElement).find('.hidden').each(function(){
+ $(this).removeClass('hidden');
});
- $elem.data('id', this._id);
- break;
-
- case MenuNodeType.Folder:
- $elem.append($expandButton);
- $elem.append($title);
- $elem.append($editButton);
- $elem.append($deleteButton);
- $elem.addClass('folder');
- this._bindDroppable($elem);
- $elem.attr('data-id', this._id);
- break;
-
- case MenuNodeType.Starred:
- $elem.append($title);
- $elem.addClass('filter');
- break;
-
- default:
- break;
- }
-
- // recursively append children
- var $subList = $('<ul>');
- for(var i=0; i<this._children.length; i++){
- var node = this._children[i];
- $subList.append(node.render());
+ } else {
+ $(this._$htmlElement).find('.all_read').each(function(){
+ if(!$(this).hasClass('hidden')){
+ $(this).addClass('hidden');
+ }
+ });
+ }
+
}
- $elem.append($subList);
-
- return $elem;
- }
-
- /* #### private #### */
-
- /**
- * Handles every important change that has to be made if the link was
- * clicked
- */
- MenuNode.prototype._click = function(){
- this._setSelected(this);
}
/**
- * Handles every important change that has to be made if the delete button was
- * clicked
- */
- MenuNode.prototype._deleteClick = function(){
- // TODO: send delete event
- }
-
- /**
- * Handles every important change that has to be made if the edit button was
- * clicked
- */
- MenuNode.prototype._editClick = function(){
- // TODO: show edit window
- }
-
- /**
- * Handles every important change that has to be made if the expand button
- * was clicked
+ * Sets the showAll value
+ * @param showAll if true, all read folders and feeds are being shown
+ * if false only unread ones are shown
*/
- MenuNode.prototype._expandClick = function(){
- this._$htmlElement.children('.collapsable').toggleClass('triggered');
- var $subList = this._$htmlElement.children('ul');
- if($subList.length > 0){
- $subList.toggle();
- }
+ Menu.prototype.setShowAll = function(showAll){
+ this._showAll = showAll;
+ this.triggerHideRead();
}
/**
- * Sets the unread count and handles the appropriate css
- * classes
- * @param unreadCount the count of unread items
+ * Shortcut for toggling show all
*/
- MenuNode.prototype._setUnreadCount = function(unreadCount){
- unreadCount = parseInt(unreadCount);
-
- if(unreadCount === 0){
- this._$htmlElement.addClass('all_read');
- }
-
- if(this._unreadCount !== undefined && this._unreadCount === 0
- && unreadCount > 0){
- this._$htmlElement.removeClass('all_read hidden');
- }
-
- this._unreadCount = unreadCount;
+ Menu.prototype.toggleShowAll = function(){
+ this.setShowAll(!this._showAll);
}