PDA

View Full Version : Ext.Element.doLayout. Intelligent flow layout of child nodes



Animal
13 Jan 2010, 4:51 AM
Following on from http://www.extjs.com/forum/showthread.php?t=82595, I have taken the logic and allowed it to be applied just to Elements rather than Components.

So now, you can ask an Element to lay out its child nodes according to the same rules.

So for instance you might use this in a DataView to explicitly flow the data items across the view and have them clear all the way back to the left margin at end of line clearing any tall items on the line. Or vertically centering items on a line within the line height.

As an example, drop the following file into examples/view:



<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>DataView Example</title>
<link rel="stylesheet" type="text/css" href="../../resources/css/ext-all.css" />
<script type="text/javascript" src="../../adapter/ext/ext-base-debug.js"></script>
<script type="text/javascript" src="../../ext-all-debug.js"></script>
<script type="text/javascript" src="../ux/DataView-more.js"></script>
<script type="text/javascript">
var data = {
"images": [{
"name": "kids_hug2.jpg",
"size": 2476,
"lastmod": 1260763250000,
"url": "images\/thumbs\/kids_hug2.jpg"
},
{
"name": "zack_hat.jpg",
"size": 2323,
"lastmod": 1260763250000,
"url": "images\/thumbs\/zack_hat.jpg"
},
{
"name": "zack.jpg",
"size": 2901,
"lastmod": 1260763250000,
"url": "images\/thumbs\/zack.jpg"
},
{
"name": "zack_sink.jpg",
"size": 2303,
"lastmod": 1260763250000,
"url": "images\/thumbs\/zack_sink.jpg"
},
{
"name": "sara_pink.jpg",
"size": 2154,
"lastmod": 1260763250000,
"url": "images\/thumbs\/sara_pink.jpg"
},
{
"name": "dance_fever.jpg",
"size": 2067,
"lastmod": 1260763250000,
"url": "images\/thumbs\/dance_fever.jpg"
},
{
"name": "gangster_zack.jpg",
"size": 2115,
"lastmod": 1260763250000,
"url": "images\/thumbs\/gangster_zack.jpg"
},
{
"name": "zacks_grill.jpg",
"size": 2825,
"lastmod": 1260763250000,
"url": "images\/thumbs\/zacks_grill.jpg"
},
{
"name": "kids_hug.jpg",
"size": 2477,
"lastmod": 1260763250000,
"url": "images\/thumbs\/kids_hug.jpg"
},
{
"name": "zack_dress.jpg",
"size": 2645,
"lastmod": 1260763250000,
"url": "images\/thumbs\/zack_dress.jpg"
},
{
"name": "sara_pumpkin.jpg",
"size": 2588,
"lastmod": 1260763250000,
"url": "images\/thumbs\/sara_pumpkin.jpg"
},
{
"name": "sara_smile.jpg",
"size": 2410,
"lastmod": 1260763250000,
"url": "images\/thumbs\/sara_smile.jpg"
},
{
"name": "up_to_something.jpg",
"size": 2120,
"lastmod": 1260763250000,
"url": "images\/thumbs\/up_to_something.jpg"
}]
};

