PDA

View Full Version : Ext.FloatLayout A layout which floats the child Components



Animal
10 Oct 2009, 9:03 AM
This layout floats each child item across its available width.

Like qooxdoo's Flow layout: http://demo.qooxdoo.org/current/demobrowser/#layout~Flow.html

When the next item won't fit in the width, it explicitly styles it with clear: left so that it wraps to position zero which would not happen if just naturally floated.



Ext.layout.FloatLayout = Ext.extend(Ext.layout.ContainerLayout, {
setContainer: function(ct) {
Ext.layout.FloatLayout.superclass.setContainer.apply(this, arguments);
ct.addClass('ux-float-layout-ct');
},

renderAll: function(ct) {
this.cr = this.container.getLayoutTarget().getBox(true).right;
Ext.layout.FloatLayout.superclass.renderAll.apply(this, arguments);
},

renderItem : function(c, position, target){
var r = c.rendered,
e, p, pe, w;
Ext.layout.FloatLayout.superclass.renderItem.apply(this, arguments);
e = c.getDomPositionEl();
if (!r) {
e.setStyle('float', 'left');
}
e.setStyle('clear', 'none');
w = e.getWidth() + e.getMargins('lr');
if (p = c.previousSibling()) {
pe = p.getDomPositionEl();
if ((pe.getBox().right + pe.getMargins('r') + w) > this.cr) {
e.setStyle('clear', 'left');
}
}
},

isValidParent: function() {
return false;
}
});
Ext.Container.LAYOUTS['float'] = Ext.layout.FloatLayout;


Post #4 contains correct layout embedded in an example page.

Condor
10 Oct 2009, 11:15 AM
ColumnLayout is already a floating layout (simply don't specify a columnWidth).

Animal
10 Oct 2009, 11:23 PM
It won't clear items though if you want a layout like http://blog.mozilla.com/webdev/2009/02/20/cross-browser-inline-block/

Animal
10 Oct 2009, 11:31 PM
To see this, run this example page in examples/menu



<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Toolbar with Menus</title>
<link rel="stylesheet" type="text/css" href="../../resources/css/ext-all.css" />
<script type="text/javascript" src="../../adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../../ext-all-debug.js"></script>
<script type="text/javascript" src="../ux/FieldLabeler.js"></script>
<script type="text/javascript" src="../form/states.js"></script>
<style type="text/css">
.ux-float-layout-ct {
position: relative;
}
.ux-float-layout-ct .ux-float-layout-sizer {
background: transparent none;
border: 0 none;
padding: 0;
margin: 0;
width: 0;
}

.ux-float-layout-ct .x-box-item {
position: absolute;
}

.test-item {
height: 77px;
width: 60px;
}

.show-icons .test-item-inner {
margin-left: 5px;
margin-top: 5px;
background-image: url(http://demo.qooxdoo.org/current/demobrowser/resource/qx/icon/Tango/48/devices/computer.png);
height:48px;
width:48px;
}

.hide-icons .test-item {
border: 1px solid #99BBE8;
height: auto;
margin: 3px;
padding: 2px 4px;
width: 40px;
}

.test-item-desc {
margin-top: 5px;
text-align: center;
}

.x-btn-text-icon em {
display: block;
height: 100%;
}

button.test-button {
height: 100%!important;
width: 100%!important;
background-image: url(http://demo.qooxdoo.org/current/demobrowser/resource/qx/icon/Tango/48/devices/computer.png)!important;
padding-left: 40px!important;
}

</style>
<script type="text/javascript">
Ext.override(Ext.Element, (function(){
var pxMatch = /(\d+\.?\d+)px/;

return {
// private.
// Return the addressable area of the element using explicit CSS style in preference to offsetHeight/offsetWidth.
// Optionally subtract padding if contentBox parameter is truthy
getStyleSize : function(contentBox){
var doc = document,
me = this,
d = me.dom,
extdom = Ext.lib.Dom,
isDoc = (d == doc || d == doc.body),
isBB, w, h, tbBorder = 0, lrBorder = 0,
tbPadding = 0, lrPadding = 0;
if (isDoc) {
return { width: extdom.getViewWidth(), height: extdom.getViewHeight() };
}
isBB = me.isBorderBox();
tbBorder = me.getBorderWidth('tb');
lrBorder = me.getBorderWidth('lr');
tbPadding = me.getPadding('tb');
lrPadding = me.getPadding('lr');

// Width calcs
// Try the style first, then offsetWidth
if (w = me.getStyle('width').match(pxMatch)){
if ((w = parseFloat(w[1])) && isBB){
// Style includes the padding and border if isBB
w -= (lrBorder + lrPadding);
}
w += lrPadding;
} else if (w = d.offsetWidth){
w -= lrBorder;
}
w && contentBox && (w -= lrPadding);

// Height calcs
// Try the style first, then offsetHeight
if (h = me.getStyle('height').match(pxMatch)){
if ((h = parseFloat(h[1])) && isBB){
// Style includes the padding and border if isBB
h -= (tbBorder + tbPadding);
}
h += tbPadding;
} else if (h = d.offsetHeight){
h -= tbBorder;
}
h && contentBox && (h -= tbPadding);

return {
width : w,
height : h
};
},

/**
* <p>Returns the dimensions of the element available to lay content out in.<p>
* <p>If the element (or any ancestor element) has CSS style <code>display : none</code>, the dimensions will be zero.</p>
* example:<pre><code>
var vpSize = Ext.getBody().getViewSize();

// all Windows created afterwards will have a default value of 90% height and 95% width
Ext.Window.override({
width: vpSize.width * 0.9,
height: vpSize.height * 0.95
});
// To handle window resizing you would have to hook onto onWindowResize.
</code></pre>
* @param {Boolean} contentBox True to return the W3 content box <i>within</i> the padding area of the element. False
* or omitted to return the full area of the element within the border. See <a href="http://www.w3.org/TR/CSS2/box.html">http://www.w3.org/TR/CSS2/box.html</a>
* @return {Object} An object containing the elements's area: <code>{width: &lt;element width>, height: &lt;element height>}</code>
*/
getViewSize : function(contentBox){
var doc = document,
me = this,
d = me.dom,
extdom = Ext.lib.Dom,
isDoc = (d == doc || d == doc.body),
ss = Ext.isIE ? me.getStyleSize(contentBox) : null, w, h;
if (isDoc) {
return { width: extdom.getViewWidth(), height: extdom.getViewHeight() };
}

// Width calcs
// Try clientWidth, then styleWidth
if (w = d.clientWidth){

// IE sometimes overestimates client size
Ext.isIE && w > ss.width && (w = ss.width);

if (contentBox){
w -= me.getPadding('lr');
}
} else {
if (!ss) ss = me.getStyleSize(contentBox);
w = ss.width;
}

// Height calcs
// Try clientHeight, then styleHeight
if (h = d.clientHeight){

// IE sometimes overestimates client size
Ext.isIE && h > ss.height && (h = ss.height);
if (contentBox){
h -= me.getPadding('tb');
}
} else {
if (!ss) ss = me.getStyleSize(contentBox);
h = ss.height;
}

return {
width : w,
height : h
};
}
};
})());

/**
* @class Ext.layout.FloatLayout
* @extends Ext.layout.BoxLayout
* <p>A layout that arranges items 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.</p>
* <p>Child items may use <code>columnWidth</code> config to specify a fraction of the available width that the item is to occupy.</p>
*/
Ext.layout.FloatLayout = Ext.extend(Ext.layout.BoxLayout, {
/**
* @cfg {Boolean} animate
* <p><code>true</code> to animate child items into their calculated positions.</p>
* <p>Defaults to <code>false</code>.</p>
*/

/**
* @cfg {String} verticalAlign
* <p>Vertical alignment of child items within each row. Defaults to 'middle'. Values may be</p>
* <code>'top'</code>, <code>'middle'</code> or <code>'bottom'</code>
* <p>May be overriden by an individual child item.</p>
*/
verticalAlign: 'middle',

/**
* @cfg {String} horizontalAlign
* <p>Horizontal alignment of child items within each row. Defaults to 'left'. Values may be</p>
* <code>'left'</code>, <code>'center'</code>, <code>'right'</code> or <code>'justify'</code>
* <p>May be overriden by an individual child item.</p>
*/
horizontalAlign: 'left',

/**
* @cfg {Number} horizontalSpacing
* <p>Horizontal space to leave between items.</p>
*/
horizontalSpacing: 0,

/**
* @cfg {Number} verticalSpacing
* <p>Vertical space to leave between rows.</p>
*/
verticalSpacing: 0,

/**
* @cfg {Number} rightLimit
* <p>Max rightmost point to which the right edge of items are allowed to reach before wrapping.</p>
*/
rightLimit: 0,

targetCls : 'ux-float-layout-ct',

setContainer: function(ct) {
var ce = ct.getLayoutTarget();
this.sizer = ce.insertFirst({
cls: 'ux-float-layout-sizer'
});
this.padding = this.parseMargins(this.padding||'0');
Ext.layout.FloatLayout.superclass.setContainer.apply(this, arguments);
},

renderAll : function(ct, target){
Ext.layout.BoxLayout.superclass.renderAll.apply(this, arguments);
},

onLayout: function() {
var ct = this.container,
cte = ct.getLayoutTarget(),
s = cte.getStyleSize(),
padding = Ext.apply({}, this.padding);

padding.top = this.getMarginValue(this.padding.top, s.height);
padding.right = this.getMarginValue(this.padding.right, s.width) - this.horizontalSpacing;
padding.bottom = this.getMarginValue(this.padding.bottom, s.height) - this.verticalSpacing;
padding.left = this.getMarginValue(this.padding.left, s.width);

var paddingLeft = padding.left + cte.getPadding('l'),
paddingTop = padding.top + cte.getPadding('t'),
rightLimit = s.width -= (padding.right + cte.getPadding('r')),
bottomLimit = s.height - (padding.bottom + cte.getPadding('b')),
rowStart = 0,
rowHeight = 0,
rowWidth = 0,
x = paddingLeft,
y = paddingTop,
items = this.getRenderedItems(ct),
l = items.length, i, c, lc, ce, cs, m, rm, r,
itemPos = [], firstFillItem;

// We never want to see horizontal scroll bars if possible
cte.setStyle('overflow-x', 'hidden');

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

if (this.rightLimit && this.rightLimit < rightLimit) {
rightLimit = this.rightLimit;
}

for (var i = 0, l = items.length; i < l; i++) {
c = items[i];
ce = c.getPositionEl();

// Capure first fill item
if (c.fillHeight) {
c.setHeight('auto');
if (!firstFillItem) {
firstFillItem = i;
}
}
cs = c.getSize();

// Allow floating point allocation of available width
if (c.columnWidth) {
c.setWidth(cs.width = (Math.round(s.width * c.columnWidth) - this.horizontalSpacing));
}
if (c.cellHeight) {
c.setHeight(cs.height = (Math.round(s.height * c.cellHeight) - this.verticalSpacing));
}

rm = ce.getMargins('r');
cs.width += ce.getMargins('lr');
cs.height += ce.getMargins('tb');
m = c.margins;
x += m.left;
r = x + cs.width;

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

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

// We are laying out an autoScroll Container, and this item is going to cause a vertical scrollbar:
// Adjust the right padding to account for that scrollbar, and reflow.
if (ct.autoScroll && !this.reflow && ((y + rowHeight) > bottomLimit)) {
r = this.padding.right;
this.padding.right += Ext.getScrollBarWidth();
this.reflow = true;
this.onLayout();
delete this.reflow;
this.padding.right = r;
return;
}

itemPos.push([x, y]);
x += cs.width + m.right + this.horizontalSpacing;
lc = c;
}

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

// Stretch the container heightwise.
this.sizer.setHeight(r = (y + rowHeight - cte.getPadding('t')));

if (firstFillItem) {
var spare = s.height - r - padding.bottom - padding.top;
rowHeight = 0;
for (i = firstFillItem; i < items.length && itemPos[i][1] == itemPos[firstFillItem][1]; i++) {
c = items[i];
ce = c.getPositionEl();
cs = c.getSize();
cs.height += ce.getMargins('tb');
rowHeight = Math.max(rowHeight, cs.height);
}
rowHeight += spare;
for (i = firstFillItem; i < items.length && itemPos[i][1] == itemPos[firstFillItem][1]; i++) {
c = items[i];
if (c.fillHeight) {
ce = c.getPositionEl();
cs = c.getSize();
c.setHeight(rowHeight - ce.getMargins('tb'));
}
}
} else {
}

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

getMarginValue: function(v, s) {
if (Ext.isNumber(v)) {
return (v < 1) ? s * v : v;
}
return 0;
},

parseMargins : function(v){
if(Ext.isNumber(v)){
v = v.toString();
}
var ms = v.split(' ');
var len = ms.length;
if(len == 1){
ms[1] = ms[2] = ms[3] = ms[0];
} else if(len == 2){
ms[2] = ms[0];
ms[3] = ms[1];
} else if(len == 3){
ms[3] = ms[1];
}
return {
top: parseFloat(ms[0], 10) || 0,
right: parseFloat(ms[1], 10) || 0,
bottom: parseFloat(ms[2], 10) || 0,
left: parseFloat(ms[3], 10) || 0
};
},

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

switch (this.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.getPositionEl().getMargins('tb');
itemPos[i][0] += alignmentIncrement;
alignmentIncrement += j;
switch (c.verticalAlign || this.verticalAlign) {
case 'middle':
case 'center':
itemPos[i][1] += (rowHeight - h) / 2;
break;
case 'bottom':
itemPos[i][1] += (rowHeight - h);
}
}
},

isValidParent: function(c, e) {
return c.getPositionEl().dom.parentNode === e.dom;
}
});
Ext.Container.LAYOUTS['float'] = Ext.layout.FloatLayout;

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)',
animate: false,
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, m, 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'
});
}

