github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/server/camlistored/ui/blob_item_container.js (about) 1 /* 2 Copyright 2013 Google Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 goog.provide('cam.BlobItemContainer'); 18 19 goog.require('goog.dom'); 20 goog.require('goog.dom.classes'); 21 goog.require('goog.events.Event'); 22 goog.require('goog.events.EventHandler'); 23 goog.require('goog.events.EventType'); 24 goog.require('goog.events.FileDropHandler'); 25 goog.require('goog.ui.Container'); 26 27 goog.require('cam.BlobItem'); 28 goog.require('cam.SearchSession'); 29 goog.require('cam.ServerConnection'); 30 31 // An infinite scrolling list of BlobItem. The heights of rows and clip of individual items is adjusted to get a fully justified appearance. 32 cam.BlobItemContainer = function(connection, opt_domHelper) { 33 goog.base(this, opt_domHelper); 34 35 this.checkedBlobItems_ = []; 36 37 this.connection_ = connection; 38 39 this.searchSession_ = null; 40 41 this.eh_ = new goog.events.EventHandler(this); 42 43 // BlobRef of the permanode defined as the current collection/set. Selected blobitems will be added as members of that collection upon relevant actions (e.g click on the 'Add to Set' toolbar button). 44 this.currentCollec_ = ""; 45 46 // Whether our content has changed since last layout. 47 this.isLayoutDirty_ = false; 48 49 // An id for a timer we use to know when the drag has ended. 50 this.dragEndTimer_ = 0; 51 52 // Whether the blobItems within can be selected. 53 this.isSelectionEnabled = false; 54 55 // Whether users can drag files onto the container to upload. 56 this.isFileDragEnabled = false; 57 58 // A lookup of blobRef->cam.BlobItem. This allows us to quickly find and reuse existing controls when we're updating the UI in response to a server push. 59 this.itemCache_ = {}; 60 61 this.setFocusable(false); 62 }; 63 goog.inherits(cam.BlobItemContainer, goog.ui.Container); 64 65 // Margin between items in the layout. 66 cam.BlobItemContainer.BLOB_ITEM_MARGIN = 7; 67 68 // If the last row uses at least this much of the available width before adjustments, we'll call it "close enough" and adjust things so that it fills the entire row. Less than this, and we'll leave the last row unaligned. 69 cam.BlobItemContainer.LAST_ROW_CLOSE_ENOUGH_TO_FULL = 0.85; 70 71 cam.BlobItemContainer.THUMBNAIL_SIZES_ = [75, 100, 150, 200, 250]; 72 73 // Distance from the bottom of the page at which we will trigger loading more data. 74 cam.BlobItemContainer.INFINITE_SCROLL_THRESHOLD_PX_ = 100; 75 76 cam.BlobItemContainer.NUM_ITEMS_PER_PAGE = 50; 77 78 cam.BlobItemContainer.prototype.fileDropHandler_ = null; 79 80 cam.BlobItemContainer.prototype.dragActiveElement_ = null; 81 82 // Constants for events fired by BlobItemContainer 83 cam.BlobItemContainer.EventType = { 84 SELECTION_CHANGED: 'Camlistore_BlobItemContainer_SelectionChanged', 85 }; 86 87 cam.BlobItemContainer.prototype.thumbnailSize_ = 200; 88 89 cam.BlobItemContainer.prototype.smaller = function() { 90 var index = cam.BlobItemContainer.THUMBNAIL_SIZES_.indexOf(this.thumbnailSize_); 91 if (index == 0) { 92 return false; 93 } 94 var el = this.getElement(); 95 goog.dom.classes.remove(el, 'cam-blobitemcontainer-' + this.thumbnailSize_); 96 this.thumbnailSize_ = cam.BlobItemContainer.THUMBNAIL_SIZES_[index-1]; 97 goog.dom.classes.add(el, 'cam-blobitemcontainer-' + this.thumbnailSize_); 98 return true; 99 }; 100 101 cam.BlobItemContainer.prototype.bigger = function() { 102 var index = cam.BlobItemContainer.THUMBNAIL_SIZES_.indexOf( 103 this.thumbnailSize_); 104 if (index == cam.BlobItemContainer.THUMBNAIL_SIZES_.length - 1) { 105 return false; 106 } 107 var el = this.getElement(); 108 goog.dom.classes.remove(el, 'cam-blobitemcontainer-' + this.thumbnailSize_); 109 this.thumbnailSize_ = cam.BlobItemContainer.THUMBNAIL_SIZES_[index+1]; 110 goog.dom.classes.add(el, 'cam-blobitemcontainer-' + this.thumbnailSize_); 111 return true; 112 }; 113 114 cam.BlobItemContainer.prototype.createDom = function() { 115 this.decorateInternal(this.dom_.createElement('div')); 116 }; 117 118 cam.BlobItemContainer.prototype.decorateInternal = function(element) { 119 cam.BlobItemContainer.superClass_.decorateInternal.call(this, element); 120 this.layout_(); 121 122 var el = this.getElement(); 123 el.style.marginLeft = '36px'; 124 goog.dom.classes.add(el, 'cam-blobitemcontainer'); 125 goog.dom.classes.add(el, 'cam-blobitemcontainer-' + this.thumbnailSize_); 126 }; 127 128 cam.BlobItemContainer.prototype.disposeInternal = function() { 129 cam.BlobItemContainer.superClass_.disposeInternal.call(this); 130 this.eh_.dispose(); 131 }; 132 133 cam.BlobItemContainer.prototype.addChildAt = function(child, index, opt_render) { 134 goog.base(this, "addChildAt", child, index, opt_render); 135 child.setEnabled(this.isSelectionEnabled); 136 if (!this.isLayoutDirty_) { 137 var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; 138 // It's OK if raf not supported, the timer loop we have going will pick up the layout a little later. 139 if (raf) { 140 raf(goog.bind(this.layout_, this, false)); 141 } 142 143 this.isLayoutDirty_ = true; 144 } 145 }; 146 147 cam.BlobItemContainer.prototype.removeChildAt = function(index, opt_render) { 148 goog.base(this, "removeChildAt", index, opt_render); 149 this.isLayoutDirty_ = true; 150 }; 151 152 cam.BlobItemContainer.prototype.enterDocument = function() { 153 cam.BlobItemContainer.superClass_.enterDocument.call(this); 154 155 this.resetChildren_(); 156 this.listenToBlobItemEvents_(); 157 158 if (this.isFileDragEnabled) { 159 this.fileDragListener_ = goog.bind(this.handleFileDrag_, this); 160 this.eh_.listen(document, goog.events.EventType.DRAGOVER, this.fileDragListener_); 161 this.eh_.listen(document, goog.events.EventType.DRAGENTER, this.fileDragListener_); 162 163 this.fileDropHandler_ = new goog.events.FileDropHandler(document); 164 this.registerDisposable(this.fileDropHandler_); 165 this.eh_.listen(this.fileDropHandler_, goog.events.FileDropHandler.EventType.DROP, this.handleFileDrop_); 166 } 167 168 this.eh_.listen(document, goog.events.EventType.SCROLL, this.handleScroll_); 169 170 // We can't catch everything that could cause us to need to relayout. Instead, be lazy and just poll every second. 171 window.setInterval(goog.bind(this.layout_, this, false), 1000); 172 }; 173 174 cam.BlobItemContainer.prototype.exitDocument = function() { 175 cam.BlobItemContainer.superClass_.exitDocument.call(this); 176 this.eh_.removeAll(); 177 }; 178 179 cam.BlobItemContainer.prototype.showSearchSession = function(session) { 180 var changeType = cam.SearchSession.SEARCH_SESSION_CHANGE_TYPE.APPEND; 181 182 if (this.searchSession_ != session) { 183 if (this.searchSession_) { 184 this.eh_.unlisten(this.searchSession_, cam.SearchSession.SEARCH_SESSION_CHANGED, this.searchDone_); 185 } 186 this.resetChildren_(); 187 this.itemCache_ = {}; 188 this.layout_(); 189 this.searchSession_ = session; 190 this.eh_.listen(session, cam.SearchSession.SEARCH_SESSION_CHANGED, this.searchDone_); 191 changeType = cam.SearchSession.SEARCH_SESSION_CHANGE_TYPE.NEW; 192 } 193 194 this.searchDone_({changeType:changeType}); 195 }; 196 197 cam.BlobItemContainer.prototype.getSearchSession = function() { 198 return this.searchSession_; 199 }; 200 201 cam.BlobItemContainer.prototype.searchDone_ = function(e) { 202 if (e.changeType == cam.SearchSession.SEARCH_SESSION_CHANGE_TYPE.NEW) { 203 this.resetChildren_(); 204 this.itemCache_ = {}; 205 } 206 207 this.populateChildren_(this.searchSession_.getCurrentResults(), e.changeType == cam.SearchSession.SEARCH_SESSION_CHANGE_TYPE.APPEND); 208 209 if (this.searchSession_.isComplete()) { 210 return; 211 } 212 213 // If we haven't filled the window with results, add some more. 214 this.layout_(); 215 var docHeight = goog.dom.getDocumentHeight(); 216 var viewportHeight = goog.dom.getViewportSize().height; 217 if (docHeight < (viewportHeight * 1.5)) { 218 this.searchSession_.loadMoreResults(); 219 } 220 }; 221 222 cam.BlobItemContainer.prototype.findByBlobref_ = function(blobref) { 223 this.connection_.describeWithThumbnails( 224 blobref, this.thumbnailSize_, 225 goog.bind(this.findByBlobrefDone_, this, blobref), 226 function(msg) { alert(msg); }); 227 }; 228 229 cam.BlobItemContainer.prototype.getCheckedBlobItems = function() { 230 return this.checkedBlobItems_; 231 }; 232 233 cam.BlobItemContainer.prototype.listenToBlobItemEvents_ = function() { 234 var doc = goog.dom.getOwnerDocument(this.element_); 235 this.eh_.listen(this, goog.ui.Component.EventType.CHECK, this.handleBlobItemChecked_); 236 this.eh_.listen(this, goog.ui.Component.EventType.UNCHECK, this.handleBlobItemChecked_); 237 this.eh_.listen(doc, goog.events.EventType.KEYDOWN, this.handleKeyDownEvent_); 238 this.eh_.listen(doc, goog.events.EventType.KEYUP, this.handleKeyUpEvent_); 239 }; 240 241 cam.BlobItemContainer.prototype.isShiftKeyDown_ = false; 242 243 cam.BlobItemContainer.prototype.isCtrlKeyDown_ = false; 244 245 // Sets state for whether or not the shift or ctrl key is down. 246 cam.BlobItemContainer.prototype.handleKeyDownEvent_ = function(e) { 247 if (e.keyCode == goog.events.KeyCodes.SHIFT) { 248 this.isShiftKeyDown_ = true; 249 this.isCtrlKeyDown_ = false; 250 return; 251 } 252 if (e.keyCode == goog.events.KeyCodes.CTRL) { 253 this.isCtrlKeyDown_ = true; 254 this.isShiftKeyDown_ = false; 255 return; 256 } 257 }; 258 259 // Sets state for whether or not the shift or ctrl key is up. 260 cam.BlobItemContainer.prototype.handleKeyUpEvent_ = function(e) { 261 this.isShiftKeyDown_ = false; 262 this.isCtrlKeyDown_ = false; 263 }; 264 265 cam.BlobItemContainer.prototype.handleBlobItemChecked_ = function(e) { 266 // Because the CHECK/UNCHECK event dispatches before isChecked is set. 267 // We stop the default behaviour because want to control manually here whether 268 // the source blobitem gets checked or not. See http://cam.org/issue/134 269 e.preventDefault(); 270 var blobItem = e.target; 271 var isCheckingItem = !blobItem.isChecked(); 272 var isShiftMultiSelect = this.isShiftKeyDown_; 273 var isCtrlMultiSelect = this.isCtrlKeyDown_; 274 275 if (isShiftMultiSelect || isCtrlMultiSelect) { 276 var lastChildSelected = this.checkedBlobItems_[this.checkedBlobItems_.length - 1]; 277 var firstChildSelected = this.checkedBlobItems_[0]; 278 var lastChosenIndex = this.indexOfChild(lastChildSelected); 279 var firstChosenIndex = this.indexOfChild(firstChildSelected); 280 var thisIndex = this.indexOfChild(blobItem); 281 } 282 283 if (isShiftMultiSelect) { 284 // deselect all items after the chosen one 285 for (var i = lastChosenIndex; i > thisIndex; i--) { 286 var item = this.getChildAt(i); 287 item.setState(goog.ui.Component.State.CHECKED, false); 288 if (goog.array.contains(this.checkedBlobItems_, item)) { 289 goog.array.remove(this.checkedBlobItems_, item); 290 } 291 } 292 // make sure all the others are selected. 293 for (var i = firstChosenIndex; i <= thisIndex; i++) { 294 var item = this.getChildAt(i); 295 item.setState(goog.ui.Component.State.CHECKED, true); 296 if (!goog.array.contains(this.checkedBlobItems_, item)) { 297 this.checkedBlobItems_.push(item); 298 } 299 } 300 } else if (isCtrlMultiSelect) { 301 if (isCheckingItem) { 302 blobItem.setState(goog.ui.Component.State.CHECKED, true); 303 if (!goog.array.contains(this.checkedBlobItems_, blobItem)) { 304 var pos = -1; 305 for (var i = 0; i <= this.checkedBlobItems_.length; i++) { 306 var idx = this.indexOfChild(this.checkedBlobItems_[i]); 307 if (idx > thisIndex) { 308 pos = i; 309 break; 310 } 311 } 312 if (pos != -1) { 313 goog.array.insertAt(this.checkedBlobItems_, blobItem, pos) 314 } else { 315 this.checkedBlobItems_.push(blobItem); 316 } 317 } 318 } else { 319 blobItem.setState(goog.ui.Component.State.CHECKED, false); 320 if (goog.array.contains(this.checkedBlobItems_, blobItem)) { 321 var done = goog.array.remove(this.checkedBlobItems_, blobItem); 322 if (!done) { 323 alert("Failed to remove item from selection"); 324 } 325 } 326 } 327 } else { 328 blobItem.setState(goog.ui.Component.State.CHECKED, isCheckingItem); 329 if (isCheckingItem) { 330 this.checkedBlobItems_.push(blobItem); 331 } else { 332 goog.array.remove(this.checkedBlobItems_, blobItem); 333 } 334 } 335 this.dispatchEvent(cam.BlobItemContainer.EventType.SELECTION_CHANGED); 336 }; 337 338 cam.BlobItemContainer.prototype.unselectAll = function() { 339 goog.array.forEach(this.checkedBlobItems_, function(item) { 340 item.setState(goog.ui.Component.State.CHECKED, false); 341 }); 342 this.checkedBlobItems_ = []; 343 this.dispatchEvent(cam.BlobItemContainer.EventType.SELECTION_CHANGED); 344 }; 345 346 cam.BlobItemContainer.prototype.populateChildren_ = function(result, append) { 347 var i = append ? this.getChildCount() : 0; 348 for (var blob; blob = result.blobs[i]; i++) { 349 var blobRef = blob.blob; 350 var item = this.itemCache_[blobRef]; 351 var render = true; 352 353 // If there's already an item for this blob, reuse it so that we don't lose any of the UI state (like whether it is selected). 354 if (item) { 355 item.update(blobRef, result.description.meta); 356 item.updateDom(); 357 render = false; 358 } else { 359 item = new cam.BlobItem(blobRef, result.description.meta); 360 this.itemCache_[blobRef] = item; 361 } 362 363 if (append) { 364 this.addChild(item, render); 365 } else { 366 this.addChildAt(item, i, render); 367 } 368 } 369 370 // Remove any children we don't need anymore. 371 if (!append) { 372 var numBlobs = result.blobs.length; 373 while (this.getChildCount() > numBlobs) { 374 this.itemCache_[this.getChildAt(numBlobs).getBlobRef()] = null; 375 this.removeChildAt(numBlobs, true); 376 } 377 } 378 }; 379 380 cam.BlobItemContainer.prototype.layout_ = function(force) { 381 var el = this.getElement(); 382 var availWidth = el.clientWidth; 383 384 if (!this.isVisible()) { 385 return; 386 } 387 388 if (!force && !this.isLayoutDirty_ && availWidth == this.lastClientWidth_) { 389 return; 390 } 391 392 this.isLayoutDirty_ = false; 393 this.lastClientWidth_ = availWidth; 394 395 var currentTop = this.constructor.BLOB_ITEM_MARGIN; 396 var currentWidth = this.constructor.BLOB_ITEM_MARGIN; 397 var rowStart = 0; 398 var lastItem = this.getChildCount() - 1; 399 400 for (var i = rowStart; i <= lastItem; i++) { 401 var item = this.getChildAt(i); 402 403 var nextWidth = currentWidth + this.thumbnailSize_ * item.getThumbAspect() + this.constructor.BLOB_ITEM_MARGIN; 404 if (i != lastItem && nextWidth < availWidth) { 405 currentWidth = nextWidth; 406 continue; 407 } 408 409 // Decide how many items are going to be in this row. We choose the number that will result in the smallest adjustment to the image sizes having to be done. 410 var rowEnd, rowWidth; 411 if (i == lastItem) { 412 rowEnd = lastItem; 413 rowWidth = nextWidth; 414 if (nextWidth / availWidth < 415 this.constructor.LAST_ROW_CLOSE_ENOUGH_TO_FULL) { 416 availWidth = nextWidth; 417 } 418 } else if (availWidth - currentWidth <= nextWidth - availWidth) { 419 rowEnd = i - 1; 420 rowWidth = currentWidth; 421 } else { 422 rowEnd = i; 423 rowWidth = nextWidth; 424 } 425 426 currentTop += this.layoutRow_(rowStart, rowEnd, availWidth, rowWidth, currentTop) + this.constructor.BLOB_ITEM_MARGIN; 427 428 currentWidth = this.constructor.BLOB_ITEM_MARGIN; 429 rowStart = rowEnd + 1; 430 i = rowEnd; 431 } 432 433 el.style.height = currentTop + this.constructor.BLOB_ITEM_MARGIN + 'px'; 434 }; 435 436 // @param {Number} startIndex The index of the first item in the row. 437 // @param {Number} endIndex The index of the last item in the row. 438 // @param {Number} availWidth The width available to the row for layout. 439 // @param {Number} usedWidth The width that the contents of the row consume 440 // using their initial dimensions, before any scaling or clipping. 441 // @param {Number} top The position of the top of the row. 442 // @return {Number} The height of the row after layout. 443 cam.BlobItemContainer.prototype.layoutRow_ = function(startIndex, endIndex, availWidth, usedWidth, top) { 444 var currentLeft = 0; 445 var rowHeight = Number.POSITIVE_INFINITY; 446 447 var numItems = endIndex - startIndex + 1; 448 var availThumbWidth = availWidth - (this.constructor.BLOB_ITEM_MARGIN * (numItems + 1)); 449 var usedThumbWidth = usedWidth - (this.constructor.BLOB_ITEM_MARGIN * (numItems + 1)); 450 451 for (var i = startIndex; i <= endIndex; i++) { 452 var item = this.getChildAt(i); 453 454 // We figure out the amount to adjust each item in this slightly non- intuitive way so that the adjustment is split up as fairly as possible. Figuring out a ratio up front and applying it to all items uniformly can end up with a large amount left over because of rounding. 455 var numItemsLeft = (endIndex + 1) - i; 456 var delta = Math.round((availThumbWidth - usedThumbWidth) / numItemsLeft); 457 var originalWidth = this.thumbnailSize_ * item.getThumbAspect(); 458 var width = originalWidth + delta; 459 var ratio = width / originalWidth; 460 var height = Math.round(this.thumbnailSize_ * ratio); 461 462 var elm = item.getElement(); 463 elm.style.left = currentLeft + this.constructor.BLOB_ITEM_MARGIN + 'px'; 464 elm.style.top = top + 'px'; 465 item.setSize(width, height); 466 467 currentLeft += width + this.constructor.BLOB_ITEM_MARGIN; 468 usedThumbWidth += delta; 469 rowHeight = Math.min(rowHeight, height); 470 } 471 472 for (var i = startIndex; i <= endIndex; i++) { 473 this.getChildAt(i).setHeight(rowHeight); 474 } 475 476 return rowHeight; 477 }; 478 479 cam.BlobItemContainer.prototype.handleScroll_ = function() { 480 if (!this.isVisible()) { 481 return; 482 } 483 484 var docHeight = goog.dom.getDocumentHeight(); 485 var scroll = goog.dom.getDocumentScroll(); 486 var viewportSize = goog.dom.getViewportSize(); 487 488 if ((docHeight - scroll.y - viewportSize.height) > 489 this.constructor.INFINITE_SCROLL_THRESHOLD_PX_) { 490 return; 491 } 492 493 if (this.searchSession_) { 494 this.searchSession_.loadMoreResults(); 495 } 496 }; 497 498 cam.BlobItemContainer.prototype.findByBlobrefDone_ = function(permanode, result) { 499 this.resetChildren_(); 500 if (!result) { 501 return; 502 } 503 var meta = result.meta; 504 if (!meta || !meta[permanode]) { 505 return; 506 } 507 var item = new cam.BlobItem(permanode, meta); 508 this.addChild(item, true); 509 }; 510 511 // Clears all children from this container, reseting to the default state. 512 cam.BlobItemContainer.prototype.resetChildren_ = function() { 513 this.removeChildren(true); 514 }; 515 516 cam.BlobItemContainer.prototype.handleFileDrop_ = function(e) { 517 var recipient = this.dragActiveElement_; 518 if (!recipient) { 519 console.log("No valid target to drag and drop on."); 520 return; 521 } 522 523 goog.dom.classes.remove(recipient.getElement(), 'cam-dropactive'); 524 this.dragActiveElement_ = null; 525 526 var files = e.getBrowserEvent().dataTransfer.files; 527 for (var i = 0, n = files.length; i < n; i++) { 528 var file = files[i]; 529 // TODO(bslatkin): Add an uploading item placeholder while the upload is in progress. Somehow pipe through the POST progress. 530 this.connection_.uploadFile(file, goog.bind(this.handleUploadSuccess_, this, file, recipient.blobRef_)); 531 } 532 }; 533 534 cam.BlobItemContainer.prototype.handleUploadSuccess_ = function(file, recipient, blobRef) { 535 this.connection_.createPermanode( 536 goog.bind(this.handleCreatePermanodeSuccess_, this, file, recipient, blobRef)); 537 }; 538 539 cam.BlobItemContainer.prototype.handleCreatePermanodeSuccess_ = function(file, recipient, blobRef, permanode) { 540 this.connection_.newSetAttributeClaim(permanode, 'camliContent', blobRef, 541 goog.bind(this.handleSetAttributeSuccess_, this, file, recipient, blobRef, permanode)); 542 }; 543 544 cam.BlobItemContainer.prototype.handleSetAttributeSuccess_ = function(file, recipient, blobRef, permanode) { 545 this.connection_.describeWithThumbnails(permanode, this.thumbnailSize_, 546 goog.bind(this.handleDescribeSuccess_, this, recipient, permanode)); 547 }; 548 549 cam.BlobItemContainer.prototype.handleDescribeSuccess_ = function(recipient, permanode, describeResult) { 550 if (recipient) { 551 this.connection_.newAddAttributeClaim(recipient, 'camliMember', permanode); 552 } 553 554 if (this.searchSession_ && this.searchSession_.supportsChangeNotifications()) { 555 // We'll find this when we reload. 556 return; 557 } 558 559 var item = new cam.BlobItem(permanode, describeResult.meta); 560 this.addChildAt(item, 0, true); 561 if (!recipient) { 562 return; 563 } 564 }; 565 566 cam.BlobItemContainer.prototype.handleFileDrag_ = function(e) { 567 if (this.dragEndTimer_) { 568 this.dragEndTimer_ = window.clearTimeout(this.dragEndTimer_); 569 } 570 this.dragEndTimer_ = window.setTimeout(this.fileDragListener_, 2000); 571 572 var activeElement = e ? this.getOwnerControl(e.target) : e; 573 if (activeElement) { 574 if (!activeElement.isCollection()) { 575 activeElement = this; 576 } 577 } else if (e) { 578 activeElement = this; 579 } 580 581 if (activeElement == this.dragActiveElement_) { 582 return; 583 } 584 585 if (this.dragActiveElement_) { 586 goog.dom.classes.remove(this.dragActiveElement_.getElement(), 'cam-dropactive'); 587 } 588 589 this.dragActiveElement_ = activeElement; 590 591 if (this.dragActiveElement_) { 592 goog.dom.classes.add(this.dragActiveElement_.getElement(), 'cam-dropactive'); 593 } 594 }; 595 596 cam.BlobItemContainer.prototype.hide_ = function() { 597 goog.dom.classes.remove(this.getElement(), 'cam-blobitemcontainer-' + this.thumbnailSize_); 598 goog.dom.classes.add(this.getElement(), 'cam-blobitemcontainer-hidden'); 599 }; 600 601 cam.BlobItemContainer.prototype.show_ = function() { 602 goog.dom.classes.remove(this.getElement(), 'cam-blobitemcontainer-hidden'); 603 goog.dom.classes.add(this.getElement(), 'cam-blobitemcontainer-' + this.thumbnailSize_); 604 this.layout_(true); 605 };