Ext.override(Ext.Element, {

/**
* Lay out the child elements of this element in a left to right flow similar to using HTML's <code>float: left</code> style.</p>
* <p>When a Component's width (including its padding) won't fit within the available width of the Container (within its padding)
* then the Component is wrapped to the beginning of a new line, clearing the tallest item on the current line.</p>
* <p>Items may be vertically aligned with the row they happen to fall within. A line's height is the height of its tallest child
* item (including its margins), and vertical alignment take place within this space. Vertical alignment is specified for the layout using the
* {@link #verticalAlign} config, and may be overriden for a single item by configuring an item with a verticalAlign value.</p>
* <p>Row content may be aligned within the available width of the Container using the {@link #horizontalAlign} config.</p>
* <p>Child items may be cleared left or right by configuring a <b>clear</b> value in the item.</p>
* <p>Row and column spacing may be specified using the {@link #verticalSpacing} and {@link #horizontalSpacing} options.
*/
doLayout: function(config) {
this.layout = Ext.apply({
itemSelector: '/*:not(.ux-float-layout-sizer)',
verticalAlign: 'middle',
horizontalAlign: 'left',
horizontalSpacing: 0,
verticalSpacing: 0,
padding: 0
}, config);
this.layout.padding = Ext.layout.ContainerLayout.prototype.parseMargins(this.layout.padding);

var items = this.query(this.layout.itemSelector),
me = this,
paddingLeft = this.layout.padding.left + me.getPadding('l'),
paddingTop = this.layout.padding.top + me.getPadding('t'),
s = me.getStyleSize(),
rightLimit = (s.width -= (this.layout.padding.right + me.getPadding('r'))),
bottomLimit = s.height - (this.layout.padding.bottom + me.getPadding('b')),
rowStart = 0,
rowHeight = 0,
rowWidth = 0,
x = paddingLeft,
y = paddingTop,
l = items.length, i, c, cs, r,
itemPos = [], a;

// Nothing to lay out.
if (!items.length) return;

if (!(me.sizer = this.child('.ux-float-layout-sizer'))) {
me.sizer = me.insertFirst({
cls: 'ux-float-layout-sizer',
style: {
width: 0,
'background-color': 'transparent',
margin: 0,
padding: 0,
border: '0 none'
}
});
}

// We never want to see horizontal scroll bars if possible
me.setStyle({
'overflow-x': 'hidden',
position: 'relative'
});

// Content Width.
s.width -= paddingLeft;

for (var i = 0, l = items.length; i < l; i++) {
c = (items[i] = Ext.get(items[i]));
c.setStyle({
position: 'absolute'
});
cs = c.getSize();
cs.width += c.getMargins('lr');
cs.height += c.getMargins('tb');
r = x + cs.width;

// This item won't fit on the row.
if ((r > rightLimit) || (c.getStyle('clear') == 'left') || ((i > 0) && items[i - 1].getStyle('clear') == 'right')) {
me.adjustRow(items, itemPos, rowStart, i - 1, rowHeight, rowWidth, s.width);
x = paddingLeft;
y += rowHeight + me.layout.verticalSpacing;
r = x + cs.width;
rowStart = i;
rowHeight = 0;
rowWidth = 0;
}

rowHeight = Math.max(rowHeight, cs.height);
rowWidth += cs.width;

// This item is going to cause a vertical scrollbar:
// Adjust the right padding to account for that scrollbar, and reflow.
if (!me.reflow && ((y + rowHeight) > bottomLimit)) {
r = me.layout.padding.right;
me.layout.padding.right += Ext.getScrollBarWidth();
me.reflow = true;
me.doLayout.apply(me, arguments);
delete me.reflow;
me.layout.padding.right = r;
return;
}

itemPos.push([x, y]);
x = r + me.layout.horizontalSpacing;
}

// Adjust the last row
me.adjustRow(items, itemPos, rowStart, i - 1, rowHeight, rowWidth, s.width);

// Stretch the container heightwise.
me.sizer.setHeight(y + rowHeight - me.getPadding('t'));

// Animate child items into place.
for (var i = 0, l = items.length; i < l; i++) {
if (me.layout.animate) {
a = Ext.lib.Anim.motion(items[i].dom, {left: {to: itemPos[i][0]}, top: {to: itemPos[i][1]}});
a.animate();
} else {
items[i].setStyle({
left: itemPos[i][0] + 'px',
top: itemPos[i][1] + 'px'
});
}
}
},

// Adjust vertical alignment within row.
// Adjust horizontal alignment if required.
adjustRow: function(items, itemPos, rowStart, rowEnd, rowHeight, rowWidth, availWidth) {
var me = this, i, c, h, j = 0, gaps = rowEnd - rowStart, spareWidth, alignmentIncrement = 0;
rowWidth += gaps * me.layout.horizontalSpacing;
spareWidth = availWidth - rowWidth;

switch (me.layout.horizontalAlign) {
case 'middle':
case 'center':
alignmentIncrement = Math.max(spareWidth / 2, 0);
break;
case 'right':
alignmentIncrement = Math.max(spareWidth, 0);
break;
case 'justify':
if (gaps) {
j = Math.max(spareWidth / gaps, 0);
}
}

for (i = rowStart; i <= rowEnd; i++) {
c = items[i];
h = c.getHeight() + c.getMargins('tb');
itemPos[i][0] += alignmentIncrement;
alignmentIncrement += j;
switch (c.dom.style.verticalAlign || me.layout.verticalAlign) {
case 'middle':
case 'center':
itemPos[i][1] += (rowHeight - h) / 2;
break;
case 'bottom':
itemPos[i][1] += (rowHeight - h);
}
}
}
});


