gt: 		if (visitingDataWidget) {
context: package org.skyve.impl.web.service.smartclient;
  
  import java.util.ArrayList;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.TreeMap;
  
  import org.skyve.domain.Bean;
  import org.skyve.domain.PersistentBean;
  import org.skyve.impl.bind.BindUtil;
  import org.skyve.impl.metadata.customer.CustomerImpl;
  import org.skyve.impl.metadata.model.document.DocumentImpl;
  import org.skyve.impl.metadata.model.document.InverseOne;
  import org.skyve.impl.metadata.module.ModuleImpl;
  import org.skyve.impl.metadata.repository.AbstractRepository;
  import org.skyve.impl.metadata.user.UserImpl;
  import org.skyve.impl.metadata.view.ActionImpl;
  import org.skyve.impl.metadata.view.HorizontalAlignment;
  import org.skyve.impl.metadata.view.Inject;
  import org.skyve.impl.metadata.view.InjectBinding;
  import org.skyve.impl.metadata.view.ViewImpl;
  import org.skyve.impl.metadata.view.ViewVisitor;
  import org.skyve.impl.metadata.view.WidgetReference;
  import org.skyve.impl.metadata.view.container.HBox;
  import org.skyve.impl.metadata.view.container.Tab;
  import org.skyve.impl.metadata.view.container.TabPane;
  import org.skyve.impl.metadata.view.container.VBox;
  import org.skyve.impl.metadata.view.container.form.Form;
  import org.skyve.impl.metadata.view.container.form.FormColumn;
  import org.skyve.impl.metadata.view.container.form.FormItem;
  import org.skyve.impl.metadata.view.container.form.FormRow;
  import org.skyve.impl.metadata.view.event.Addable;
  import org.skyve.impl.metadata.view.event.Changeable;
  import org.skyve.impl.metadata.view.event.Editable;
  import org.skyve.impl.metadata.view.event.EventSource;
  import org.skyve.impl.metadata.view.event.Focusable;
  import org.skyve.impl.metadata.view.event.Removable;
  import org.skyve.impl.metadata.view.event.RerenderEventAction;
  import org.skyve.impl.metadata.view.event.Selectable;
  import org.skyve.impl.metadata.view.event.ServerSideActionEventAction;
  import org.skyve.impl.metadata.view.event.SetDisabledEventAction;
  import org.skyve.impl.metadata.view.event.SetInvisibleEventAction;
  import org.skyve.impl.metadata.view.event.ToggleDisabledEventAction;
  import org.skyve.impl.metadata.view.event.ToggleVisibilityEventAction;
  import org.skyve.impl.metadata.view.reference.ActionReference;
  import org.skyve.impl.metadata.view.reference.ContentReference;
  import org.skyve.impl.metadata.view.reference.DefaultListViewReference;
  import org.skyve.impl.metadata.view.reference.EditViewReference;
  import org.skyve.impl.metadata.view.reference.ExternalReference;
  import org.skyve.impl.metadata.view.reference.ImplicitActionReference;
  import org.skyve.impl.metadata.view.reference.QueryListViewReference;
  import org.skyve.impl.metadata.view.reference.ReferenceProcessor;
  import org.skyve.impl.metadata.view.reference.ReferenceTarget;
  import org.skyve.impl.metadata.view.reference.ReferenceTarget.ReferenceTargetType;
  import org.skyve.impl.metadata.view.reference.ReportReference;
  import org.skyve.impl.metadata.view.reference.ResourceReference;
  import org.skyve.impl.metadata.view.widget.Blurb;
  import org.skyve.impl.metadata.view.widget.Button;
  import org.skyve.impl.metadata.view.widget.Chart;
  import org.skyve.impl.metadata.view.widget.DialogButton;
  import org.skyve.impl.metadata.view.widget.DynamicImage;
  import org.skyve.impl.metadata.view.widget.Link;
  import org.skyve.impl.metadata.view.widget.MapDisplay;
  import org.skyve.impl.metadata.view.widget.Spacer;
  import org.skyve.impl.metadata.view.widget.StaticImage;
  import org.skyve.impl.metadata.view.widget.bound.Label;
  import org.skyve.impl.metadata.view.widget.bound.ProgressBar;
  import org.skyve.impl.metadata.view.widget.bound.input.CheckBox;
  import org.skyve.impl.metadata.view.widget.bound.input.CheckMembership;
  import org.skyve.impl.metadata.view.widget.bound.input.ColourPicker;
  import org.skyve.impl.metadata.view.widget.bound.input.Combo;
  import org.skyve.impl.metadata.view.widget.bound.input.Comparison;
  import org.skyve.impl.metadata.view.widget.bound.input.ContentImage;
  import org.skyve.impl.metadata.view.widget.bound.input.ContentLink;
  import org.skyve.impl.metadata.view.widget.bound.input.ContentSignature;
  import org.skyve.impl.metadata.view.widget.bound.input.Geometry;
  import org.skyve.impl.metadata.view.widget.bound.input.GeometryMap;
  import org.skyve.impl.metadata.view.widget.bound.input.HTML;
  import org.skyve.impl.metadata.view.widget.bound.input.InputWidget;
  import org.skyve.impl.metadata.view.widget.bound.input.ListMembership;
  import org.skyve.impl.metadata.view.widget.bound.input.Lookup;
  import org.skyve.impl.metadata.view.widget.bound.input.LookupDescription;
  import org.skyve.impl.metadata.view.widget.bound.input.Password;
  import org.skyve.impl.metadata.view.widget.bound.input.Radio;
  import org.skyve.impl.metadata.view.widget.bound.input.RichText;
  import org.skyve.impl.metadata.view.widget.bound.input.Slider;
  import org.skyve.impl.metadata.view.widget.bound.input.Spinner;
  import org.skyve.impl.metadata.view.widget.bound.input.TextArea;
  import org.skyve.impl.metadata.view.widget.bound.input.TextField;
  import org.skyve.impl.metadata.view.widget.bound.tabular.AbstractDataWidget;
  import org.skyve.impl.metadata.view.widget.bound.tabular.DataGrid;
  import org.skyve.impl.metadata.view.widget.bound.tabular.DataGridBoundColumn;
  import org.skyve.impl.metadata.view.widget.bound.tabular.DataGridContainerColumn;
  import org.skyve.impl.metadata.view.widget.bound.tabular.DataRepeater;
  import org.skyve.impl.metadata.view.widget.bound.tabular.ListGrid;
  import org.skyve.impl.metadata.view.widget.bound.tabular.ListRepeater;
  import org.skyve.impl.metadata.view.widget.bound.tabular.TabularColumn;
  import org.skyve.impl.metadata.view.widget.bound.tabular.TreeGrid;
  import org.skyve.impl.persistence.AbstractPersistence;
  import org.skyve.impl.util.UtilImpl;
  import org.skyve.impl.web.AbstractWebContext;
  import org.skyve.impl.web.DynamicImageServlet;
  import org.skyve.impl.web.WebUtil;
  import org.skyve.metadata.MetaDataException;
  import org.skyve.metadata.controller.ImplicitActionName;
  import org.skyve.metadata.model.Attribute;
  import org.skyve.metadata.model.Attribute.AttributeType;
  import org.skyve.metadata.model.document.Association;
  import org.skyve.metadata.model.document.Bizlet.DomainValue;
  import org.skyve.metadata.model.document.Collection;
  import org.skyve.metadata.model.document.Collection.CollectionType;
  import org.skyve.metadata.model.document.Document;
  import org.skyve.metadata.model.document.DomainType;
  import org.skyve.metadata.model.document.Reference;
  import org.skyve.metadata.model.document.Relation;
  import org.skyve.metadata.module.Module;
  import org.skyve.metadata.user.User;
  import org.skyve.metadata.view.Action;
  import org.skyve.metadata.view.Disableable;
  import org.skyve.metadata.view.Invisible;
  import org.skyve.metadata.view.View;
  import org.skyve.metadata.view.TextOutput.Sanitisation;
  import org.skyve.metadata.view.View.ViewType;
  import org.skyve.metadata.view.model.comparison.ComparisonComposite;
  import org.skyve.metadata.view.model.comparison.ComparisonModel;
  import org.skyve.metadata.view.model.comparison.ComparisonProperty;
  import org.skyve.metadata.view.widget.FilterParameter;
  import org.skyve.metadata.view.widget.bound.Parameter;
  import org.skyve.util.Binder;
  import org.skyve.util.Binder.TargetMetaData;
  import org.skyve.util.JSON;
  import org.skyve.util.OWASP;
  import org.skyve.web.WebContext;
  
  // Note: We cannot cache the bindings required for each view as it may be different 
  // depending on the security principal
  class ViewJSONManipulator extends ViewVisitor {
  	// Generate href expressions for references for smart client
  	private class HrefProcessor extends ReferenceProcessor {
  		@Override
  		public void processActionReference(ActionReference reference) {
  			htmlGuts.append("javascript:").append(generateWidgetId());
  			htmlGuts.append("._view.doAction('").append(reference.getActionName()).append("',false,'");
  			htmlGuts.append(currentBindings.getBindingPrefix());
  			htmlGuts.append("','{bizModule}','{bizDocument}','{bizId}')");
  		}
  
  		@Override
  		public void processContentReference(ContentReference reference) {
  			String binding = reference.getBinding();
  			
  			htmlGuts.append("content?").append(AbstractWebContext.REPORT_NAME).append("={").append(binding);
  			htmlGuts.append("}&").append(AbstractWebContext.DOCUMENT_NAME).append("={bizModule}.{bizDocument}&");
  			htmlGuts.append(AbstractWebContext.BINDING_NAME).append('=').append(binding);
  		}
  
  		@Override
  		public void processDefaultListViewReference(DefaultListViewReference reference) {
  		}
  
  		@Override
  		public void processEditViewReference(EditViewReference reference) {
  			String binding = reference.getBinding();
  			htmlGuts.append(org.skyve.util.Util.getDocumentUrl(reference.getModuleName(),
  																reference.getDocumentName(),
  																(binding == null) ? 
  																	null : 
  																	new StringBuilder(64).append('{').append(binding).append('}').toString()));
  		}
  
  		@Override
  		public void processExternalReference(ExternalReference reference) {
  			htmlGuts.append(reference.getHref());
  		}
  
  		@Override
  		public void processImplicitActionReference(ImplicitActionReference reference) {
  			ImplicitActionName implicitAction = reference.getImplicitActionName();
  
  			if (visitingDataWidget) {
  				if (ImplicitActionName.Remove.equals(implicitAction)) {
  					htmlGuts.append("javascript:").append(generateWidgetId());
  					htmlGuts.append(".remove('{bizId}')");
  				}
  			}
  		}
  
  		@Override
  		public void processQueryListViewReference(QueryListViewReference reference) {
  		}
  
  		@Override
  		public void processReportReference(ReportReference reference) {
  			htmlGuts.append("report.rpt?_format=").append(reference.getFormat()).append("&_id={bizId}");
  			htmlGuts.append("&_n=").append(reference.getReportName());
  			htmlGuts.append("&_doc=").append(reference.getModuleName()).append('.').append(reference.getDocumentName());
  			for (Parameter parameter : reference.getParameters()) {
  				htmlGuts.append('&').append(parameter.getName()).append('=');
  				String stuff = parameter.getValueBinding();
  				if (stuff != null) {
  					htmlGuts.append('{').append(stuff).append('}');
  				}
  				else {
  					stuff = parameter.getValue();
  					if (stuff != null) {
  						htmlGuts.append(stuff);
  					}
  				}
  			}
  		}
  
  		@Override
  		public void processResourceReference(ResourceReference reference) {
  			htmlGuts.append("resources?").append(AbstractWebContext.DOCUMENT_NAME).append("={bizModule}.{bizDocument}&");
  			htmlGuts.append(AbstractWebContext.REPORT_NAME).append('=').append(reference.getRelativeFile());
  		}
  		
  		@SuppressWarnings("synthetic-access")
  		private String generateWidgetId() {
  			StringBuilder id = new StringBuilder(64);
  
  			String name = view.getName();
  			id.append(module.getName()).append('_');
  			id.append(document.getName()).append('_');
  			id.append(name).append('_');
  			if (ViewType.create.toString().equals(name)) {
  				id.append(createIdCounter);
  			}
  			else {
  				id.append(editIdCounter);
  			}
  			
  			return id.toString();
  		}
  	}
  	
  	private User user;
  	private Bean bean;
  	// we are applying values, so don't include bindings for
  	// disabled widgets or lookup description bindings
  	private boolean forApply;
  	
  	// The bindings required to be resolved to display this view 
  	// for the document in its current state for the current user
  	// This a composite tree where the root node represents top level bindings,
  	// The sub-nodes are either associations or collections that must be processed recursively.
  	private ViewBindings bindingTree = new ViewBindings(document);
  
  	// The current binding getting processed at present
  	// NB This is initially set to the root node until we traverse down through a data grid or comparison.
  	private ViewBindings currentBindings = bindingTree;
  	
  	// This is the value maps required for variant and dynamic domain values
  	private Map<String, LinkedHashMap<String, String>> valueMaps = new TreeMap<>();
  	
  	// These are format strings keyed by the binding prefix
  	// binding prefix ("" if no prefix - see bindings above), and then a map of 
  	// form/grid column item names -> format expression objects
  	private Map<String, Map<String, ViewFormat>> formats = new TreeMap<>();
  
  	// There is no binding prefixes for comparisons - too hard
  	// The map is for comparison bindings -> comparison JSON tree required to populate the widget
  	private Map<String, Iterable<Map<String, Object>>> comparisons = new TreeMap<>();
  	
  	// This is used to assign names to boilerplate values which have binding expressions in them, such as links.
  	// These values are evaluated server side and stashed in the JSON response under a bogus binding.
  	private int formatCounter = 0;
  	
  	// This is used to generate smart client widget IDs that correspond to what is generated,
  	// so that code generated server-side can look up the appropriate controls
  	private int editIdCounter = 0;
  	private int createIdCounter = 0;
  
  	// HrefProcessor - takes a Reference and appends a suitable href in htmlGuts
  	HrefProcessor hrefProcessor = new HrefProcessor();
  	private StringBuilder htmlGuts = new StringBuilder(64);
  
  	ViewJSONManipulator(User user,
  							Module module, 
  							Document document, 
  							View view,
  							Bean bean,
  							int editIdCounter, // the base number which is incremented for view component IDs for uniqueness
  							int createIdCounter, // the base number which is incremented for view component IDs for uniqueness
  							boolean forApply) {
  		super((CustomerImpl) user.getCustomer(),
  				(ModuleImpl) module,
  				(DocumentImpl) document,
  				(ViewImpl) view);
  		this.user = user;
  		this.bean = bean;
  		this.editIdCounter = editIdCounter;
  		this.createIdCounter = createIdCounter;
  		this.forApply = forApply;
  	}
  	
  	String toJSON(AbstractWebContext webContextToReference,
  					String redirectUrl) // to redirect the browser location when the response is processed
  	throws Exception {
  		Map<String, Object> result = new TreeMap<>();
  
  		// Put the redirecturl in if required
  		if (redirectUrl != null) {
  			result.put("_redirectUrl", redirectUrl);
  		}
  		
  		// Encode the context bean
  		String webId = webContextToReference.getWebId();
  		result.put(AbstractWebContext.CONTEXT_NAME, webId);
  		
  		// put the view title in
  		result.put("_title", BindUtil.formatMessage(view.getLocalisedTitle(), bean));
  
  		// put the view changed/dirty flag in
  		result.put("_changed", Boolean.valueOf(webContextToReference.getCurrentBean().hasChanged()));
  
  		// put web context growls and messages in
  		List<Map<String, String>> messages = webContextToReference.getGrowls();
  		if (messages != null) {
  			result.put("_growls", messages);
  		}
  		messages = webContextToReference.getMessages();
  		if (messages != null) {
  			result.put("_messages", messages);
  		}
  		
  		constructJSONObjectFromBinding(bindingTree, result, webId);
  		
  		if (! valueMaps.isEmpty()) {
  			result.put("_valueMaps", valueMaps);
  		}
  		
  		for (String binding : comparisons.keySet()) {
  			result.put(binding, comparisons.get(binding));
  		}
  		
  		return JSON.marshall(user.getCustomer(), result);
  	}
  	
  	private void constructJSONObjectFromBinding(ViewBindings bindings,
  													Map<String, Object> json,
  													String webId)
  	throws Exception {
  		String bindingPrefix = bindings.getBindingPrefix();
  		if (bindingPrefix == null) { // root node (top level)
  			addBindingsAndFormatValues(bindings, bean, json, webId);
  		}
  		else { // reference bindings (sub-bindings)
  			Object value = BindUtil.get(bean, bindingPrefix);
  			if (value instanceof List) {
  				@SuppressWarnings("unchecked")
  				List<Bean> list = (List<Bean>) value;
  				List<Map<String, Object>> values = new ArrayList<>(list.size());
  				for (Bean element : list) {
  					Map<String, Object> elementValues = new TreeMap<>();
  					addBindingsAndFormatValues(bindings, element, elementValues, webId);
  					values.add(elementValues);
  				}
  				json.put(BindUtil.sanitiseBinding(bindingPrefix), values);
  			}
  			else {
  				if (value == null) {
  					UtilImpl.LOGGER.warning(String.format("Careful - the value of binding %s for %s yields null",
  															bindingPrefix, 
  															bean));
  				}
  				else {
  					Bean currentBean = (Bean) value;
  					Map<String, Object> beanValues = new TreeMap<>();
  					addBindingsAndFormatValues(bindings, currentBean, beanValues, webId);
  					json.put(BindUtil.sanitiseBinding(bindingPrefix), beanValues);
  				}
  			}
  		}
  	}
  	
  	private void addBindingsAndFormatValues(ViewBindings bindings,
  												Bean aBean,
  												Map<String, Object> toAddTo,
  												String webId)
  	throws Exception {
  		// Add bindings
  		for (String binding : bindings.getBindings()) {
  			ViewBinding viewBinding = bindings.getBinding(binding);
  			Object value = BindUtil.get(aBean, binding);
  			boolean escape = viewBinding.isEscape();
  			Sanitisation sanitise = viewBinding.getSanitise();
  			// escape and sanitise string values if needed
  			if ((escape || ((sanitise != null) && (! Sanitisation.none.equals(sanitise)))) && (value instanceof String)) {
  				String string = (String) value;
  				string = OWASP.sanitise(sanitise, string);
  				if (escape) {
  					string = OWASP.escapeHtml(string);
  				}
  				value = string;
  			}
  			else if (value instanceof Bean) {
  				value = ((Bean) value).getBizId();
  			}
  			// Coerce boolean and numbers into strings if they have a domain defined
  			// because SmartClient needs strings in its FormItem "valueMap" property 
  			// and the item value has to match for a domain value to be selected.
  			else if ((value instanceof Boolean) || (value instanceof Number)) {
  				Attribute attribute = null;
  				try {
  					TargetMetaData target = BindUtil.getMetaDataForBinding(customer, module, document, binding);
  					attribute = (target != null) ? target.getAttribute() : null;
  				}
  				catch (@SuppressWarnings("unused") MetaDataException e) {
  					// not an attribute
  				}
  				if ((attribute != null) && (attribute.getDomainType() != null)) {
  					value = value.toString();
  				}
  			}
  			toAddTo.put(BindUtil.sanitiseBinding(binding), value);
  		}
  		
  		// Add formats
  		String bindingKey = bindings.getBindingPrefix();
  		if (bindingKey == null) {
  			bindingKey = "";
  		}
  		Map<String, ViewFormat> formatMap = formats.get(bindingKey);
  		if (formatMap != null) {
  			for (String name : formatMap.keySet()) {
  				ViewFormat viewFormat = formatMap.get(name);
  				String format = viewFormat.getFormat();
  				// replace the "{CONTEXT}" placeholder with the current webId
  				format = format.replace("{CONTEXT}", webId);
  				// now format the message
  				Sanitisation sanitisation = viewFormat.getSanitise();
  				if (viewFormat.isEscape()) {
  					format = BindUtil.formatMessage(format, displayValue -> OWASP.sanitiseAndEscapeHtml(sanitisation, displayValue), aBean);
  				}
  				else {
  					format = BindUtil.formatMessage(format, displayValue -> OWASP.sanitise(sanitisation, displayValue), aBean);
  				}
  				// remove the display style if its true
  				format = format.replace("display:true;", "");
  				// change to display none if display style is false
  				format = format.replace("display:false;", "display:none;");
  				// change any null valued image urls to point to blank
  				format = format.replaceAll("src=\\\"content\\?_n=&_doc=.*?\\\"", "src=\"images/blank.gif\"");
  				toAddTo.put(name, format);
  			}
  		}
  		
  		for (String childBinding : bindings.getChildren()) {
  			constructJSONObjectFromBinding(bindings.putOrGetChild(childBinding, null), toAddTo, webId);
  		}
  	}
  /*	
  	private static void displayViewBindings(ViewBindings bindings) {
  		for (String binding : bindings.getBindings()) {
  			UtilImpl.LOGGER.info(bindings.getFullyQualifiedBindingPrefix() + " - " + bindings.getBindingPrefix() + " : " + binding);
  		}
  		
  		for (String binding : bindings.getChildren()) {
  			ViewBindings childViewBindings = bindings.putOrGetChild(binding, null);
  			displayViewBindings(childViewBindings);
  		}
  	}
  */	
  	void applyJSON(String json, AbstractPersistence persistence, WebContext webContext) throws Exception {
  		@SuppressWarnings("unchecked")
  		Map<String, Object> values = (Map<String, Object>) JSON.unmarshall(user, json);
  
  //		displayViewBindings(bindingTree);
  		applyJSON(bindingTree, document, values, bean, persistence, webContext);
  	}
  
  	@SuppressWarnings("unchecked")
  	private void applyJSON(ViewBindings bindings,
  							Document appliedToDoc,
  							Map<String, Object> values,
  							Bean appliedTo,
  							AbstractPersistence persistence,
  							WebContext webContext)
  	throws Exception {
  //UtilImpl.LOGGER.info("FQ BINDING PREFIX = " + bindings.getFullyQualifiedBindingPrefix());
  
  		applyJSONProperties(bindings, appliedToDoc, values, appliedTo, persistence, webContext);
  
  		for (String childBindingPrefix : bindings.getChildren()) {
  			ViewBindings childBindings = bindings.putOrGetChild(childBindingPrefix, null);
  			// Get the reference target metadata
  			TargetMetaData target = BindUtil.getMetaDataForBinding(customer, 
  																	module, 
  																	appliedToDoc, 
  																	childBindingPrefix);
  			Relation relation = (Relation) target.getAttribute();
  			Document relatedDocument = module.getDocument(customer, relation.getDocumentName());
  			AttributeType relationType = relation.getAttributeType();
  			if (List.class.equals(relationType.getImplementingType())) { // relation is a collection (or many to many inverse)
  				// We get the JSON list and apply all elements to the existing elements
  				// in the persisted list.
  				//*Any persisted elements that are not present in the JSON list are removed
  				//*Any JSON elements that are not present in the persisted elements are 
  				// added to the persisted list
  				
  				// The list of values from the JSON post
  				// If the rows are references (membership widgets),
  				// the rows should exist this will be a List of bizId Strings.
  				// If the rows are aggregated/composed (grid widget),
  				// then each row property is updated to add or modify the rows.
  				List<Object> requestList = (List<Object>) BindUtil.get(values, BindUtil.unsanitiseBinding(childBindingPrefix));
  				// If the requestList is null then it was not sent from the client - it is irrelevant.
  				// A data grid binding can be struck out of the request when a zoom out occurs.
  				// This ensures that old values are not updated when the view is refreshed - see EditView.js where action == 'ZoomOut'
  				if (requestList == null) {
  					continue;
  				}
  
  				// sort requestList by bizOrdinal if appropriate
  //				if (! requestList.isEmpty()) {
  //					Object first = requestList.get(0);
  //					if (first instanceof Map) {
  //						if (((Map<String, Object>) first).containsKey(ChildBean.ORDINAL_KEY)) {
  //							BindUtil.sortCollectionByOrdering(requestList, CORE.newOrdering(ChildBean.ORDINAL_KEY, SortDirection.ascending));
  //						}
  //					}
  //				}
  				
  				// Get the existing beans in the list
  				List<Bean> beanList = (List<Bean>) BindUtil.get(appliedTo, childBindingPrefix);
  				// beanList could be null if we have a datagrid that is bound to a compound binding...
  				// eg <dataGrid binding="foo.bars" />
  				// Now, if foo is made null by another control - maybe a combo empty value is chosen then
  				// 1) foo is null
  				// 2) foo.bars yields null and we don't need to apply any processing
  				if (beanList != null) {
  					// process the existing beans applying the changes and ordering from the requestList
  					int newIndex = 0;
  					for (Object requestListItem : requestList) {
  						String thisBizId = null;
  						Map<String, Object> thisMap = null;
  						if (requestListItem instanceof String) { // reference
  							thisBizId = (String) requestListItem;
  						}
  						else {
  							thisMap = (Map<String, Object>) requestListItem;
  							thisBizId = (String) thisMap.get(Bean.DOCUMENT_ID);
  						}
  						Bean thisBean = null;
  						if (thisBizId != null) {
  							thisBean = BindUtil.getElementInCollection(beanList, thisBizId);
  						}
  	
  						if (thisBean == null) { // DNE in beanList
  							if (thisMap == null) { // reference
  								thisBean = WebUtil.findReferencedBean(relatedDocument, thisBizId, persistence, bean, webContext);
  							}
  							else {
  								// create a new one with new Instance
  								thisBean = relatedDocument.newInstance(user);
  								applyJSON(childBindings,
  											relatedDocument,
  											thisMap,
  											thisBean,
  											persistence,
  											webContext);
  							}
  							BindUtil.addElement(appliedTo, childBindingPrefix, newIndex, thisBean);
  						}
  						else { // found
  							// Only move the bean in the collection if required
  							// NB We do this conditionally so we don't upset hibernate collection dirtiness
  							if (beanList.indexOf(thisBean) != newIndex) {
  								beanList.remove(thisBean);
  								beanList.add(newIndex, thisBean);
  							}
  	
  							// apply the properties from the JSON to the bean element, if its not a reference
  							if (thisMap != null) {
  								applyJSON(childBindings,
  											relatedDocument,
  											thisMap,
  											thisBean,
  											persistence,
  											webContext);
  							}
  						}
  						newIndex++;
  					}
  	
  					// delete any left over beans in the list as these were not present in the requestList
  					while (beanList.size() > newIndex) {
  						BindUtil.removeElement(appliedTo, childBindingPrefix, newIndex);
  					}
  					
  					if (relation instanceof Collection) { // NB it could be an inverse
  						BindUtil.sortCollectionByMetaData(appliedTo, customer, module, appliedToDoc, childBindingPrefix);
  					}
  				}
  			}
  			else { // relation is an association (or one to one / one to many inverse)
  				// Get the existing bean referenced
  				Bean referencedBean = (Bean) BindUtil.get(appliedTo, childBindingPrefix);
  				Object requestObject = BindUtil.get(values, BindUtil.unsanitiseBinding(childBindingPrefix));
  				if (requestObject == null) {
  					if (referencedBean != null) {
  						BindUtil.set(appliedTo, childBindingPrefix, null);
  					}
  				}
  				else {
  					if (requestObject instanceof String) { // a bizId
  						String requestBizId = (String) requestObject;
  						// find the existing bean with retrieve if not the same as in the request
  						if ((referencedBean == null) || (! referencedBean.getBizId().equals(requestBizId))) {
  							referencedBean = WebUtil.findReferencedBean(relatedDocument, requestBizId, persistence, bean, webContext);
  							BindUtil.set(appliedTo, childBindingPrefix, referencedBean);
  						}
  					}
  					else { // a JSON object
  						Map<String, Object> referencedMap = (Map<String, Object>) requestObject;
  						
  						// create a new one if required
  						if (referencedBean == null) {
  							referencedBean = relatedDocument.newInstance(user);
  						}
  						applyJSON(childBindings, relatedDocument, referencedMap, referencedBean, persistence, webContext);
  					}
  				}
  			}
  		}
  	}
  
  	private void applyJSONProperties(ViewBindings bindings,
  										Document documentToApply,
  										Map<String, Object> valuesToApply,
  										Bean beanToApplyTo,
  										AbstractPersistence persistence,
  										WebContext webContext)
  	throws Exception {
  		for (String binding : bindings.getBindings()) {
  //UtilImpl.LOGGER.info(currentBindings.getFullyQualifiedBindingPrefix() + " : " + binding);
  			ViewBinding vb = bindings.getBinding(binding);
  			if ((vb != null) && vb.isMutable()) {
  				applyJSONProperty(documentToApply, binding, valuesToApply, beanToApplyTo, persistence, webContext);
  			}
  		}
  	}
  	
  	private void applyJSONProperty(Document startingDocument,
  									String binding, 
  									Map<String, Object> values, 
  									Bean targetBean,
  									AbstractPersistence persistence,
  									WebContext webContext) 
  	throws Exception {
  		String valueKey = BindUtil.sanitiseBinding(binding);
  		if (! values.containsKey(valueKey)) {
  			return;
  		}
  		
  		try {
  			TargetMetaData target = BindUtil.getMetaDataForBinding(customer, 
  																	module, 
  																	startingDocument, 
  																	binding);
  			Attribute attribute = target.getAttribute();
  			if ((attribute instanceof Association) || (attribute instanceof InverseOne)) {
  				String documentName = ((Relation) attribute).getDocumentName();
  				Document relatedDocument = module.getDocument(customer, documentName);
  
  				Bean oldRelatedBean = (Bean) BindUtil.get(targetBean, binding);
  				Bean newRelatedBean = null;
  				boolean dirty = false;
  				
  				// put the new related bean (or null) into the values
  				Object relatedValue = values.get(valueKey);
  				if (relatedValue == null) {
  					dirty = (oldRelatedBean != null);
  				}
  				// Don't try to traverse an embedded association or inverseOne object here recursively.
  				// The correct bindings are created when visiting the view during the apply.
  				// So here we only need to effect the replacement of bizId Strings with retrieved objects
  				else if (relatedValue instanceof String) { // a bizId (not a JSON object)
  					String relatedId = (String) relatedValue;
  					// old value id and new value id are different
  					if ((oldRelatedBean == null) || (! oldRelatedBean.getBizId().equals(relatedId))) {
  						newRelatedBean = WebUtil.findReferencedBean(relatedDocument, relatedId, persistence, bean, webContext);
  						dirty = true;
  					}
  				}
  				
  				if (dirty) {
  					BindUtil.populateProperty(user, targetBean, binding, newRelatedBean, true);
  				}
  			}
  			// We have a binding to a document attribute OR
  			// we have a binding to an implicit property that is mutable.
  			else if ((attribute != null) ||
  					binding.endsWith(PersistentBean.FLAG_COMMENT_NAME) ||
  					binding.endsWith(Bean.ORDINAL_NAME)) {
  //UtilImpl.LOGGER.info("SET " + targetBean + '.' + binding + " = " + values.get(valueKey));
  				Object value = values.get(valueKey);
  				if (value instanceof String) {
  					value = OWASP.unescapeHtmlChars((String) value);
  				}
  				BindUtil.populateProperty(user, targetBean, binding, value, true);
  			}
  		}
  		catch (MetaDataException e) {
  			// do nothing useful as the binding isn't an attribute
  			UtilImpl.LOGGER.warning(e.toString());
  		}
  	}
  	
  	private void addBinding(String binding, boolean mutable, boolean escape, Sanitisation sanitise) {
  		addBinding(binding, mutable, escape, sanitise, false);
  	}
  
  	private void addBinding(String binding,
  								boolean mutable,
  								boolean escape,
  								Sanitisation sanitise,
  								boolean noPrefix) {
  		if (binding != null) {
  		    ViewBindings bindings = (noPrefix ? bindingTree : currentBindings);
  			bindings.putBinding(binding, mutable, escape, (sanitise == null) ? Sanitisation.relaxed : sanitise);
  		}
  	}
  
  	private void addFormat(String valueTemplate, boolean escape, Sanitisation sanitise) {
  		if (valueTemplate != null) {
  			String currentBindingPrefix = currentBindings.getBindingPrefix();
  			String formatKey = (currentBindingPrefix == null) ? "" : currentBindingPrefix;
  			Map<String, ViewFormat> formatMap = formats.get(formatKey);
  			if (formatMap == null) {
  				formatMap = new TreeMap<>();
  				formats.put(formatKey, formatMap);
  			}
  			String name = "_" + formatCounter++;
  			formatMap.put(name, new ViewFormat(valueTemplate, escape, sanitise));
  		}
  	}
  
  	private void addCondition(String condition) {
  		if ((condition != null) && (! condition.equals("true")) && (! condition.equals("false"))) {
  			addBinding(condition, false, false, Sanitisation.none, true);
  		}
  	}
  	
  	/**
  	 * Put variant and dynamic domain values in the valueMaps variable
  	 * (if not there already) for inclusion in the instance.
  	 * 
  	 * @param binding	The binding for the domain values lookup.
  	 */
  	private void putVariantAndDynamicDomainValuesInValueMaps(String binding) {
  		String safeBinding = BindUtil.sanitiseBinding(binding);
  		if (! valueMaps.containsKey(safeBinding)) {
              TargetMetaData target = BindUtil.getMetaDataForBinding(customer, module, document, binding);
              Attribute attribute = target.getAttribute();
              DomainType domainType = attribute.getDomainType();
  
              // Keep the domain values ordered with a LinkedHashMap
              LinkedHashMap<String, String> values = new LinkedHashMap<>();
  
              if (DomainType.variant.equals(domainType)) {
              	DocumentImpl targetDocument = (DocumentImpl) target.getDocument();
                  for (DomainValue domainValue : targetDocument.getDomainValues(customer, 
      																			domainType, 
      																			attribute, 
      																			null,
      																			true)) {
                      values.put(domainValue.getCode(), domainValue.getDescription());
                  }
                  valueMaps.put(safeBinding, values);
              }
              else if (DomainType.dynamic.equals(domainType)) {
              	List<DomainValue> domainValues = null;
              	
              	int lastDotIndex = binding.lastIndexOf('.');
  				if (lastDotIndex >= 0) {
  					Bean owningBean = null;
  					try {
  						// This could be a collection!!!
  						// for example in a view with <datagrid binding="collectionName"><column binding="dynamic"/></dataGrid>
  						// the binding is "collectionName.dynamic".
  						Object owner = BindUtil.get(bean, binding.substring(0, lastDotIndex));
  						if (owner instanceof Bean) {
  							owningBean = (Bean) owner;
  						}
  					}
  					catch (Exception e) {
  						throw new MetaDataException("Could not get owning bean to create dynamic values", e);
  					}
  					if (owningBean != null) {
  		            	DocumentImpl targetDocument = (DocumentImpl) target.getDocument();
  						domainValues = targetDocument.getDomainValues(customer,
  																		domainType,
  																		attribute,
  																		owningBean,
  																		true);
  					}
  				}
  				else {
  					domainValues = document.getDomainValues(customer, domainType, attribute, bean, true);						
  				}
                  
  				if (domainValues != null) {
  					for (DomainValue domainValue : domainValues) {
  	                    values.put(domainValue.getCode(), domainValue.getDescription());
  					}
  	                valueMaps.put(safeBinding, values);
  				}
              }
          }
  	}
  	
  	@Override
  	protected boolean visible(Invisible invisible) {
  		return evaluateConditionInOppositeSense(invisible.getInvisibleConditionName());
  	}
  
  	@Override
  	protected boolean enabled(Disableable disableable) {
  		return evaluateConditionInOppositeSense(disableable.getDisabledConditionName());
  	}
  	
  	private boolean evaluateConditionInOppositeSense(String conditionName) {
  		boolean result = true;
  
  		if (conditionName != null) {
  			if ("true".equals(conditionName)) {
  				result = false;
  			}
  			else if (! "false".equals(conditionName)) {
  				result = ! bean.evaluateCondition(conditionName);
  			}
  		}
  		
  		return result;
  	}
  
  	@Override
  	public void visitView() {
  		addCondition(Bean.PERSISTED_KEY); // for inplicit actions in toolbar
  		addCondition(Bean.CREATED_KEY); // for create/edit view to operate
  		addCondition(Bean.NOT_CREATED_KEY); // for create/edit view to operate
  	}
  
  	@Override
  	public void visitTabPane(TabPane tabPane,
  								boolean parentVisible,
  								boolean parentEnabled) {
  		addCondition(tabPane.getInvisibleConditionName());
  		addCondition(tabPane.getDisabledConditionName());
  		addBinding(tabPane.getSelectedTabIndexBinding(), false, false, Sanitisation.text);
  	}
  
  	@Override
  	public void visitedTabPane(TabPane tabPane,
  								boolean parentVisible,
  								boolean parentEnabled) {
  		// nothing to do here
  	}
  
  	@Override
  	public void visitTab(Tab tab,
  							boolean parentVisible,
  							boolean parentEnabled) {
  		addCondition(tab.getInvisibleConditionName());
  		addCondition(tab.getDisabledConditionName());
  	}
  
  	@Override
  	public void visitedTab(Tab tab,
  							boolean parentVisible,
  							boolean parentEnabled) {
  		// nothing to do here
  	}
  
  	@Override
  	public void visitVBox(VBox vbox,
  							boolean parentVisible,
  							boolean parentEnabled) {
  		addCondition(vbox.getInvisibleConditionName());
  	}
  
  	@Override
  	public void visitHBox(HBox hbox,
  							boolean parentVisible,
  							boolean parentEnabled) {
  		addCondition(hbox.getInvisibleConditionName());
  	}
  
  	@Override
  	public void visitForm(Form form,
  							boolean parentVisible,
  							boolean parentEnabled) {
  		addCondition(form.getDisabledConditionName());
  		addCondition(form.getInvisibleConditionName());
  	}
  
  	@Override
  	public void visitedForm(Form form,
  								boolean parentVisible,
  								boolean parentEnabled) {
  		// keep this in sync with the generated edit views
  		if (ViewType.create.toString().equals(view.getName())) {
  			createIdCounter++;
  		}
  		else {
  			editIdCounter++;
  		}
  	}
  
  	@Override
  	public void visitFormColumn(FormColumn column,
  									boolean parentVisible,
  									boolean parentEnabled) {
  		// not bound
  	}
  
  	@Override
  	public void visitFormRow(FormRow row,
  								boolean parentVisible,
  								boolean parentEnabled) {
  		// not bound
  	}
  
  	@Override
  	public void visitFormItem(FormItem item, boolean parentVisible, boolean parentEnabled) {
  		// not bound
  	}
  
  	@Override
  	public void visitedFormItem(FormItem item,
  								boolean parentVisible,
  								boolean parentEnabled) {
  		// not bound
  	}
  
  	@Override
  	public void visitedFormRow(FormRow row,
  								boolean parentVisible,
  								boolean parentEnabled) {
  		// not bound
  	}
  
  	@Override
  	public void visitButton(Button button,
  								boolean parentVisible,
  								boolean parentEnabled) {
  		Action action = view.getAction(button.getActionName());
  		addCondition(action.getInvisibleConditionName());
  		addCondition(action.getDisabledConditionName());
  	}
  	
  	@Override
  	public void visitGeometry(Geometry geometry,
  								boolean parentVisible,
  								boolean parentEnabled) {
  		if (visitingDataWidget) {
  			return;
  		}
  		
  		if (parentVisible && visible(geometry)) {
  			if ((! forApply) || 
  					(forApply && parentEnabled)) {
  				addBinding(geometry.getBinding(), true, false, Sanitisation.none);
  			}
  		}
  		addCondition(geometry.getInvisibleConditionName());
  		addCondition(geometry.getDisabledConditionName());
  	}
  
  	@Override
  	public void visitedGeometry(Geometry geometry,
  									boolean parentVisible,
  									boolean parentEnabled) {
  		// do nothing
  	}
  	
  	@Override
  	public void visitGeometryMap(GeometryMap geometry,
  									boolean parentVisible,
  									boolean parentEnabled) {
  		if (parentVisible && visible(geometry)) {
  			if ((! forApply) || 
  					(forApply && parentEnabled)) {
  				addBinding(geometry.getBinding(), true, false, Sanitisation.none);
  			}
  		}
  		addCondition(geometry.getInvisibleConditionName());
  		addCondition(geometry.getDisabledConditionName());
  	}
  
  	@Override
  	public void visitedGeometryMap(GeometryMap geometry,
  									boolean parentVisible,
  									boolean parentEnabled) {
  		// do nothing
  	}
  	
  	@Override
  	public void visitMap(MapDisplay map,
  							boolean parentVisible,
  							boolean parentEnabled) {
  		addCondition(map.getInvisibleConditionName());
  	}
  
  	@Override
  	public void visitChart(Chart chart,
  							boolean parentVisible,
  							boolean parentEnabled) {
  		addCondition(chart.getInvisibleConditionName());
  	}
  
  	@Override
  	public void visitDialogButton(DialogButton button,
  									boolean parentVisible,
  									boolean parentEnabled) {
  		addCondition(button.getInvisibleConditionName());
  		addCondition(button.getDisabledConditionName());
  	}
  
  	@Override
  	public void visitDynamicImage(DynamicImage image,
  									boolean parentVisible,
  									boolean parentEnabled) {
  		if (visitingDataWidget) {
  			if (htmlGuts.length() > 0) {
  				htmlGuts.append("&nbsp;");
  			}
  			// TODO - should make the URL dependent on the image format
  			htmlGuts.append("<img src=\"dynamic.png?_n=").append(image.getName());
  			htmlGuts.append("&_doc={bizModule}.{bizDocument}");
  			
  			Integer pixelWidth = image.getPixelHeight();
  			Integer pixelHeight = image.getPixelHeight();
  			Integer initialPixelWidth = image.getImageInitialPixelWidth();
  			Integer initialPixelHeight = image.getImageInitialPixelHeight();
  			if (pixelWidth != null) {
  				htmlGuts.append('&').append(DynamicImageServlet.IMAGE_WIDTH_NAME).append('=').append(pixelWidth);
  			}
  			else {
  				htmlGuts.append('&').append(DynamicImageServlet.IMAGE_WIDTH_NAME).append('=').append(initialPixelWidth);
  			}
  			if (pixelHeight != null) {
  				htmlGuts.append('&').append(DynamicImageServlet.IMAGE_HEIGHT_NAME).append('=').append(pixelHeight);
  			}
  			else {
  				htmlGuts.append('&').append(DynamicImageServlet.IMAGE_HEIGHT_NAME).append('=').append(initialPixelHeight);
  			}
  			htmlGuts.append('&').append(DynamicImageServlet.IMAGE_WIDTH_ZOOM_NAME).append("=100&");
  			htmlGuts.append(DynamicImageServlet.IMAGE_HEIGHT_ZOOM_NAME).append("=100&");
  
  			htmlGuts.append(AbstractWebContext.CONTEXT_NAME).append("={CONTEXT}&");
  			htmlGuts.append(Bean.DOCUMENT_ID).append("={bizId}");
  			appendHtmlGutsStyle(image.getPixelWidth(), image.getPixelHeight(), null, image.getInvisibleConditionName());
  			htmlGuts.append("/>");
  		}
  		else {
  			addCondition(image.getInvisibleConditionName());
  		}
  	}
  
  	@Override
  	public void visitSpacer(Spacer spacer) {
  		// nothing to do here
  	}
  
  	@Override
  	public void visitStaticImage(StaticImage image,
  									boolean parentVisible,
  									boolean parentEnabled) {
  		if (visitingDataWidget) {
  			if (htmlGuts.length() > 0) {
  				htmlGuts.append("&nbsp;");
  			}
  			htmlGuts.append("<img src=\"resources?_n=").append(image.getRelativeFile());
  			htmlGuts.append("&_doc={bizModule}.{bizDocument}&_b=null\"");
  			appendHtmlGutsStyle(image.getPixelWidth(), image.getPixelHeight(), null, image.getInvisibleConditionName());
  			htmlGuts.append("/>");
  		}
  		else {
  			addCondition(image.getInvisibleConditionName());
  		}
  	}
  
  	@Override
  	public void visitContentImage(ContentImage image,
  									boolean parentVisible, 
  									boolean parentEnabled) {
  		if (visitingDataWidget) {
  			if (htmlGuts.length() > 0) {
  				htmlGuts.append("&nbsp;");
  			}
  			String binding = image.getBinding();
  			htmlGuts.append("<img src=\"content?_n={").append(binding);
  			htmlGuts.append("}&_doc={bizModule}.{bizDocument}&_b=");
  			htmlGuts.append(binding).append('"');
  			appendHtmlGutsStyle(image.getPixelWidth(), image.getPixelHeight(), null, image.getInvisibleConditionName());
  			htmlGuts.append("/>");
  		}
  		else {
  			if (parentVisible && visible(image)) {
  				if ((! forApply) || 
  						(forApply && parentEnabled && (! Boolean.FALSE.equals(image.getEditable())))) {
  					addBinding(image.getBinding(), true, false, Sanitisation.text);
  				}
  			}
  			addCondition(image.getInvisibleConditionName());
  			addCondition(image.getDisabledConditionName());
  			addBinding(Bean.MODULE_KEY, false, false, Sanitisation.text);
  			addBinding(Bean.DOCUMENT_KEY, false, false, Sanitisation.text);
  			addBinding(Bean.DATA_GROUP_ID, false, false, Sanitisation.text);
  			addBinding(Bean.USER_ID, false, false, Sanitisation.text);
  		}
  	}
  
  	@Override
  	public void visitContentSignature(ContentSignature signature,
  										boolean parentVisible, 
  										boolean parentEnabled) {
  		// TODO not implemented for SC yet - use ContentImage
  		if (parentVisible && visible(signature)) {
  			if ((! forApply) || (forApply && parentEnabled)) {
  				addBinding(signature.getBinding(), true, false, Sanitisation.text);
  			}
  		}
  		addCondition(signature.getInvisibleConditionName());
  		addCondition(signature.getDisabledConditionName());
  		addBinding(Bean.MODULE_KEY, false, false, Sanitisation.text);
  		addBinding(Bean.DOCUMENT_KEY, false, false, Sanitisation.text);
  		addBinding(Bean.DATA_GROUP_ID, false, false, Sanitisation.text);
  		addBinding(Bean.USER_ID, false, false, Sanitisation.text);
  	}
  
  	@Override
  	public void visitBlurb(Blurb blurb,
  							boolean parentVisible,
  							boolean parentEnabled) {
  		if (visitingDataWidget) {
  			if (htmlGuts.length() > 0) {
  				htmlGuts.append("&nbsp;");
  			}
  			htmlGuts.append("<div");
  			appendHtmlGutsStyle(blurb.getPixelWidth(),
  									blurb.getPixelHeight(),
  									blurb.getTextAlignment(),
  									blurb.getInvisibleConditionName());
  			htmlGuts.append('>').append(blurb.getLocalisedMarkup()).append("</div>");
  		}
  		else {
  			String markup = blurb.getLocalisedMarkup();
  			if (BindUtil.messageIsBound(markup)) { // has a binding expression
  				if (parentVisible && visible(blurb)) {
  					if ((! forApply) || 
  							(forApply && parentEnabled)) {
  						addFormat(markup, ! Boolean.FALSE.equals(blurb.getEscape()), blurb.getSanitise());
  					}
  					else {
  						// ensure the format counter is incremented to stay in sync with the generated edit view
  						formatCounter++;
  					}
  				}
  				else {
  					// ensure the format counter is incremented to stay in sync with the generated edit view
  					formatCounter++;
  				}
  			}
  			addCondition(blurb.getInvisibleConditionName());
  		}
  	}
  
  	@Override
  	public void visitLabel(Label label,
  							boolean parentVisible,
  							boolean parentEnabled) {
  		if (visitingDataWidget) {
  			if (htmlGuts.length() > 0) {
  				htmlGuts.append("&nbsp;");
  			}
  			htmlGuts.append("<span");
  			appendHtmlGutsStyle(label.getPixelWidth(),
  									label.getPixelHeight(),
  									label.getTextAlignment(),
  									label.getInvisibleConditionName());
  			htmlGuts.append('>');
  			String binding = label.getBinding();
  			if (binding != null) {
  				htmlGuts.append('{').append(binding).append('}');
  			}
  			else {
  				String value = label.getLocalisedValue();
  				if (value != null) {
  					htmlGuts.append(value);
  				}
  				else {
  					htmlGuts.append(label.getFor());
  				}
  			}
  			htmlGuts.append("</span>");
  		}
  		else {
  			String value = label.getLocalisedValue();
  			boolean boundValue = (value != null) && BindUtil.messageIsBound(value);
  			if (boundValue) { // has a binding expression
  				if (parentVisible && visible(label)) {
  					if ((! forApply) || 
  							(forApply && parentEnabled)) {
  						addFormat(value, ! Boolean.FALSE.equals(label.getEscape()), label.getSanitise());
  					}
  					else {
  						// ensure the format counter is incremented to stay in sync with the generated edit view
  						formatCounter++;
  					}
  				}
  				else {
  					// ensure the format counter is incremented to stay in sync with the generated edit view
  					formatCounter++;
  				}
  			}
  			else {
  				if (parentVisible && visible(label)) {
  					if ((! forApply) || 
  							(forApply && parentEnabled)) {
  						addBinding(label.getBinding(), false, ! Boolean.FALSE.equals(label.getEscape()), label.getSanitise());
  					}
  				}
  			}
  			addCondition(label.getInvisibleConditionName());
  		}
  	}
  
  	@Override
  	public void visitLink(Link link,
  							boolean parentVisible,
  							boolean parentEnabled) {
  