boxmodel.lzx
<library>
<script when="immediate">
/** @access private */
class boxmodelSupport {
static const CAPABILITY_BOX_DIMENSION_OFFSET = {capability: 'css2boxmodel', isdimension: true, affectsoffset: true};
static const CAPABILITY_BOX = {capability: 'css2boxmodel'};
static const SIDE_DIMENSION_MAP = {top: 0, right: 1, bottom: 2, left: 3};
static const DIMENSION_SIDE_MAP = ['top', 'right', 'bottom', 'left'];
static const SPACE = new RegExp('\\s+');
static const DIMENSION_PATTERN = RegExp("^\\s*(\\d+(?:\\.\\d*)?|\\.\\d+)\\s*$");
static function camelCase(...parts) {
var str:String = parts[0];
for (var i = 1, l = parts.length; i < l; i++) {
var part:String = parts[i];
str += part.substring(0, 1).toUpperCase();
str += part.substring(1);
}
return str;
}
static function parseSideDimensions (value, node:LzNode, attribute:String) {
var dimensionmap = boxmodelSupport.SIDE_DIMENSION_MAP
// default dimensions per CSS.
var dimensions = value.split(boxmodelSupport.SPACE);
// Compute defaults
var l = dimensions.length;
// If right is omitted it is the same as top.
if (l <= dimensionmap.right) dimensions[dimensionmap.right] = dimensions[dimensionmap.top];
// If bottom is omitted it is the same as top.
if (l <= dimensionmap.bottom) dimensions[dimensionmap.bottom] = dimensions[dimensionmap.top];
// If left is omitted it is the same as right.
if (l <= dimensionmap.left) dimensions[dimensionmap.left] = dimensions[dimensionmap.right];
for (var i = 0, l = dimensions.length; i < l; i++) {
// TODO: accept a CSS dimension
dimensions[i] = parseFloat(dimensions[i]);
}
return dimensions;
}
static function unparseSideDimensions(value, node:LzNode, attribute:String) {
var dimensionmap = boxmodelSupport.SIDE_DIMENSION_MAP;
var str = '' + value[dimensionmap.top];
var needright = (value[dimensionmap.right] != value[dimensionmap.top]);
var needbottom = (value[dimensionmap.bottom] != value[dimensionmap.top]);
var needleft = (value[dimensionmap.left] != value[dimensionmap.right]);
if (needright || needbottom || needleft) {
str += ' ' + value[dimensionmap.right];
}
if (needbottom || needleft) {
str += ' ' + value[dimensionmap.bottom];
}
if (needleft) {
str += ' ' + value[dimensionmap.left];
}
return str;
}
};
</script>
<type name="boxsidedimensions">
<accept args="value, node:LzNode, attribute:String">
return boxmodelSupport.parseSideDimensions(value, node, attribute);
</accept>
<present args="value, node:LzNode, attribute:String">
return boxmodelSupport.unparseSideDimensions(value, node, attribute);
</present>
</type>
<mixin name="boxmodel">
<doc>
<tag name="shortdesc"><text>A mixin that adds CSS2 box model support to view.</text></tag>
<text>
<p>The <tagname>boxmodel</tagname> mixin adds CSS2 box model support to <sgmltag class="element" role="LzView"><view></sgmltag></p>
<p><tagname>boxmodel</tagname> implements a subset of the CSS2 box model spec
<a href="http://www.w3.org/TR/CSS2/box.html">http://www.w3.org/TR/CSS2/box.html</a>
</p>
<example title="Simple view with boxmodel" query-parameters="lzr=swf10"><programlisting>
<canvas height="200" bgcolor="antiquewhite">
<stylesheet>
#thebox {
padding: 1 3 5 7;
border-width: 2 4 6 8;
border-color: cornflowerblue;
margin: 3 7 11 15;
}
text { color: navy; }
.margin { background-color: aliceblue; }
.borderwidth { color: white; background-color: cornflowerblue; }
.padding { background-color: skyblue; }
.content { background-color: white; }
colortext { border-width: 1; }
colortext.margin, colortext.borderwidth { border-bottom-width: 0 }
</stylesheet>
<class name="colorview" extends="view">
<attribute name="bgcolor" style="background-color" value="transparent" />
<attribute name="fgcolor" style="color" value="black" />
</class>
<class name="colortext" extends="text" with="boxmodel" width="100%" multiline="true">
<attribute name="bgcolor" style="background-color" value="transparent" />
<attribute name="fgcolor" style="color" value="black" />
</class>
<colorview styleclass="margin" x="5" y="5" >
<colorview id="thebox" with="boxmodel" styleclass="padding" width="250">
<colorview styleclass="content" layout="axis: y; spacing:0" width="100%">
<text>Click me to demonstrate updating:</text>
<text name="op" />
<colortext styleclass="margin">
<handler name="onmargin" reference="thebox" method="display" />
<handler name="oninit" method="display" />
<method name="display" args="ignore">
this.format("margin: %s", thebox.presentAttribute('margin'));
</method>
</colortext>
<colortext styleclass="borderwidth">
<handler name="onborderwidth" reference="thebox" method="display" />
<handler name="oninit" method="display" />
<method name="display" args="ignore">
this.format("borderwidth: %s", thebox.presentAttribute('borderwidth'));
</method>
</colortext>
<colortext styleclass="padding">
<handler name="onpadding" reference="thebox" method="display" />
<handler name="oninit" method="display" />
<method name="display" args="ignore">
this.format("padding: %s", thebox.presentAttribute('padding'));
</method>
</colortext>
<attribute name="attribute" type="string" />
<attribute name="amount" type="number" />
<handler name="onclick" method="doit" />
<handler name="oninit" method="doit" />
<method name="doit" args="ignore">
if (this.attribute) {
thebox.setAttribute(this.attribute, thebox[this.attribute] + amount);
}
var r = Math.floor(Math.random()*3);
var prefix = ['margin', 'border', 'padding'][r];
var suffix = ['','width',''][r];
this.attribute = prefix+['top','right','bottom','left'][Math.floor(Math.random()*4)]+suffix;
this.amount = Math.round(Math.random()*10);
op.format("%s += %w", attribute, amount);
</method>
</colorview>
</colorview>
</colorview>
</canvas>
</programlisting></example>
<p><classname>boxmodel</classname> is a mixin intended for use with <sgmltag class="element" role="LzView"><view></sgmltag> and its subclasses.
</p>
</text>
</doc>
<method name="construct" args="p, a">
super.construct(p,a);
// set these lz.view.setCSS() knows how to process these attributes
var style = this.__styleinfo;
style.marginTop = style.marginRight = style.marginBottom = style.marginLeft =
boxmodelSupport.CAPABILITY_BOX_DIMENSION_OFFSET;
style.paddingTop = style.paddingRight = style.paddingBottom = style.paddingLeft =
boxmodelSupport.CAPABILITY_BOX_DIMENSION_OFFSET;
style.borderTopWidth = style.borderRightWidth = style.borderBottomWidth = style.borderLeftWidth =
boxmodelSupport.CAPABILITY_BOX_DIMENSION_OFFSET;
style.borderColor = boxmodelSupport.CAPABILITY_BOX;
// initialize default values
// @devnote [2010-12-30 ptw] Because the style bindings and
// initializations can happen in any order, we set the default
// values here, then set the fallback values for each styled
// attribute to `null`, which tells us the attribute was not
// styled so we should do nothing. If the attribute _is_
// styled, the setter will be called and the value will
// propagate to both the array (abbreviated value) and the
// individual value
this.margin = [0,0,0,0];
this.margintop = 0;
this.marginright = 0;
this.marginbottom = 0;
this.marginleft = 0;
this.borderwidth = [0,0,0,0];
this.bordertopwidth = 0;
this.borderrightwidth = 0;
this.borderbottomwidth = 0;
this.borderleftwidth = 0;
this.padding = [0,0,0,0];
this.paddingtop = 0;
this.paddingright = 0;
this.paddingbottom = 0;
this.paddingleft = 0;
</method>
<method name="_expandBoxSideDimensionProperty" args="property, value:String">
var parts = property.split('-');
var prefix = parts[0];
var suffix = (parts.length == 2 ? parts[1] : '');
var attr = prefix + suffix;
var dimensions = lz.Type.acceptTypeValue('boxsidedimensions', value, this, attr);
var dsm = boxmodelSupport.DIMENSION_SIDE_MAP;
var result = {};
for (var index = 0, l = dsm.length; index < l; index++) {
var side = dsm[index];
var attr = prefix + side + suffix;
var prop = prefix + '-' + side;
if (suffix) { prop += '-' + suffix; }
result[prop] = lz.Type.presentTypeValue('number', dimensions[index], this, attr);
}
return result;
</method>
<method name="_setAttributeDimensionArray" args="prefix:String, suffix:String, value:Array">
if (value == null) { return false; }
var attr = prefix + suffix;
var current = this[attr];
var changed = false;
var dsm = boxmodelSupport.DIMENSION_SIDE_MAP;
for (var index = 0, l = dsm.length; index < l; index++) {
var side = dsm[index];
if (current[index] != value[index]) {
changed |= this._setAttributeDimensionInternal(prefix, suffix, side, value[index]);
}
}
if (! changed) { return false; }
var event = 'on' + attr;
if (this[event] && this[event].ready) { this[event].sendEvent(value); }
return true;
</method>
<method name="_setAttributeDimensionInternal" args="prefix:String, suffix:String, side:String, value">
var attr = prefix + side + suffix;
if (this[attr] == value) { return false; }
this[attr] = value;
// Update dimension array
var attrs = prefix + suffix;
var dimensions = this[attrs];
var sdm = boxmodelSupport.SIDE_DIMENSION_MAP;
dimensions[sdm[side]] = value;
// Update view dimensions before you make the call to setCSS
this.__xoffset = this.marginleft + this.borderleftwidth + this.paddingleft;
this.__widthoffset = this.__xoffset + this.paddingright + this.borderrightwidth + this.marginright;
this.__yoffset = this.margintop + this.bordertopwidth + this.paddingtop;
this.__heightoffset = this.__yoffset + this.paddingbottom + this.borderbottomwidth + this.marginbottom;
// Update CSS
this.setCSS(boxmodelSupport.camelCase(prefix, side, suffix), this.presentAttribute(attr));
var event = 'on' + attr;
if (this[event] && this[event].ready) { this[event].sendEvent(value); }
return true;
</method>
<method name="_setAttributeDimension" args="prefix:String, suffix:String, side:String, value">
if (value == null) { return false; }
var changed = this._setAttributeDimensionInternal(prefix, suffix, side, value);
if (changed) {
var attrs = prefix + suffix;
// Send the array event
var event = 'on' + attrs;
if (this[event] && this[event].ready) { this[event].sendEvent(this[attrs]); }
}
return changed;
</method>
<attribute name="margin" type="boxsidedimensions" value="${null}" style="margin" expander="_expandBoxSideDimensionProperty"/>
<event name="onmargin"/>
<setter name="margin" args="value">
this._setAttributeDimensionArray('margin', '', value);
</setter>
<attribute name="margintop" type="number" value="${null}" style="margin-top"/>
<event name="onmargintop"/>
<setter name="margintop" args="value">
this._setAttributeDimension('margin', '', 'top', value)
</setter>
<attribute name="marginright" type="number" value="${null}" style="margin-right"/>
<event name="onmarginright"/>
<setter name="marginright" args="value">
this._setAttributeDimension('margin', '', 'right', value);
</setter>
<attribute name="marginbottom" type="number" value="${null}" style="margin-bottom"/>
<event name="onmarginbottom"/>
<setter name="marginbottom" args="value">
this._setAttributeDimension('margin', '', 'bottom', value);
</setter>
<attribute name="marginleft" type="number" value="${null}" style="margin-left"/>
<event name="onmarginleft"/>
<setter name="marginleft" args="value">
this._setAttributeDimension('margin', '', 'left', value);
</setter>
<attribute name="borderwidth" type="boxsidedimensions" value="${null}" style="border-width" expander="_expandBoxSideDimensionProperty"/>
<event name="onborderwidth"/>
<setter name="borderwidth" args="borderwidth">
this._setAttributeDimensionArray('border', 'width', borderwidth);
</setter>
<attribute name="bordertopwidth" type="number" value="${null}" style="border-top-width"/>
<event name="onbordertopwidth"/>
<setter name="bordertopwidth" args="value">
this._setAttributeDimension('border', 'width', 'top', value);
</setter>
<attribute name="borderrightwidth" type="number" value="${null}" style="border-right-width"/>
<event name="onborderrightwidth"/>
<setter name="borderrightwidth" args="value">
this._setAttributeDimension('border', 'width', 'right', value);
</setter>
<attribute name="borderbottomwidth" type="number" value="${null}" style="border-bottom-width"/>
<event name="onborderbottomwidth"/>
<setter name="borderbottomwidth" args="value">
this._setAttributeDimension('border', 'width', 'bottom', value);
</setter>
<attribute name="borderleftwidth" type="number" value="${null}" style="border-left-width"/>
<event name="onborderleftwidth"/>
<setter name="borderleftwidth" args="value">
this._setAttributeDimension('border', 'width', 'left', value);
</setter>
<attribute name="padding" type="boxsidedimensions" value="${null}" style="padding" expander="_expandBoxSideDimensionProperty"/>
<event name="onpadding"/>
<setter name="padding" args="value">
this._setAttributeDimensionArray('padding', '', value);
</setter>
<attribute name="paddingtop" type="number" value="${null}" style="padding-top"/>
<event name="onpaddingtop"/>
<setter name="paddingtop" args="value">
this._setAttributeDimension('padding', '', 'top', value);
</setter>
<attribute name="paddingright" type="number" value="${null}" style="padding-right"/>
<event name="onpaddingright"/>
<setter name="paddingright" args="value">
this._setAttributeDimension('padding', '', 'right', value);
</setter>
<attribute name="paddingbottom" type="number" value="${null}" style="padding-bottom"/>
<event name="onpaddingbottom"/>
<setter name="paddingbottom" args="value">
this._setAttributeDimension('padding', '', 'bottom', value);
</setter>
<attribute name="paddingleft" type="number" value="${null}" style="padding-left"/>
<event name="onpaddingleft"/>
<setter name="paddingleft" args="value">
this._setAttributeDimension('padding', '', 'left', value);
</setter>
<attribute name="bordercolor" type="color" value="black
" style="border-color"/>
<event name="onbordercolor"/>
<setter name="bordercolor" args="bordercolor">
if (this.bordercolor != bordercolor) {
this.bordercolor = bordercolor;
this.setCSS('borderColor', this.presentAttribute('bordercolor'));
}
if (this.onbordercolor.ready) this.onbordercolor.sendEvent(bordercolor);
</setter>
</mixin>
</library>
Cross References
Named Instances