Introduction
In ZK, some comopnents like textbox, button, link are not resizable, this article describe how to use a draggable div to make those non resizable components resizable.
The Program
make_components_resizable.zul
A resizer div and several custom resizable components in this page, will display the resizer div while mouseover the right-side or the bottom of custom resizable component, fire event while resizer div is dragged to resize component.
<!--
Tested with ZK 6.0.2 (Chrome, IE9)
-->
<zk xmlns:w="client">
<style>
.custom-sizer {
position: absolute;
display: none;
border: 4px solid #aaa;
z-index: 999999;
}
.custom-resizable-comp {
display: block;
}
</style>
<script><![CDATA[
// get the dom element of custom resizer div
function _getCustomResizer () {
return jq('.custom-sizer')[0];
}
// hide the custom resizer div
function _hideCustomResizer () {
if (!zk.Widget.customResizing)
_getCustomResizer().style.display = 'none';
}
// override zk.Widget to make components resizable
zk.afterLoad("zul", function () {
var _wgt = {};
zk.override(zk.Widget.prototype, _wgt, {
doMouseMove_: function (evt) {
_wgt.doMouseMove_.apply(this, arguments);
// do nothing if resizing
if (zk.Widget.customResizing) return;
// is custom resizable component
if (this._isResizable()) {
// keep widget instance
zk.Widget.customResizableWidget = this;
// at the right side of the component
if (this._atRightSide(evt)) {
// keep 'resizing width'
zk.Widget.customResizableWidgetDir = 'w';
// show custom resizer as vertical bar
// at the right side of this component
this._showRightResizer();
} else if (this._atBottom(evt)) { // at bottom of the component
// keep 'resizing height'
zk.Widget.customResizableWidgetDir = 'h';
// show custom resizer as horizontal bar
// at the bottom of this component
this._showBottomResizer();
} else { // not close to right side/bottom
// hide custom resizer
_hideCustomResizer();
}
}
},
// is resizable if has the specific class
// 'custom-resizable-comp'
_isResizable: function () {
return jq(this.$n()).hasClass('custom-resizable-comp');
},
// the mouse cursor is near the right side of component
_atRightSide: function (evt) {
var left = evt.pageX, // cursor x position
$n = jq(this.$n()),
wRight = $n.offset().left + $n.width(); // the right side of component
// their distance is smaller than 3px
return (Math.abs(left - wRight) < 3);
},
// the mouse cursor is near the bottom of component
_atBottom: function (evt) {
var top = evt.pageY, // cursor y position
$n = jq(this.$n()),
wBottom = $n.offset().top + $n.height(); // the bottom of component
// their distance is smaller than 3px
return (Math.abs(top - wBottom) < 3);
},
// display custom resizer at the right side of component
_showRightResizer: function () {
// do nothing if resizing
if (zk.Widget.customResizing) return;
var resizer = _getCustomResizer(),
rstyle = resizer.style,
$n = jq(this.$n()),
noffset = $n.offset();
// adjust the position of custom resizer
rstyle.left = noffset.left + $n.width() + 'px';
rstyle.top = noffset.top + 'px';
// adjust the size of custom resizer
rstyle.width = '0px';
rstyle.height = $n.height() + 'px';
// adjust cursor and show custom resizer
rstyle.cursor = 'e-resize';
rstyle.display = 'block';
},
_showBottomResizer: function () {
if (zk.Widget.customResizing) return;
var resizer = _getCustomResizer(),
rstyle = resizer.style,
$n = jq(this.$n()),
noffset = $n.offset();
// adjust the position of custom resizer
rstyle.left = noffset.left + 'px';
rstyle.top = noffset.top + $n.height() + 'px';
// adjust the size of custom resizer
rstyle.width = $n.width() + 'px';
rstyle.height = '0px';
// adjust cursor and show custom resizer
rstyle.cursor = 's-resize';
rstyle.display = 'block';
}
});
});
]]></script>
<div id="customSizer" sclass="custom-sizer" draggable="true" use="test.custom.component.div.CustomResizer">
<attribute w:name="doMouseOut_"><![CDATA[
function (evt) {
// hide custom resizer if mouseout it
this.$doMouseOut_(evt);
_hideCustomResizer();
}
]]></attribute>
<attribute w:name="getDragOptions_"><![CDATA[
function (map) {
var dragOptions = {};
for (var key in map) {
dragOptions[key] = map[key];
}
var oldBeforeSizing = dragOptions.starteffect,
oldAfterSizing = dragOptions.endeffect,
oldDragging = dragOptions.change;
// at the beginning of dragging
dragOptions.starteffect = function (dg) {
// mark resizing
zk.Widget.customResizing = true;
// save the starting x position if resizing width
// or save the starting y position if resizing height
dg._delta = zk.Widget.customResizableWidgetDir == 'w'?
dg._currentDelta()[0] : dg._currentDelta()[1];
// do original function
oldBeforeSizing(dg);
};
// at the end of dragging
dragOptions.endeffect = function (dg, evt) {
// remove resizing mark
zk.Widget.customResizing = false;
// get resizing direction (w: width, h: height)
// evaluate new size
// get the uuid of the component to resize
var resizeWidth = zk.Widget.customResizableWidgetDir == 'w',
$szwgt = jq(zk.Widget.customResizableWidget),
delta = resizeWidth?
(dg._currentDelta()[0] - dg._delta) : (dg._currentDelta()[1] - dg._delta),
newValue = resizeWidth?
($szwgt.width() + delta) : ($szwgt.height() + delta),
uuid = zk.Widget.customResizableWidget.uuid,
data;
if (newValue <= 0) newValue = 1;
// fire onCustomResize event to custom resizer
// 'value', 'reference' and 'resizeAttribute' are
// the keys of data map
data = {value: newValue,
reference: uuid,
resizeAttribute: (resizeWidth? 'width' : 'height')};
zk.Widget.$('$customSizer').fire('onCustomResize', data);
// call original function
oldAfterSizing(dg, evt);
// hide custom resizer
_getCustomResizer().style.display = 'none';
};
// while dragging
dragOptions.change = function (drag, pt, evt) {
// call original function
oldDragging(drag, pt, evt);
var wgt = drag.control,
n = wgt.$n(),
$szwgt = jq(zk.Widget.customResizableWidget);
if (n.style.display != 'block') n.style.display = 'block';
// keep 'top' unchanged and make 'left' follow mouse position
// if resizing width,
// keep 'left' unchanged and make 'top' follow mouse position
// if resizing height,
n.style.left = (zk.Widget.customResizableWidgetDir == 'h'?
$szwgt.offset().left+'px' : evt.pageX+'px');
n.style.top = (zk.Widget.customResizableWidgetDir == 'w'?
$szwgt.offset().top+'px' : evt.pageY+'px');
}
// clear ghosting function to
// avoid the faker dom element while dragging
dragOptions.ghosting = null;
return dragOptions;
}
]]></attribute>
</div>
<vlayout>
<hlayout>
<textbox sclass="custom-resizable-comp"
value="resizable textbox" />
<checkbox sclass="custom-resizable-comp"
label="resizable checkbox" />
<vlayout>
<a sclass="custom-resizable-comp"
href="http://www.zkoss.org">
resizable link
</a>
resizable menubar
<menubar sclass="custom-resizable-comp">
<menu sclass="custom-resizable-comp" label="File (resizable)">
<menupopup sclass="custom-resizable-comp">
<menuitem sclass="custom-resizable-comp" label="New (resizable)" onClick="alert(self.label)"/>
<menuitem sclass="custom-resizable-comp" label="Exit (resizable)" onClick="alert(self.label)"/>
</menupopup>
</menu>
</menubar>
</vlayout>
</hlayout>
<button sclass="custom-resizable-comp"
mold="trendy"
label="resizable button" />
</vlayout>
</zk>
CustomResizer.java
The class extends div, listening to custom resize event and resizing component as needed.
package test.custom.component.div;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.HtmlBasedComponent;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Div;
import org.zkoss.zul.impl.XulElement;
import test.custom.component.event.CustomResizeEvent;
/**
* Tested with ZK 6.0.2
* @author benbai123
*
*/
public class CustomResizer extends Div {
private static final long serialVersionUID = 5597493971151879186L;
static {
// listen to custom resize event
addClientEvent(CustomResizer.class, CustomResizeEvent.ON_CUSTOM_RESIZE, CE_DUPLICATE_IGNORE
| CE_IMPORTANT | CE_NON_DEFERRABLE);
}
public void service(org.zkoss.zk.au.AuRequest request, boolean everError) {
final String cmd = request.getCommand();
// custom resize event
if (CustomResizeEvent.ON_CUSTOM_RESIZE.equals(cmd)) {
CustomResizeEvent evt = CustomResizeEvent.getCustomResizeEvent(request);
// get the component to resize
Component ref = evt.getReference();
if (ref != null) {
// get new size
int value = evt.getValue();
// get direction (width/height)
String dir = evt.getResizeAttribute();
// do resize
if ("width".equals(dir)) {
if (ref instanceof XulElement) {
((XulElement)ref).setWidth(value+"px");
} else if (ref instanceof HtmlBasedComponent) {
((HtmlBasedComponent)ref).setWidth(value+"px");
}
} else if ("height".equals(dir)) {
if (ref instanceof XulElement) {
((XulElement)ref).setHeight(value+"px");
} else if (ref instanceof HtmlBasedComponent) {
((HtmlBasedComponent)ref).setHeight(value+"px");
}
}
}
// post event
Events.postEvent(evt);
} else {
super.service(request, everError);
}
}
}
CustomResizeEvent.java
Used to keep the data of custom resize event.
package test.custom.component.event;
import java.util.Map;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
/**
* Tested with ZK 6.0.2
* Basically you can think an Event object
* is just a POJO to keep the data within an event
* @author benbai123
*
*/
public class CustomResizeEvent extends Event {
private static final long serialVersionUID = -8839289786864776054L;
public static final String ON_CUSTOM_RESIZE = "onCustomResize";
private int _value;
private final Component _reference;
private String _resizeAttribute;
@SuppressWarnings("rawtypes")
public static CustomResizeEvent getCustomResizeEvent (org.zkoss.zk.au.AuRequest request) {
// get data map
Map data = request.getData();
// get values by keys
int value = (Integer)data.get("value");
final Component reference = request.getDesktop().getComponentByUuidIfAny((String)data.get("reference"));
String resizeAttribute = (String)data.get("resizeAttribute");
// create event instance, return it
return new CustomResizeEvent(ON_CUSTOM_RESIZE, request.getComponent(), value, reference, resizeAttribute);
}
// Constructor
public CustomResizeEvent (String name, Component target,
int value, Component reference, String resizeAttribute) {
super(name, target);
_value = value;
_reference = reference;
_resizeAttribute = resizeAttribute;
}
// getters
public int getValue () {
return _value;
}
public Component getReference () {
return _reference;
}
public String getResizeAttribute () {
return _resizeAttribute;
}
}
The Result
View demo on line
http://screencast.com/t/DxeGJvPlWw
Reference
Source code of widget.js
https://github.com/zkoss/zk/blob/6.0/zk/src/archive/web/js/zk/widget.js
Source code of drag.js
https://github.com/zkoss/zk/blob/6.0/zk/src/archive/web/js/zk/drag.js
ZK Client Side Programming
http://books.zkoss.org/wiki/Small_Talks/2010/April/Client_Side_Programming
Download
make_components_resizable.zul
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/WebContent/make_components_resizable.zul
CustomResizer.java
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/src/test/custom/component/div/CustomResizer.java
CustomResizeEvent.java
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/src/test/custom/component/event/CustomResizeEvent.java
Demo Flash
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/make_components_resizable.swf