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
Do you know Have any idea about this?
ReplyDeletehttp://stackoverflow.com/questions/15201515/combobox-issue-in-zk-framework
Is this a ZK issue or my code issue?
I've viewed it several days ago but the sample at zkfiddle cannot be scrolled (might related to resolution).
DeleteThis seems a known issue but not fixed yet since some performance concern.
Hi Ben i am getting a strange issue with abosulteLayout can you guide me what i am doing wrong
ReplyDeletehttp://stackoverflow.com/questions/15267692/zk-absolute-layout-issue-with-zul-page
seems caused by the tr and td of table are missing
DeleteHi ben i am using AbsoulteLayout for displaying components in web page my question is that how can i enable or disable drag and drop of components inside AbsoluteChildren . I will want on certain menuclick drag-drop should enable otherwise it work without drag and drop?
DeleteYes, you can change the draggable/droppable attribute as needed, official document:
Deletehttp://books.zkoss.org/wiki/ZK_Developer's_Reference/UI_Patterns/Drag_and_Drop
I have bind these attributes with a Variable and it worked
DeleteCan we add drop placeholder with absolutelayout right now if we are dropping any component it drop over a component.
ReplyDeleteI'm afraid I don't know what the "drop placeholder" is, is there any sample I can refer to?
DeleteYes you can check this http://jqueryui.com/sortable/#placeholder
ReplyDeleteDo you mean if you dragging an absolutechildren then mouseover another absolutechildren, change the position of mouseovered absolutechildren?
DeleteMay be or you can say if we are dragging any absolutechildren over anyother absolutechildren it will work as placeholder right now it drag over another component.
DeleteIt is hard to do, but you can try to specify draggable/droppable attributes properly, please refer to the sample at zkfiddle
Deletehttp://zkfiddle.org/sample/1cud977/1-Draggable-droppable-test
Can we save drag n drop component state so that we can use same state in future
ReplyDeleteSure, if you have proper data structure and table schema for it, then you can:
Delete1. Load data from database.
2. Modify the layout, order, size, etc of components with data.
3. Update data with drag-n-drop event, and save it to database as needed.