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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/metro/base/content/startui/BookmarksView.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,385 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +/**
    1.11 + * Wraps a list/grid control implementing nsIDOMXULSelectControlElement and
    1.12 + * fills it with the user's bookmarks.
    1.13 + *
    1.14 + * @param           aSet    Control implementing nsIDOMXULSelectControlElement.
    1.15 + * @param           aRoot   Bookmark root to show in the view.
    1.16 + */
    1.17 +function BookmarksView(aSet, aRoot, aFilterUnpinned) {
    1.18 +  View.call(this, aSet);
    1.19 +
    1.20 +  this._inBatch = false; // batch up grid updates to avoid redundant arrangeItems calls
    1.21 +
    1.22 +  // View monitors this for maximum tile display counts
    1.23 +  this.tilePrefName = "browser.display.startUI.bookmarks.maxresults";
    1.24 +  this.showing = this.maxTiles > 0;
    1.25 +
    1.26 +  this._filterUnpinned = aFilterUnpinned;
    1.27 +  this._bookmarkService = PlacesUtils.bookmarks;
    1.28 +  this._navHistoryService = gHistSvc;
    1.29 +
    1.30 +  this._changes = new BookmarkChangeListener(this);
    1.31 +  this._pinHelper = new ItemPinHelper("metro.bookmarks.unpinned");
    1.32 +  this._bookmarkService.addObserver(this._changes, false);
    1.33 +  StartUI.chromeWin.addEventListener('MozAppbarDismissing', this, false);
    1.34 +  StartUI.chromeWin.addEventListener('BookmarksNeedsRefresh', this, false);
    1.35 +  window.addEventListener("TabClose", this, true);
    1.36 +
    1.37 +  this.root = aRoot;
    1.38 +}
    1.39 +
    1.40 +BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
    1.41 +  _set: null,
    1.42 +  _changes: null,
    1.43 +  _root: null,
    1.44 +  _sort: 0, // Natural bookmark order.
    1.45 +  _toRemove: null,
    1.46 +
    1.47 +  // For View's showing property
    1.48 +  get vbox() {
    1.49 +    return document.getElementById("start-bookmarks");
    1.50 +  },
    1.51 +
    1.52 +  get sort() {
    1.53 +    return this._sort;
    1.54 +  },
    1.55 +
    1.56 +  set sort(aSort) {
    1.57 +    this._sort = aSort;
    1.58 +    this.clearBookmarks();
    1.59 +    this.getBookmarks();
    1.60 +  },
    1.61 +
    1.62 +  get root() {
    1.63 +    return this._root;
    1.64 +  },
    1.65 +
    1.66 +  set root(aRoot) {
    1.67 +    this._root = aRoot;
    1.68 +  },
    1.69 +
    1.70 +  destruct: function bv_destruct() {
    1.71 +    this._bookmarkService.removeObserver(this._changes);
    1.72 +    if (StartUI.chromeWin) {
    1.73 +      StartUI.chromeWin.removeEventListener('MozAppbarDismissing', this, false);
    1.74 +      StartUI.chromeWin.removeEventListener('BookmarksNeedsRefresh', this, false);
    1.75 +    }
    1.76 +    View.prototype.destruct.call(this);
    1.77 +  },
    1.78 +
    1.79 +  refreshView: function () {
    1.80 +    this.clearBookmarks();
    1.81 +    this.getBookmarks();
    1.82 +  },
    1.83 +
    1.84 +  handleItemClick: function bv_handleItemClick(aItem) {
    1.85 +    let url = aItem.getAttribute("value");
    1.86 +    StartUI.goToURI(url);
    1.87 +  },
    1.88 +
    1.89 +  _getItemForBookmarkId: function bv__getItemForBookmark(aBookmarkId) {
    1.90 +    return this._set.querySelector("richgriditem[anonid='" + aBookmarkId + "']");
    1.91 +  },
    1.92 +
    1.93 +  _getBookmarkIdForItem: function bv__getBookmarkForItem(aItem) {
    1.94 +    return +aItem.getAttribute("anonid");
    1.95 +  },
    1.96 +
    1.97 +  _updateItemWithAttrs: function dv__updateItemWithAttrs(anItem, aAttrs) {
    1.98 +    for (let name in aAttrs)
    1.99 +      anItem.setAttribute(name, aAttrs[name]);
   1.100 +  },
   1.101 +
   1.102 +  getBookmarks: function bv_getBookmarks(aRefresh) {
   1.103 +    let options = this._navHistoryService.getNewQueryOptions();
   1.104 +    options.queryType = options.QUERY_TYPE_BOOKMARKS;
   1.105 +    options.excludeQueries = true; // Don't include "smart folders"
   1.106 +    options.sortingMode = this._sort;
   1.107 +
   1.108 +    let limit = this.maxTiles;
   1.109 +
   1.110 +    let query = this._navHistoryService.getNewQuery();
   1.111 +    query.setFolders([Bookmarks.metroRoot], 1);
   1.112 +
   1.113 +    let result = this._navHistoryService.executeQuery(query, options);
   1.114 +    let rootNode = result.root;
   1.115 +    rootNode.containerOpen = true;
   1.116 +    let childCount = rootNode.childCount;
   1.117 +
   1.118 +    this._inBatch = true; // batch up grid updates to avoid redundant arrangeItems calls
   1.119 +
   1.120 +    for (let i = 0, addedCount = 0; i < childCount && addedCount < limit; i++) {
   1.121 +      let node = rootNode.getChild(i);
   1.122 +
   1.123 +      // Ignore folders, separators, undefined item types, etc.
   1.124 +      if (node.type != node.RESULT_TYPE_URI)
   1.125 +        continue;
   1.126 +
   1.127 +      // If item is marked for deletion, skip it.
   1.128 +      if (this._toRemove && this._toRemove.indexOf(node.itemId) !== -1)
   1.129 +        continue;
   1.130 +
   1.131 +      let item = this._getItemForBookmarkId(node.itemId);
   1.132 +
   1.133 +      // Item has been unpinned.
   1.134 +      if (this._filterUnpinned && !this._pinHelper.isPinned(node.itemId)) {
   1.135 +        if (item)
   1.136 +          this.removeBookmark(node.itemId);
   1.137 +
   1.138 +        continue;
   1.139 +      }
   1.140 +
   1.141 +      if (!aRefresh || !item) {
   1.142 +        // If we're not refreshing or the item is not in the grid, add it.
   1.143 +        this.addBookmark(node.itemId, addedCount);
   1.144 +      } else if (aRefresh && item) {
   1.145 +        // Update context action in case it changed in another view.
   1.146 +        this._setContextActions(item);
   1.147 +      }
   1.148 +
   1.149 +      addedCount++;
   1.150 +    }
   1.151 +
   1.152 +    // Remove extra items in case a refresh added more than the limit.
   1.153 +    // This can happen when undoing a delete.
   1.154 +    if (aRefresh) {
   1.155 +      while (this._set.itemCount > limit)
   1.156 +        this._set.removeItemAt(this._set.itemCount - 1, true);
   1.157 +    }
   1.158 +    this._set.arrangeItems();
   1.159 +    this._inBatch = false;
   1.160 +    rootNode.containerOpen = false;
   1.161 +  },
   1.162 +
   1.163 +  inCurrentView: function bv_inCurrentView(aParentId, aItemId) {
   1.164 +    if (this._root && aParentId != this._root)
   1.165 +      return false;
   1.166 +
   1.167 +    return !!this._getItemForBookmarkId(aItemId);
   1.168 +  },
   1.169 +
   1.170 +  clearBookmarks: function bv_clearBookmarks() {
   1.171 +    if ('clearAll' in this._set)
   1.172 +      this._set.clearAll();
   1.173 +  },
   1.174 +
   1.175 +  addBookmark: function bv_addBookmark(aBookmarkId, aPos) {
   1.176 +    let index = this._bookmarkService.getItemIndex(aBookmarkId);
   1.177 +    let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
   1.178 +    let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
   1.179 +    let item = this._set.insertItemAt(aPos || index, title, uri.spec, this._inBatch);
   1.180 +    item.setAttribute("anonid", aBookmarkId);
   1.181 +    this._setContextActions(item);
   1.182 +    this._updateFavicon(item, uri);
   1.183 +  },
   1.184 +
   1.185 +  _setContextActions: function bv__setContextActions(aItem) {
   1.186 +    let itemId = this._getBookmarkIdForItem(aItem);
   1.187 +    aItem.setAttribute("data-contextactions", "delete," + (this._pinHelper.isPinned(itemId) ? "hide" : "pin"));
   1.188 +    if (aItem.refresh) aItem.refresh();
   1.189 +  },
   1.190 +
   1.191 +  _sendNeedsRefresh: function bv__sendNeedsRefresh(){
   1.192 +    // Event sent when all view instances need to refresh.
   1.193 +    let event = document.createEvent("Events");
   1.194 +    event.initEvent("BookmarksNeedsRefresh", true, false);
   1.195 +    window.dispatchEvent(event);
   1.196 +  },
   1.197 +
   1.198 +  updateBookmark: function bv_updateBookmark(aBookmarkId) {
   1.199 +    let item = this._getItemForBookmarkId(aBookmarkId);
   1.200 +
   1.201 +    if (!item)
   1.202 +      return;
   1.203 +
   1.204 +    let oldIndex = this._set.getIndexOfItem(item);
   1.205 +    let index = this._bookmarkService.getItemIndex(aBookmarkId);
   1.206 +
   1.207 +    if (oldIndex != index) {
   1.208 +      this.removeBookmark(aBookmarkId);
   1.209 +      this.addBookmark(aBookmarkId);
   1.210 +      return;
   1.211 +    }
   1.212 +
   1.213 +    let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
   1.214 +    let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
   1.215 +
   1.216 +    item.setAttribute("anonid", aBookmarkId);
   1.217 +    item.setAttribute("value", uri.spec);
   1.218 +    item.setAttribute("label", title);
   1.219 +
   1.220 +    this._updateFavicon(item, uri);
   1.221 +  },
   1.222 +
   1.223 +  removeBookmark: function bv_removeBookmark(aBookmarkId) {
   1.224 +    let item = this._getItemForBookmarkId(aBookmarkId);
   1.225 +    let index = this._set.getIndexOfItem(item);
   1.226 +    this._set.removeItemAt(index, this._inBatch);
   1.227 +  },
   1.228 +
   1.229 +  doActionOnSelectedTiles: function bv_doActionOnSelectedTiles(aActionName, aEvent) {
   1.230 +    let tileGroup = this._set;
   1.231 +    let selectedTiles = tileGroup.selectedItems;
   1.232 +
   1.233 +    switch (aActionName){
   1.234 +      case "delete":
   1.235 +        Array.forEach(selectedTiles, function(aNode) {
   1.236 +          if (!this._toRemove) {
   1.237 +            this._toRemove = [];
   1.238 +          }
   1.239 +
   1.240 +          let itemId = this._getBookmarkIdForItem(aNode);
   1.241 +
   1.242 +          this._toRemove.push(itemId);
   1.243 +          this.removeBookmark(itemId);
   1.244 +        }, this);
   1.245 +
   1.246 +        // stop the appbar from dismissing
   1.247 +        aEvent.preventDefault();
   1.248 +
   1.249 +        // at next tick, re-populate the context appbar.
   1.250 +        setTimeout(function(){
   1.251 +          // fire a MozContextActionsChange event to update the context appbar
   1.252 +          let event = document.createEvent("Events");
   1.253 +          // we need the restore button to show (the tile node will go away though)
   1.254 +          event.actions = ["restore"];
   1.255 +          event.initEvent("MozContextActionsChange", true, false);
   1.256 +          tileGroup.dispatchEvent(event);
   1.257 +        }, 0);
   1.258 +        break;
   1.259 +
   1.260 +      case "restore":
   1.261 +        // clear toRemove and let _sendNeedsRefresh update the items.
   1.262 +        this._toRemove = null;
   1.263 +        break;
   1.264 +
   1.265 +      case "unpin":
   1.266 +        Array.forEach(selectedTiles, function(aNode) {
   1.267 +          let itemId = this._getBookmarkIdForItem(aNode);
   1.268 +
   1.269 +          if (this._filterUnpinned)
   1.270 +            this.removeBookmark(itemId);
   1.271 +
   1.272 +          this._pinHelper.setUnpinned(itemId);
   1.273 +        }, this);
   1.274 +        break;
   1.275 +
   1.276 +      case "pin":
   1.277 +        Array.forEach(selectedTiles, function(aNode) {
   1.278 +          let itemId = this._getBookmarkIdForItem(aNode);
   1.279 +
   1.280 +          this._pinHelper.setPinned(itemId);
   1.281 +        }, this);
   1.282 +        break;
   1.283 +
   1.284 +      default:
   1.285 +        return;
   1.286 +    }
   1.287 +
   1.288 +    // Send refresh event so all view are in sync.
   1.289 +    this._sendNeedsRefresh();
   1.290 +  },
   1.291 +
   1.292 +  handleEvent: function bv_handleEvent(aEvent) {
   1.293 +    switch (aEvent.type){
   1.294 +      case "MozAppbarDismissing":
   1.295 +        // If undo wasn't pressed, time to do definitive actions.
   1.296 +        if (this._toRemove) {
   1.297 +          for (let bookmarkId of this._toRemove) {
   1.298 +            this._bookmarkService.removeItem(bookmarkId);
   1.299 +          }
   1.300 +          this._toRemove = null;
   1.301 +        }
   1.302 +        break;
   1.303 +
   1.304 +      case "BookmarksNeedsRefresh":
   1.305 +        this.getBookmarks(true);
   1.306 +        break;
   1.307 +
   1.308 +      case "TabClose":
   1.309 +        // Flush any pending actions - appbar will call us back
   1.310 +        // before this returns with 'MozAppbarDismissing' above.
   1.311 +        StartUI.chromeWin.ContextUI.dismissContextAppbar();
   1.312 +      break;
   1.313 +    }
   1.314 +  }
   1.315 +});
   1.316 +
   1.317 +let BookmarksStartView = {
   1.318 +  _view: null,
   1.319 +  get _grid() { return document.getElementById("start-bookmarks-grid"); },
   1.320 +
   1.321 +  init: function init() {
   1.322 +    this._view = new BookmarksView(this._grid, Bookmarks.metroRoot, true);
   1.323 +    this._view.getBookmarks();
   1.324 +    this._grid.removeAttribute("fade");
   1.325 +  },
   1.326 +
   1.327 +  uninit: function uninit() {
   1.328 +    if (this._view) {
   1.329 +      this._view.destruct();
   1.330 +    }
   1.331 +  },
   1.332 +};
   1.333 +
   1.334 +/**
   1.335 + * Observes bookmark changes and keeps a linked BookmarksView updated.
   1.336 + *
   1.337 + * @param aView An instance of BookmarksView.
   1.338 + */
   1.339 +function BookmarkChangeListener(aView) {
   1.340 +  this._view = aView;
   1.341 +}
   1.342 +
   1.343 +BookmarkChangeListener.prototype = {
   1.344 +  //////////////////////////////////////////////////////////////////////////////
   1.345 +  //// nsINavBookmarkObserver
   1.346 +  onBeginUpdateBatch: function () { },
   1.347 +  onEndUpdateBatch: function () { },
   1.348 +
   1.349 +  onItemAdded: function bCL_onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded, aGUID, aParentGUID) {
   1.350 +    this._view.getBookmarks(true);
   1.351 +  },
   1.352 +
   1.353 +  onItemChanged: function bCL_onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aNewValue, aLastModified, aItemType, aParentId, aGUID, aParentGUID) {
   1.354 +    let itemIndex = PlacesUtils.bookmarks.getItemIndex(aItemId);
   1.355 +    if (!this._view.inCurrentView(aParentId, aItemId))
   1.356 +      return;
   1.357 +
   1.358 +    this._view.updateBookmark(aItemId);
   1.359 +  },
   1.360 +
   1.361 +  onItemMoved: function bCL_onItemMoved(aItemId, aOldParentId, aOldIndex, aNewParentId, aNewIndex, aItemType, aGUID, aOldParentGUID, aNewParentGUID) {
   1.362 +    let wasInView = this._view.inCurrentView(aOldParentId, aItemId);
   1.363 +    let nowInView = this._view.inCurrentView(aNewParentId, aItemId);
   1.364 +
   1.365 +    if (!wasInView && nowInView)
   1.366 +      this._view.addBookmark(aItemId);
   1.367 +
   1.368 +    if (wasInView && !nowInView)
   1.369 +      this._view.removeBookmark(aItemId);
   1.370 +
   1.371 +    this._view.getBookmarks(true);
   1.372 +  },
   1.373 +
   1.374 +  onBeforeItemRemoved: function (aItemId, aItemType, aParentId, aGUID, aParentGUID) { },
   1.375 +  onItemRemoved: function bCL_onItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID, aParentGUID) {
   1.376 +    if (!this._view.inCurrentView(aParentId, aItemId))
   1.377 +      return;
   1.378 +
   1.379 +    this._view.removeBookmark(aItemId);
   1.380 +    this._view.getBookmarks(true);
   1.381 +  },
   1.382 +
   1.383 +  onItemVisited: function(aItemId, aVisitId, aTime, aTransitionType, aURI, aParentId, aGUID, aParentGUID) { },
   1.384 +
   1.385 +  //////////////////////////////////////////////////////////////////////////////
   1.386 +  //// nsISupports
   1.387 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
   1.388 +};

mercurial