Processing...
Description & Source Code
  • Description
  • View
    index.zul
    stepbar.zul
    stepbar.css
    destination.zul
    accommodation.zul
    rent-car.zul
    personal-details.zul
    payment.zul
    finish.zul
  • View Model
  • Model
    StepBarModel.java
    NavModel.java

This example features a combined use case of apply, if and forEach in order to implement a dynamic Step Bar navigation.

Additional details are described in the related Small Talk: Template Examples - Stepbar Navigation

index.zul
<?component name="stepbar" templateURI="/widgets/shadow_elements/step_bar/stepbar.zul"?>
<zk>
	<style src="/widgets/shadow_elements/step_bar/stepbar.css"/>
	<div viewModel="@id('vm') @init('demo.shadow_elements.step_bar.HolidayOrderViewModel')" 
		 validationMessages="@id('vmsgs')">
		<stepbar stepBarModel="@init(vm.stepBarModel)" gotoStepCommand="gotoStep"/>
		
		<div style="padding: 0 57px; margin-top: 16px;">
			<label sclass="page-title" value="@load(vm.stepBarModel.current.label)"/>
			<div style="margin-top: 26px;" sclass="stepbar-content">
				<apply templateURI="@load(vm.stepBarModel.current.uri)" step="@load(vm.stepBarModel.current)"/>
			</div>
		</div>
		<div style="padding: 0 57px; position: relative; margin-top: 32px;">
			<if test="@load(!vm.stepBarModel.first and !vm.stepBarModel.last)">
				<button zclass="stepbar-button" sclass="secondary" onClick="@command('back')" label="Back"/>
			</if>
			<if test="@load(!vm.stepBarModel.last)">
				<button zclass="stepbar-button pull-right" sclass="primary" onClick="@command('next')" label="Next"/>
			</if>
		</div>
		<div style="clear: both;"/>
	</div>
</zk>
stepbar.zul
<div sclass="stepbar">
	<forEach items="@init(stepBarModel.items)" var="step">
		<div sclass="@load(('step ' += step.status))" 
			onClick="@command(step.done ? gotoStepCommand : null, step=step)">
			<span sclass="@init(('step-icon ' += step.icon))"/>
			<label sclass="step-label" value="@init(step.label)"/>
		</div>
	</forEach>
</div>
stepbar.css
.stepbar {
	display: flex;
}

.stepbar > .step {
	position: relative;
	flex: 1;
	display: inline-block;
	text-align: center;
	z-index: 0;
}

.stepbar > .step > .step-icon {
	display: inline-block;
	background-color: LightGrey;
	color: DarkGray;
	border-radius: 20px;
	width: 40px;
	height: 40px;
	font-size: 26px;
	line-height: 40px;
	transition: all 0.5s;
}

.stepbar > .step:before {
	content: ' ';
	position: absolute;
	display: block;
	top: 18px;
	height: 4px;
	left: 0px;
	right: 0px;
	background-color: LightGrey;
	z-index: -1;
}

.stepbar > .step.previous {
	cursor: pointer;
}

.stepbar > .step:first-child:before {
	left: 50%;
}

.stepbar > .step:last-child:before {
	right: 50%;
}

.stepbar > .step.previous:before {
	background-color: DarkCyan;
}

.stepbar > .step.current:before {
	background-image: linear-gradient(to right, DarkCyan, LightSeaGreen 50%, LightGrey);
}

.stepbar > .step:first-child.current:before {
	background-image: linear-gradient(to right, LightSeaGreen, LightGrey);
}
.stepbar > .step:last-child.current:before {
	background-image: linear-gradient(to right, DarkCyan, LightSeaGreen);
}

.stepbar > .step.previous > .step-icon {
	background-color: DarkCyan;
	color: white;
}

.stepbar > .step.current > .step-icon {
	background-color: LightSeaGreen;
	color: white;
}

.stepbar > .step > .step-label {
	display: block;
	padding: 5px;
}