// 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();
m = /*c.margins ||*/ {top: 0, right: 0, bottom: 0, left: 0};
cs.width += c.getMargins('lr');
cs.height += c.getMargins('tb');
r = x + m.left + cs.width + m.right;

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

// 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(){
Ext.QuickTips.init();

// Menus can be prebuilt and passed by reference
var dateMenu = new Ext.menu.DateMenu({
handler: function(dp, date){
Ext.example.msg('Date Selected', 'You chose {0}.', date.format('M j, Y'));
}
});

var colorMenu = new Ext.menu.ColorMenu({
handler: function(cm, color){
Ext.example.msg('Color Selected', 'You chose {0}.', color);
}
});

var store = new Ext.data.ArrayStore({
fields: ['abbr', 'state'],
data : Ext.exampledata.states // from states.js
});

var combo = new Ext.form.ComboBox({
store: store,
displayField: 'state',
typeAhead: true,
mode: 'local',
triggerAction: 'all',
emptyText: 'Select a state...',
selectOnFocus: true,
width: 135,
getListParent: function() {
return this.el.up('.x-menu');
},
iconCls: 'no-icon'
});

var menu = new Ext.menu.Menu({
id: 'mainMenu',
style: {
overflow: 'visible' // For the Combo popup
},
items: [
combo, // A Field in a Menu
{
text: 'I like Ext',
checked: true, // when checked has a boolean value, it is assumed to be a CheckItem
checkHandler: onItemCheck
}, '-', {
text: 'Radio Options',
menu: { // <-- submenu by nested config object
items: [
// stick any markup in a menu
'<b class="menu-title">Choose a Theme</b>',
{
text: 'Aero Glass',
checked: true,
group: 'theme',
checkHandler: onItemCheck
}, {
text: 'Vista Black',
checked: false,
group: 'theme',
checkHandler: onItemCheck
}, {
text: 'Gray Theme',
checked: false,
group: 'theme',
checkHandler: onItemCheck
}, {
text: 'Default Theme',
checked: false,
group: 'theme',
checkHandler: onItemCheck
}
]
}
},{
text: 'Choose a Date',
iconCls: 'calendar',
menu: dateMenu // <-- submenu by reference
},{
text: 'Choose a Color',
menu: colorMenu // <-- submenu by reference
}
]
});

var tb = new Ext.Toolbar({
id: 'tb',
layout: {
type: 'float',
verticalSpacing: 4,
horizontalSpacing: 4
}
});

tb.add({
text:'Button w/ Menu',
iconCls: 'bmenu', // <-- icon
menu: menu // assign menu by instance
}, {
text: 'Users',
iconCls: 'user',
menu: {
xtype: 'menu',
plain: true,
items: {
xtype: 'buttongroup',
title: 'User options',
autoWidth: true,
columns: 2,
defaults: {
xtype: 'button',
scale: 'large',
width: '100%',
iconAlign: 'left'
},
items: [{
text: 'User<br/>manager',
iconCls: 'edit'
},{
iconCls: 'add',
width: 'auto',
tooltip: 'Add user'
},{
colspan: 2,
text: 'Import',
scale: 'small'
},{
colspan: 2,
text: 'Who is online?',
scale: 'small'
}]
}
}
},
new Ext.Toolbar.SplitButton({
text: 'Split Button',
handler: onButtonClick,
tooltip: {text:'This is a an example QuickTip for a toolbar item', title:'Tip Title'},
iconCls: 'blist',
// Menus can be built/referenced by using nested menu config objects
menu : {
items: [{
text: '<b>Bold</b>', handler: onItemClick
}, {
text: '<i>Italic</i>', handler: onItemClick
}, {
text: '<u>Underline</u>', handler: onItemClick
}, '-', {
text: 'Pick a Color',
handler: onItemClick,
menu: {
items: [
new Ext.ColorPalette({
listeners: {
select: function(cp, color){
Ext.example.msg('Color Selected', 'You chose {0}.', color);
}
}
}), '-',
{
text: 'More Colors...',
handler: onItemClick
}
]
}
}, {
text: 'Extellent!',
handler: onItemClick
}]
}
}), '-', {
text: 'Toggle Me',
enableToggle: true,
toggleHandler: onItemToggle,
pressed: true
});

menu.addSeparator();
// Menus have a rich api for
// adding and removing elements dynamically
var item = menu.add({
text: 'Dynamically added Item'
});
// items support full Observable API
item.on('click', onItemClick);

// items can easily be looked up
menu.add({
text: 'Disabled Item',
id: 'disableMe' // <-- Items can also have an id for easy lookup
// disabled: true <-- allowed but for sake of example we use long way below
});
// access items by id or index
menu.items.get('disableMe').disable();

// They can also be referenced by id in or components
tb.add('-', {
icon: 'list-items.gif', // icons can also be specified inline
cls: 'x-btn-icon',
tooltip: '<b>Quick Tips</b><br/>Icon only button with tooltip'
}, '-');

var scrollMenu = new Ext.menu.Menu();
for (var i = 0; i < 50; ++i){
scrollMenu.add({
text: 'Item ' + (i + 1)
});
}

tb.add(new Ext.form.TextField());