Ext.onReady(function(){
var xd = Ext.data;

var store = new Ext.data.Store({
data: data,
reader: new Ext.data.JsonReader({
root: 'images'
},
['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date', dateFormat:'timestamp'}]
)
});

var tpl = new Ext.XTemplate(
'<tpl for=".">',
'<div class="image-block">',
'<div class="details">',
'<div class="details-info">',
'<b>Image Name: </b>',
'<span>{name} </span>',
'<b>Size: </b>',
'<span>{sizeString} </span>',
'<b>Last Modified: </b>',
'<span>{dateString}</span>',
'</div>',
'</div>',
'<div class="thumb-wrap" id="{name}">',
'<div class="thumb"><img src="{url}" title="{name}"></div>',
'<span class="x-editable">{shortName}</span>',
'</div>',
'</div>',
'</tpl>',
'<div class="x-clear"></div>'
);

panel = new Ext.Panel({
id: 'images-view',
frame: true,
width: 535,
height: 400,
collapsible: true,
layout: 'fit',
title: 'Simple DataView (0 items selected)',
renderTo: document.body,

items: v = new Ext.DataView({
store: store,
tpl: tpl,
autoHeight:true,
multiSelect: true,
cls: 'images-view',
overClass: 'x-view-over',
itemSelector: 'div.image-block',
emptyText: 'No images to display',

plugins: [
new Ext.DataView.DragSelector(),
new Ext.DataView.LabelEditor({dataIndex: 'name'})
],

prepareData: function(data){
data.shortName = Ext.util.Format.ellipsis(data.name, 15);
data.sizeString = Ext.util.Format.fileSize(data.size);
data.dateString = data.lastmod.format("m/d/Y g:i a");
return data;
},
refresh: function() {
Ext.DataView.prototype.refresh.apply(this, arguments);
this.el.doLayout({animate: true, itemSelector: 'div.image-block'});
},

listeners: {
selectionchange: {
fn: function(dv,nodes){
var l = nodes.length;
var s = l != 1 ? 's' : '';
panel.setTitle('Simple DataView ('+l+' item'+s+' selected)');
}
}
}
}),
bbar: ['View type:', {
xtype: 'combo',
editable: false,
triggerAction: 'all',
value: 'Thumbnail',
store: [
'Thumbnail',
'Detail'
],
listeners: {
select: onViewChange
}
}]
});
});

function onViewChange(c, rec, idx) {
if (idx == 0) {
v.el.addClass('images-view');
v.el.removeClass('detail-view');
v.el.doLayout.defer(1, v.el, [{animate: true, itemSelector: 'div.image-block'}]);
} else {
v.el.removeClass('images-view');
v.el.addClass('detail-view');
v.el.doLayout.defer(1, v.el, [{padding: 10, animate: true, itemSelector: 'div.image-block'}]);
}
}
</script>
<link rel="stylesheet" type="text/css" href="../shared/examples.css" />
<style type="text/css">
#images-view .x-panel-body{
background: white;
font: 11px Arial, Helvetica, sans-serif;
}
#images-view .thumb{
background: #dddddd;
padding: 3px;
}
#images-view .thumb img{
height: 60px;
width: 80px;
}
#images-view .thumb-wrap{
margin: 4px;
margin-right: 0;
padding: 5px;
}
#images-view .thumb-wrap span{
display: block;
overflow: hidden;
text-align: center;
}

#images-view .x-view-over{
border:1px solid #dddddd;
background: #efefef url(../../resources/images/default/grid/row-over.gif) repeat-x left top;
}

#images-view .x-view-selected{
background: #eff5fb url(images/selected.gif) no-repeat right bottom;
border:1px solid #99bbe8;
}
#images-view .x-view-selected .thumb{
background:transparent;
}

#images-view .loading-indicator {
font-size:11px;
background-image:url('../../resources/images/default/grid/loading.gif');
background-repeat: no-repeat;
background-position: left;
padding-left:20px;
margin:10px;
}

.images-view {
position: relative;
}

.images-view .image-block {
border: 1px solid transparent;
position: absolute;
}

.detail-view .image-block {
border: 1px solid transparent;
position: absolute;
clear: right;
}

.images-view .details {
display: none;
}

.detail-view .details {
display: block;
white-space: nowrap;
}

.detail-view .thumb-wrap {
display: none;
}

</style>
</head>
<body>
<script type="text/javascript" src="../shared/examples.js"></script><!-- EXAMPLES -->

<h1>DataView Example</h1>
<p>This example shows how to use an Ext.DataView. It demonstrates editable labels (click<br />
any of the photo labels), basic multi-select (using ctrl or shift) and drag selection.</p>
<br />
The source for this example can bie viewed by clicking on <a href="data-view.js">data-view.js</a>. <br />
It uses the plugins in the <a href="../ux/DataView-more.js">../ux/DataView-more.js</a> file.
</body>
</html>