1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
|
PIE.RendererBase = {
/**
* Create a new Renderer class, with the standard constructor, and augmented by
* the RendererBase's members.
* @param proto
*/
newRenderer: function( proto ) {
function Renderer( el, boundsInfo, styleInfos, parent ) {
this.targetElement = el;
this.boundsInfo = boundsInfo;
this.styleInfos = styleInfos;
this.parent = parent;
}
PIE.Util.merge( Renderer.prototype, PIE.RendererBase, proto );
return Renderer;
},
/**
* Flag indicating the element has already been positioned at least once.
* @type {boolean}
*/
isPositioned: false,
/**
* Determine if the renderer needs to be updated
* @return {boolean}
*/
needsUpdate: function() {
return false;
},
/**
* Run any preparation logic that would affect the main update logic of this
* renderer or any of the other renderers, e.g. things that might affect the
* element's size or style properties.
*/
prepareUpdate: PIE.emptyFn,
/**
* Tell the renderer to update based on modified properties
*/
updateProps: function() {
this.destroy();
if( this.isActive() ) {
this.draw();
}
},
/**
* Tell the renderer to update based on modified element position
*/
updatePos: function() {
this.isPositioned = true;
},
/**
* Tell the renderer to update based on modified element dimensions
*/
updateSize: function() {
if( this.isActive() ) {
this.draw();
} else {
this.destroy();
}
},
/**
* Add a layer element, with the given z-order index, to the renderer's main box element. We can't use
* z-index because that breaks when the root rendering box's z-index is 'auto' in IE8+ standards mode.
* So instead we make sure they are inserted into the DOM in the correct order.
* @param {number} index
* @param {Element} el
*/
addLayer: function( index, el ) {
this.removeLayer( index );
for( var layers = this._layers || ( this._layers = [] ), i = index + 1, len = layers.length, layer; i < len; i++ ) {
layer = layers[i];
if( layer ) {
break;
}
}
layers[index] = el;
this.getBox().insertBefore( el, layer || null );
},
/**
* Retrieve a layer element by its index, or null if not present
* @param {number} index
* @return {Element}
*/
getLayer: function( index ) {
var layers = this._layers;
return layers && layers[index] || null;
},
/**
* Remove a layer element by its index
* @param {number} index
*/
removeLayer: function( index ) {
var layer = this.getLayer( index ),
box = this._box;
if( layer && box ) {
box.removeChild( layer );
this._layers[index] = null;
}
},
/**
* Get a VML shape by name, creating it if necessary.
* @param {string} name A name identifying the element
* @param {string=} subElName If specified a subelement of the shape will be created with this tag name
* @param {Element} parent The parent element for the shape; will be ignored if 'group' is specified
* @param {number=} group If specified, an ordinal group for the shape. 1 or greater. Groups are rendered
* using container elements in the correct order, to get correct z stacking without z-index.
*/
getShape: function( name, subElName, parent, group ) {
var shapes = this._shapes || ( this._shapes = {} ),
shape = shapes[ name ],
s;
if( !shape ) {
shape = shapes[ name ] = PIE.Util.createVmlElement( 'shape' );
if( subElName ) {
shape.appendChild( shape[ subElName ] = PIE.Util.createVmlElement( subElName ) );
}
if( group ) {
parent = this.getLayer( group );
if( !parent ) {
this.addLayer( group, doc.createElement( 'group' + group ) );
parent = this.getLayer( group );
}
}
parent.appendChild( shape );
s = shape.style;
s.position = 'absolute';
s.left = s.top = 0;
s['behavior'] = 'url(#default#VML)';
}
return shape;
},
/**
* Delete a named shape which was created by getShape(). Returns true if a shape with the
* given name was found and deleted, or false if there was no shape of that name.
* @param {string} name
* @return {boolean}
*/
deleteShape: function( name ) {
var shapes = this._shapes,
shape = shapes && shapes[ name ];
if( shape ) {
shape.parentNode.removeChild( shape );
delete shapes[ name ];
}
return !!shape;
},
/**
* For a given set of border radius length/percentage values, convert them to concrete pixel
* values based on the current size of the target element.
* @param {Object} radii
* @return {Object}
*/
getRadiiPixels: function( radii ) {
var el = this.targetElement,
bounds = this.boundsInfo.getBounds(),
w = bounds.w,
h = bounds.h,
tlX, tlY, trX, trY, brX, brY, blX, blY, f;
tlX = radii.x['tl'].pixels( el, w );
tlY = radii.y['tl'].pixels( el, h );
trX = radii.x['tr'].pixels( el, w );
trY = radii.y['tr'].pixels( el, h );
brX = radii.x['br'].pixels( el, w );
brY = radii.y['br'].pixels( el, h );
blX = radii.x['bl'].pixels( el, w );
blY = radii.y['bl'].pixels( el, h );
// If any corner ellipses overlap, reduce them all by the appropriate factor. This formula
// is taken straight from the CSS3 Backgrounds and Borders spec.
f = Math.min(
w / ( tlX + trX ),
h / ( trY + brY ),
w / ( blX + brX ),
h / ( tlY + blY )
);
if( f < 1 ) {
tlX *= f;
tlY *= f;
trX *= f;
trY *= f;
brX *= f;
brY *= f;
blX *= f;
blY *= f;
}
return {
x: {
'tl': tlX,
'tr': trX,
'br': brX,
'bl': blX
},
y: {
'tl': tlY,
'tr': trY,
'br': brY,
'bl': blY
}
}
},
/**
* Return the VML path string for the element's background box, with corners rounded.
* @param {Object.<{t:number, r:number, b:number, l:number}>} shrink - if present, specifies number of
* pixels to shrink the box path inward from the element's four sides.
* @param {number=} mult If specified, all coordinates will be multiplied by this number
* @param {Object=} radii If specified, this will be used for the corner radii instead of the properties
* from this renderer's borderRadiusInfo object.
* @return {string} the VML path
*/
getBoxPath: function( shrink, mult, radii ) {
mult = mult || 1;
var r, str,
bounds = this.boundsInfo.getBounds(),
w = bounds.w * mult,
h = bounds.h * mult,
radInfo = this.styleInfos.borderRadiusInfo,
floor = Math.floor, ceil = Math.ceil,
shrinkT = shrink ? shrink.t * mult : 0,
shrinkR = shrink ? shrink.r * mult : 0,
shrinkB = shrink ? shrink.b * mult : 0,
shrinkL = shrink ? shrink.l * mult : 0,
tlX, tlY, trX, trY, brX, brY, blX, blY;
if( radii || radInfo.isActive() ) {
r = this.getRadiiPixels( radii || radInfo.getProps() );
tlX = r.x['tl'] * mult;
tlY = r.y['tl'] * mult;
trX = r.x['tr'] * mult;
trY = r.y['tr'] * mult;
brX = r.x['br'] * mult;
brY = r.y['br'] * mult;
blX = r.x['bl'] * mult;
blY = r.y['bl'] * mult;
str = 'm' + floor( shrinkL ) + ',' + floor( tlY ) +
'qy' + floor( tlX ) + ',' + floor( shrinkT ) +
'l' + ceil( w - trX ) + ',' + floor( shrinkT ) +
'qx' + ceil( w - shrinkR ) + ',' + floor( trY ) +
'l' + ceil( w - shrinkR ) + ',' + ceil( h - brY ) +
'qy' + ceil( w - brX ) + ',' + ceil( h - shrinkB ) +
'l' + floor( blX ) + ',' + ceil( h - shrinkB ) +
'qx' + floor( shrinkL ) + ',' + ceil( h - blY ) + ' x e';
} else {
// simplified path for non-rounded box
str = 'm' + floor( shrinkL ) + ',' + floor( shrinkT ) +
'l' + ceil( w - shrinkR ) + ',' + floor( shrinkT ) +
'l' + ceil( w - shrinkR ) + ',' + ceil( h - shrinkB ) +
'l' + floor( shrinkL ) + ',' + ceil( h - shrinkB ) +
'xe';
}
return str;
},
/**
* Get the container element for the shapes, creating it if necessary.
*/
getBox: function() {
var box = this.parent.getLayer( this.boxZIndex ), s;
if( !box ) {
box = doc.createElement( this.boxName );
s = box.style;
s.position = 'absolute';
s.top = s.left = 0;
this.parent.addLayer( this.boxZIndex, box );
}
return box;
},
/**
* Hide the actual border of the element. In IE7 and up we can just set its color to transparent;
* however IE6 does not support transparent borders so we have to get tricky with it. Also, some elements
* like form buttons require removing the border width altogether, so for those we increase the padding
* by the border size.
*/
hideBorder: function() {
var el = this.targetElement,
cs = el.currentStyle,
rs = el.runtimeStyle,
tag = el.tagName,
isIE6 = PIE.ieVersion === 6,
sides, side, i;
if( ( isIE6 && ( tag in PIE.childlessElements || tag === 'FIELDSET' ) ) ||
tag === 'BUTTON' || ( tag === 'INPUT' && el.type in PIE.inputButtonTypes ) ) {
rs.borderWidth = '';
sides = this.styleInfos.borderInfo.sides;
for( i = sides.length; i--; ) {
side = sides[ i ];
rs[ 'padding' + side ] = '';
rs[ 'padding' + side ] = ( PIE.getLength( cs[ 'padding' + side ] ) ).pixels( el ) +
( PIE.getLength( cs[ 'border' + side + 'Width' ] ) ).pixels( el ) +
( PIE.ieVersion !== 8 && i % 2 ? 1 : 0 ); //needs an extra horizontal pixel to counteract the extra "inner border" going away
}
rs.borderWidth = 0;
}
else if( isIE6 ) {
// Wrap all the element's children in a custom element, set the element to visiblity:hidden,
// and set the wrapper element to visiblity:visible. This hides the outer element's decorations
// (background and border) but displays all the contents.
// TODO find a better way to do this that doesn't mess up the DOM parent-child relationship,
// as this can interfere with other author scripts which add/modify/delete children. Also, this
// won't work for elements which cannot take children, e.g. input/button/textarea/img/etc. Look into
// using a compositor filter or some other filter which masks the border.
if( el.childNodes.length !== 1 || el.firstChild.tagName !== 'ie6-mask' ) {
var cont = doc.createElement( 'ie6-mask' ),
s = cont.style, child;
s.visibility = 'visible';
s.zoom = 1;
while( child = el.firstChild ) {
cont.appendChild( child );
}
el.appendChild( cont );
rs.visibility = 'hidden';
}
}
else {
rs.borderColor = 'transparent';
}
},
unhideBorder: function() {
},
/**
* Destroy the rendered objects. This is a base implementation which handles common renderer
* structures, but individual renderers may override as necessary.
*/
destroy: function() {
this.parent.removeLayer( this.boxZIndex );
delete this._shapes;
delete this._layers;
}
};
|