browser/metro/base/content/bindings/grid.xml

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 <?xml version="1.0"?>
michael@0 2 <!-- This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 - License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
michael@0 5
michael@0 6 <bindings
michael@0 7 xmlns="http://www.mozilla.org/xbl"
michael@0 8 xmlns:xbl="http://www.mozilla.org/xbl"
michael@0 9 xmlns:html="http://www.w3.org/1999/xhtml"
michael@0 10 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
michael@0 11
michael@0 12 <binding id="richgrid"
michael@0 13 extends="chrome://global/content/bindings/general.xml#basecontrol">
michael@0 14
michael@0 15 <content>
michael@0 16 <html:div id="grid-div" anonid="grid" class="richgrid-grid" xbl:inherits="compact">
michael@0 17 <children/>
michael@0 18 </html:div>
michael@0 19 </content>
michael@0 20
michael@0 21 <implementation implements="nsIDOMXULSelectControlElement">
michael@0 22 <property name="_grid" readonly="true" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'grid');"/>
michael@0 23
michael@0 24 <property name="isBound" readonly="true" onget="return !!this._grid"/>
michael@0 25 <property name="isArranging" readonly="true" onget="return !!this._scheduledArrangeItemsTimerId"/>
michael@0 26
michael@0 27 <field name="controller">null</field>
michael@0 28
michael@0 29 <!-- collection of child items excluding empty tiles -->
michael@0 30 <property name="items" readonly="true" onget="return this.querySelectorAll('richgriditem[value]');"/>
michael@0 31 <property name="itemCount" readonly="true" onget="return this.items.length;"/>
michael@0 32
michael@0 33 <method name="isItem">
michael@0 34 <parameter name="anItem"/>
michael@0 35 <body>
michael@0 36 <![CDATA[
michael@0 37 // only non-empty child nodes are considered items
michael@0 38 return anItem && anItem.hasAttribute("value") &&
michael@0 39 anItem.parentNode == this;
michael@0 40 ]]>
michael@0 41 </body>
michael@0 42 </method>
michael@0 43
michael@0 44 <!-- nsIDOMXULMultiSelectControlElement (not fully implemented) -->
michael@0 45
michael@0 46 <method name="clearSelection">
michael@0 47 <body>
michael@0 48 <![CDATA[
michael@0 49 // 'selection' and 'selected' are confusingly overloaded here
michael@0 50 // as richgrid is adopting multi-select behavior, but select/selected are already being
michael@0 51 // used to describe triggering the default action of a tile
michael@0 52 if (this._selectedItem){
michael@0 53 this._selectedItem.removeAttribute("selected");
michael@0 54 this._selectedItem = null;
michael@0 55 }
michael@0 56
michael@0 57 for (let childItem of this.selectedItems) {
michael@0 58 childItem.removeAttribute("selected");
michael@0 59 }
michael@0 60 ]]>
michael@0 61 </body>
michael@0 62 </method>
michael@0 63
michael@0 64 <method name="toggleItemSelection">
michael@0 65 <parameter name="anItem"/>
michael@0 66 <body>
michael@0 67 <![CDATA[
michael@0 68 if (!this.isItem(anItem))
michael@0 69 return;
michael@0 70
michael@0 71 let wasSelected = anItem.selected;
michael@0 72 if ("single" == this.getAttribute("seltype")) {
michael@0 73 this.clearSelection();
michael@0 74 }
michael@0 75 this._selectedItem = wasSelected ? null : anItem;
michael@0 76 if (wasSelected)
michael@0 77 anItem.removeAttribute("selected");
michael@0 78 else
michael@0 79 anItem.setAttribute("selected", true);
michael@0 80 this._fireEvent("selectionchange");
michael@0 81 ]]>
michael@0 82 </body>
michael@0 83 </method>
michael@0 84
michael@0 85 <method name="selectItem">
michael@0 86 <parameter name="anItem"/>
michael@0 87 <body>
michael@0 88 <![CDATA[
michael@0 89 if (!this.isItem(anItem))
michael@0 90 return;
michael@0 91 let wasSelected = anItem.selected,
michael@0 92 isSingleMode = ("single" == this.getAttribute("seltype"));
michael@0 93 if (isSingleMode) {
michael@0 94 this.clearSelection();
michael@0 95 }
michael@0 96 this._selectedItem = anItem;
michael@0 97 if (wasSelected) {
michael@0 98 return;
michael@0 99 }
michael@0 100 anItem.setAttribute("selected", true);
michael@0 101 if (isSingleMode) {
michael@0 102 this._fireEvent("select");
michael@0 103 } else {
michael@0 104 this._fireEvent("selectionchange");
michael@0 105 }
michael@0 106 ]]>
michael@0 107 </body>
michael@0 108 </method>
michael@0 109
michael@0 110 <method name="selectNone">
michael@0 111 <body>
michael@0 112 <![CDATA[
michael@0 113 let selectedCount = this.selectedItems.length;
michael@0 114 this.clearSelection();
michael@0 115 if (selectedCount && "single" != this.getAttribute("seltype")) {
michael@0 116 this._fireEvent("selectionchange");
michael@0 117 }
michael@0 118 ]]>
michael@0 119 </body>
michael@0 120 </method>
michael@0 121
michael@0 122 <method name="handleItemClick">
michael@0 123 <parameter name="aItem"/>
michael@0 124 <parameter name="aEvent"/>
michael@0 125 <body>
michael@0 126 <![CDATA[
michael@0 127 if (!(this.isBound && this.isItem(aItem)))
michael@0 128 return;
michael@0 129
michael@0 130 if ("single" == this.getAttribute("seltype")) {
michael@0 131 // we'll republish this as a selectionchange event on the grid
michael@0 132 aEvent.stopPropagation();
michael@0 133 this.selectItem(aItem);
michael@0 134 }
michael@0 135
michael@0 136 if (this.controller && this.controller.handleItemClick)
michael@0 137 this.controller.handleItemClick(aItem, aEvent);
michael@0 138 ]]>
michael@0 139 </body>
michael@0 140 </method>
michael@0 141
michael@0 142 <method name="handleItemContextMenu">
michael@0 143 <parameter name="aItem"/>
michael@0 144 <parameter name="aEvent"/>
michael@0 145 <body>
michael@0 146 <![CDATA[
michael@0 147 if (!this.isBound || this.noContext || !this.isItem(aItem))
michael@0 148 return;
michael@0 149 // we'll republish this as a selectionchange event on the grid
michael@0 150 aEvent.stopPropagation();
michael@0 151 this.toggleItemSelection(aItem);
michael@0 152 ]]>
michael@0 153 </body>
michael@0 154 </method>
michael@0 155
michael@0 156 <property name="contextSetName" readonly="true"
michael@0 157 onget="return this.getAttribute('set-name');"/>
michael@0 158
michael@0 159 <property name="contextActions">
michael@0 160 <getter>
michael@0 161 <![CDATA[
michael@0 162 // return the subset of verbs that apply to all selected tiles
michael@0 163 let tileNodes = this.selectedItems;
michael@0 164 if (!tileNodes.length) {
michael@0 165 return new Set();
michael@0 166 }
michael@0 167
michael@0 168 // given one or more sets of values,
michael@0 169 // return a set with only those values present in each
michael@0 170 let initialItem = tileNodes[0];
michael@0 171
michael@0 172 let verbSet = new Set(initialItem.contextActions);
michael@0 173 for (let i=1; i<tileNodes.length; i++){
michael@0 174 let set = tileNodes[i].contextActions;
michael@0 175 for (let item of verbSet) {
michael@0 176 if (!set.has(item)){
michael@0 177 verbSet.delete(item);
michael@0 178 }
michael@0 179 }
michael@0 180 }
michael@0 181 // add the clear-selection button if more than one tiles are selected
michael@0 182 if (tileNodes.length > 1) {
michael@0 183 verbSet.add('clear');
michael@0 184 }
michael@0 185 // returns Set
michael@0 186 return verbSet;
michael@0 187 ]]>
michael@0 188 </getter>
michael@0 189 </property>
michael@0 190
michael@0 191 <!-- nsIDOMXULSelectControlElement -->
michael@0 192
michael@0 193 <field name="_selectedItem">null</field>
michael@0 194 <property name="selectedItem" onget="return this._selectedItem;">
michael@0 195 <setter>
michael@0 196 <![CDATA[
michael@0 197 this.selectItem(val);
michael@0 198 ]]>
michael@0 199 </setter>
michael@0 200 </property>
michael@0 201
michael@0 202 <!-- partial implementation of multiple selection interface -->
michael@0 203 <property name="selectedItems">
michael@0 204 <getter>
michael@0 205 <![CDATA[
michael@0 206 return this.querySelectorAll("richgriditem[value][selected]");
michael@0 207 ]]>
michael@0 208 </getter>
michael@0 209 </property>
michael@0 210
michael@0 211 <property name="selectedIndex">
michael@0 212 <getter>
michael@0 213 <![CDATA[
michael@0 214 return this.getIndexOfItem(this._selectedItem);
michael@0 215 ]]>
michael@0 216 </getter>
michael@0 217 <setter>
michael@0 218 <![CDATA[
michael@0 219 if (val >= 0) {
michael@0 220 let selected = this.getItemAtIndex(val);
michael@0 221 this.selectItem(selected);
michael@0 222 } else {
michael@0 223 this.selectNone();
michael@0 224 }
michael@0 225 ]]>
michael@0 226 </setter>
michael@0 227 </property>
michael@0 228
michael@0 229 <method name="appendItem">
michael@0 230 <parameter name="aLabel"/>
michael@0 231 <parameter name="aValue"/>
michael@0 232 <parameter name="aSkipArrange"/>
michael@0 233 <body>
michael@0 234 <![CDATA[
michael@0 235 let item = this.nextSlot();
michael@0 236 item.setAttribute("value", aValue);
michael@0 237 item.setAttribute("label", aLabel);
michael@0 238
michael@0 239 if (!aSkipArrange)
michael@0 240 this.arrangeItems();
michael@0 241 return item;
michael@0 242 ]]>
michael@0 243 </body>
michael@0 244 </method>
michael@0 245
michael@0 246 <method name="_slotValues">
michael@0 247 <body><![CDATA[
michael@0 248 return Array.map(this.children, (cnode) => cnode.getAttribute("value"));
michael@0 249 ]]></body>
michael@0 250 </method>
michael@0 251
michael@0 252 <property name="minSlots" readonly="true"
michael@0 253 onget="return this.getAttribute('minSlots') || 3;"/>
michael@0 254
michael@0 255 <method name="clearAll">
michael@0 256 <parameter name="aSkipArrange"/>
michael@0 257 <body>
michael@0 258 <![CDATA[
michael@0 259 const ELEMENT_NODE_TYPE = Components.interfaces.nsIDOMNode.ELEMENT_NODE;
michael@0 260 let slotCount = this.minSlots;
michael@0 261 let childIndex = 0;
michael@0 262 let child = this.firstChild;
michael@0 263 while (child) {
michael@0 264 // remove excess elements and non-element nodes
michael@0 265 if (child.nodeType !== ELEMENT_NODE_TYPE || childIndex+1 > slotCount) {
michael@0 266 let orphanNode = child;
michael@0 267 child = orphanNode.nextSibling;
michael@0 268 this.removeChild(orphanNode);
michael@0 269 continue;
michael@0 270 }
michael@0 271 if (child.hasAttribute("value")) {
michael@0 272 this._releaseSlot(child);
michael@0 273 }
michael@0 274 child = child.nextSibling;
michael@0 275 childIndex++;
michael@0 276 }
michael@0 277 // create our quota of item slots
michael@0 278 for (let count = this.childElementCount; count < slotCount; count++) {
michael@0 279 this.appendChild( this._createItemElement() );
michael@0 280 }
michael@0 281
michael@0 282 if (!aSkipArrange)
michael@0 283 this.arrangeItems();
michael@0 284 ]]>
michael@0 285 </body>
michael@0 286 </method>
michael@0 287
michael@0 288 <method name="_slotAt">
michael@0 289 <parameter name="anIndex"/>
michael@0 290 <body>
michael@0 291 <![CDATA[
michael@0 292 // backfill with new slots as necessary
michael@0 293 let count = Math.max(1+anIndex, this.minSlots) - this.childElementCount;
michael@0 294 for (; count > 0; count--) {
michael@0 295 this.appendChild( this._createItemElement() );
michael@0 296 }
michael@0 297 return this.children[anIndex];
michael@0 298 ]]>
michael@0 299 </body>
michael@0 300 </method>
michael@0 301
michael@0 302 <method name="nextSlot">
michael@0 303 <body>
michael@0 304 <![CDATA[
michael@0 305 if (!this.itemCount) {
michael@0 306 return this._slotAt(0);
michael@0 307 }
michael@0 308 let lastItem = this.items[this.itemCount-1];
michael@0 309 let nextIndex = 1 + Array.indexOf(this.children, lastItem);
michael@0 310 return this._slotAt(nextIndex);
michael@0 311 ]]>
michael@0 312 </body>
michael@0 313 </method>
michael@0 314
michael@0 315 <method name="_releaseSlot">
michael@0 316 <parameter name="anItem"/>
michael@0 317 <body>
michael@0 318 <![CDATA[
michael@0 319 // Flush out data and state attributes so we can recycle this slot/element
michael@0 320 let exclude = { value: 1, tiletype: 1 };
michael@0 321 let attrNames = [attr.name for (attr of anItem.attributes)];
michael@0 322 for (let attrName of attrNames) {
michael@0 323 if (!(attrName in exclude))
michael@0 324 anItem.removeAttribute(attrName);
michael@0 325 }
michael@0 326 // clear out inline styles
michael@0 327 anItem.removeAttribute("style");
michael@0 328 // finally clear the value, which should apply the richgrid-empty-item binding
michael@0 329 anItem.removeAttribute("value");
michael@0 330 ]]>
michael@0 331 </body>
michael@0 332 </method>
michael@0 333
michael@0 334 <method name="insertItemAt">
michael@0 335 <parameter name="anIndex"/>
michael@0 336 <parameter name="aLabel"/>
michael@0 337 <parameter name="aValue"/>
michael@0 338 <parameter name="aSkipArrange"/>
michael@0 339 <body>
michael@0 340 <![CDATA[
michael@0 341 anIndex = Math.min(this.itemCount, anIndex);
michael@0 342 let insertedItem;
michael@0 343 let existing = this.getItemAtIndex(anIndex);
michael@0 344 if (existing) {
michael@0 345 // use an empty slot if we have one, otherwise insert it
michael@0 346 let childIndex = Array.indexOf(this.children, existing);
michael@0 347 if (childIndex > 0 && !this.children[childIndex-1].hasAttribute("value")) {
michael@0 348 insertedItem = this.children[childIndex-1];
michael@0 349 } else {
michael@0 350 insertedItem = this.insertBefore(this._createItemElement(),existing);
michael@0 351 }
michael@0 352 }
michael@0 353 if (!insertedItem) {
michael@0 354 insertedItem = this._slotAt(anIndex);
michael@0 355 }
michael@0 356 insertedItem.setAttribute("value", aValue);
michael@0 357 insertedItem.setAttribute("label", aLabel);
michael@0 358 if (!aSkipArrange)
michael@0 359 this.arrangeItems();
michael@0 360 return insertedItem;
michael@0 361 ]]>
michael@0 362 </body>
michael@0 363 </method>
michael@0 364
michael@0 365 <method name="removeItemAt">
michael@0 366 <parameter name="anIndex"/>
michael@0 367 <parameter name="aSkipArrange"/>
michael@0 368 <body>
michael@0 369 <![CDATA[
michael@0 370 let item = this.getItemAtIndex(anIndex);
michael@0 371 if (!item)
michael@0 372 return null;
michael@0 373 return this.removeItem(item, aSkipArrange);
michael@0 374 ]]>
michael@0 375 </body>
michael@0 376 </method>
michael@0 377
michael@0 378 <method name="removeItem">
michael@0 379 <parameter name="aItem"/>
michael@0 380 <parameter name="aSkipArrange"/>
michael@0 381 <body>
michael@0 382 <![CDATA[
michael@0 383 if (!this.isItem(aItem))
michael@0 384 return null;
michael@0 385
michael@0 386 let removal = this.removeChild(aItem);
michael@0 387 // replace the slot if necessary
michael@0 388 if (this.childElementCount < this.minSlots) {
michael@0 389 this.nextSlot();
michael@0 390 }
michael@0 391
michael@0 392 if (removal && !aSkipArrange)
michael@0 393 this.arrangeItems();
michael@0 394
michael@0 395 // note that after removal the node is unbound
michael@0 396 // so none of the richgriditem binding methods & properties are available
michael@0 397 return removal;
michael@0 398 ]]>
michael@0 399 </body>
michael@0 400 </method>
michael@0 401
michael@0 402 <method name="getIndexOfItem">
michael@0 403 <parameter name="anItem"/>
michael@0 404 <body>
michael@0 405 <![CDATA[
michael@0 406 if (!this.isItem(anItem))
michael@0 407 return -1;
michael@0 408
michael@0 409 return Array.indexOf(this.items, anItem);
michael@0 410 ]]>
michael@0 411 </body>
michael@0 412 </method>
michael@0 413
michael@0 414 <method name="getItemAtIndex">
michael@0 415 <parameter name="anIndex"/>
michael@0 416 <body>
michael@0 417 <![CDATA[
michael@0 418 if (!this._isIndexInBounds(anIndex))
michael@0 419 return null;
michael@0 420 return this.items.item(anIndex);
michael@0 421 ]]>
michael@0 422 </body>
michael@0 423 </method>
michael@0 424
michael@0 425 <method name="getItemsByUrl">
michael@0 426 <parameter name="aUrl"/>
michael@0 427 <body>
michael@0 428 <![CDATA[
michael@0 429 return this.querySelectorAll('richgriditem[value="'+aUrl+'"]');
michael@0 430 ]]>
michael@0 431 </body>
michael@0 432 </method>
michael@0 433
michael@0 434 <!-- Interface for offsetting selection and checking bounds -->
michael@0 435
michael@0 436 <property name="isSelectionAtStart" readonly="true"
michael@0 437 onget="return this.selectedIndex == 0;"/>
michael@0 438
michael@0 439 <property name="isSelectionAtEnd" readonly="true"
michael@0 440 onget="return this.selectedIndex == (this.itemCount - 1);"/>
michael@0 441
michael@0 442 <property name="isSelectionInStartRow" readonly="true">
michael@0 443 <getter>
michael@0 444 <![CDATA[
michael@0 445 return this.selectedIndex < this.columnCount;
michael@0 446 ]]>
michael@0 447 </getter>
michael@0 448 </property>
michael@0 449
michael@0 450 <property name="isSelectionInEndRow" readonly="true">
michael@0 451 <getter>
michael@0 452 <![CDATA[
michael@0 453 let lowerBound = (this.rowCount - 1) * this.columnCount;
michael@0 454 let higherBound = this.rowCount * this.columnCount;
michael@0 455
michael@0 456 return this.selectedIndex >= lowerBound &&
michael@0 457 this.selectedIndex < higherBound;
michael@0 458 ]]>
michael@0 459 </getter>
michael@0 460 </property>
michael@0 461
michael@0 462 <method name="offsetSelection">
michael@0 463 <parameter name="aOffset"/>
michael@0 464 <body>
michael@0 465 <![CDATA[
michael@0 466 let newIndex = this.selectedIndex + aOffset;
michael@0 467 if (this._isIndexInBounds(newIndex))
michael@0 468 this.selectedIndex = newIndex;
michael@0 469 ]]>
michael@0 470 </body>
michael@0 471 </method>
michael@0 472
michael@0 473 <method name="offsetSelectionByRow">
michael@0 474 <parameter name="aRowOffset"/>
michael@0 475 <body>
michael@0 476 <![CDATA[
michael@0 477 let newIndex = this.selectedIndex + (this.columnCount * aRowOffset);
michael@0 478 if (this._isIndexInBounds(newIndex))
michael@0 479 this.selectedIndex -= this.columnCount;
michael@0 480 ]]>
michael@0 481 </body>
michael@0 482 </method>
michael@0 483
michael@0 484 <!-- Interface for grid layout management -->
michael@0 485
michael@0 486 <field name="_rowCount">0</field>
michael@0 487 <property name="rowCount" readonly="true" onget="return this._rowCount;"/>
michael@0 488 <field name="_columnCount">0</field>
michael@0 489 <property name="columnCount" readonly="true" onget="return this._columnCount;"/>
michael@0 490 <property name="_containerSize">
michael@0 491 <getter><![CDATA[
michael@0 492 // return the rect that represents our bounding box
michael@0 493 let containerNode = this.hasAttribute("flex") ? this : this.parentNode;
michael@0 494 let rect = containerNode.getBoundingClientRect();
michael@0 495 // return falsy if the container has no height
michael@0 496 return rect.height ? {
michael@0 497 width: rect.width,
michael@0 498 height: rect.height
michael@0 499 } : null;
michael@0 500 ]]></getter>
michael@0 501 </property>
michael@0 502
michael@0 503 <property name="_itemSize">
michael@0 504 <getter><![CDATA[
michael@0 505 // return the dimensions that represent an item in the grid
michael@0 506
michael@0 507 // grab tile/item dimensions
michael@0 508 this._tileSizes = this._getTileSizes();
michael@0 509
michael@0 510 let type = this.getAttribute("tiletype") || "default";
michael@0 511 let dims = this._tileSizes && this._tileSizes[type];
michael@0 512 if (!dims) {
michael@0 513 throw new Error("Missing tile sizes for '" + type + "' type");
michael@0 514 }
michael@0 515 return dims;
michael@0 516 ]]></getter>
michael@0 517 </property>
michael@0 518
michael@0 519 <!-- do conditions allow layout/arrange of the grid? -->
michael@0 520 <property name="_canLayout" readonly="true">
michael@0 521 <getter>
michael@0 522 <![CDATA[
michael@0 523 if (!(this._grid && this._grid.style)) {
michael@0 524 return false;
michael@0 525 }
michael@0 526
michael@0 527 let gridItemSize = this._itemSize;
michael@0 528
michael@0 529 // If we don't have valid item dimensions we can't arrange yet
michael@0 530 if (!(gridItemSize && gridItemSize.height)) {
michael@0 531 return false;
michael@0 532 }
michael@0 533
michael@0 534 let container = this._containerSize;
michael@0 535 // If we don't have valid container dimensions we can't arrange yet
michael@0 536 if (!(container && container.height)) {
michael@0 537 return false;
michael@0 538 }
michael@0 539 return true;
michael@0 540 ]]>
michael@0 541 </getter>
michael@0 542 </property>
michael@0 543
michael@0 544 <field name="_scheduledArrangeItemsTimerId">null</field>
michael@0 545 <field name="_scheduledArrangeItemsTries">0</field>
michael@0 546 <field name="_maxArrangeItemsRetries">5</field>
michael@0 547
michael@0 548 <method name="_scheduleArrangeItems">
michael@0 549 <parameter name="aTime"/>
michael@0 550 <body>
michael@0 551 <![CDATA[
michael@0 552 // cap the number of times we reschedule calling arrangeItems
michael@0 553 if (
michael@0 554 !this._scheduledArrangeItemsTimerId &&
michael@0 555 this._maxArrangeItemsRetries > this._scheduledArrangeItemsTries
michael@0 556 ) {
michael@0 557 this._scheduledArrangeItemsTimerId = setTimeout(this.arrangeItems.bind(this), aTime || 0);
michael@0 558 // track how many times we've attempted arrangeItems
michael@0 559 this._scheduledArrangeItemsTries++;
michael@0 560 }
michael@0 561 ]]>
michael@0 562 </body>
michael@0 563 </method>
michael@0 564
michael@0 565 <method name="arrangeItems">
michael@0 566 <body>
michael@0 567 <![CDATA[
michael@0 568 if (this.hasAttribute("deferlayout")) {
michael@0 569 return;
michael@0 570 }
michael@0 571 if (!this._canLayout) {
michael@0 572 // try again later
michael@0 573 this._scheduleArrangeItems();
michael@0 574 return;
michael@0 575 }
michael@0 576
michael@0 577 let itemDims = this._itemSize;
michael@0 578 let containerDims = this._containerSize;
michael@0 579 let slotsCount = this.childElementCount;
michael@0 580
michael@0 581 // reset the flags
michael@0 582 if (this._scheduledArrangeItemsTimerId) {
michael@0 583 clearTimeout(this._scheduledArrangeItemsTimerId);
michael@0 584 delete this._scheduledArrangeItemsTimerId;
michael@0 585 }
michael@0 586 this._scheduledArrangeItemsTries = 0;
michael@0 587
michael@0 588 // clear explicit width and columns before calculating from avail. height again
michael@0 589 let gridStyle = this._grid.style;
michael@0 590 gridStyle.removeProperty("min-width");
michael@0 591 gridStyle.removeProperty("-moz-column-count");
michael@0 592
michael@0 593 if (this.hasAttribute("vertical")) {
michael@0 594 this._columnCount = Math.floor(containerDims.width / itemDims.width) || 1;
michael@0 595 this._rowCount = Math.floor(slotsCount / this._columnCount);
michael@0 596 } else {
michael@0 597 // rows attribute is fixed number of rows
michael@0 598 let maxRows = Math.floor(containerDims.height / itemDims.height);
michael@0 599 this._rowCount = this.getAttribute("rows") ?
michael@0 600 // fit indicated rows when possible
michael@0 601 Math.min(maxRows, this.getAttribute("rows")) :
michael@0 602 // at least 1 row
michael@0 603 Math.min(maxRows, slotsCount) || 1;
michael@0 604
michael@0 605 // columns attribute is min number of cols
michael@0 606 this._columnCount = Math.ceil(slotsCount / this._rowCount) || 1;
michael@0 607 if (this.getAttribute("columns")) {
michael@0 608 this._columnCount = Math.max(this._columnCount, this.getAttribute("columns"));
michael@0 609 }
michael@0 610 }
michael@0 611
michael@0 612 // width is typically auto, cap max columns by truncating items collection
michael@0 613 // or, setting max-width style property with overflow hidden
michael@0 614 if (this._columnCount) {
michael@0 615 gridStyle.MozColumnCount = this._columnCount;
michael@0 616 }
michael@0 617 this._fireEvent("arranged");
michael@0 618 ]]>
michael@0 619 </body>
michael@0 620 </method>
michael@0 621 <method name="arrangeItemsNow">
michael@0 622 <body>
michael@0 623 <![CDATA[
michael@0 624 this.removeAttribute("deferlayout");
michael@0 625 // cancel any scheduled arrangeItems and reset flags
michael@0 626 if (this._scheduledArrangeItemsTimerId) {
michael@0 627 clearTimeout(this._scheduledArrangeItemsTimerId);
michael@0 628 delete this._scheduledArrangeItemsTimerId;
michael@0 629 }
michael@0 630 this._scheduledArrangeItemsTries = 0;
michael@0 631 // pass over any params
michael@0 632 return this.arrangeItems.apply(this, arguments);
michael@0 633 ]]>
michael@0 634 </body>
michael@0 635 </method>
michael@0 636
michael@0 637 <!-- Inteface to suppress selection events -->
michael@0 638 <property name="suppressOnSelect"
michael@0 639 onget="return this.getAttribute('suppressonselect') == 'true';"
michael@0 640 onset="this.setAttribute('suppressonselect', val);"/>
michael@0 641 <property name="noContext"
michael@0 642 onget="return this.hasAttribute('nocontext');"
michael@0 643 onset="if (val) this.setAttribute('nocontext', true); else this.removeAttribute('nocontext');"/>
michael@0 644 <property name="crossSlideBoundary"
michael@0 645 onget="return this.hasAttribute('crossslideboundary')? this.getAttribute('crossslideboundary') : Infinity;"/>
michael@0 646
michael@0 647 <!-- Internal methods -->
michael@0 648 <field name="_xslideHandler"/>
michael@0 649 <constructor>
michael@0 650 <![CDATA[
michael@0 651 // create our quota of item slots
michael@0 652 for (let count = this.childElementCount, slotCount = this.minSlots;
michael@0 653 count < slotCount; count++) {
michael@0 654 this.appendChild( this._createItemElement() );
michael@0 655 }
michael@0 656 if (this.controller && this.controller.gridBoundCallback != undefined)
michael@0 657 this.controller.gridBoundCallback();
michael@0 658 // XXX This event was never actually implemented (bug 223411).
michael@0 659 let event = document.createEvent("Events");
michael@0 660 event.initEvent("contentgenerated", true, true);
michael@0 661 this.dispatchEvent(event);
michael@0 662 ]]>
michael@0 663 </constructor>
michael@0 664
michael@0 665 <destructor>
michael@0 666 <![CDATA[
michael@0 667 this.disableCrossSlide();
michael@0 668 ]]>
michael@0 669 </destructor>
michael@0 670 <method name="enableCrossSlide">
michael@0 671 <body>
michael@0 672 <![CDATA[
michael@0 673 // set up cross-slide gesture handling for multiple-selection grids
michael@0 674 if (!this._xslideHandler &&
michael@0 675 "undefined" !== typeof CrossSlide && !this.noContext) {
michael@0 676 this._xslideHandler = new CrossSlide.Handler(this, {
michael@0 677 REARRANGESTART: this.crossSlideBoundary
michael@0 678 });
michael@0 679 }
michael@0 680 ]]>
michael@0 681 </body>
michael@0 682 </method>
michael@0 683
michael@0 684 <method name="disableCrossSlide">
michael@0 685 <body>
michael@0 686 <![CDATA[
michael@0 687 if (this._xslideHandler) {
michael@0 688 this.removeEventListener("touchstart", this._xslideHandler);
michael@0 689 this.removeEventListener("touchmove", this._xslideHandler);
michael@0 690 this.removeEventListener("touchend", this._xslideHandler);
michael@0 691 this._xslideHandler = null;
michael@0 692 }
michael@0 693 ]]>
michael@0 694 </body>
michael@0 695 </method>
michael@0 696
michael@0 697 <property name="tileWidth" readonly="true" onget="return this._itemSize.width"/>
michael@0 698 <property name="tileHeight" readonly="true" onget="return this._itemSize.height"/>
michael@0 699 <field name="_tileStyleSheetName">"tiles.css"</field>
michael@0 700 <method name="_getTileSizes">
michael@0 701 <body>
michael@0 702 <![CDATA[
michael@0 703 // Tile sizes are constants, this avoids the need to measure a rendered item before grid layout
michael@0 704 // The defines.inc used by the theme CSS is the single source of truth for these values
michael@0 705 // This method locates and parses out (just) those dimensions from the stylesheet
michael@0 706
michael@0 707 let typeSizes = this.ownerDocument.defaultView._richgridTileSizes;
michael@0 708 if (typeSizes && typeSizes["default"]) {
michael@0 709 return typeSizes;
michael@0 710 }
michael@0 711
michael@0 712 // cache sizes on the global window object, for reuse between bound nodes
michael@0 713 typeSizes = this.ownerDocument.defaultView._richgridTileSizes = {};
michael@0 714
michael@0 715 let sheets = this.ownerDocument.styleSheets;
michael@0 716 // The (first matching) rules that will give us tile type => width/height values
michael@0 717 // The keys in this object are string-matched against the selectorText
michael@0 718 // of rules in our stylesheet. Quoted values in a selector will always use " not '
michael@0 719 let typeSelectors = {
michael@0 720 'richgriditem' : "default",
michael@0 721 'richgriditem[tiletype="thumbnail"]': "thumbnail",
michael@0 722 'richgriditem[search]': "search",
michael@0 723 'richgriditem[compact]': "compact"
michael@0 724 };
michael@0 725 let rules, sheet;
michael@0 726 for (let i=0; (sheet=sheets[i]); i++) {
michael@0 727 if (sheet.href && sheet.href.endsWith( this._tileStyleSheetName )) {
michael@0 728 rules = sheet.cssRules;
michael@0 729 break;
michael@0 730 }
michael@0 731 }
michael@0 732 if (rules) {
michael@0 733 // walk the stylesheet rules until we've matched all our selectors
michael@0 734 for (let i=0, rule;(rule=rules[i]); i++) {
michael@0 735 let type = rule.selectorText && typeSelectors[rule.selectorText];
michael@0 736 if (type) {
michael@0 737 let sizes = typeSizes[type] = {};
michael@0 738 typeSelectors[type] = null;
michael@0 739 delete typeSelectors[type];
michael@0 740 // we assume px unit for tile dimension values
michael@0 741 sizes.width = parseInt(rule.style.getPropertyValue("width"));
michael@0 742 sizes.height = parseInt(rule.style.getPropertyValue("height"));
michael@0 743 }
michael@0 744 if (!Object.keys(typeSelectors).length)
michael@0 745 break;
michael@0 746 }
michael@0 747 } else {
michael@0 748 throw new Error("Failed to find stylesheet to parse out richgriditem dimensions\n");
michael@0 749 }
michael@0 750 return typeSizes;
michael@0 751 ]]>
michael@0 752 </body>
michael@0 753 </method>
michael@0 754
michael@0 755 <method name="_isIndexInBounds">
michael@0 756 <parameter name="anIndex"/>
michael@0 757 <body>
michael@0 758 <![CDATA[
michael@0 759 return anIndex >= 0 && anIndex < this.itemCount;
michael@0 760 ]]>
michael@0 761 </body>
michael@0 762 </method>
michael@0 763
michael@0 764 <method name="_createItemElement">
michael@0 765 <parameter name="aLabel"/>
michael@0 766 <parameter name="aValue"/>
michael@0 767 <body>
michael@0 768 <![CDATA[
michael@0 769 let item = this.ownerDocument.createElement("richgriditem");
michael@0 770 if (aValue) {
michael@0 771 item.setAttribute("value", aValue);
michael@0 772 }
michael@0 773 if (aLabel) {
michael@0 774 item.setAttribute("label", aLabel);
michael@0 775 }
michael@0 776 if (this.hasAttribute("tiletype")) {
michael@0 777 item.setAttribute("tiletype", this.getAttribute("tiletype"));
michael@0 778 }
michael@0 779 return item;
michael@0 780 ]]>
michael@0 781 </body>
michael@0 782 </method>
michael@0 783
michael@0 784 <method name="_fireEvent">
michael@0 785 <parameter name="aType"/>
michael@0 786 <body>
michael@0 787 <![CDATA[
michael@0 788 switch (aType) {
michael@0 789 case "select" :
michael@0 790 case "selectionchange" :
michael@0 791 if (this.suppressOnSelect)
michael@0 792 return;
michael@0 793 break;
michael@0 794 case "arranged" :
michael@0 795 break;
michael@0 796 }
michael@0 797
michael@0 798 let event = document.createEvent("Events");
michael@0 799 event.initEvent(aType, true, true);
michael@0 800 this.dispatchEvent(event);
michael@0 801 ]]>
michael@0 802 </body>
michael@0 803 </method>
michael@0 804
michael@0 805 <method name="bendItem">
michael@0 806 <parameter name="aItem"/>
michael@0 807 <parameter name="aEvent"/>
michael@0 808 <body><![CDATA[
michael@0 809 // apply the transform to the contentBox element of the item
michael@0 810 let bendNode = this.isItem(aItem) ? aItem._contentBox : null;
michael@0 811 if (!bendNode || aItem.hasAttribute("bending"))
michael@0 812 return;
michael@0 813
michael@0 814 let event = aEvent;
michael@0 815 let rect = bendNode.getBoundingClientRect();
michael@0 816 let angle;
michael@0 817 let x = (event.clientX - rect.left) / rect.width;
michael@0 818 let y = (event.clientY - rect.top) / rect.height;
michael@0 819 let perspective = '450px';
michael@0 820 // scaling factors for the angle of deflection,
michael@0 821 // based on the aspect-ratio of the tile
michael@0 822 let aspectRatio = rect.width/rect.height;
michael@0 823 let deflectX = 10 * Math.ceil(1/aspectRatio);
michael@0 824 let deflectY = 10 * Math.ceil(aspectRatio);
michael@0 825
michael@0 826 if (Math.abs(x - .5) < .1 && Math.abs(y - .5) < .1) {
michael@0 827 bendNode.style.transform = "perspective("+perspective+") translateZ(-10px)";
michael@0 828 }
michael@0 829 else if (x > y) {
michael@0 830 if (1 - y > x) {
michael@0 831 angle = Math.ceil((.5 - y) * deflectY);
michael@0 832 bendNode.style.transform = "perspective("+perspective+") rotateX(" + angle + "deg)";
michael@0 833 bendNode.style.transformOrigin = "center bottom";
michael@0 834 } else {
michael@0 835 angle = Math.ceil((x - .5) * deflectX);
michael@0 836 bendNode.style.transform = "perspective("+perspective+") rotateY(" + angle + "deg)";
michael@0 837 bendNode.style.transformOrigin = "left center";
michael@0 838 }
michael@0 839 } else {
michael@0 840 if (1 - y < x) {
michael@0 841 angle = -Math.ceil((y - .5) * deflectY);
michael@0 842 bendNode.style.transform = "perspective("+perspective+") rotateX(" + angle + "deg)";
michael@0 843 bendNode.style.transformOrigin = "center top";
michael@0 844 } else {
michael@0 845 angle = -Math.ceil((.5 - x) * deflectX);
michael@0 846 bendNode.style.transform = "perspective("+perspective+") rotateY(" + angle + "deg)";
michael@0 847 bendNode.style.transformOrigin = "right center";
michael@0 848 }
michael@0 849 }
michael@0 850 // mark when bend effect is applied
michael@0 851 aItem.setAttribute("bending", true);
michael@0 852 ]]></body>
michael@0 853 </method>
michael@0 854
michael@0 855 <method name="unbendItem">
michael@0 856 <parameter name="aItem"/>
michael@0 857 <body><![CDATA[
michael@0 858 // clear the 'bend' transform on the contentBox element of the item
michael@0 859 let bendNode = 'richgriditem' == aItem.nodeName && aItem._contentBox;
michael@0 860 if (bendNode && aItem.hasAttribute("bending")) {
michael@0 861 bendNode.style.removeProperty('transform');
michael@0 862 bendNode.style.removeProperty('transformOrigin');
michael@0 863 aItem.removeAttribute("bending");
michael@0 864 }
michael@0 865 ]]></body>
michael@0 866 </method>
michael@0 867 </implementation>
michael@0 868 <handlers>
michael@0 869 <!-- item bend effect handlers -->
michael@0 870 <handler event="mousedown" button="0" phase="capturing" action="this.bendItem(event.target, event)"/>
michael@0 871 <handler event="touchstart" action="this.bendItem(event.target, event.touches[0])"/>
michael@0 872 <handler event="mouseup" button="0" action="this.unbendItem(event.target)"/>
michael@0 873 <handler event="mouseout" button="0" action="this.unbendItem(event.target)"/>
michael@0 874 <handler event="touchend" action="this.unbendItem(event.target)"/>
michael@0 875 <handler event="touchcancel" action="this.unbendItem(event.target)"/>
michael@0 876 <!-- /item bend effect handler -->
michael@0 877
michael@0 878 <handler event="context-action">
michael@0 879 <![CDATA[
michael@0 880 // context-action is an event fired by the appbar typically
michael@0 881 // which directs us to do something to the selected tiles
michael@0 882 switch (event.action) {
michael@0 883 case "clear":
michael@0 884 this.selectNone();
michael@0 885 break;
michael@0 886 default:
michael@0 887 if (this.controller && this.controller.doActionOnSelectedTiles) {
michael@0 888 this.controller.doActionOnSelectedTiles(event.action, event);
michael@0 889 }
michael@0 890 }
michael@0 891 ]]>
michael@0 892 </handler>
michael@0 893 <handler event="MozCrossSliding">
michael@0 894 <![CDATA[
michael@0 895 // MozCrossSliding is swipe gesture across a tile
michael@0 896 // The tile should follow the drag to reinforce the gesture
michael@0 897 // (with inertia/speedbump behavior)
michael@0 898 let state = event.crossSlidingState;
michael@0 899 let thresholds = this._xslideHandler.thresholds;
michael@0 900 let transformValue;
michael@0 901 switch (state) {
michael@0 902 case "cancelled":
michael@0 903 this.unbendItem(event.target);
michael@0 904 event.target.removeAttribute('crosssliding');
michael@0 905 // hopefully nothing else is transform-ing the tile
michael@0 906 event.target.style.removeProperty('transform');
michael@0 907 break;
michael@0 908 case "dragging":
michael@0 909 case "selecting":
michael@0 910 // remove bend/depress effect when a cross-slide begins
michael@0 911 this.unbendItem(event.target);
michael@0 912
michael@0 913 event.target.setAttribute("crosssliding", true);
michael@0 914 // just track the mouse in the initial phases of the drag gesture
michael@0 915 transformValue = (event.direction=='x') ?
michael@0 916 'translateX('+event.delta+'px)' :
michael@0 917 'translateY('+event.delta+'px)';
michael@0 918 event.target.style.transform = transformValue;
michael@0 919 break;
michael@0 920 case "selectSpeedBumping":
michael@0 921 case "speedBumping":
michael@0 922 event.target.setAttribute('crosssliding', true);
michael@0 923 // in speed-bump phase, we add inertia to the drag
michael@0 924 let offset = CrossSlide.speedbump(
michael@0 925 event.delta,
michael@0 926 thresholds.SPEEDBUMPSTART,
michael@0 927 thresholds.SPEEDBUMPEND
michael@0 928 );
michael@0 929 transformValue = (event.direction=='x') ?
michael@0 930 'translateX('+offset+'px)' :
michael@0 931 'translateY('+offset+'px)';
michael@0 932 event.target.style.transform = transformValue;
michael@0 933 break;
michael@0 934 // "rearranging" case not used or implemented here
michael@0 935 case "completed":
michael@0 936 event.target.removeAttribute('crosssliding');
michael@0 937 event.target.style.removeProperty('transform');
michael@0 938 break;
michael@0 939 }
michael@0 940 ]]>
michael@0 941 </handler>
michael@0 942 <handler event="MozCrossSlideSelect">
michael@0 943 <![CDATA[
michael@0 944 if (this.noContext)
michael@0 945 return;
michael@0 946 this.toggleItemSelection(event.target);
michael@0 947 ]]>
michael@0 948 </handler>
michael@0 949 </handlers>
michael@0 950 </binding>
michael@0 951
michael@0 952 <binding id="richgrid-item">
michael@0 953 <content>
michael@0 954 <html:div anonid="anon-tile" class="tile-content" xbl:inherits="customImage">
michael@0 955 <html:div class="tile-start-container" xbl:inherits="customImage">
michael@0 956 <html:div class="tile-icon-box" anonid="anon-tile-icon-box"><xul:image anonid="anon-tile-icon" xbl:inherits="src=iconURI"/></html:div>
michael@0 957 </html:div>
michael@0 958 <html:div anonid="anon-tile-label" class="tile-desc" xbl:inherits="xbl:text=label"/>
michael@0 959 </html:div>
michael@0 960 </content>
michael@0 961
michael@0 962 <implementation>
michael@0 963 <property name="isBound" readonly="true" onget="return !!this._icon"/>
michael@0 964 <constructor>
michael@0 965 <![CDATA[
michael@0 966 this.refresh();
michael@0 967 ]]>
michael@0 968 </constructor>
michael@0 969 <property name="_contentBox" onget="return document.getAnonymousElementByAttribute(this, 'class', 'tile-content');"/>
michael@0 970 <property name="_textbox" onget="return document.getAnonymousElementByAttribute(this, 'class', 'tile-desc');"/>
michael@0 971 <property name="_top" onget="return document.getAnonymousElementByAttribute(this, 'class', 'tile-start-container');"/>
michael@0 972 <property name="_icon" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-tile-icon');"/>
michael@0 973 <property name="_iconBox" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-tile-icon-box');"/>
michael@0 974 <property name="_label" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-tile-label');"/>
michael@0 975 <property name="iconSrc"
michael@0 976 onset="this._icon.src = val; this.setAttribute('iconURI', val);"
michael@0 977 onget="return this._icon.src;" />
michael@0 978
michael@0 979 <property name="selected"
michael@0 980 onget="return this.hasAttribute('selected');"
michael@0 981 onset="if (val) this.setAttribute('selected', val); else this.removeAttribute('selected');" />
michael@0 982 <property name="url"
michael@0 983 onget="return this.getAttribute('value')"
michael@0 984 onset="this.setAttribute('value', val);"/>
michael@0 985 <property name="label"
michael@0 986 onget="return this._label.getAttribute('value')"
michael@0 987 onset="this.setAttribute('label', val); this._label.setAttribute('value', val);"/>
michael@0 988 <property name="pinned"
michael@0 989 onget="return this.hasAttribute('pinned')"
michael@0 990 onset="if (val) { this.setAttribute('pinned', val) } else this.removeAttribute('pinned');"/>
michael@0 991
michael@0 992 <method name="refresh">
michael@0 993 <body>
michael@0 994 <![CDATA[
michael@0 995 // Prevent an exception in case binding is not done yet.
michael@0 996 if (!this.isBound)
michael@0 997 return;
michael@0 998
michael@0 999 // Seed the binding properties from bound-node attribute values
michael@0 1000 // Usage: node.refresh()
michael@0 1001 // - reinitializes all binding properties from their associated attributes
michael@0 1002
michael@0 1003 this.iconSrc = this.getAttribute('iconURI');
michael@0 1004 this.color = this.getAttribute("customColor");
michael@0 1005 this.label = this.getAttribute('label');
michael@0 1006 // url getter just looks directly at attribute
michael@0 1007 // selected getter just looks directly at attribute
michael@0 1008 // pinned getter just looks directly at attribute
michael@0 1009 // value getter just looks directly at attribute
michael@0 1010 this._contextActions = null;
michael@0 1011 this.refreshBackgroundImage();
michael@0 1012 ]]>
michael@0 1013 </body>
michael@0 1014 </method>
michael@0 1015
michael@0 1016 <property name="control">
michael@0 1017 <getter><![CDATA[
michael@0 1018 let parent = this.parentNode;
michael@0 1019 while (parent && parent != this.ownerDocument.documentElement) {
michael@0 1020 if (parent instanceof Components.interfaces.nsIDOMXULSelectControlElement)
michael@0 1021 return parent;
michael@0 1022 parent = parent.parentNode;
michael@0 1023 }
michael@0 1024 return null;
michael@0 1025 ]]></getter>
michael@0 1026 </property>
michael@0 1027
michael@0 1028 <property name="color" onget="return this.getAttribute('customColor');">
michael@0 1029 <setter><![CDATA[
michael@0 1030 if (val) {
michael@0 1031 this.setAttribute("customColor", val);
michael@0 1032 this._contentBox.style.backgroundColor = val;
michael@0 1033
michael@0 1034 // overridden in tiles.css for non-thumbnail types
michael@0 1035 this._label.style.backgroundColor = val.replace(/rgb\(([^\)]+)\)/, 'rgba($1, 0.8)');
michael@0 1036
michael@0 1037 // Small icons get a border+background-color treatment.
michael@0 1038 // See tiles.css for large icon overrides
michael@0 1039 this._iconBox.style.borderColor = val.replace(/rgb\(([^\)]+)\)/, 'rgba($1, 0.6)');
michael@0 1040 this._iconBox.style.backgroundColor = this.hasAttribute("tintColor") ?
michael@0 1041 this.getAttribute("tintColor") : "#fff";
michael@0 1042 } else {
michael@0 1043 this.removeAttribute("customColor");
michael@0 1044 this._contentBox.style.removeProperty("background-color");
michael@0 1045 this._label.style.removeProperty("background-color");
michael@0 1046 this._iconBox.style.removeProperty("border-color");
michael@0 1047 this._iconBox.style.removeProperty("background-color");
michael@0 1048 }
michael@0 1049 ]]></setter>
michael@0 1050 </property>
michael@0 1051
michael@0 1052 <property name="backgroundImage" onget="return this.getAttribute('customImage');">
michael@0 1053 <setter><![CDATA[
michael@0 1054 if (val) {
michael@0 1055 this.setAttribute("customImage", val);
michael@0 1056 this._top.style.backgroundImage = val;
michael@0 1057 } else {
michael@0 1058 this.removeAttribute("customImage");
michael@0 1059 this._top.style.removeProperty("background-image");
michael@0 1060 }
michael@0 1061 ]]></setter>
michael@0 1062 </property>
michael@0 1063
michael@0 1064 <method name="refreshBackgroundImage">
michael@0 1065 <body><![CDATA[
michael@0 1066 if (!this.isBound)
michael@0 1067 return;
michael@0 1068 if (this.backgroundImage) {
michael@0 1069 this._top.style.removeProperty("background-image");
michael@0 1070 this._top.style.setProperty("background-image", this.backgroundImage);
michael@0 1071 }
michael@0 1072 ]]></body>
michael@0 1073 </method>
michael@0 1074
michael@0 1075 <field name="_contextActions">null</field>
michael@0 1076 <property name="contextActions">
michael@0 1077 <getter>
michael@0 1078 <![CDATA[
michael@0 1079 if (!this._contextActions) {
michael@0 1080 this._contextActions = new Set();
michael@0 1081 let actionSet = this._contextActions;
michael@0 1082 let actions = this.getAttribute("data-contextactions");
michael@0 1083 if (actions) {
michael@0 1084 actions.split(/[,\s]+/).forEach(function(verb){
michael@0 1085 actionSet.add(verb);
michael@0 1086 });
michael@0 1087 }
michael@0 1088 }
michael@0 1089 return this._contextActions;
michael@0 1090 ]]>
michael@0 1091 </getter>
michael@0 1092 </property>
michael@0 1093 </implementation>
michael@0 1094
michael@0 1095 <handlers>
michael@0 1096 <handler event="click" button="0">
michael@0 1097 <![CDATA[
michael@0 1098 // left-click/touch handler
michael@0 1099 this.control.handleItemClick(this, event);
michael@0 1100 // Stop this from bubbling, when the richgrid container
michael@0 1101 // receives click events, we blur the nav bar.
michael@0 1102 event.stopPropagation();
michael@0 1103 ]]>
michael@0 1104 </handler>
michael@0 1105
michael@0 1106 <handler event="contextmenu">
michael@0 1107 <![CDATA[
michael@0 1108 // fires for right-click, long-click and (keyboard) contextmenu input
michael@0 1109 // toggle the selected state of tiles in a grid
michael@0 1110 let gridParent = this.control;
michael@0 1111 if (!this.isBound || !gridParent)
michael@0 1112 return;
michael@0 1113 gridParent.handleItemContextMenu(this, event);
michael@0 1114 ]]>
michael@0 1115 </handler>
michael@0 1116 </handlers>
michael@0 1117 </binding>
michael@0 1118
michael@0 1119 <binding id="richgrid-empty-item">
michael@0 1120 <content>
michael@0 1121 <html:div anonid="anon-tile" class="tile-content"></html:div>
michael@0 1122 </content>
michael@0 1123 </binding>
michael@0 1124
michael@0 1125 </bindings>

mercurial