.stepbar-content > img {
	max-width: 100%;
}

.stepbar-button {
	min-width: 100px;
	background: none;
	border-radius: 4px;
	border: none;
	font-weight: bold;
	font-size: 16px;
	padding: 10px 55px;
	white-space: nowrap;
}

.stepbar-button.primary {
	background-color: LightSeaGreen;
	border: 1px solid MediumTurquoise;
	color: white;
}
.stepbar-button.primary:hover {
	background-color: MediumTurquoise;
}

.stepbar-button.secondary {
	background-color: #F8F8F8;
	color: LightSeaGreen;
}
.stepbar-button.secondary:hover {
	background-color: #ECECEC
}

.stepbar-button.outline {
	font-family: Arial,Helvetica,Tahoma,Verdana,sans-serif;
	background-color: white;
	color: LightSeaGreen;
	border: 1px solid LightSeaGreen;
}
.stepbar-button.outline:hover {
	border-color: MediumTurquoise;
	color: MediumTurquoise;
}

.page-title {
	font-size: 22px;
	font-weight: bold;
}

.highlight {
	height: 40px;
	background-color: #F8F8F8;
	border-radius: 4px;
	font-size: 16px;
	color: DimGray;
	font-weight: bold;
	line-height: 40px;
    padding-left: 11px;
}

.pull-right {
	float: right;
}
destination.zul
<zk>
	<image src="/widgets/shadow_elements/step_bar/steps/destination.png" tooltiptext="dummy content"/>
	<if test="@load(!vm.carAdded)">
		<hlayout style="position: relative; margin-top: 12px" spacing="8px">
			<div hflex="1" sclass="highlight">
				<span sclass="z-icon-car"/>
				<label zclass=" " value="Do you need a car?"/>
			</div>
			<button zclass="stepbar-button" width="245px" sclass="outline" onClick="@command('addCar')" label="Yes, I need a Car!"/>
		</hlayout>
	</if>
</zk>
accommodation.zul
<zk>
	<image src="/widgets/shadow_elements/step_bar/steps/accommodation.png" tooltiptext="dummy content"/>
</zk>
rent-car.zul
<zk>
	<image src="/widgets/shadow_elements/step_bar/steps/rent-car.png" tooltiptext="dummy content"/>
	<hlayout style="position: relative; margin-top: 12px;" spacing="8px">
		<div hflex="1" sclass="highlight">
			<span sclass="z-icon-car"/>
			<label zclass=" " value="I changed my mind - remove car."/>
		</div>
		<button zclass="stepbar-button" width="245px" sclass="outline" onClick="@command('removeCar')" label="Remove Car"/>
	</hlayout>
</zk>
personal-details.zul
<zk>
	<image src="/widgets/shadow_elements/step_bar/steps/personal-details.png" tooltiptext="dummy content"/>
</zk>
payment.zul
<zk>
	<image src="/widgets/shadow_elements/step_bar/steps/payment.png" tooltiptext="dummy content"/>
</zk>
finish.zul
<zk>
	<label style="color: DimGray; font-size: 14px" value="Hooray! You're all set now. Enjoy your holiday."/>
</zk>
HolidayOrderViewModel.java
package demo.shadow_elements.step_bar;

import org.zkoss.bind.annotation.BindingParam;
import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.Init;
import org.zkoss.bind.annotation.NotifyChange;

import demo.shadow_elements.step_bar.StepBarModel.Step;

public class HolidayOrderViewModel {
	private StepBarModel stepBarModel;
	private boolean carAdded = false;
	@Init
	public void init() {
		stepBarModel = new StepBarModel();
		addStep("Destination", "z-icon-globe", "destination.zul");
		addStep("Accommodation", "z-icon-hotel", "accommodation.zul");
		addStep("Personal Details", "z-icon-user", "personal-details.zul");
		addStep("Payment", "z-icon-credit-card", "payment.zul");
		addStep("Enjoy Holiday", "z-icon-smile-o", "finish.zul");
		stepBarModel.getItems().addToSelection(stepBarModel.getItems().get(0));
	}
	
