diff options
author | Bernhard Posselt <nukeawhale@gmail.com> | 2012-08-31 22:49:07 +0200 |
---|---|---|
committer | Bernhard Posselt <nukeawhale@gmail.com> | 2012-09-01 00:31:39 +0200 |
commit | 62deb561f12ee034435d3ba61353fc2fa8f87d1c (patch) | |
tree | 7e84d00f6fd9b6f98ecdb2fa27bf28e207435011 /js/items.js | |
parent | 2477d7e8e186ef8273b5186381d7165820b58ac3 (diff) |
implemented clientside caching of items
Diffstat (limited to 'js/items.js')
-rw-r--r-- | js/items.js | 355 |
1 files changed, 268 insertions, 87 deletions
diff --git a/js/items.js b/js/items.js index b3bd46ca3..3d86d97e3 100644 --- a/js/items.js +++ b/js/items.js @@ -18,20 +18,62 @@ var t = t || function(app, string){ return string; }; // mock translation for lo (function(){ + /*########################################################################## + * Items + *########################################################################*/ /** * Creates a new item instance and tells it to put the items into * the selected div * @param cssSelector the selector of the div which holds the ul for the feeds */ var Items = function(cssSelector){ + var self = this; this._$articleList = $(cssSelector); this._$articleList.scrollTop(0); + this._itemCache = new ItemCache(); + // this array is used to store ids to prevent sending too // many posts when scrolling. the structure is: feed_id: boolean this._processing = {}; + + // mark items whose title was hid under the top edge as read + // when the bottom is reached, mark all items as read + this._$articleList.scroll(function(){ + var boxHeight = $(this).height(); + var scrollHeight = $(this).prop('scrollHeight'); + var scrolled = $(this).scrollTop() + boxHeight; + $(this).children('ul').children('.feed_item:not(.read)').each(function(){ + var item = this; + var itemOffset = $(this).position().top; + if(itemOffset <= 0 || scrolled >= scrollHeight){ + // wait and check if the item is still under the top edge + setTimeout(function(){ self._markItemAsReadTimeout(item);}, 1000); + } + }) + }); } /** + * Marks an item as read which is called by the timeout + * @param item the dom item + */ + Items.prototype._markItemAsReadTimeout = function(item) { + var itemId = parseInt($(item).data('id')); + var itemOffset = $(item).position().top; + var boxHeight = this._$articleList.height(); + var scrollHeight = this._$articleList.prop('scrollHeight'); + var scrolled = this._$articleList.scrollTop() + boxHeight; + if(itemOffset < 0 || scrolled >= scrollHeight){ + if(this._processing[itemId] === undefined || this._processing[itemId] === false){ + // mark item as processing to prevent unecessary post requests + this._processing[itemId] = true; + var handler = new News.ItemStatusHandler(itemId); + handler.setRead(true); + } + } + }; + + /** * Loads the feeds into the righ view * @param type the type (MenuNodeType) * @param id the id @@ -41,7 +83,8 @@ var t = t || function(app, string){ return string; }; // mock translation for lo var self = this; var data = { feedId: id, - feedType: type + feedType: type, + getMostRecentItemId: this._itemCache.getMostRecentItemId(type, id) }; this._$articleList.addClass('loading'); @@ -49,9 +92,11 @@ var t = t || function(app, string){ return string; }; // mock translation for lo $.post(OC.filePath('news', 'ajax', 'loadfeed.php'), data, function(jsonData) { if(jsonData.status == 'success'){ - // FIXME: caching? split whole html into single articles - self._$articleList.empty().html(jsonData.data.feedItems) - self._bindItemEventListeners(); + self._$articleList.empty() + self._itemCache.populate(jsonData.data.feedItems); + + var $items = self._itemCache.getFeedHtml(type, id); + self._$articleList.append($items); self._$articleList.scrollTop(0); onSuccessCallback(); } else { @@ -62,13 +107,227 @@ var t = t || function(app, string){ return string; }; // mock translation for lo }); }; + /** + * Returns the most recent id of a feed from the cache + * @param type the type (MenuNodeType) + * @param id the id + * @return the most recent id that is loaded on the page or 0 + */ + Items.prototype.getMostRecentItemId = function(type, id) { + return this._itemCache.getMostRecentItemId(type, id); + }; + + /** + * Returns a jquery node by searching for its id + * @param id the id of the node + * @return the jquery node + */ + Items.prototype._findNodeById = function(id) { + id = parseInt(id); + return this._$articleList.find('.feed_item[data-id="' + id + '"]'); + }; + + + Items.prototype._toggleImportant = function(itemId) { + var $currentItem = $() + var $currentItemStar = $currentItem.children('.utils').children('.primary_item_utils').children('.star'); + var important = $currentItemStar.hasClass('important'); + if(_important){ + status = 'unimportant'; + } else { + status = 'important'; + } + + var data = { + itemId: _itemId, + status: status + }; + + $.post(OC.filePath('news', 'ajax', 'setitemstatus.php'), data, function(jsondata){ + if(jsondata.status == 'success'){ + if(_important){ + _$currentItemStar.removeClass('important'); + } else { + _$currentItemStar.addClass('important'); + } + } else{ + OC.dialogs.alert(jsondata.data.message, t('news', 'Error')); + } + }); + }; + + News.Items = Items; + + + /*########################################################################## + * ItemCache + *########################################################################*/ + /** + * A cache which holds the items of all loaded feeds + */ + var ItemCache = function() { + this._items = {}; + this._feeds = {}; + } + + /** + * Adds Html elements to the cache + * @param html the html for a complete list with items + */ + ItemCache.prototype.populate = function(html) { + var self = this; + $html = $(html); + $html.children('.feed_item').each(function(){ + var item = new Item(this); + self._items[item.getId()] = item; + self._feeds[item.getFeedId()] = self._feeds[item.getFeedId()] || {}; + self._feeds[item.getFeedId()][item.getId()] = item; + }); + }; + + /** + * Returns all the ids of feeds for a type sorted by id ascending + * @param type the type (MenuNodeType) + * @param id the id + * @return all the ids of feeds for a type sorted by id ascending + */ + ItemCache.prototype._getSortedItemIds = function(type, id) { + var itemIds = new Array(); + if(Object.keys(this._feeds).length === 0 || Object.keys(this._items).length === 0){ + return itemIds; + } + + switch(type){ + + case MenuNodeType.Feed: + if(this._feeds[id] === undefined){ + return itemIds; + } + $.each(this._feeds[id], function(key, value){ + itemIds.push(value.getId()); + }); + break; + + case MenuNodeType.Folder: + // this is a bit of a hack and not that beautiful^^ + var feedIds = News.Objects.Menu.getFeedIdsOfFolder(id); + for(var i=0; i<feedIds.length; i++){ + itemIds.concat(this._getSortedItemIds(MenuNodeType.Feed, feedIds[i])); + } + break; + + case MenuNodeType.Subscriptions: + $.each(this._items, function(key, value){ + itemIds.push(value.getId()); + }); + break; + + case MenuNodeType.Starred: + $.each(this._items, function(key, value){ + if(value.isStarred()){ + itemIds.push(value.getId()); + } + }); + break; + } + return itemIds.sort(); + }; - // FIXME + /** + * Returns the most recent id of a feed + * @param type the type (MenuNodeType) + * @param id the id + * @return the most recent id that is loaded on the page or 0 + */ + ItemCache.prototype.getMostRecentItemId = function(type, id) { + var itemIds = this._getSortedItemIds(type, id); + if(itemIds.length === 0){ + return 0; + } else { + return itemIds[itemIds.length-1]; + } + }; + + /** + * Returns the html for a specific feed + * @param type the type (MenuNodeType) + * @param id the id + * @return the jquery html element for a complete feed + */ + ItemCache.prototype.getFeedHtml = function(type, id) { + var itemIds = this._getSortedItemIds(type, id); + itemIds.reverse(); // reverse for showing newest item first + var $html = $('<ul>'); + for(var i=0; i<itemIds.length; i++){ + $html.append(this._items[itemIds[i]].getHtml()); + } + return $html; + }; + + /*########################################################################## + * Item + *########################################################################*/ + /** + * An item which binds the appropriate html and event handlers + * @param html the html to populate the item + */ + var Item = function(html){ + this._starred = false; + this._$html = $(html); + this._bindItemEventListeners(); + this._id = parseInt(this._$html.data('id')); + this._feedId = parseInt(this._$html.data('feedid')); + } + + /** + * Returns the html code for the element + * @return the html for the item + */ + Item.prototype.render = function() { + return this._$html[0]; + }; + + /** + * Returns the id of an item + * @return the id of the item + */ + Item.prototype.getId = function() { + return this._id; + }; + + + /** + * Returns the feedid of an item + * @return the feeid of the item + */ + Item.prototype.getFeedId = function() { + return this._feedId; + }; + + /** + * Returns the html of an item + * @return the jquery html of the item + */ + Item.prototype.getHtml = function() { + return this._$html; + }; + + /** + * Returns true if an item is starred + * @return true if starred, otherwise false + */ + Item.prototype.isStarred = function() { + return this._starred; + }; + + // FIXME /** * Binds a listener on the feed item list to detect scrolling and mark previous * items as read */ - Items.prototype._bindItemEventListeners = function() { + Item.prototype._bindItemEventListeners = function() { + var self = this; + // single hover on item should mark it as read too $('#feed_items h1.item_title a').click(function(){ var $item = $(this).parent().parent('.feed_item'); @@ -89,8 +348,7 @@ var t = t || function(app, string){ return string; }; // mock translation for lo $('#feed_items li.star').click(function(){ var $item = $(this).parent().parent().parent('.feed_item'); var itemId = $item.data('id'); - var handler = new News.ItemStatusHandler(itemId); - handler.toggleImportant(); + self._toggleImportant(itemId); }); // toggle logic for the keep unread handler @@ -117,16 +375,6 @@ var t = t || function(app, string){ return string; }; // mock translation for lo $("time.timeago").timeago(); }; - - Items.prototype.length = function(type, id) { - return 0; - }; - - Items.prototype.mostRecentItemId = function(type, id) { - return 0; - }; - - News.Items = Items; })(); @@ -134,35 +382,12 @@ var t = t || function(app, string){ return string; }; // mock translation for lo // TODO: integrate This - - - - /** - * Marks an item as read which is called by the timeout - * @param item the dom item - */ - function markItemAsRead(scrollArea, item){ - var itemId = parseInt($(item).data('id')); - var itemOffset = $(item).position().top; - var boxHeight = $(scrollArea).height(); - var scrollHeight = $(scrollArea).prop('scrollHeight'); - var scrolled = $(scrollArea).scrollTop() + boxHeight; - if(itemOffset < 0 || scrolled >= scrollHeight){ - if(News.Feed.processing[itemId] === undefined || News.Feed.processing[itemId] === false){ - // mark item as processing to prevent unecessary post requests - News.Feed.processing[itemId] = true; - var handler = new News.ItemStatusHandler(itemId); - handler.setRead(true); - } - } - } - /** * This handler handles changes in the ui when the itemstatus changes */ - ItemStatusHandler = function(itemId){ + News.ItemStatusHandler = function(itemId){ var _itemId = parseInt(itemId); - var _$currentItem = $('#feed_items li[data-id="' + itemId + '"]'); + var _feedId = _$currentItem.data('feedid'); var _read = _$currentItem.hasClass('read'); @@ -170,30 +395,7 @@ var t = t || function(app, string){ return string; }; // mock translation for lo * Switches important items to unimportant and vice versa */ var _toggleImportant = function(){ - var _$currentItemStar = _$currentItem.children('.utils').children('.primary_item_utils').children('.star'); - var _important = _$currentItemStar.hasClass('important'); - if(_important){ - status = 'unimportant'; - } else { - status = 'important'; - } - var data = { - itemId: _itemId, - status: status - }; - - $.post(OC.filePath('news', 'ajax', 'setitemstatus.php'), data, function(jsondata){ - if(jsondata.status == 'success'){ - if(_important){ - _$currentItemStar.removeClass('important'); - } else { - _$currentItemStar.addClass('important'); - } - } else{ - OC.dialogs.alert(jsondata.data.message, t('news', 'Error')); - } - }); }; /** @@ -274,26 +476,5 @@ var t = t || function(app, string){ return string; }; // mock translation for lo this.toggleKeepUnread = function(){ _toggleKeepUnread(); }; }; - // mark items whose title was hid under the top edge as read - // when the bottom is reached, mark all items as read - $('#feed_items').scroll(function(){ - var boxHeight = $(this).height(); - var scrollHeight = $(this).prop('scrollHeight'); - var scrolled = $(this).scrollTop() + boxHeight; - var scrollArea = this; - $(this).children('ul').children('.feed_item:not(.read)').each(function(){ - var item = this; - var itemOffset = $(this).position().top; - if(itemOffset <= 0 || scrolled >= scrollHeight){ - // wait and check if the item is still under the top edge - setTimeout(function(){ markItemAsRead(scrollArea, item);}, 1000); - } - }) - }); - $(document).keydown(function(e) { - if ((e.keyCode || e.which) == 74) { // 'j' key shortcut - - } - });
\ No newline at end of file |