// scrollable menu
tb.add({
scale: 'large',
icon: 'preview.png',
cls: 'x-btn-text-icon',
text: 'Scrolling Menu',
menu: scrollMenu
});

// add a combobox to the toolbar
var combo = new Ext.form.ComboBox({
store: store,
displayField: 'state',
typeAhead: true,
mode: 'local',
triggerAction: 'all',
emptyText:'Select a state...',
selectOnFocus:true,
width:135
});
tb.addField(combo);

tb.add({
text: 'Another'
}, {
text: 'Yet another'
}, {
text: 'Still another'
}, {
text: 'The last one!'
});

// sample static data for the store
var myData = [
['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
['Altria Group Inc',83.81,0.28,0.34,'9/1 12:00am'],
['American Express Company',52.55,0.01,0.02,'9/1 12:00am'],
['American International Group, Inc.',64.13,0.31,0.49,'9/1 12:00am'],
['AT&T Inc.',31.61,-0.48,-1.54,'9/1 12:00am'],
['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
['Caterpillar Inc.',67.27,0.92,1.39,'9/1 12:00am'],
['Citigroup, Inc.',49.37,0.02,0.04,'9/1 12:00am'],
['E.I. du Pont de Nemours and Company',40.48,0.51,1.28,'9/1 12:00am'],
['Exxon Mobil Corp',68.1,-0.43,-0.64,'9/1 12:00am'],
['General Electric Company',34.14,-0.08,-0.23,'9/1 12:00am'],
['General Motors Corporation',30.27,1.09,3.74,'9/1 12:00am'],
['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
['Honeywell Intl Inc',38.77,0.05,0.13,'9/1 12:00am'],
['Intel Corporation',19.88,0.31,1.58,'9/1 12:00am'],
['International Business Machines',81.41,0.44,0.54,'9/1 12:00am'],
['Johnson & Johnson',64.72,0.06,0.09,'9/1 12:00am'],
['JP Morgan & Chase & Co',45.73,0.07,0.15,'9/1 12:00am'],
['McDonald\'s Corporation',36.76,0.86,2.40,'9/1 12:00am'],
['Merck & Co., Inc.',40.96,0.41,1.01,'9/1 12:00am'],
['Microsoft Corporation',25.84,0.14,0.54,'9/1 12:00am'],
['Pfizer Inc',27.96,0.4,1.45,'9/1 12:00am'],
['The Coca-Cola Company',45.07,0.26,0.58,'9/1 12:00am'],
['The Home Depot, Inc.',34.64,0.35,1.02,'9/1 12:00am'],
['The Procter & Gamble Company',61.91,0.01,0.02,'9/1 12:00am'],
['United Technologies Corporation',63.26,0.55,0.88,'9/1 12:00am'],
['Verizon Communications',35.57,0.39,1.11,'9/1 12:00am'],
['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
];

/**
* Custom function used for column renderer
* @param {Object} val
*/
function change(val){
if(val > 0){
return '<span style="color:green;">' + val + '</span>';
}else if(val < 0){
return '<span style="color:red;">' + val + '</span>';
}
return val;
}

/**
* Custom function used for column renderer
* @param {Object} val
*/
function pctChange(val){
if(val > 0){
return '<span style="color:green;">' + val + '%</span>';
}else if(val < 0){
return '<span style="color:red;">' + val + '%</span>';
}
return val;
}

// create the data store
var store = new Ext.data.ArrayStore({
fields: [
{name: 'company'},
{name: 'price', type: 'float'},
{name: 'change', type: 'float'},
{name: 'pctChange', type: 'float'},
{name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
]
});

// manually load local data
store.loadData(myData);

// create the Grid
var grid = new Ext.grid.GridPanel({
store: store,
columns: [
{id:'company',header: 'Company', width: 160, sortable: true, dataIndex: 'company'},
{header: 'Price', width: 75, sortable: true, renderer: 'usMoney', dataIndex: 'price'},
{header: 'Change', width: 75, sortable: true, renderer: change, dataIndex: 'change'},
{header: '% Change', width: 75, sortable: true, renderer: pctChange, dataIndex: 'pctChange'},
{header: 'Last Updated', width: 85, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
],
stripeRows: true,
autoExpandColumn: 'company',
border: false
});

new Ext.Window({
title: 'Test Float Toolbar',
height: 300,
width: 600,
tbar: tb,
layout: 'fit',
items: grid
}).show();

// functions to display feedback
function onButtonClick(btn){
Ext.example.msg('Button Click','You clicked the "{0}" button.', btn.text);
}

function onItemClick(item){
Ext.example.msg('Menu Click', 'You clicked the "{0}" menu item.', item.text);
}

function onItemCheck(item, checked){
Ext.example.msg('Item Check', 'You {1} the "{0}" menu item.', item.text, checked ? 'checked' : 'unchecked');
}

function onItemToggle(item, pressed){
Ext.example.msg('Button Toggled', 'Button "{0}" was toggled to {1}.', item.text, pressed);
}

var it = [];
for (var i = 0; i < 30; i++) {
it.push({
xtype: 'box',
autoEl: {
cls: 'test-item',
cn: [{
cls: 'test-item-inner'
}, {
cls: 'test-item-desc',
html: 'Item ' + (i + 1)
}]
}
});
}

w = new Ext.Window({
title: 'Flow Layout',
cls: 'show-icons',
x: 100,
y: 100,
width: 314,
height: 523,
autoScroll: true,
layout: {
type: 'float',
rightLimit: 500,
animate: true
},
items: it,
bbar: {
xtype: 'container',
layout: {
type: 'float',
horizontalAlign: 'justify'
},
cls: 'x-toolbar',
style: {
overflow: 'hidden',
paddingLeft: 0,
paddingRight: 0
},
defaultType: 'container',
defaults: {
style: {
width: '33%'
}
},
items: [{
layout: {
type: 'float',
padding: '0 0 0 2'
},
items: {
xtype: 'button',
text: 'View icons',
enableToggle: true,
pressed: true,
toggleHandler: function(b, state) {
if (state) {
w.addClass('show-icons');
w.removeClass('hide-icons');
} else {
w.addClass('hide-icons');
w.removeClass('show-icons');
}
w.doLayout();
}
}
}, {
layout: {
type: 'float',
horizontalAlign: 'center'
},
items: {
xtype: 'button',
text: 'Centre Button'
}
}, {
layout: {
type: 'float',
horizontalAlign: 'right',
padding: '0 2 0 0'
},
items: {
xtype: 'button',
text: 'Right Button'
}
}]
}
}).show();

it = [];
it.push({
xtype: 'button',
iconCls: 'test-button',
text: '1. First Button',
width: 140,
height: 56
});
it.push({
xtype: 'button',
iconCls: 'test-button',
text: '2. Second longer Button...',
width: 200,
height: 56,
clear: 'right'
});
it.push({
xtype: 'button',
iconCls: 'test-button',
text: '3. Third really, really long Button',
width: 240,
height: 100
});
it.push({
xtype: 'button',
iconCls: 'test-button',
text: 'Number 4',
width: 120,
height: 56,
verticalAlign: 'bottom'
});
it.push({
xtype: 'button',
text: '20px Margins around the great big 5th button!',
width: 240,
height: 100,
clear: 'right',
style: {
margin: '20px'
}
});
it.push({
xtype: 'button',
iconCls: 'test-button',
text: 'Number 6',
width: 120,
height: 56
});
it.push({
xtype: 'button',
text: '7th a wide, short button',
width: 120,
verticalAlign: 'top'
});

new Ext.Window({
title: 'Flow Layout',
x: 500,
y: 100,
width: 500,
height: 400,
autoScroll: true,
layout: {
type: 'float',
horizontalAlign: 'center',
animate: true
},
items: it
}).show();

it = [];
for (i = 0; i < 10; i++) {
it.push({
xtype: 'box',
columnWidth: 0.25,
html: 'columnWidth:0.25'
});
}
it[4].cellHeight = 0.5;
it[4].html += ', cellHeight: 0.5';
it[5].cellHeight = 0.5;
it[6].cellHeight = 0.5;
it[6].html += ', cellHeight: 0.5';
it[5].columnWidth = 0.5;
it[5].html = 'columnWidth:0.5, cellHeight: 0.5';
it[9].columnWidth = 0.5;
it[9].html = 'columnWidth:0.5';
it[7].fillHeight = true;
it[7].html += ',<br>fillHeight: true';

new Ext.Window({
title: 'A Flow layout window using padding: 0.125',
height: 400, width: 600,
minWidth: 530,
x: 200, y: 55,
autoScroll: true,
layout: {
type: 'float',
horizontalSpacing: 5,
verticalSpacing: 5,
verticalAlign: 'top',
padding: '5 0.125 5 0.125'
},
defaultType: 'box',
defaults: {
style: {
height: '20px',
padding: '5px 0 0 5px',
backgroundColor: 'red'
}
},
items: it
}).show();

it = [];
for (i = 0; i < 10; i++) {
it.push({ fieldLabel: 'Form item ' + (i + 1)});
}
new Ext.Window({
title: 'A Flow layout form using columnWidth: 0.5',
cls: 'the-flow-form',
height: 400, width: 600,
autoScroll: true,
layout: {
type: 'float',
horizontalSpacing: 5,
padding: '5 5 1 5'
},
defaultType: 'textfield',
defaults: {
plugins: [ Ext.ux.FieldLabeler ],
columnWidth: 0.5
},
items: it
}).show();

});
</script>
<link rel="stylesheet" type="text/css" href="menus.css" />
<link rel="stylesheet" type="text/css" href="../shared/examples.css" />
</head>
<body>
<h1>FloatLayout Toolbar with Menus</h1>
<p>The js is inline.</p>
</body>
</html>


Then change it to use layout: 'column', and then in Firebug, type



Ext.getCmp("tb").setWidth(500).doLayout();


You see it does not wrap properly because of uneven heights of the elements:

http://i131.photobucket.com/albums/p286/TimeTrialAnimal/columnlayouttoolbar.jpg

When you do this with layout: 'float', you get

http://i131.photobucket.com/albums/p286/TimeTrialAnimal/floatlayouttoolbar.jpg

Animal
11 Oct 2009, 8:33 AM
The layout class:



/**
* @class Ext.layout.FloatLayout
* @extends Ext.layout.BoxLayout
* <p>A layout that arranges items 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.</p>
* <p>Child items may use <code>columnWidth</code> config to specify a fraction of the available width that the item is to occupy.</p>
*/
Ext.layout.FloatLayout = Ext.extend(Ext.layout.BoxLayout, {
/**
* @cfg {Boolean} animate
* <p><code>true</code> to animate child items into their calculated positions.</p>
* <p>Defaults to <code>false</code>.</p>
*/

/**
* @cfg {String} verticalAlign
* <p>Vertical alignment of child items within each row. Defaults to 'middle'. Values may be</p>
* <code>'top'</code>, <code>'middle'</code> or <code>'bottom'</code>
* <p>May be overriden by an individual child item.</p>
*/
verticalAlign: 'middle',

/**
* @cfg {String} horizontalAlign
* <p>Horizontal alignment of child items within each row. Defaults to 'left'. Values may be</p>
* <code>'left'</code>, <code>'center'</code>, <code>'right'</code> or <code>'justify'</code>
* <p>May be overriden by an individual child item.</p>
*/
horizontalAlign: 'left',

/**
* @cfg {Number} horizontalSpacing
* <p>Horizontal space to leave between items.</p>
*/
horizontalSpacing: 0,

/**
* @cfg {Number} verticalSpacing
* <p>Vertical space to leave between rows.</p>
*/
verticalSpacing: 0,

/**
* @cfg {Number} rightLimit
* <p>Max rightmost point to which the right edge of items are allowed to reach before wrapping.</p>
*/
rightLimit: 0,

targetCls : 'ux-float-layout-ct',

setContainer: function(ct) {
var ce = ct.getLayoutTarget();
this.sizer = ce.insertFirst({
cls: 'ux-float-layout-sizer'
});
this.padding = this.parseMargins(this.padding||'0');
Ext.layout.FloatLayout.superclass.setContainer.apply(this, arguments);
},

renderAll : function(ct, target){
Ext.layout.BoxLayout.superclass.renderAll.apply(this, arguments);
},

onLayout: function() {
var ct = this.container,
cte = ct.getLayoutTarget(),
s = cte.getStyleSize(),
padding = Ext.apply({}, this.padding);

padding.top = this.getMarginValue(this.padding.top, s.height);
padding.right = this.getMarginValue(this.padding.right, s.width) - this.horizontalSpacing;
padding.bottom = this.getMarginValue(this.padding.bottom, s.height) - this.verticalSpacing;
padding.left = this.getMarginValue(this.padding.left, s.width);

var paddingLeft = padding.left + cte.getPadding('l'),
paddingTop = padding.top + cte.getPadding('t'),
rightLimit = s.width -= (padding.right + cte.getPadding('r')),
bottomLimit = s.height - (padding.bottom + cte.getPadding('b')),
rowStart = 0,
rowHeight = 0,
rowWidth = 0,
x = paddingLeft,
y = paddingTop,
items = this.getRenderedItems(ct),
l = items.length, i, c, lc, ce, cs, m, rm, r,
itemPos = [], firstFillItem;

// We never want to see horizontal scroll bars if possible
cte.setStyle('overflow-x', 'hidden');

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

if (this.rightLimit && this.rightLimit < rightLimit) {
rightLimit = this.rightLimit;
}

for (var i = 0, l = items.length; i < l; i++) {
c = items[i];
ce = c.getPositionEl();

// Capure first fill item
if (c.fillHeight) {
c.setHeight('auto');
if (!firstFillItem) {
firstFillItem = i;
}
}
cs = c.getSize();

// Allow floating point allocation of available width
if (c.columnWidth) {
c.setWidth(cs.width = (Math.round(s.width * c.columnWidth) - this.horizontalSpacing));
}
if (c.cellHeight) {
c.setHeight(cs.height = (Math.round(s.height * c.cellHeight) - this.verticalSpacing));
}

rm = ce.getMargins('r');
cs.width += ce.getMargins('lr');
cs.height += ce.getMargins('tb');
m = c.margins;
x += m.left;
r = x + cs.width;

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

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

// We are laying out an autoScroll Container, and this item is going to cause a vertical scrollbar:
// Adjust the right padding to account for that scrollbar, and reflow.
if (ct.autoScroll && !this.reflow && ((y + rowHeight) > bottomLimit)) {
r = this.padding.right;
this.padding.right += Ext.getScrollBarWidth();
this.reflow = true;
this.onLayout();
delete this.reflow;
this.padding.right = r;
return;
}

itemPos.push([x, y]);
x += cs.width + m.right + this.horizontalSpacing;
lc = c;
}

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

// Stretch the container heightwise.
this.sizer.setHeight(r = (y + rowHeight - cte.getPadding('t')));

if (firstFillItem) {
var spare = s.height - r - padding.bottom - padding.top;
rowHeight = 0;
for (i = firstFillItem; i < items.length && itemPos[i][1] == itemPos[firstFillItem][1]; i++) {
c = items[i];
ce = c.getPositionEl();
cs = c.getSize();
cs.height += ce.getMargins('tb');
rowHeight = Math.max(rowHeight, cs.height);
}
rowHeight += spare;
for (i = firstFillItem; i < items.length && itemPos[i][1] == itemPos[firstFillItem][1]; i++) {
c = items[i];
if (c.fillHeight) {
ce = c.getPositionEl();
cs = c.getSize();
c.setHeight(rowHeight - ce.getMargins('tb'));
}
}
} else {
}

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

getMarginValue: function(v, s) {
if (Ext.isNumber(v)) {
return (v < 1) ? s * v : v;
}
return 0;
},

parseMargins : function(v){
if(Ext.isNumber(v)){
v = v.toString();
}
var ms = v.split(' ');
var len = ms.length;
if(len == 1){
ms[1] = ms[2] = ms[3] = ms[0];
} else if(len == 2){
ms[2] = ms[0];
ms[3] = ms[1];
} else if(len == 3){
ms[3] = ms[1];
}
return {
top: parseFloat(ms[0], 10) || 0,
right: parseFloat(ms[1], 10) || 0,
bottom: parseFloat(ms[2], 10) || 0,
left: parseFloat(ms[3], 10) || 0
};
},

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

switch (this.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.getPositionEl().getMargins('tb');
itemPos[i][0] += alignmentIncrement;
alignmentIncrement += j;
switch (c.verticalAlign || this.verticalAlign) {
case 'middle':
case 'center':
itemPos[i][1] += (rowHeight - h) / 2;
break;
case 'bottom':
itemPos[i][1] += (rowHeight - h);
}
}
},

isValidParent: function(c, e) {
return c.getPositionEl().dom.parentNode === e.dom;
}
});
Ext.Container.LAYOUTS['float'] = Ext.layout.FloatLayout;



CSS:



.ux-float-layout-ct {
position: relative;
}
.ux-float-layout-ct .ux-float-layout-sizer {
background: transparent none;
border: 0 none;
padding: 0;
margin: 0;
width: 0;
}
.ux-float-layout-ct .x-box-item {
position: absolute;
}

Condor
11 Oct 2009, 9:22 AM
I like the first version better.

You could also add support for optional float:right and clear:both (based on item config).

Animal
11 Oct 2009, 9:28 AM
The float version has problems in IE. Try that newer test page which places the toolbar in a resizeable Window.

The uneven heights of the rows still caused items on the next row not to be able to flow to the left properly.

Animal
10 Jan 2010, 10:52 AM
Example in post #4 updated to use Ext 3.1.1 layout. Currently only available to those with SVN access.

Items animate into their new positions.

Items can be aligned within the row they are in. The layout takes a verticalAlign config.

Also optional horizontalSpacing and verticalSpacing configs to space items out.

Animal
11 Jan 2010, 3:25 AM
Example in post #4 updated to show dynamic, animated reflowing in a Window accounting for the appearance of scrollbars, to emulate http://demo.qooxdoo.org/current/demobrowser/#layout~Flow.html

Animal
11 Jan 2010, 9:53 AM
Now with horizontalAlign setting meaning that each row block may be aligned left, centered, right, or justified (evenly spaced).

animate: true/false allows optional animation upon reflow.

Also, each item may use a clear: 'left'/'right' config to force a row break.

The example page in #4 emulates qooxdoo's Flow layout demo now.

James Goddard
26 Apr 2010, 8:40 AM
This needs a tweak on onLayout:


onLayout: function() {
var ct = this.containe,
cte = ct.getLayoutTarget(),
s = cte.getStyleSize(),
padding = Ext.apply({}, this.padding);

this.renderAll(ct, cte);Without this line you cannot add components to the container after it is initially rendered.

Animal
27 Apr 2010, 4:21 AM
The following example works with the latest updates to the BoxLayout class. The code is a little smaller because BoxLayout handles calculating child positions and actually positioning them seperately, and we can use this.

Warning: You might need the latest SVN code for it to work.



<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Toolbar with Menus</title>
<link rel="stylesheet" type="text/css" href="../../resources/css/ext-all.css" />
<script type="text/javascript" src="../../adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../../ext-all-debug.js"></script>
<script type="text/javascript" src="../form/states.js"></script>
<style type="text/css">
.ux-float-layout-ct {
position: relative;
}
.ux-float-layout-ct .ux-float-layout-sizer {
background: transparent none;
border: 0 none;
padding: 0;
margin: 0;
width: 0;
}

.ux-float-layout-ct .x-box-inner {
overflow: auto;
}

.x-box-item {
position: absolute!important;
}

.test-item {
height: 77px;
width: 60px;
}

.test-item-inner {
margin-left: 5px;
margin-top: 5px;
background-image: url(http://demo.qooxdoo.org/current/demobrowser/resource/qx/icon/Tango/48/devices/computer.png);
height:48px;
width:48px;
}

.test-item-desc {
margin-top: 5px;
text-align: center;
}

.x-btn-text-icon em {
display: block;
height: 100%;
}

button.test-button {
height: 100%!important;
width: 100%!important;
background-image: url(http://demo.qooxdoo.org/current/demobrowser/resource/qx/icon/Tango/48/devices/computer.png)!important;
padding-left: 40px!important;
}

</style>
<script type="text/javascript">
Ext.override(Ext.Element, (function(){
var pxMatch = /(\d+\.?\d+)px/;

return {
// private.
// Return the addressable area of the element using explicit CSS style in preference to offsetHeight/offsetWidth.
// Optionally subtract padding if contentBox parameter is truthy
getStyleSize : function(contentBox){
var doc = document,
me = this,
d = me.dom,
extdom = Ext.lib.Dom,
isDoc = (d == doc || d == doc.body),
isBB, w, h, tbBorder = 0, lrBorder = 0,
tbPadding = 0, lrPadding = 0;
if (isDoc) {
return { width: extdom.getViewWidth(), height: extdom.getViewHeight() };
}
isBB = me.isBorderBox();
tbBorder = me.getBorderWidth('tb');
lrBorder = me.getBorderWidth('lr');
tbPadding = me.getPadding('tb');
lrPadding = me.getPadding('lr');

// Width calcs
// Try the style first, then offsetWidth
if (w = me.getStyle('width').match(pxMatch)){
if ((w = parseFloat(w[1])) && isBB){
// Style includes the padding and border if isBB
w -= (lrBorder + lrPadding);
}
w += lrPadding;
} else if (w = d.offsetWidth){
w -= lrBorder;
}
w && contentBox && (w -= lrPadding);

// Height calcs
// Try the style first, then offsetHeight
if (h = me.getStyle('height').match(pxMatch)){
if ((h = parseFloat(h[1])) && isBB){
// Style includes the padding and border if isBB
h -= (tbBorder + tbPadding);
}
h += tbPadding;
} else if (h = d.offsetHeight){
h -= tbBorder;
}
h && contentBox && (h -= tbPadding);

return {
width : w,
height : h
};
},

/**
* <p>Returns the dimensions of the element available to lay content out in.<p>
* <p>If the element (or any ancestor element) has CSS style <code>display : none</code>, the dimensions will be zero.</p>
* example:<pre><code>
var vpSize = Ext.getBody().getViewSize();

// all Windows created afterwards will have a default value of 90% height and 95% width
Ext.Window.override({
width: vpSize.width * 0.9,
height: vpSize.height * 0.95
});
// To handle window resizing you would have to hook onto onWindowResize.
</code></pre>
* @param {Boolean} contentBox True to return the W3 content box <i>within</i> the padding area of the element. False
* or omitted to return the full area of the element within the border. See <a href="http://www.w3.org/TR/CSS2/box.html">http://www.w3.org/TR/CSS2/box.html</a>
* @return {Object} An object containing the elements's area: <code>{width: &lt;element width>, height: &lt;element height>}</code>
*/
getViewSize : function(contentBox){
var doc = document,
me = this,
d = me.dom,
extdom = Ext.lib.Dom,
isDoc = (d == doc || d == doc.body),
ss = Ext.isIE ? me.getStyleSize(contentBox) : null, w, h;
if (isDoc) {
return { width: extdom.getViewWidth(), height: extdom.getViewHeight() };
}

// Width calcs
// Try clientWidth, then styleWidth
if (w = d.clientWidth){

// IE sometimes overestimates client size
Ext.isIE && w > ss.width && (w = ss.width);

if (contentBox){
w -= me.getPadding('lr');
}
} else {
if (!ss) ss = me.getStyleSize(contentBox);
w = ss.width;
}

// Height calcs
// Try clientHeight, then styleHeight
if (h = d.clientHeight){

// IE sometimes overestimates client size
Ext.isIE && h > ss.height && (h = ss.height);
if (contentBox){
h -= me.getPadding('tb');
}
} else {
if (!ss) ss = me.getStyleSize(contentBox);
h = ss.height;
}

return {
width : w,
height : h
};
}
};
})());

/**
* @class Ext.layout.FloatLayout
* @extends Ext.layout.BoxLayout
* <p>A layout that arranges items 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.
*/
Ext.layout.FloatLayout = Ext.extend(Ext.layout.BoxLayout, {
/**
* @cfg {Boolean} animate
* <p><code>true</code> to animate child items into their calculated positions.</p>
* <p>Defaults to <code>false</code>.</p>
*/

/**
* @cfg {String} verticalAlign
* <p>Vertical alignment of child items within each row. Defaults to 'middle'. Values may be</p>
* <code>'top'</code>, <code>'middle'</code> or <code>'bottom'</code>
* <p>May be overriden by an individual child item.</p>
*/
verticalAlign: 'middle',

/**
* @cfg {String} horizontalAlign
* <p>Horizontal alignment of child items within each row. Defaults to 'left'. Values may be</p>
* <code>'left'</code>, <code>'center'</code>, <code>'right'</code> or <code>'justify'</code>
* <p>May be overriden by an individual child item.</p>
*/
horizontalAlign: 'left',

/**
* @cfg {Number} horizontalSpacing
* <p>Horizontal space to leave between items.</p>
*/
horizontalSpacing: 0,

/**
* @cfg {Number} verticalSpacing
* <p>Vertical space to leave between rows.</p>
*/
verticalSpacing: 0,

targetCls : 'ux-float-layout-ct',

updateChildBoxes: function(b) {
for (var i = 0, l = b.length; i < l; i++) {
if (this.animate) {
Ext.lib.Anim.motion(b[i].component.getPositionEl().dom, {left: {to: b[i].left}, top: {to: b[i].top}}).animate();
} else {
b[i].component.setPosition([b[i].left, b[i].top]);
}
}
},

calculateChildBoxes: function() {
var ct = this.container,
ce = ct.getLayoutTarget(),
paddingLeft = this.padding.left + ce.getPadding('l'),
paddingTop = this.padding.top + ce.getPadding('t'),
s = ce.getStyleSize(),
rightLimit = (s.width -= (this.padding.right + ce.getPadding('r'))),
bottomLimit = s.height - (this.padding.bottom + ce.getPadding('b')),
rowStart = 0,
rowHeight = 0,
rowWidth = 0,
x = paddingLeft,
y = paddingTop,
items = this.getRenderedItems(ct),
l = items.length, i, c, cs, m, r,
boxes = [], a;

// We never want to see horizontal scroll bars if possible
ce.setStyle('overflow-x', 'hidden');

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

for (var i = 0, l = items.length; i < l; i++) {
c = items[i];
cs = c.getSize();
cs.width += c.getPositionEl().getMargins('lr');
cs.height += c.getPositionEl().getMargins('tb');
m = c.margins;
r = x + m.left + cs.width + m.right;

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

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

// We are laying out an autoScroll Container, and this item is going to cause a vertical scrollbar:
// Adjust the right padding to account for that scrollbar, and reflow.
if (ct.autoScroll && !this.reflow && ((y + rowHeight) > bottomLimit)) {
r = this.padding.right;
this.padding.right += Ext.getScrollBarWidth();
this.reflow = true;
this.onLayout(ct, ce);
delete this.reflow;
this.padding.right = r;
return this.childBoxCache;
}

boxes.push({
component: c,
left : x,
top : y,
height : cs.height,
width : cs.width
});
x = r + this.horizontalSpacing;
}

// Adjust the last row
this.adjustRow(boxes, rowStart, i - 1, rowHeight, rowWidth, s.width);

// Stretch the container heightwise.
if (!this.sizer) {
this.sizer = this.innerCt.insertFirst({
cls: 'ux-float-layout-sizer'
});
}
this.sizer.setHeight(y + rowHeight - paddingTop);

return {
boxes: boxes,
meta: {}
};
},

// Adjust vertical alignment within row.
// Adjust horizontal alignment if required.
adjustRow: function(boxes, rowStart, rowEnd, rowHeight, rowWidth, availWidth) {
var i, c, h, j = 0, spareWidth = availWidth - rowWidth, gaps = rowEnd - rowStart, alignmentIncrement = 0;

switch (this.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 = boxes[i].component;
h = c.getHeight() + c.getPositionEl().getMargins('tb');
boxes[i].left += alignmentIncrement;
alignmentIncrement += j;
switch (c.verticalAlign || this.verticalAlign) {
case 'middle':
case 'center':
boxes[i].top += (rowHeight - h) / 2;
break;
case 'bottom':
boxes[i].top += (rowHeight - h);
}
}
}
});
Ext.Container.LAYOUTS['float'] = Ext.layout.FloatLayout;

Ext.onReady(function(){
Ext.QuickTips.init();

// Menus can be prebuilt and passed by reference
var dateMenu = new Ext.menu.DateMenu({
handler: function(dp, date){
Ext.example.msg('Date Selected', 'You chose {0}.', date.format('M j, Y'));
}
});

var colorMenu = new Ext.menu.ColorMenu({
handler: function(cm, color){
Ext.example.msg('Color Selected', 'You chose {0}.', color);
}
});

var store = new Ext.data.ArrayStore({
fields: ['abbr', 'state'],
data : Ext.exampledata.states // from states.js
});

var combo = new Ext.form.ComboBox({
store: store,
displayField: 'state',
typeAhead: true,
mode: 'local',
triggerAction: 'all',
emptyText: 'Select a state...',
selectOnFocus: true,
width: 135,
getListParent: function() {
return this.el.up('.x-menu');
},
iconCls: 'no-icon'
});

var menu = new Ext.menu.Menu({
id: 'mainMenu',
style: {
overflow: 'visible' // For the Combo popup
},
items: [
combo, // A Field in a Menu
{
text: 'I like Ext',
checked: true, // when checked has a boolean value, it is assumed to be a CheckItem
checkHandler: onItemCheck
}, '-', {
text: 'Radio Options',
menu: { // <-- submenu by nested config object
items: [
// stick any markup in a menu
'<b class="menu-title">Choose a Theme</b>',
{
text: 'Aero Glass',
checked: true,
group: 'theme',
checkHandler: onItemCheck
}, {
text: 'Vista Black',
checked: false,
group: 'theme',
checkHandler: onItemCheck
}, {
text: 'Gray Theme',
checked: false,
group: 'theme',
checkHandler: onItemCheck
}, {
text: 'Default Theme',
checked: false,
group: 'theme',
checkHandler: onItemCheck
}
]
}
},{
text: 'Choose a Date',
iconCls: 'calendar',
menu: dateMenu // <-- submenu by reference
},{
text: 'Choose a Color',
menu: colorMenu // <-- submenu by reference
}
]
});

var tb = new Ext.Toolbar({
id: 'tb',
layout: {
type: 'float',
verticalSpacing: 4,
horizontalSpacing: 4
}
});

tb.add({
text:'Button w/ Menu',
iconCls: 'bmenu', // <-- icon
menu: menu // assign menu by instance
}, {
text: 'Users',
iconCls: 'user',
menu: {
xtype: 'menu',
plain: true,
items: {
xtype: 'buttongroup',
title: 'User options',
autoWidth: true,
columns: 2,
defaults: {
xtype: 'button',
scale: 'large',
width: '100%',
iconAlign: 'left'
},
items: [{
text: 'User<br/>manager',
iconCls: 'edit'
},{
iconCls: 'add',
width: 'auto',
tooltip: 'Add user'
},{
colspan: 2,
text: 'Import',
scale: 'small'
},{
colspan: 2,
text: 'Who is online?',
scale: 'small'
}]
}
}
},
new Ext.Toolbar.SplitButton({
text: 'Split Button',
handler: onButtonClick,
tooltip: {text:'This is a an example QuickTip for a toolbar item', title:'Tip Title'},
iconCls: 'blist',
// Menus can be built/referenced by using nested menu config objects
menu : {
items: [{
text: '<b>Bold</b>', handler: onItemClick
}, {
text: '<i>Italic</i>', handler: onItemClick
}, {
text: '<u>Underline</u>', handler: onItemClick
}, '-', {
text: 'Pick a Color',
handler: onItemClick,
menu: {
items: [
new Ext.ColorPalette({
listeners: {
select: function(cp, color){
Ext.example.msg('Color Selected', 'You chose {0}.', color);
}
}
}), '-',
{
text: 'More Colors...',
handler: onItemClick
}
]
}
}, {
text: 'Extellent!',
handler: onItemClick
}]
}
}), '-', {
text: 'Toggle Me',
enableToggle: true,
toggleHandler: onItemToggle,
pressed: true
});

menu.addSeparator();
// Menus have a rich api for
// adding and removing elements dynamically
var item = menu.add({
text: 'Dynamically added Item'
});
// items support full Observable API
item.on('click', onItemClick);

// items can easily be looked up
menu.add({
text: 'Disabled Item',
id: 'disableMe' // <-- Items can also have an id for easy lookup
// disabled: true <-- allowed but for sake of example we use long way below
});
// access items by id or index
menu.items.get('disableMe').disable();

// They can also be referenced by id in or components
tb.add('-', {
icon: 'list-items.gif', // icons can also be specified inline
cls: 'x-btn-icon',
tooltip: '<b>Quick Tips</b><br/>Icon only button with tooltip'
}, '-');

var scrollMenu = new Ext.menu.Menu();
for (var i = 0; i < 50; ++i){
scrollMenu.add({
text: 'Item ' + (i + 1)
});
}

tb.add(new Ext.form.TextField());

// scrollable menu
tb.add({
scale: 'large',
icon: 'preview.png',
cls: 'x-btn-text-icon',
text: 'Scrolling Menu',
menu: scrollMenu
});

// add a combobox to the toolbar
var combo = new Ext.form.ComboBox({
store: store,
displayField: 'state',
typeAhead: true,
mode: 'local',
triggerAction: 'all',
emptyText:'Select a state...',
selectOnFocus:true,
width:135
});
tb.addField(combo);

tb.add({
text: 'Another'
}, {
text: 'Yet another'
}, {
text: 'Still another'
}, {
text: 'The last one!'
});

// sample static data for the store
var myData = [
['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
['Altria Group Inc',83.81,0.28,0.34,'9/1 12:00am'],
['American Express Company',52.55,0.01,0.02,'9/1 12:00am'],
['American International Group, Inc.',64.13,0.31,0.49,'9/1 12:00am'],
['AT&T Inc.',31.61,-0.48,-1.54,'9/1 12:00am'],
['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
['Caterpillar Inc.',67.27,0.92,1.39,'9/1 12:00am'],
['Citigroup, Inc.',49.37,0.02,0.04,'9/1 12:00am'],
['E.I. du Pont de Nemours and Company',40.48,0.51,1.28,'9/1 12:00am'],
['Exxon Mobil Corp',68.1,-0.43,-0.64,'9/1 12:00am'],
['General Electric Company',34.14,-0.08,-0.23,'9/1 12:00am'],
['General Motors Corporation',30.27,1.09,3.74,'9/1 12:00am'],
['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
['Honeywell Intl Inc',38.77,0.05,0.13,'9/1 12:00am'],
['Intel Corporation',19.88,0.31,1.58,'9/1 12:00am'],
['International Business Machines',81.41,0.44,0.54,'9/1 12:00am'],
['Johnson & Johnson',64.72,0.06,0.09,'9/1 12:00am'],
['JP Morgan & Chase & Co',45.73,0.07,0.15,'9/1 12:00am'],
['McDonald\'s Corporation',36.76,0.86,2.40,'9/1 12:00am'],
['Merck & Co., Inc.',40.96,0.41,1.01,'9/1 12:00am'],
['Microsoft Corporation',25.84,0.14,0.54,'9/1 12:00am'],
['Pfizer Inc',27.96,0.4,1.45,'9/1 12:00am'],
['The Coca-Cola Company',45.07,0.26,0.58,'9/1 12:00am'],
['The Home Depot, Inc.',34.64,0.35,1.02,'9/1 12:00am'],
['The Procter & Gamble Company',61.91,0.01,0.02,'9/1 12:00am'],
['United Technologies Corporation',63.26,0.55,0.88,'9/1 12:00am'],
['Verizon Communications',35.57,0.39,1.11,'9/1 12:00am'],
['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
];

/**
* Custom function used for column renderer
* @param {Object} val
*/
function change(val){
if(val > 0){
return '<span style="color:green;">' + val + '</span>';
}else if(val < 0){
return '<span style="color:red;">' + val + '</span>';
}
return val;
}

/**
* Custom function used for column renderer
* @param {Object} val
*/
function pctChange(val){
if(val > 0){
return '<span style="color:green;">' + val + '%</span>';
}else if(val < 0){
return '<span style="color:red;">' + val + '%</span>';
}
return val;
}

// create the data store
var store = new Ext.data.ArrayStore({
fields: [
{name: 'company'},
{name: 'price', type: 'float'},
{name: 'change', type: 'float'},
{name: 'pctChange', type: 'float'},
{name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
]
});

// manually load local data
store.loadData(myData);

// create the Grid
var grid = new Ext.grid.GridPanel({
store: store,
columns: [
{id:'company',header: 'Company', width: 160, sortable: true, dataIndex: 'company'},
{header: 'Price', width: 75, sortable: true, renderer: 'usMoney', dataIndex: 'price'},
{header: 'Change', width: 75, sortable: true, renderer: change, dataIndex: 'change'},
{header: '% Change', width: 75, sortable: true, renderer: pctChange, dataIndex: 'pctChange'},
{header: 'Last Updated', width: 85, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
],
stripeRows: true,
autoExpandColumn: 'company',
border: false
});

new Ext.Window({
title: 'Test Float Toolbar',
height: 300,
width: 600,
tbar: tb,
layout: 'fit',
items: grid
}).show();

// functions to display feedback
function onButtonClick(btn){
Ext.example.msg('Button Click','You clicked the "{0}" button.', btn.text);
}

function onItemClick(item){
Ext.example.msg('Menu Click', 'You clicked the "{0}" menu item.', item.text);
}

function onItemCheck(item, checked){
Ext.example.msg('Item Check', 'You {1} the "{0}" menu item.', item.text, checked ? 'checked' : 'unchecked');
}

function onItemToggle(item, pressed){
Ext.example.msg('Button Toggled', 'Button "{0}" was toggled to {1}.', item.text, pressed);
}

var it = [];
for (var i = 0; i < 30; i++) {
it.push({
xtype: 'box',
autoEl: {
cls: 'test-item',
cn: [{
cls: 'test-item-inner'
}, {
cls: 'test-item-desc',
html: 'Item ' + (i + 1)
}]
}
});
}
new Ext.Window({
title: 'Flow Layout',
x: 100,
y: 100,
width: 314,
height: 521,
autoScroll: true,
layout: {
type: 'float',
animate: true
},
items: it,
bbar: {
xtype: 'container',
layout: {
type: 'hbox'
},
cls: 'x-toolbar',
style: {
overflow: 'hidden',
paddingLeft: 0,
paddingRight: 0,
},
defaultType: 'container',
items: [{
flex: 1,
autoHeight: true,
layout: {
type: 'float',
padding: '0 0 0 2'
},
items: {
xtype: 'button',
text: 'Left Button'
}
}, {
xtype: 'button',
text: 'Centre Button'
}, {
flex: 1,
autoHeight: true,
layout: {
type: 'float',
horizontalAlign: 'right',
padding: '0 2 0 0'
},
items: {
xtype: 'button',
text: 'Right Button'
}
}]
}
}).show();

it = [];
it.push({
xtype: 'button',
iconCls: 'test-button',
text: '1. First Button',
width: 140,
height: 56
});
it.push({
xtype: 'button',
iconCls: 'test-button',
text: '2. Second longer Button...',
width: 200,
height: 56,
clear: 'right'
});
it.push({
xtype: 'button',
iconCls: 'test-button',
text: '3. Third really, really long Button',
width: 240,
height: 100
});
it.push({
xtype: 'button',
iconCls: 'test-button',
text: 'Number 4',
width: 120,
height: 56,
verticalAlign: 'bottom'
});
it.push({
xtype: 'button',
text: '20px Margins around the great big 5th button!',
width: 240,
height: 100,
clear: 'right',
style: {
margin: '20px'
}
});
it.push({
xtype: 'button',
iconCls: 'test-button',
text: 'Number 6',
width: 120,
height: 56
});
it.push({
xtype: 'button',
text: '7th a wide, short button',
width: 120,
verticalAlign: 'top'
});

new Ext.Window({
title: 'Flow Layout',
x: 500,
y: 100,
width: 500,
height: 400,
autoScroll: true,
layout: {
type: 'float',
horizontalAlign: 'justify',
animate: true
},
items: it
}).show();

});
</script>
</head>
<body>
<h1>FloatLayout Toolbar with Menus</h1>
<p>The js is inline.</p>
</body>
</html>

Saint Father
27 May 2010, 10:30 PM
many thanks for this stuff, you saved me a lot of work! :)

Interesting observation:
The block "friendsBlock" comprises 8 pictures customised on width so to take places in 2 strings.

var it = [];
for (var i = 0; i < 8; i++) {
it.push({
xtype: 'box',
margins: '0 0 0 2px',
autoEl: {
tag: 'img',
src: '/images/friend_0' + (i + 1) + '.jpg',
alt: 'Pers #'+(i + 1),
}
});
}
var friendsBlock = {
layout: {
type: 'float',
animate: true
},
items: it,
};I have placed this block "friendsBlock" in the "Portal" block.

var indexCenterPanel = {
xtype:'portal',
items:[
{
columnWidth:.33,
items:[ friendsBlock ]
} ...And when I drag and drop the block "friendsBlock" (portal item) - pictures in the block "friendsBlock" take places already in 3 strings.
Changing width of pictures I has calculated that at calculation of a parent window width the skroll-bar width is considered.

At refresh the browser - the block "friendsBlock" is drawn again without skroll-bar width.

autoScroll: false - has solved this problem ... But it- in my opinion - is "not sports" :)

Animal
6 Jul 2010, 12:09 PM
Possibly image size is not known at the time the boxes are evaluated by the layout manager.

Images are loaded asynchronously.

You might have to use a load event on each image, and when it has fired <# of images> times, call doLayout.

mestrona
12 Aug 2010, 11:16 PM
great layout! I was looking for a floating layout and this one works very well. Thank you for your contribution.
I added one feature: tightFitting

with thightFitting: true it looks like that:
21941


here is the code

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Toolbar with Menus</title>



<!-- Ext Libraray CSS -->
<link rel="stylesheet" type="text/css" href="/_files/ext/resources/css/ext-all.css"/>


<!-- Ext 3.2.1 Library JS -->
<script type="text/javascript" src="/_files/ext/adapter/ext/ext-base-debug.js"></script>
<script type="text/javascript" src="/_files/ext/ext-all-debug.js"></script>
<script type="text/javascript" src="/_files/ext/examples/form/states.js"></script>

<style type="text/css">
.ux-float-layout-ct {
position: relative;
}
.ux-float-layout-ct .ux-float-layout-sizer {
background: transparent none;
border: 0 none;
padding: 0;
margin: 0;
width: 0;
}

.ux-float-layout-ct .x-box-inner {
overflow: visible;
}

.x-box-item {
position: absolute!important;
}

.test-item {
height: 77px;
width: 60px;
}

.test-item-inner {
margin-left: 5px;
margin-top: 5px;
background-image: url(http://demo.qooxdoo.org/current/demobrowser/resource/qx/icon/Tango/48/devices/computer.png);
height:48px;
width:48px;
}

.test-item-desc {
margin-top: 5px;
text-align: center;
}

.x-btn-text-icon em {
display: block;
height: 100%;
}

button.test-button {
height: 100%!important;
width: 100%!important;
background-image: url(http://demo.qooxdoo.org/current/demobrowser/resource/qx/icon/Tango/48/devices/computer.png)!important;
padding-left: 40px!important;
}

</style>
<script type="text/javascript">
Ext.override(Ext.Element, (function(){
var pxMatch = /(\d+\.?\d+)px/;

return {
// private.
// Return the addressable area of the element using explicit CSS style in preference to offsetHeight/offsetWidth.
// Optionally subtract padding if contentBox parameter is truthy
getStyleSize : function(contentBox){
var doc = document,
me = this,
d = me.dom,
extdom = Ext.lib.Dom,
isDoc = (d == doc || d == doc.body),
isBB, w, h, tbBorder = 0, lrBorder = 0,
tbPadding = 0, lrPadding = 0;
if (isDoc) {
return { width: extdom.getViewWidth(), height: extdom.getViewHeight() };
}
isBB = me.isBorderBox();
tbBorder = me.getBorderWidth('tb');
lrBorder = me.getBorderWidth('lr');
tbPadding = me.getPadding('tb');
lrPadding = me.getPadding('lr');

// Width calcs
// Try the style first, then offsetWidth
if (w = me.getStyle('width').match(pxMatch)){
if ((w = parseFloat(w[1])) && isBB){
// Style includes the padding and border if isBB
w -= (lrBorder + lrPadding);
}
w += lrPadding;
} else if (w = d.offsetWidth){
w -= lrBorder;
}
w && contentBox && (w -= lrPadding);

// Height calcs
// Try the style first, then offsetHeight
if (h = me.getStyle('height').match(pxMatch)){
if ((h = parseFloat(h[1])) && isBB){
// Style includes the padding and border if isBB
h -= (tbBorder + tbPadding);
}
h += tbPadding;
} else if (h = d.offsetHeight){
h -= tbBorder;
}
h && contentBox && (h -= tbPadding);

return {
width : w,
height : h
};
},

/**
* <p>Returns the dimensions of the element available to lay content out in.<p>
* <p>If the element (or any ancestor element) has CSS style <code>display : none</code>, the dimensions will be zero.</p>
* example:<pre><code>
var vpSize = Ext.getBody().getViewSize();

// all Windows created afterwards will have a default value of 90% height and 95% width
Ext.Window.override({
width: vpSize.width * 0.9,
height: vpSize.height * 0.95
});
// To handle window resizing you would have to hook onto onWindowResize.
</code></pre>
* @param {Boolean} contentBox True to return the W3 content box <i>within</i> the padding area of the element. False
* or omitted to return the full area of the element within the border. See <a href="http://www.w3.org/TR/CSS2/box.html">http://www.w3.org/TR/CSS2/box.html</a>
* @return {Object} An object containing the elements's area: <code>{width: &lt;element width>, height: &lt;element height>}</code>
*/
getViewSize : function(contentBox){
var doc = document,
me = this,
d = me.dom,
extdom = Ext.lib.Dom,
isDoc = (d == doc || d == doc.body),
ss = Ext.isIE ? me.getStyleSize(contentBox) : null, w, h;
if (isDoc) {
return { width: extdom.getViewWidth(), height: extdom.getViewHeight() };
}

// Width calcs
// Try clientWidth, then styleWidth
if (w = d.clientWidth){

// IE sometimes overestimates client size
Ext.isIE && w > ss.width && (w = ss.width);

if (contentBox){
w -= me.getPadding('lr');
}
} else {
if (!ss) ss = me.getStyleSize(contentBox);
w = ss.width;
}

// Height calcs
// Try clientHeight, then styleHeight
if (h = d.clientHeight){

// IE sometimes overestimates client size
Ext.isIE && h > ss.height && (h = ss.height);
if (contentBox){
h -= me.getPadding('tb');
}
} else {
if (!ss) ss = me.getStyleSize(contentBox);
h = ss.height;
}

return {
width : w,
height : h
};
}
};
})());

/**
* @class Ext.layout.FloatLayout
* @extends Ext.layout.BoxLayout
* <p>A layout that arranges items 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.
*/
Ext.layout.FloatLayout = Ext.extend(Ext.layout.BoxLayout, {
/**
* @cfg {Boolean} animate
* <p><code>true</code> to animate child items into their calculated positions.</p>
* <p>Defaults to <code>false</code>.</p>
*/

/**
* @cfg {String} verticalAlign
* <p>Vertical alignment of child items within each row. Defaults to 'middle'. Values may be</p>
* <code>'top'</code>, <code>'middle'</code> or <code>'bottom'</code>
* <p>May be overriden by an individual child item.</p>
*/
verticalAlign: 'middle',

/**
* @cfg {String} horizontalAlign
* <p>Horizontal alignment of child items within each row. Defaults to 'left'. Values may be</p>
* <code>'left'</code>, <code>'center'</code>, <code>'right'</code> or <code>'justify'</code>
* <p>May be overriden by an individual child item.</p>
*/
horizontalAlign: 'left',

/**
* @cfg {Number} horizontalSpacing
* <p>Horizontal space to leave between items.</p>
*/
horizontalSpacing: 0,

/**
* @cfg {Number} verticalSpacing
* <p>Vertical space to leave between rows.</p>
*/
verticalSpacing: 0,

/**
* @cfg {Boolean} verticalSpacing
* <p>Vertical space to leave between rows.</p>
*/
tightFitting: false,

targetCls : 'ux-float-layout-ct',

updateChildBoxes: function(b) {
for (var i = 0, l = b.length; i < l; i++) {
if (this.animate) {
Ext.lib.Anim.motion(b[i].component.getPositionEl().dom, {left: {to: b[i].left}, top: {to: b[i].top}}).animate();
} else {
b[i].component.setPosition([b[i].left, b[i].top]);
}
}
},

calculateChildBoxes: function() {
var ct = this.container,
ce = ct.getLayoutTarget(),
paddingLeft = this.padding.left + ce.getPadding('l'),
paddingTop = this.padding.top + ce.getPadding('t'),
s = ce.getStyleSize(),
rightLimit = (s.width -= (this.padding.right + ce.getPadding('r'))),
bottomLimit = s.height - (this.padding.bottom + ce.getPadding('b')),
rowStart = 0,
rowHeight = 0,
rowWidth = 0,
rowNum = 0,
x = paddingLeft,
y = paddingTop,
items = this.getRenderedItems(ct),
l = items.length, i, c, cs, m, r,
boxes = [], a, maxHeight;

// We never want to see horizontal scroll bars if possible
ce.setStyle('overflow-x', 'hidden');

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

for (var i = 0, l = items.length; i < l; i++) {
c = items[i];
cs = c.getSize();
cs.width += c.getPositionEl().getMargins('lr');
cs.height += c.getPositionEl().getMargins('tb');
m = c.margins;
r = x + m.left + cs.width + m.right;

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

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

// We are laying out an autoScroll Container, and this item is going to cause a vertical scrollbar:
// Adjust the right padding to account for that scrollbar, and reflow.
if (ct.autoScroll && !this.reflow && ((y + rowHeight) > bottomLimit)) {
r = this.padding.right;
this.padding.right += Ext.getScrollBarWidth();
this.reflow = true;
this.onLayout(ct, ce);
delete this.reflow;
this.padding.right = r;
return this.childBoxCache;
}

// adjust top value
maxHeight = 0;
if(rowNum > 0 && this.tightFitting) {
for (var j = 0; j < i; j++) {
if(boxes[j].row == (rowNum - 1)) {
if((boxes[j].left <= (x + cs.width)) && ((boxes[j].left + boxes[j].width) >= x)) {
if(maxHeight < boxes[j].height) {
maxHeight = boxes[j].height;
y = boxes[j].top + boxes[j].height + this.verticalSpacing;
}
}
}
}
}

boxes.push({
component: c,
left : x,
top : y,
height : cs.height,
width : cs.width,
row : rowNum
});
x = r + this.horizontalSpacing;
}

// Adjust the last row
this.adjustRow(boxes, rowStart, i - 1, rowHeight, rowWidth, s.width);

// Stretch the container heightwise.
if (!this.sizer) {
this.sizer = this.innerCt.insertFirst({
cls: 'ux-float-layout-sizer'
});
}
this.sizer.setHeight(y + rowHeight - paddingTop);

return {
boxes: boxes
};
},

// Adjust vertical alignment within row.
// Adjust horizontal alignment if required.
adjustRow: function(boxes, rowStart, rowEnd, rowHeight, rowWidth, availWidth) {
var i, c, h, j = 0, spareWidth = availWidth - rowWidth, gaps = rowEnd - rowStart, alignmentIncrement = 0;

switch (this.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 = boxes[i].component;
h = c.getHeight() + c.getPositionEl().getMargins('tb');
boxes[i].left += alignmentIncrement;
alignmentIncrement += j;
switch (c.verticalAlign || this.verticalAlign) {
case 'middle':
case 'center':
boxes[i].top += (rowHeight - h) / 2;
break;
case 'bottom':
boxes[i].top += (rowHeight - h);
}
}
}
});
Ext.Container.LAYOUTS['float'] = Ext.layout.FloatLayout;

Ext.onReady(function(){
Ext.QuickTips.init();

new Ext.Window({
title: 'Flow Layout',
x: 500,
y: 100,
width: 500,
height: 400,
autoScroll: true,
layout: {
type: 'float',
horizontalAlign: 'left',
verticalAlign: 'top',
animate: false,
horizontalSpacing: 10,
verticalSpacing: 10,
tightFitting: true,
padding: 10
},
items: [
{xtype:'panel',title:'Title',width:100,height:150,html:'test'},
{xtype:'panel',title:'Title',width:210,height:100,html:'test'},
{xtype:'panel',title:'Title',width:100,height:80,html:'test'},
{xtype:'panel',title:'Title',width:100,height:100,html:'test'},
{xtype:'panel',title:'Title',width:320,height:100,html:'test'},
{xtype:'panel',title:'Title',width:100,height:50,html:'test'},
{xtype:'panel',title:'Title',width:100,height:70,html:'test'},
{xtype:'panel',title:'Title',width:100,height:90,html:'test'}
]
}).show();

});
</script>
</head>
<body>
<h1>FloatLayout Toolbar with Menus</h1>
<p>The js is inline.</p>
</body>
</html>

Animal
12 Aug 2010, 11:51 PM
Cool feature!

I have been wondering about how to do that. How to allow overflowed components to fit more snugly.

What's the order there? 3 across the top, but then the "next row" is what, the long rectangle or the square. I couldn't work out an algorithm for this.

mestrona
12 Aug 2010, 11:57 PM
both. the second row is the square an the long rectangle. it really simple, i just added:


// adjust top value
maxHeight = 0;
if(rowNum > 0 && this.tightFitting) {
for (var j = 0; j < i; j++) {
if(boxes[j].row == (rowNum - 1)) {
if((boxes[j].left <= (x + cs.width)) && ((boxes[j].left + boxes[j].width) >= x)) {
if(maxHeight < boxes[j].height) {
maxHeight = boxes[j].height;
y = boxes[j].top + boxes[j].height + this.verticalSpacing;
}
}
}
}
}
i recalculated only the top-values of the boxes ;-)

I tested it in FF3.6.8 and IE8 so far. Works with all horizontal/vertical alignments and spacings.

Animal
13 Aug 2010, 12:03 AM
calculateChildBoxes just needs




return {
boxes: boxes,
meta: {}
};



at the end to be compatible with Ext 3.3.

I just tweaked the example to show which child items were which:

http://i131.photobucket.com/albums/p286/TimeTrialAnimal/flow-layout.jpg

Animal
13 Aug 2010, 12:05 AM
This is a very nice layout now. Does almost everything.

I think it should be adopted as part of the framework. But called "FlowLayout" which makes more sense.

I really like watching the animate: true config working. That should be a standard part of the layout system too!

We have app components where an arbitrary number informational blocks (They are FieldSets containing tabular consignment information) must be "flowed" across and then down in whatever space is available.

Of course I couldn't tight fit them before which wasted a lot of space. This will help a lot! Thanks!

mestrona
13 Aug 2010, 12:37 AM
you re right. its a nice animation, especially if you add more chaos:

{xtype:'panel',title:'Title',width:100,height:150,html:'test1'},
{xtype:'panel',title:'Title',width:210,height:100,html:'test2'},
{xtype:'panel',title:'Title',width:100,height:80,html:'test3'},
{xtype:'panel',title:'Title',width:100,height:100,html:'test4'},
{xtype:'panel',title:'Title',width:320,height:100,html:'test5'},
{xtype:'panel',title:'Title',width:80,height:80,html:'test6'},
{xtype:'panel',title:'Title',width:70,height:100,html:'test7'},
{xtype:'panel',title:'Title',width:30,height:100,html:'test8'},
{xtype:'panel',title:'Title',width:90,height:80,html:'test9'},
{xtype:'panel',title:'Title',width:105,height:100,html:'test10'},
{xtype:'panel',title:'Title',width:320,height:100,html:'test11'},
{xtype:'panel',title:'Title',width:100,height:50,html:'test12'},
{xtype:'panel',title:'Title',width:33,height:70,html:'test13'},
{xtype:'panel',title:'Title',width:100,height:90,html:'test14'}

Thank you for the hint regarding Ext 3.3!
I also would like to see FlowLayout as part of Ext.layout.* - it is the only layout, missing right from the start

heratech
14 May 2011, 2:57 PM
Any chance of this for Extjs 4.0?
thanks