| <!-- |
| Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
| The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
| Code distributed by Google as part of the polymer project is also |
| subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt |
| --> |
| |
| |
| <!-- |
| @group Polymer Core Elements |
| |
| @element core-layout-grid |
| @status beta |
| @homepage github.io |
| |
| TODO |
| --> |
| <link rel="import" href="../polymer/polymer.html"> |
| |
| <polymer-element name="core-layout-grid" attributes="nodes layout auto"> |
| <script> |
| (function() { |
| |
| Polymer('core-layout-grid', { |
| |
| nodes: null, |
| layout: null, |
| auto: false, |
| |
| created: function() { |
| this.layout = []; |
| }, |
| |
| nodesChanged: function() { |
| this.invalidate(); |
| }, |
| |
| layoutChanged: function() { |
| this.invalidate(); |
| }, |
| |
| autoNodes: function() { |
| this.nodes = this.parentNode.children.array().filter( |
| function(node) { |
| switch(node.localName) { |
| case 'core-layout-grid': |
| case 'style': |
| return false; |
| } |
| return true; |
| } |
| ); |
| }, |
| |
| invalidate: function() { |
| if (this.layout && this.layout.length) { |
| // job debounces layout, only letting it occur every N ms |
| this.layoutJob = this.job(this.layoutJob, this.relayout); |
| } |
| }, |
| |
| relayout: function() { |
| if (!this.nodes || this.auto) { |
| this.autoNodes(); |
| } |
| layout(this.layout, this.nodes); |
| this.asyncFire('core-layout'); |
| } |
| |
| }); |
| |
| // |
| |
| var lineParent; |
| |
| function line(axis, p, d) { |
| var l = document.createElement('line'); |
| var extent = (axis === 'left' ? 'width' : |
| (axis === 'top' ? 'height' : axis)); |
| l.setAttribute('extent', extent); |
| if (d < 0) { |
| axis = (axis === 'left' ? 'right' : |
| (axis === 'top' ? 'bottom' : axis)); |
| } |
| p = Math.abs(p); |
| l.style[axis] = p + 'px'; |
| l.style[extent] = '0px'; |
| lineParent.appendChild(l); |
| } |
| |
| var colCount, colOwners, rowCount, rowOwners; |
| |
| function matrixillate(matrix) { |
| // mesaure the matrix, must be rectangular |
| rowCount = matrix.length; |
| colCount = rowCount && matrix[0].length || 0; |
| // transpose matrix |
| var transpose = []; |
| for (var i=0; i<colCount; i++) { |
| var c = []; |
| for (var j=0; j<rowCount; j++) { |
| c.push(matrix[j][i]); |
| } |
| transpose.push(c); |
| } |
| // assign sizing control |
| colOwners = findOwners(matrix); |
| rowOwners = findOwners(transpose); |
| //console.log('colOwners', colOwners); |
| //console.log('rowOwners', rowOwners); |
| } |
| |
| function findOwners(matrix) { |
| var majCount = matrix.length; |
| var minCount = majCount && matrix[0].length || 0; |
| var owners = []; |
| // for each column (e.g.) |
| for (var i=0; i<minCount; i++) { |
| // array of contained areas |
| var contained = {}; |
| // look at each row to find a containing area |
| for (var j=0; j<majCount; j++) { |
| // get the row vector |
| var vector = matrix[j] |
| // node index at [i,j] |
| var nodei = vector[i]; |
| // if a node is there |
| if (nodei) { |
| // determine if it bounds this column |
| var owns = false; |
| if (i === 0) { |
| owns = (i === minCount-1) || (nodei !== vector[i+1]); |
| } else if (i === minCount - 1) { |
| owns = (i === 0) || (nodei !== vector[i-1]); |
| } else { |
| owns = nodei !== vector[i-1] && nodei !== vector[i+1]; |
| } |
| if (owns) { |
| contained[nodei] = 1; |
| } |
| } |
| // store the owners for this column |
| owners[i] = contained; |
| } |
| } |
| return owners; |
| } |
| |
| var nodes; |
| |
| function colWidth(i) { |
| for (var col in colOwners[i]) { |
| col = Number(col); |
| if (col === 0) { |
| return 96; |
| } |
| var node = nodes[col - 1]; |
| if (node.hasAttribute('h-flex') || node.hasAttribute('flex')) { |
| return -1; |
| } |
| var w = node.offsetWidth; |
| //console.log('colWidth(' + i + ') ==', w); |
| return w; |
| } |
| return -1; |
| } |
| |
| function rowHeight(i) { |
| for (var row in rowOwners[i]) { |
| row = Number(row); |
| if (row === 0) { |
| return 96; |
| } |
| var node = nodes[row - 1]; |
| if (node.hasAttribute('v-flex') || node.hasAttribute('flex')) { |
| return -1; |
| } |
| var h = node.offsetHeight; |
| //console.log('rowHeight(' + i + ') ==', h); |
| return h; |
| } |
| return -1; |
| } |
| |
| var m = 0; |
| |
| function railize(count, sizeFn) { |
| // |
| // create rails for `count` tracks using |
| // sizing function `sizeFn(trackNo)` |
| // |
| // for n tracks there are (n+1) rails |
| // |
| // |track|track|track| |
| // 0|->sz0|->sz1|<-sz2|0 |
| // |
| // |track|track|track| |
| // 0|->sz0| |<-sz2|0 |
| // |
| // there can be one elastic track per set |
| // |
| // |track|track|track|track| |
| // 0|-->s0|-->s1|<--s1|<--s2|0 |
| // |
| // sz1 spans multiple tracks which makes |
| // it elastic (it's underconstrained) |
| // |
| var rails = []; |
| var a = 0; |
| for (var i=0, x; i<count; i++) { |
| rails[i] = {p: a, s: 1}; |
| x = sizeFn(i) + m + m; |
| if (x == -1) { |
| break; |
| } |
| a += x; |
| } |
| if (i === count) { |
| rails[i] = {p: 0, s: -1}; |
| } |
| var b = 0; |
| for (var ii=count, x; ii>i; ii--) { |
| rails[ii] = {p: b, s: -1}; |
| x = sizeFn(ii - 1) + m + m; |
| if (x !== -1) { |
| b += x; |
| } |
| } |
| return rails; |
| } |
| |
| // TODO(sjmiles): this code tries to preserve actual position, |
| // so 'unposition' is really 'naturalize' or something |
| function unposition(box) { |
| var style = box.style; |
| //style.right = style.bottom = style.width = style.height = ''; |
| style.position = 'absolute'; |
| style.display = 'inline-block'; |
| style.boxSizing = style.mozBoxSizing = 'border-box'; |
| } |
| |
| function _position(style, maj, min, ext, a, b) { |
| style[maj] = style[min] = ''; |
| style[ext] = 'auto'; |
| if (a.s < 0 && b.s < 0) { |
| var siz = a.p - b.p - m - m; |
| style[ext] = siz + 'px'; |
| var c = 'calc(100% - ' + (b.p + siz + m) + 'px' + ')'; |
| style[maj] = '-webkit-' + c; |
| style[maj] = c; |
| } else if (b.s < 0) { |
| style[maj] = a.p + m + 'px'; |
| style[min] = b.p + m + 'px'; |
| } else { |
| style[maj] = a.p + m + 'px'; |
| style[ext] = b.p - a.p - m - m + 'px'; |
| } |
| } |
| |
| function position(elt, left, right, top, bottom) { |
| _position(elt.style, 'top', 'bottom', 'height', rows[top], |
| rows[bottom]); |
| _position(elt.style, 'left', 'right', 'width', columns[left], |
| columns[right]); |
| } |
| |
| function layout(matrix, anodes, alineParent) { |
| //console.group('layout'); |
| |
| lineParent = alineParent; |
| nodes = anodes; |
| matrixillate(matrix); |
| |
| nodes.forEach(unposition); |
| |
| columns = railize(colCount, colWidth); |
| rows = railize(rowCount, rowHeight); |
| |
| if (alineParent) { |
| //console.group('column rails'); |
| columns.forEach(function(c) { |
| //console.log(c.p, c.s); |
| line('left', c.p, c.s); |
| }); |
| //console.groupEnd(); |
| |
| //console.group('row rails'); |
| rows.forEach(function(r) { |
| //console.log(r.p, r.s); |
| line('top', r.p, r.s); |
| }); |
| //console.groupEnd(); |
| } |
| |
| //console.group('rail boundaries'); |
| nodes.forEach(function(node, i) { |
| // node indices are 1-based |
| var n = i + 1; |
| // boundary rails |
| var l, r, t = 1e10, b = -1e10; |
| matrix.forEach(function(vector, i) { |
| var f = vector.indexOf(n); |
| if (f > -1) { |
| l = f; |
| r = vector.lastIndexOf(n) + 1; |
| t = Math.min(t, i); |
| b = Math.max(b, i) + 1; |
| } |
| }); |
| if (l == undefined) { |
| //console.log('unused'); |
| node.style.position = 'absolute'; |
| var offscreen = node.getAttribute('offscreen'); |
| switch (offscreen) { |
| case 'basement': |
| node.style.zIndex = 0; |
| break; |
| case 'left': |
| case 'top': |
| node.style[offscreen] = node.offsetWidth * -2 + 'px'; |
| break; |
| case 'right': |
| node.style.left = node.offsetParent.offsetWidth |
| + node.offsetWidth + 'px'; |
| break; |
| case 'bottom': |
| node.style.top = node.parentNode.offsetHeight |
| + node.offsetHeight + 'px'; |
| break; |
| default: |
| node.style[Math.random() >= 0.5 ? 'left' : 'top'] = '-110%'; |
| } |
| //node.style.opacity = 0; |
| node.style.pointerEvents = 'none'; |
| } else { |
| node.style.pointerEvents = ''; |
| //node.style.opacity = ''; |
| //console.log(l, r, t, b); |
| position(node, l, r, t, b); |
| } |
| }); |
| //console.groupEnd(); |
| //console.groupEnd(); |
| } |
| |
| })(); |
| </script> |
| </polymer-element> |