Template Examples - Stepbar Navigation"

From Documentation
Line 128: Line 128:
 
== Usage in HolidayOrderViewModel ==
 
== Usage in HolidayOrderViewModel ==
  
Let's look into the actual usage in our View Model class which is equally trivial.
+
Let's look into the actual usage in our View Model class which is equally trivial. We initialize the <code>StepBarModel</code> with the necessary navigation information (label, icon, uri).  
  
 
<source lang="java" high="">
 
<source lang="java" high="">
Line 177: Line 177:
 
}
 
}
 
}
 
}
 +
</source>
 +
 +
The command handlers <code>gotoStep(), next(), back()</code) delegate into the SideBarModel providing the perfect hooks to add your application logic such as validation, loading/saving of related information from/to DB - allowing to block progress or skip steps if needed.
 +
 +
The omitted sections contain additional commands to add or remove an optional step based on a user decision.
 +
If a rented car is requested a "Rent Car" step is inserted into the current StepBarModel (or removed if decided otherwise).
 +
 +
<source lang="java" high="4,14">
 +
@Command
 +
@NotifyChange("carAdded")
 +
public void addCar() {
 +
addStep(2, "Rent Car", "z-icon-car", "steps/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 boolean isCarAdded() {
 +
return carAdded;
 +
}
 +
 
</source>
 
</source>
  

Revision as of 08:27, 28 March 2017

DocumentationSmall Talks2017AprilTemplate Examples - Stepbar Navigation
Template Examples - Stepbar Navigation

Author
Robert Wenzel, Engineer, Potix Corporation
Date
April 2017
Version
ZK 8.0

Introduction

Sometimes we get asked does ZK have this component or that feature. In some cases those features are reasonable additions to the framework in other cases when the feature requested is highly individual it's not a suitable candidate for a general feature. This is especially true for layout intensive changes, where the actual code is little and the custom styling is dominating.

e.g. for navigation components

While the recipe might be different ZK contains all the major ingredients to implement your requirements.

Navigation Model

Modelling a simple navigation case requires a collection of navigable items and a selected item defining the current navigation target. I deliberately use abstract terms here since the idea is quite abstract itself. So far the idea is the same independent of how it's presented to the user.

An existing ZK API class matching our requirements is e.g. a ListModelList (which will come in handy in a minute).

Abstract Navigation Model

By wrapping a ListModelList into our own application specific NavModel class we can enrich it with our own functionalities for seamless integration into our use case. The most important control methods are navigateTo(), getCurrent(), getItems(), accompanied by a few derived navigation and state checking operations - next(), back(), isFirst(), isLast(). For instant MVVM integration the class automatically invokes notify change when the current navigation item changes. Derived attributes using @DependsOn trigger a change-notification automatically.

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());
	}
}

Specialized StepBarModel

Substituting the generic type parameter <NavType> by a concrete Class this model class above is capable to support various navigation scenarios.

e.g. a Step Bar (as in the screenshot below)

Template-examples-stepbar.png

The additional information such as the label, icon, current state and the navigation target uri can be represented by a Step class nested inside a StepBarModel class (providing additional step bar specific navigation behavior overriding the navigateTo() method).

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;
		}
	}
}

The Step class itself is straight forward, simply providing information we need later when rendering the stepbar template.

Usage in HolidayOrderViewModel

Let's look into the actual usage in our View Model class which is equally trivial. We initialize the StepBarModel with the necessary navigation information (label, icon, uri).

public class HolidayOrderViewModel {
	private StepBarModel stepBarModel;
	private boolean carAdded = false;
	
	@Init
	public void init() {
		stepBarModel = new StepBarModel();
		addStep("Destination", "z-icon-globe", "steps/destination.zul");
		addStep("Accommodation", "z-icon-hotel", "steps/accommodation.zul");
		addStep("Personal Details", "z-icon-user", "steps/personal-details.zul");
		addStep("Payment", "z-icon-credit-card", "steps/payment.zul");
		addStep("Enjoy Holiday", "z-icon-smile-o", "steps/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();
	}

	...
	
	public StepBarModel getStepBarModel() {
		return stepBarModel;
	}

	...
	
	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) {
		stepBarModel.getItems().add(index, stepBarModel.new Step(label, icon, uri));
	}
}

The command handlers gotoStep(), next(), back()</code) delegate into the SideBarModel providing the perfect hooks to add your application logic such as validation, loading/saving of related information from/to DB - allowing to block progress or skip steps if needed.

The omitted sections contain additional commands to add or remove an optional step based on a user decision. If a rented car is requested a "Rent Car" step is inserted into the current StepBarModel (or removed if decided otherwise).

	@Command
	@NotifyChange("carAdded")
	public void addCar() {
		addStep(2, "Rent Car", "z-icon-car", "steps/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 boolean isCarAdded() {
		return carAdded;
	}

Render your Model

Dynamic updates

Summary

Example Sources

The code examples are available on githup in the zk-mvc-shadow repository

zul files https://github.com/zkoss-demo/zk-mvc-shadow/tree/part-2/src/main/webapp/forEach
java classes https://github.com/zkoss-demo/zk-mvc-shadow/tree/part-2/src/main/java/zk/example/foreach

Running the Example

Clone the repo

   git clone [email protected]:zkoss-demo/zk-mvc-shadow.git

Checkout part-2

   git checkout part-2

The example war file can be built with maven:

   mvn clean package

Execute using jetty:

   mvn jetty:run

Then access the example http://localhost:8080/mvc-shadow/forEach


Comments



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.