browser/metro/base/content/startui/BookmarksView.js

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 /**
michael@0 8 * Wraps a list/grid control implementing nsIDOMXULSelectControlElement and
michael@0 9 * fills it with the user's bookmarks.
michael@0 10 *
michael@0 11 * @param aSet Control implementing nsIDOMXULSelectControlElement.
michael@0 12 * @param aRoot Bookmark root to show in the view.
michael@0 13 */
michael@0 14 function BookmarksView(aSet, aRoot, aFilterUnpinned) {
michael@0 15 View.call(this, aSet);
michael@0 16
michael@0 17 this._inBatch = false; // batch up grid updates to avoid redundant arrangeItems calls
michael@0 18
michael@0 19 // View monitors this for maximum tile display counts
michael@0 20 this.tilePrefName = "browser.display.startUI.bookmarks.maxresults";
michael@0 21 this.showing = this.maxTiles > 0;
michael@0 22
michael@0 23 this._filterUnpinned = aFilterUnpinned;
michael@0 24 this._bookmarkService = PlacesUtils.bookmarks;
michael@0 25 this._navHistoryService = gHistSvc;
michael@0 26
michael@0 27 this._changes = new BookmarkChangeListener(this);
michael@0 28 this._pinHelper = new ItemPinHelper("metro.bookmarks.unpinned");
michael@0 29 this._bookmarkService.addObserver(this._changes, false);
michael@0 30 StartUI.chromeWin.addEventListener('MozAppbarDismissing', this, false);
michael@0 31 StartUI.chromeWin.addEventListener('BookmarksNeedsRefresh', this, false);
michael@0 32 window.addEventListener("TabClose", this, true);
michael@0 33
michael@0 34 this.root = aRoot;
michael@0 35 }
michael@0 36
michael@0 37 BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
michael@0 38 _set: null,
michael@0 39 _changes: null,
michael@0 40 _root: null,
michael@0 41 _sort: 0, // Natural bookmark order.
michael@0 42 _toRemove: null,
michael@0 43
michael@0 44 // For View's showing property
michael@0 45 get vbox() {
michael@0 46 return document.getElementById("start-bookmarks");
michael@0 47 },
michael@0 48
michael@0 49 get sort() {
michael@0 50 return this._sort;
michael@0 51 },
michael@0 52
michael@0 53 set sort(aSort) {
michael@0 54 this._sort = aSort;
michael@0 55 this.clearBookmarks();
michael@0 56 this.getBookmarks();
michael@0 57 },
michael@0 58
michael@0 59 get root() {
michael@0 60 return this._root;
michael@0 61 },
michael@0 62
michael@0 63 set root(aRoot) {
michael@0 64 this._root = aRoot;
michael@0 65 },
michael@0 66
michael@0 67 destruct: function bv_destruct() {
michael@0 68 this._bookmarkService.removeObserver(this._changes);
michael@0 69 if (StartUI.chromeWin) {
michael@0 70 StartUI.chromeWin.removeEventListener('MozAppbarDismissing', this, false);
michael@0 71 StartUI.chromeWin.removeEventListener('BookmarksNeedsRefresh', this, false);
michael@0 72 }
michael@0 73 View.prototype.destruct.call(this);
michael@0 74 },
michael@0 75
michael@0 76 refreshView: function () {
michael@0 77 this.clearBookmarks();
michael@0 78 this.getBookmarks();
michael@0 79 },
michael@0 80
michael@0 81 handleItemClick: function bv_handleItemClick(aItem) {
michael@0 82 let url = aItem.getAttribute("value");
michael@0 83 StartUI.goToURI(url);
michael@0 84 },
michael@0 85
michael@0 86 _getItemForBookmarkId: function bv__getItemForBookmark(aBookmarkId) {
michael@0 87 return this._set.querySelector("richgriditem[anonid='" + aBookmarkId + "']");
michael@0 88 },
michael@0 89
michael@0 90 _getBookmarkIdForItem: function bv__getBookmarkForItem(aItem) {
michael@0 91 return +aItem.getAttribute("anonid");
michael@0 92 },
michael@0 93
michael@0 94 _updateItemWithAttrs: function dv__updateItemWithAttrs(anItem, aAttrs) {
michael@0 95 for (let name in aAttrs)
michael@0 96 anItem.setAttribute(name, aAttrs[name]);
michael@0 97 },
michael@0 98
michael@0 99 getBookmarks: function bv_getBookmarks(aRefresh) {
michael@0 100 let options = this._navHistoryService.getNewQueryOptions();
michael@0 101 options.queryType = options.QUERY_TYPE_BOOKMARKS;
michael@0 102 options.excludeQueries = true; // Don't include "smart folders"
michael@0 103 options.sortingMode = this._sort;
michael@0 104
michael@0 105 let limit = this.maxTiles;
michael@0 106
michael@0 107 let query = this._navHistoryService.getNewQuery();
michael@0 108 query.setFolders([Bookmarks.metroRoot], 1);
michael@0 109
michael@0 110 let result = this._navHistoryService.executeQuery(query, options);
michael@0 111 let rootNode = result.root;
michael@0 112 rootNode.containerOpen = true;
michael@0 113 let childCount = rootNode.childCount;
michael@0 114
michael@0 115 this._inBatch = true; // batch up grid updates to avoid redundant arrangeItems calls
michael@0 116
michael@0 117 for (let i = 0, addedCount = 0; i < childCount && addedCount < limit; i++) {
michael@0 118 let node = rootNode.getChild(i);
michael@0 119
michael@0 120 // Ignore folders, separators, undefined item types, etc.
michael@0 121 if (node.type != node.RESULT_TYPE_URI)
michael@0 122 continue;
michael@0 123
michael@0 124 // If item is marked for deletion, skip it.
michael@0 125 if (this._toRemove && this._toRemove.indexOf(node.itemId) !== -1)
michael@0 126 continue;
michael@0 127
michael@0 128 let item = this._getItemForBookmarkId(node.itemId);
michael@0 129
michael@0 130 // Item has been unpinned.
michael@0 131 if (this._filterUnpinned && !this._pinHelper.isPinned(node.itemId)) {
michael@0 132 if (item)
michael@0 133 this.removeBookmark(node.itemId);
michael@0 134
michael@0 135 continue;
michael@0 136 }
michael@0 137
michael@0 138 if (!aRefresh || !item) {
michael@0 139 // If we're not refreshing or the item is not in the grid, add it.
michael@0 140 this.addBookmark(node.itemId, addedCount);
michael@0 141 } else if (aRefresh && item) {
michael@0 142 // Update context action in case it changed in another view.
michael@0 143 this._setContextActions(item);
michael@0 144 }
michael@0 145
michael@0 146 addedCount++;
michael@0 147 }
michael@0 148
michael@0 149 // Remove extra items in case a refresh added more than the limit.
michael@0 150 // This can happen when undoing a delete.
michael@0 151 if (aRefresh) {
michael@0 152 while (this._set.itemCount > limit)
michael@0 153 this._set.removeItemAt(this._set.itemCount - 1, true);
michael@0 154 }
michael@0 155 this._set.arrangeItems();
michael@0 156 this._inBatch = false;
michael@0 157 rootNode.containerOpen = false;
michael@0 158 },
michael@0 159
michael@0 160 inCurrentView: function bv_inCurrentView(aParentId, aItemId) {
michael@0 161 if (this._root && aParentId != this._root)
michael@0 162 return false;
michael@0 163
michael@0 164 return !!this._getItemForBookmarkId(aItemId);
michael@0 165 },
michael@0 166
michael@0 167 clearBookmarks: function bv_clearBookmarks() {
michael@0 168 if ('clearAll' in this._set)
michael@0 169 this._set.clearAll();
michael@0 170 },
michael@0 171
michael@0 172 addBookmark: function bv_addBookmark(aBookmarkId, aPos) {
michael@0 173 let index = this._bookmarkService.getItemIndex(aBookmarkId);
michael@0 174 let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
michael@0 175 let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
michael@0 176 let item = this._set.insertItemAt(aPos || index, title, uri.spec, this._inBatch);
michael@0 177 item.setAttribute("anonid", aBookmarkId);
michael@0 178 this._setContextActions(item);
michael@0 179 this._updateFavicon(item, uri);
michael@0 180 },
michael@0 181
michael@0 182 _setContextActions: function bv__setContextActions(aItem) {
michael@0 183 let itemId = this._getBookmarkIdForItem(aItem);
michael@0 184 aItem.setAttribute("data-contextactions", "delete," + (this._pinHelper.isPinned(itemId) ? "hide" : "pin"));
michael@0 185 if (aItem.refresh) aItem.refresh();
michael@0 186 },
michael@0 187
michael@0 188 _sendNeedsRefresh: function bv__sendNeedsRefresh(){
michael@0 189 // Event sent when all view instances need to refresh.
michael@0 190 let event = document.createEvent("Events");
michael@0 191 event.initEvent("BookmarksNeedsRefresh", true, false);
michael@0 192 window.dispatchEvent(event);
michael@0 193 },
michael@0 194
michael@0 195 updateBookmark: function bv_updateBookmark(aBookmarkId) {
michael@0 196 let item = this._getItemForBookmarkId(aBookmarkId);
michael@0 197
michael@0 198 if (!item)
michael@0 199 return;
michael@0 200
michael@0 201 let oldIndex = this._set.getIndexOfItem(item);
michael@0 202 let index = this._bookmarkService.getItemIndex(aBookmarkId);
michael@0 203
michael@0 204 if (oldIndex != index) {
michael@0 205 this.removeBookmark(aBookmarkId);
michael@0 206 this.addBookmark(aBookmarkId);
michael@0 207 return;
michael@0 208 }
michael@0 209
michael@0 210 let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
michael@0 211 let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
michael@0 212
michael@0 213 item.setAttribute("anonid", aBookmarkId);
michael@0 214 item.setAttribute("value", uri.spec);
michael@0 215 item.setAttribute("label", title);
michael@0 216
michael@0 217 this._updateFavicon(item, uri);
michael@0 218 },
michael@0 219
michael@0 220 removeBookmark: function bv_removeBookmark(aBookmarkId) {
michael@0 221 let item = this._getItemForBookmarkId(aBookmarkId);
michael@0 222 let index = this._set.getIndexOfItem(item);
michael@0 223 this._set.removeItemAt(index, this._inBatch);
michael@0 224 },
michael@0 225
michael@0 226 doActionOnSelectedTiles: function bv_doActionOnSelectedTiles(aActionName, aEvent) {
michael@0 227 let tileGroup = this._set;
michael@0 228 let selectedTiles = tileGroup.selectedItems;
michael@0 229
michael@0 230 switch (aActionName){
michael@0 231 case "delete":
michael@0 232 Array.forEach(selectedTiles, function(aNode) {
michael@0 233 if (!this._toRemove) {
michael@0 234 this._toRemove = [];
michael@0 235 }
michael@0 236
michael@0 237 let itemId = this._getBookmarkIdForItem(aNode);
michael@0 238
michael@0 239 this._toRemove.push(itemId);
michael@0 240 this.removeBookmark(itemId);
michael@0 241 }, this);
michael@0 242
michael@0 243 // stop the appbar from dismissing
michael@0 244 aEvent.preventDefault();
michael@0 245
michael@0 246 // at next tick, re-populate the context appbar.
michael@0 247 setTimeout(function(){
michael@0 248 // fire a MozContextActionsChange event to update the context appbar
michael@0 249 let event = document.createEvent("Events");
michael@0 250 // we need the restore button to show (the tile node will go away though)
michael@0 251 event.actions = ["restore"];
michael@0 252 event.initEvent("MozContextActionsChange", true, false);
michael@0 253 tileGroup.dispatchEvent(event);
michael@0 254 }, 0);
michael@0 255 break;
michael@0 256
michael@0 257 case "restore":
michael@0 258 // clear toRemove and let _sendNeedsRefresh update the items.
michael@0 259 this._toRemove = null;
michael@0 260 break;
michael@0 261
michael@0 262 case "unpin":
michael@0 263 Array.forEach(selectedTiles, function(aNode) {
michael@0 264 let itemId = this._getBookmarkIdForItem(aNode);
michael@0 265
michael@0 266 if (this._filterUnpinned)
michael@0 267 this.removeBookmark(itemId);
michael@0 268
michael@0 269 this._pinHelper.setUnpinned(itemId);
michael@0 270 }, this);
michael@0 271 break;
michael@0 272
michael@0 273 case "pin":
michael@0 274 Array.forEach(selectedTiles, function(aNode) {
michael@0 275 let itemId = this._getBookmarkIdForItem(aNode);
michael@0 276
michael@0 277 this._pinHelper.setPinned(itemId);
michael@0 278 }, this);
michael@0 279 break;
michael@0 280
michael@0 281 default:
michael@0 282 return;
michael@0 283 }
michael@0 284
michael@0 285 // Send refresh event so all view are in sync.
michael@0 286 this._sendNeedsRefresh();
michael@0 287 },
michael@0 288
michael@0 289 handleEvent: function bv_handleEvent(aEvent) {
michael@0 290 switch (aEvent.type){
michael@0 291 case "MozAppbarDismissing":
michael@0 292 // If undo wasn't pressed, time to do definitive actions.
michael@0 293 if (this._toRemove) {
michael@0 294 for (let bookmarkId of this._toRemove) {
michael@0 295 this._bookmarkService.removeItem(bookmarkId);
michael@0 296 }
michael@0 297 this._toRemove = null;
michael@0 298 }
michael@0 299 break;
michael@0 300
michael@0 301 case "BookmarksNeedsRefresh":
michael@0 302 this.getBookmarks(true);
michael@0 303 break;
michael@0 304
michael@0 305 case "TabClose":
michael@0 306 // Flush any pending actions - appbar will call us back
michael@0 307 // before this returns with 'MozAppbarDismissing' above.
michael@0 308 StartUI.chromeWin.ContextUI.dismissContextAppbar();
michael@0 309 break;
michael@0 310 }
michael@0 311 }
michael@0 312 });
michael@0 313
michael@0 314 let BookmarksStartView = {
michael@0 315 _view: null,
michael@0 316 get _grid() { return document.getElementById("start-bookmarks-grid"); },
michael@0 317
michael@0 318 init: function init() {
michael@0 319 this._view = new BookmarksView(this._grid, Bookmarks.metroRoot, true);
michael@0 320 this._view.getBookmarks();
michael@0 321 this._grid.removeAttribute("fade");
michael@0 322 },
michael@0 323
michael@0 324 uninit: function uninit() {
michael@0 325 if (this._view) {
michael@0 326 this._view.destruct();
michael@0 327 }
michael@0 328 },
michael@0 329 };
michael@0 330
michael@0 331 /**
michael@0 332 * Observes bookmark changes and keeps a linked BookmarksView updated.
michael@0 333 *
michael@0 334 * @param aView An instance of BookmarksView.
michael@0 335 */
michael@0 336 function BookmarkChangeListener(aView) {
michael@0 337 this._view = aView;
michael@0 338 }
michael@0 339
michael@0 340 BookmarkChangeListener.prototype = {
michael@0 341 //////////////////////////////////////////////////////////////////////////////
michael@0 342 //// nsINavBookmarkObserver
michael@0 343 onBeginUpdateBatch: function () { },
michael@0 344 onEndUpdateBatch: function () { },
michael@0 345
michael@0 346 onItemAdded: function bCL_onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded, aGUID, aParentGUID) {
michael@0 347 this._view.getBookmarks(true);
michael@0 348 },
michael@0 349
michael@0 350 onItemChanged: function bCL_onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aNewValue, aLastModified, aItemType, aParentId, aGUID, aParentGUID) {
michael@0 351 let itemIndex = PlacesUtils.bookmarks.getItemIndex(aItemId);
michael@0 352 if (!this._view.inCurrentView(aParentId, aItemId))
michael@0 353 return;
michael@0 354
michael@0 355 this._view.updateBookmark(aItemId);
michael@0 356 },
michael@0 357
michael@0 358 onItemMoved: function bCL_onItemMoved(aItemId, aOldParentId, aOldIndex, aNewParentId, aNewIndex, aItemType, aGUID, aOldParentGUID, aNewParentGUID) {
michael@0 359 let wasInView = this._view.inCurrentView(aOldParentId, aItemId);
michael@0 360 let nowInView = this._view.inCurrentView(aNewParentId, aItemId);
michael@0 361
michael@0 362 if (!wasInView && nowInView)
michael@0 363 this._view.addBookmark(aItemId);
michael@0 364
michael@0 365 if (wasInView && !nowInView)
michael@0 366 this._view.removeBookmark(aItemId);
michael@0 367
michael@0 368 this._view.getBookmarks(true);
michael@0 369 },
michael@0 370
michael@0 371 onBeforeItemRemoved: function (aItemId, aItemType, aParentId, aGUID, aParentGUID) { },
michael@0 372 onItemRemoved: function bCL_onItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID, aParentGUID) {
michael@0 373 if (!this._view.inCurrentView(aParentId, aItemId))
michael@0 374 return;
michael@0 375
michael@0 376 this._view.removeBookmark(aItemId);
michael@0 377 this._view.getBookmarks(true);
michael@0 378 },
michael@0 379
michael@0 380 onItemVisited: function(aItemId, aVisitId, aTime, aTransitionType, aURI, aParentId, aGUID, aParentGUID) { },
michael@0 381
michael@0 382 //////////////////////////////////////////////////////////////////////////////
michael@0 383 //// nsISupports
michael@0 384 QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
michael@0 385 };

mercurial