	@Command
	public void gotoStep(@BindingParam("step") StepBarModel.Step step) {
		stepBarModel.navigateTo(step);
	}
	
	@Command
	public void next() {
		stepBarModel.next();
	}
	
	@Command
	public void back() {
		stepBarModel.back();
	}
	
	@Command
	@NotifyChange("carAdded")
	public void addCar() {
		addStep(2, "Rent Car", "z-icon-car", "rent-car.zul");
		this.carAdded = true;
	}

	@Command
	@NotifyChange("carAdded")
	public void removeCar() {
		Step carStep = stepBarModel.getCurrent();
		stepBarModel.next();
		stepBarModel.getItems().remove(carStep);
		this.carAdded = false;
	}
	
	public StepBarModel getStepBarModel() {
		return stepBarModel;
	}

	public boolean isCarAdded() {
		return carAdded;
	}
	
	public void addStep(String label, String icon, String uri) {
		addStep(stepBarModel.getItems().size(), label, icon, uri);
	}
	
	public void addStep(int index, String label, String icon, String uri) {
		String stepsFolder = "/widgets/shadow_elements/step_bar/steps";
		stepBarModel.getItems().add(index, stepBarModel.new Step(label, icon, stepsFolder + "/" + uri));
	}
}

StepBarModel.java
package demo.shadow_elements.step_bar;

import org.zkoss.bind.BindUtils;

import demo.shadow_elements.step_bar.StepBarModel.Step;

public class StepBarModel extends NavModel<Step> {
	@Override
	public void navigateTo(Step step) {
		//notify change all steps affected by random access navigation (steps between old and new index inclusive)
		int oldIndex = getItems().indexOf(getCurrent());
		int newIndex = getItems().indexOf(step);
		super.navigateTo(step);
		getItems().subList(Math.min(newIndex, oldIndex), Math.max(newIndex, oldIndex) + 1)
				  .forEach(affectedStep -> BindUtils.postNotifyChange(null, null, affectedStep, "*"));
	}

	public class Step {
		private String label;
		private String icon;
		private String uri;

		public Step(String label, String icon, String uri) {
			super();
			this.label = label;
			this.icon = icon;
			this.uri = uri;
		}

		public String getStatus() {
			return isDone() ? "previous" : (getCurrent() == this ? "current" : "following"); 
		}
		
		public boolean isDone() {
			return getItems().indexOf(this) < getItems().indexOf(getCurrent()); 
		}
		
		public String getLabel() {
			return label;
		}
		
		public String getIcon() {
			return icon;
		}
		
		public String getUri() {
			return uri;
		}
	}
}
NavModel.java
package demo.shadow_elements.step_bar;

import java.util.Set;

import org.zkoss.bind.BindUtils;
import org.zkoss.bind.annotation.DependsOn;
import org.zkoss.zul.ListModelList;

public abstract class NavModel<NavType> {
	private static final String CURRENT = "current";
	private ListModelList<NavType> items = new ListModelList<>();

	public void navigateTo(NavType item) {
		items.addToSelection(item);
		BindUtils.postNotifyChange(null, null, this, CURRENT);
	}
	
	public NavType getCurrent() {
		Set<NavType> selection = items.getSelection();
		return selection.iterator().next();
	}
	
	public void back() {
		navigateTo(items.get(getCurrentIndex() - 1));
	}
	
	public void next() {
		navigateTo(items.get(getCurrentIndex() + 1));
	}
	
	@DependsOn(CURRENT)
	public boolean isFirst() {
		return getCurrentIndex() == 0;
	}

	@DependsOn(CURRENT)
	public boolean isLast() {
		return getCurrentIndex() == items.size() - 1;
	}

	public ListModelList<NavType> getItems() {
		return items;
	}

	private int getCurrentIndex() {
		return items.indexOf(getCurrent());
	}
}