Paints the given WMultiFileWidget.

@param component the WMultiFileWidget to paint.
@param renderContext the RenderContext to paint to.
432461
@Override
	public void doRender(final WComponent component, final WebXmlRenderContext renderContext) {
		WMultiFileWidget widget = (WMultiFileWidget) component;
		XmlStringBuilder xml = renderContext.getWriter();

		// Check if rendering a file upload response
		String uploadId = widget.getFileUploadRequestId();
		if (uploadId != null) {
			handleFileUploadRequest(widget, xml, uploadId);
			return;
		}
		boolean readOnly = widget.isReadOnly();


		xml.appendTagOpen(TAG_NAME);
		xml.appendAttribute("id", component.getId());
		xml.appendOptionalAttribute("class", component.getHtmlClass());
		xml.appendOptionalAttribute("track", component.isTracking(), "true");
		xml.appendOptionalAttribute("hidden", widget.isHidden(), "true");
		if (readOnly) {
			xml.appendAttribute("readOnly", "true");
		} else {
			long maxFileSize = widget.getMaxFileSize();
			int maxFiles = widget.getMaxFiles();
			WComponent dropzone = widget.getDropzone();
			WImageEditor editor = widget.getEditor();

			xml.appendOptionalAttribute("disabled", widget.isDisabled(), "true");
			xml.appendOptionalAttribute("required", widget.isMandatory(), "true");
			xml.appendOptionalAttribute("toolTip", widget.getToolTip());
			xml.appendOptionalAttribute("accessibleText", widget.getAccessibleText());
			xml.appendOptionalAttribute("acceptedMimeTypes", typesToString(widget.getFileTypes()));
			xml.appendOptionalAttribute("maxFileSize", maxFileSize > 0, maxFileSize);
			xml.appendOptionalAttribute("maxFiles", maxFiles > 0, maxFiles);
			if (dropzone != null) {
				xml.appendAttribute("dropzone", dropzone.getId());
			}
			if (editor != null) {
				xml.appendAttribute("editor", editor.getId());
				if (editor.getUseCamera()) {
					xml.appendAttribute("camera", true);
				}
			}
		}
		if (widget.getColumns() != null) {
			xml.appendAttribute("cols", widget.getColumns());
		}
		if (widget.getFileAjaxAction() != null) {
			xml.appendAttribute("ajax", "true");
		}

		List<Diagnostic> diags = readOnly ? null : widget.getDiagnostics(Diagnostic.ERROR);

		if (widget.getFiles().isEmpty()) {
			if (readOnly || diags == null || diags.isEmpty()) {
				xml.appendEnd();
				return;
			}
			xml.appendClose();
		} else {
			xml.appendClose();
			// Render files
			int i = 0;
			for (FileWidgetUpload file : widget.getFiles()) {
				FileWidgetRendererUtil.renderFileElement(widget, xml, file, i++);
			}
		}
		if (!readOnly && diags != null && !diags.isEmpty()) {
			DiagnosticRenderUtil.renderDiagnostics(widget, renderContext);
		}
		xml.appendEndTag(TAG_NAME);
	}
50336
@Override
	public void doRender(final WComponent component, final WebXmlRenderContext renderContext) {
		WSingleSelect listBox = (WSingleSelect) component;
		XmlStringBuilder xml = renderContext.getWriter();
		String dataKey = listBox.getListCacheKey();
		boolean readOnly = listBox.isReadOnly();
		int rows = listBox.getRows();

		xml.appendTagOpen("ui:listbox");
		xml.appendAttribute("id", component.getId());
		xml.appendOptionalAttribute("class", component.getHtmlClass());
		xml.appendOptionalAttribute("track", component.isTracking(), "true");
		xml.appendOptionalAttribute("hidden", listBox.isHidden(), "true");
		if (readOnly) {
			xml.appendAttribute("readOnly", "true");
		} else {
			xml.appendOptionalAttribute("data", dataKey != null, dataKey);
			xml.appendOptionalAttribute("disabled", listBox.isDisabled(), "true");
			xml.appendOptionalAttribute("required", listBox.isMandatory(), "true");
			xml.appendOptionalAttribute("submitOnChange", listBox.isSubmitOnChange(), "true");
			xml.appendOptionalAttribute("toolTip", component.getToolTip());
			xml.appendOptionalAttribute("accessibleText", component.getAccessibleText());
			xml.appendOptionalAttribute("rows", rows >= 2, rows);
			String autocomplete = listBox.getAutocomplete();
			xml.appendOptionalAttribute("autocomplete", !Util.empty(autocomplete), autocomplete);
		}
		xml.appendAttribute("single", "true");

		xml.appendClose();

		// Options
		List<?> options = listBox.getOptions();
		boolean renderSelectionsOnly = readOnly || dataKey != null;

		if (options != null) {
			int optionIndex = 0;
			Object selectedOption = listBox.getSelected();

			for (Object option : options) {
				if (option instanceof OptionGroup) {
					xml.appendTagOpen("ui:optgroup");
					xml.appendAttribute("label", ((OptionGroup) option).getDesc());
					xml.appendClose();

					for (Object nestedOption : ((OptionGroup) option).getOptions()) {
						renderOption(listBox, nestedOption, optionIndex++, xml, selectedOption, renderSelectionsOnly);
					}

					xml.appendEndTag("ui:optgroup");
				} else {
					renderOption(listBox, option, optionIndex++, xml, selectedOption, renderSelectionsOnly);
				}
			}
		}

		if (!readOnly) {
			DiagnosticRenderUtil.renderDiagnostics(listBox, renderContext);
		}
		xml.appendEndTag("ui:listbox");
	}
195629
@Override
	public void paint(final RenderContext renderContext) {
		boolean enabled = ConfigurationProperties.getDeveloperDumpUIContext();

		super.paint(renderContext);

		if (enabled) {
			UIContext uic = UIContextHolder.getCurrent();

			// We want to dump the shared WComponent tree (UIC) first, so that all the
			// references from the UI context are shown as such
			Object[] treeAndSession
					= new Object[]{
						uic.getUI(),
						uic
					};

			try {
				ObjectGraphNode root = ObjectGraphDump.dump(treeAndSession);

				// We only want to dump the tree once
				boolean treeDumped = "true".equalsIgnoreCase((String) uic.getFwkAttribute(
						UICONTEXT_TREE_DUMPED_KEY));

				if (!treeDumped) {
					uic.setFwkAttribute(UICONTEXT_TREE_DUMPED_KEY, "true");

					// The UI will be the 1st child
					LOG.info("UI dump follows:");
					LOG.info(((ObjectGraphNode) root.getChildAt(0)).toXml());
				}

				// The UI context will be the 2nd child
				LOG.info("UI Context dump follows:");
				LOG.info(((ObjectGraphNode) root.getChildAt(1)).toXml());
			} catch (Exception e) {
				LOG.error("Failed to dump context", e);
			}
		}
	}
624540
@Override
	public void doRender(final WComponent component, final WebXmlRenderContext renderContext) {
		WNumberField field = (WNumberField) component;
		XmlStringBuilder xml = renderContext.getWriter();
		boolean readOnly = field.isReadOnly();

		BigDecimal value = field.getValue();
		String userText = field.getText();

		xml.appendTagOpen("ui:numberfield");
		xml.appendAttribute("id", component.getId());
		xml.appendOptionalAttribute("class", component.getHtmlClass());
		xml.appendOptionalAttribute("track", component.isTracking(), "true");
		xml.appendOptionalAttribute("hidden", component.isHidden(), "true");
		if (readOnly) {
			xml.appendAttribute("readOnly", "true");
		} else {
			WComponent submitControl = field.getDefaultSubmitButton();
			String submitControlId = submitControl == null ? null : submitControl.getId();
			BigDecimal min = field.getMinValue();
			BigDecimal max = field.getMaxValue();
			BigDecimal step = field.getStep();
			int decimals = field.getDecimalPlaces();
			xml.appendOptionalAttribute("disabled", field.isDisabled(), "true");
			xml.appendOptionalAttribute("required", field.isMandatory(), "true");
			xml.appendOptionalAttribute("toolTip", field.getToolTip());
			xml.appendOptionalAttribute("accessibleText", field.getAccessibleText());
			xml.appendOptionalAttribute("min", min != null, String.valueOf(min));
			xml.appendOptionalAttribute("max", max != null, String.valueOf(max));
			xml.appendOptionalAttribute("step", step != null, String.valueOf(step));
			xml.appendOptionalAttribute("decimals", decimals > 0, decimals);
			xml.appendOptionalAttribute("buttonId", submitControlId);

			String autocomplete = field.getAutocomplete();
			xml.appendOptionalAttribute("autocomplete", !Util.empty(autocomplete), autocomplete);
		}

		xml.appendClose();

		xml.appendEscaped(value == null ? userText : value.toString());
		if (!readOnly) {
			DiagnosticRenderUtil.renderDiagnostics(field, renderContext);
		}
		xml.appendEndTag("ui:numberfield");
	}
539181
@Override
	public void doRender(final WComponent component, final WebXmlRenderContext renderContext) {
		WAudio audioComponent = (WAudio) component;
		XmlStringBuilder xml = renderContext.getWriter();
		Audio[] audio = audioComponent.getAudio();

		if (audio == null || audio.length == 0) {
			return;
		}

		WAudio.Controls controls = audioComponent.getControls();
		int duration = audio[0].getDuration();

		// Check for alternative text
		String alternativeText = audioComponent.getAltText();

		if (alternativeText == null) {
			LOG.warn("Audio should have a description.");
			alternativeText = null;
		} else {
			alternativeText = I18nUtilities.format(null, alternativeText);
		}

		xml.appendTagOpen("ui:audio");
		xml.appendAttribute("id", component.getId());
		xml.appendOptionalAttribute("class", component.getHtmlClass());
		xml.appendOptionalAttribute("track", component.isTracking(), "true");
		xml.appendOptionalAttribute("alt", alternativeText);
		xml.appendOptionalAttribute("autoplay", audioComponent.isAutoplay(), "true");
		xml.appendOptionalAttribute("mediagroup", audioComponent.getMediaGroup());
		xml.appendOptionalAttribute("loop", audioComponent.isLoop(), "true");
		xml.appendOptionalAttribute("hidden", audioComponent.isHidden(), "true");
		xml.appendOptionalAttribute("disabled", audioComponent.isDisabled(), "true");
		xml.appendOptionalAttribute("toolTip", audioComponent.getToolTip());
		xml.appendOptionalAttribute("duration", duration > 0, duration);

		switch (audioComponent.getPreload()) {
			case NONE:
				xml.appendAttribute("preload", "none");
				break;

			case META_DATA:
				xml.appendAttribute("preload", "metadata");
				break;

			case AUTO:
			default:
				break;
		}

		if (controls != null && !WAudio.Controls.NATIVE.equals(controls)) {
			switch (controls) {
				case NONE:
					xml.appendAttribute("controls", "none");
					break;

				case ALL:
					xml.appendAttribute("controls", "all");
					break;

				case PLAY_PAUSE:
					xml.appendAttribute("controls", "play");
					break;

				case DEFAULT:
					xml.appendAttribute("controls", "default");
					break;

				default:
					LOG.error("Unknown control type: " + controls);
			}
		}

		xml.appendClose();

		String[] urls = audioComponent.getAudioUrls();

		for (int i = 0; i < urls.length; i++) {
			xml.appendTagOpen("ui:src");
			xml.appendUrlAttribute("uri", urls[i]);
			xml.appendOptionalAttribute("type", audio[i].getMimeType());
			xml.appendEnd();
		}

		xml.appendEndTag("ui:audio");
	}
527115
@Override
	protected void paintComponent(final RenderContext renderContext) {
		if (renderContext instanceof WebXmlRenderContext) {
			PrintWriter writer = ((WebXmlRenderContext) renderContext).getWriter();
			writer.println(getMessage());

			if (developerFriendly) {
				writer.println("<pre style=\"background: lightgrey\">");
				writer.println("Additional details for the developer:");
				writer.println();
				error.printStackTrace(writer);
				writer.println("</pre>");
			}
		}
	}
628667
@Override
	public void doRender(final WComponent component, final WebXmlRenderContext renderContext) {
		WRadioButtonSelect rbSelect = (WRadioButtonSelect) component;
		XmlStringBuilder xml = renderContext.getWriter();
		int cols = rbSelect.getButtonColumns();
		boolean readOnly = rbSelect.isReadOnly();

		xml.appendTagOpen("ui:radiobuttonselect");
		xml.appendAttribute("id", component.getId());
		xml.appendOptionalAttribute("class", component.getHtmlClass());
		xml.appendOptionalAttribute("track", component.isTracking(), "true");
		xml.appendOptionalAttribute("hidden", rbSelect.isHidden(), "true");
		if (readOnly) {
			xml.appendAttribute("readOnly", "true");
		} else {
			xml.appendOptionalAttribute("disabled", rbSelect.isDisabled(), "true");
			xml.appendOptionalAttribute("required", rbSelect.isMandatory(), "true");
			xml.appendOptionalAttribute("submitOnChange", rbSelect.isSubmitOnChange(), "true");
			xml.appendOptionalAttribute("toolTip", component.getToolTip());
			xml.appendOptionalAttribute("accessibleText", component.getAccessibleText());
		}
		xml.appendOptionalAttribute("frameless", rbSelect.isFrameless(), "true");

		switch (rbSelect.getButtonLayout()) {
			case COLUMNS:
				xml.appendAttribute("layout", "column");
				xml.appendOptionalAttribute("layoutColumnCount", cols > 0, String.valueOf(cols));
				break;
			case FLAT:
				xml.appendAttribute("layout", "flat");
				break;
			case STACKED:
				xml.appendAttribute("layout", "stacked");
				break;
			default:
				throw new SystemException("Unknown radio button layout: " + rbSelect.getButtonLayout());
		}

		xml.appendClose();

		// Options
		List<?> options = rbSelect.getOptions();
		boolean renderSelectionsOnly = readOnly;

		if (options != null) {
			int optionIndex = 0;
			Object selectedOption = rbSelect.getSelected();

			for (Object option : options) {
				if (option instanceof OptionGroup) {
					throw new SystemException("Option groups not supported in WRadioButtonSelect.");
				} else {
					renderOption(rbSelect, option, optionIndex++, xml, selectedOption, renderSelectionsOnly);
				}
			}
		}

		if (!readOnly) {
			DiagnosticRenderUtil.renderDiagnostics(rbSelect, renderContext);
		}
		xml.appendEndTag("ui:radiobuttonselect");

		if (rbSelect.isAjax()) {
			paintAjax(rbSelect, xml);
		}
	}
80989
@Override
	public void doRender(final WComponent component, final WebXmlRenderContext renderContext) {
		WTabGroup group = (WTabGroup) component;
		paintChildren(group, renderContext);
	}
1119597
@Override
	public void doRender(final WComponent component, final WebXmlRenderContext renderContext) {
		WStyledText text = (WStyledText) component;
		XmlStringBuilder xml = renderContext.getWriter();
		String textString = text.getText();

		if (textString != null && textString.length() > 0) {
			xml.appendTagOpen("ui:text");

			xml.appendOptionalAttribute("class", component.getHtmlClass());
			switch (text.getType()) {
				case EMPHASISED:
					xml.appendAttribute("type", "emphasised");
					break;

				case HIGH_PRIORITY:
					xml.appendAttribute("type", "highPriority");
					break;

				case LOW_PRIORITY:
					xml.appendAttribute("type", "lowPriority");
					break;

				case MEDIUM_PRIORITY:
					xml.appendAttribute("type", "mediumPriority");
					break;

				case ACTIVE_INDICATOR:
					xml.appendAttribute("type", "activeIndicator");
					break;

				case MATCH_INDICATOR:
					xml.appendAttribute("type", "matchIndicator");
					break;

				case INSERT:
					xml.appendAttribute("type", "insert");
					break;

				case DELETE:
					xml.appendAttribute("type", "delete");
					break;

				case MANDATORY_INDICATOR:
					xml.appendAttribute("type", "mandatoryIndicator");
					break;

				case PLAIN:
				default:
					xml.appendAttribute("type", "plain");
					break;
			}

			switch (text.getWhitespaceMode()) {
				case PARAGRAPHS:
					xml.appendAttribute("space", "paragraphs");
					break;

				case PRESERVE:
					xml.appendAttribute("space", "preserve");
					break;

				case DEFAULT:
					break;

				default:
					throw new IllegalArgumentException("Unknown white space mode: " + text.
							getWhitespaceMode());
			}

			xml.appendClose();

			if (WStyledText.WhitespaceMode.PARAGRAPHS.equals(text.getWhitespaceMode())) {
				textString = text.isEncodeText() ? WebUtilities.encode(textString) : HtmlToXMLUtil.unescapeToXML(textString);
				writeParagraphs(textString, xml);
			} else {
				xml.append(textString, text.isEncodeText());
			}

			xml.appendEndTag("ui:text");
		}
	}
367942
@Override
	public void doRender(final WComponent component, final WebXmlRenderContext renderContext) {
		WPartialDateField dateField = (WPartialDateField) component;
		XmlStringBuilder xml = renderContext.getWriter();
		boolean readOnly = dateField.isReadOnly();

		String date = formatDate(dateField);

		xml.appendTagOpen("ui:datefield");
		xml.appendAttribute("id", component.getId());
		xml.appendOptionalAttribute("class", component.getHtmlClass());
		xml.appendOptionalAttribute("track", component.isTracking(), "true");
		xml.appendOptionalAttribute("hidden", dateField.isHidden(), "true");
		xml.appendOptionalAttribute("date", date);
		if (readOnly) {
			xml.appendAttribute("readOnly", "true");
		} else {
			xml.appendAttribute("allowPartial", "true");
			xml.appendOptionalAttribute("disabled", dateField.isDisabled(), "true");
			xml.appendOptionalAttribute("required", dateField.isMandatory(), "true");
			xml.appendOptionalAttribute("toolTip", dateField.getToolTip());
			xml.appendOptionalAttribute("accessibleText", dateField.getAccessibleText());
			WComponent submitControl = dateField.getDefaultSubmitButton();
			String submitControlId = submitControl == null ? null : submitControl.getId();
			xml.appendOptionalAttribute("buttonId", submitControlId);
		}

		xml.appendClose();

		if (date == null) {
			xml.appendEscaped(dateField.getText());
		}

		if (!readOnly) {
			DiagnosticRenderUtil.renderDiagnostics(dateField, renderContext);
		}
		xml.appendEndTag("ui:datefield");
	}
63093
@Override
	public void doRender(final WComponent component, final WebXmlRenderContext renderContext) {
		WSelectToggle toggle = (WSelectToggle) component;
		XmlStringBuilder xml = renderContext.getWriter();

		xml.appendTagOpen("ui:selecttoggle");
		xml.appendAttribute("id", component.getId());
		xml.appendOptionalAttribute("class", component.getHtmlClass());
		xml.appendOptionalAttribute("track", component.isTracking(), "true");

		State state = toggle.getState();

		if (State.ALL.equals(state)) {
			xml.appendAttribute("selected", "all");
		} else if (State.NONE.equals(state)) {
			xml.appendAttribute("selected", "none");
		} else {
			xml.appendAttribute("selected", "some");
		}

		xml.appendOptionalAttribute("disabled", toggle.isDisabled(), "true");
		xml.appendAttribute("target", toggle.getTarget().getId());
		xml.appendAttribute("renderAs", toggle.isRenderAsText() ? "text" : "control");
		xml.appendOptionalAttribute("roundTrip", !toggle.isClientSide(), "true");
		xml.appendEnd();
	}
901909
@Override
	public void doRender(final WComponent component, final WebXmlRenderContext renderContext) {
		WTab tab = (WTab) component;
		XmlStringBuilder xml = renderContext.getWriter();

		xml.appendTagOpen("ui:tab");
		xml.appendAttribute("id", component.getId());
		xml.appendOptionalAttribute("class", component.getHtmlClass());
		xml.appendOptionalAttribute("track", component.isTracking(), "true");
		xml.appendOptionalAttribute("open", tab.isOpen(), "true");
		xml.appendOptionalAttribute("disabled", tab.isDisabled(), "true");
		xml.appendOptionalAttribute("hidden", tab.isHidden(), "true");
		xml.appendOptionalAttribute("toolTip", tab.getToolTip());

		switch (tab.getMode()) {
			case CLIENT:
				xml.appendAttribute("mode", "client");
				break;
			case LAZY:
				xml.appendAttribute("mode", "lazy");
				break;
			case EAGER:
				xml.appendAttribute("mode", "eager");
				break;
			case DYNAMIC:
				xml.appendAttribute("mode", "dynamic");
				break;
			case SERVER:
				xml.appendAttribute("mode", "server");
				break;
			default:
				throw new SystemException("Unknown tab mode: " + tab.getMode());
		}

		if (tab.getAccessKey() != 0) {
			xml.appendAttribute("accessKey", String.valueOf(Character.
					toUpperCase(tab.getAccessKey())));
		}

		xml.appendClose();

		// Paint label
		tab.getTabLabel().paint(renderContext);

		// Paint content
		WComponent content = tab.getContent();

		xml.appendTagOpen("ui:tabcontent");
		xml.appendAttribute("id", tab.getId() + "-content");
		xml.appendClose();

		// Render content if not EAGER Mode or is EAGER and is the current AJAX trigger
		if (content != null && (TabMode.EAGER != tab.getMode() || AjaxHelper.isCurrentAjaxTrigger(
				tab))) {
			// Visibility of content set in prepare paint
			content.paint(renderContext);
		}

		xml.appendEndTag("ui:tabcontent");

		xml.appendEndTag("ui:tab");
	}
1367313
@Override
	public void doRender(final WComponent component, final WebXmlRenderContext renderContext) {
		WPanel panel = (WPanel) component;
		XmlStringBuilder xml = renderContext.getWriter();
		BorderLayout layout = (BorderLayout) panel.getLayout();
		Size hgap = layout.getHorizontalGap();
		String hgapString = hgap == null ? null : hgap.toString();
		Size vgap = layout.getVerticalGap();
		String vgapString = vgap == null ? null : vgap.toString();

		xml.appendTagOpen("ui:borderlayout");

		xml.appendOptionalAttribute("hgap", hgapString);
		xml.appendOptionalAttribute("vgap", vgapString);

		xml.appendClose();

		// Fetch the children and their constraints.
		final int childCount = panel.getChildCount();
		List<Duplet<WComponent, BorderLayoutConstraint>> children = new ArrayList<>(childCount);

		for (int i = 0; i < childCount; i++) {
			WComponent child = panel.getChildAt(i);
			children.add(new Duplet<>(child, getConstraints(panel, child)));
		}

		// Now paint them
		paintChildrenWithConstraint(children, renderContext, BorderLayout.NORTH);
		paintChildrenWithConstraint(children, renderContext, BorderLayout.EAST);
		paintChildrenWithConstraint(children, renderContext, BorderLayout.SOUTH);
		paintChildrenWithConstraint(children, renderContext, BorderLayout.WEST);
		paintChildrenWithConstraint(children, renderContext, BorderLayout.CENTER);

		xml.appendEndTag("ui:borderlayout");
	}
20351
@Override
	public void doRender(final WComponent component, final WebXmlRenderContext renderContext) {
		WDialog dialog = (WDialog) component;
		int state = dialog.getState();

		if (state == WDialog.ACTIVE_STATE || dialog.getTrigger() != null) {
			int width = dialog.getWidth();
			int height = dialog.getHeight();
			String title = dialog.getTitle();
			XmlStringBuilder xml = renderContext.getWriter();

			xml.appendTagOpen("ui:dialog");
			xml.appendAttribute("id", component.getId());
			xml.appendOptionalAttribute("class", component.getHtmlClass());
			xml.appendOptionalAttribute("track", component.isTracking(), "true");
			xml.appendOptionalAttribute("width", width > 0, width);
			xml.appendOptionalAttribute("height", height > 0, height);
			xml.appendOptionalAttribute("modal", dialog.getMode() == WDialog.MODAL, "true");
			xml.appendOptionalAttribute("open", dialog.getState() == WDialog.ACTIVE_STATE, "true");
			xml.appendOptionalAttribute("title", title);

			DialogOpenTrigger trigger = dialog.getTrigger();
			if (trigger != null) {
				xml.appendOptionalAttribute("triggerid", trigger.getId());
				if (dialog.hasLegacyTriggerButton()) {
					xml.appendClose();
					trigger.paint(renderContext);
					xml.appendEndTag("ui:dialog");
				} else {
					xml.appendEnd();
				}
			} else {
				xml.appendEnd();
			}
		}
	}
129643
@Override
	public void paint(final RenderContext renderContext) {
		WebXmlRenderContext webRenderContext = (WebXmlRenderContext) renderContext;
		PrintWriter writer = webRenderContext.getWriter();

		beforePaint(writer);
		getBackingComponent().paint(renderContext);
		afterPaint(writer);
	}
82326
@Override
	public void doRender(final WComponent component, final WebXmlRenderContext renderContext) {
		XmlStringBuilder xml = renderContext.getWriter();
		WButton button = (WButton) component;
		String text = button.getText();
		String imageUrl = button.getImageUrl();
		String accessibleText = button.getAccessibleText();
		String toolTip = button.getToolTip();

		if (Util.empty(text) && imageUrl == null && Util.empty(accessibleText) && Util.empty(toolTip)) {
			throw new SystemException("WButton text or imageUrl must be specified");
		}

		xml.appendTagOpen(getTagName(button));

		String buttonId = button.getId();
		ImagePosition pos = button.getImagePosition();
		if (Util.empty(text) && Util.empty(toolTip) && Util.empty(accessibleText)) {
			// If the button has an imageUrl but no text equivalent get the text equivalent off of the image
			WImage imgHolder = button.getImageHolder();
			if (null != imgHolder) {
				toolTip = imgHolder.getAlternativeText();
			}
		}

		xml.appendAttribute("id", buttonId);
		xml.appendAttribute("name", buttonId);
		xml.appendAttribute("value", "x");
		xml.appendAttribute("type", getButtonType(button));
		xml.appendAttribute("class", geHtmlClassName(button));
		xml.appendOptionalAttribute("disabled", button.isDisabled(), "disabled");
		xml.appendOptionalAttribute("hidden", button.isHidden(), "hidden");
		xml.appendOptionalAttribute("title", toolTip);
		xml.appendOptionalAttribute("aria-label", accessibleText);
		xml.appendOptionalAttribute("aria-haspopup", button.isPopupTrigger(), "true");
		xml.appendOptionalAttribute("accesskey", Util.upperCase(button.getAccessKeyAsString()));
		xml.appendOptionalAttribute("data-wc-btnmsg", button.getMessage());

		if (button.isCancel()) {
			xml.appendAttribute("formnovalidate", "formnovalidate");
		} else {
			Action action = button.getAction();
			if (action instanceof ValidatingAction) {
				WComponent validationTarget = ((ValidatingAction) action).getComponentToValidate();
				xml.appendAttribute("data-wc-validate", validationTarget.getId());
			}
		}

		xml.appendClose();

		if (imageUrl != null) {
			xml.appendTagOpen("span");
			String imageHolderClass =  "wc_nti";
			if (pos != null) {
				StringBuffer imageHolderClassBuffer = new StringBuffer("wc_btn_img wc_btn_img");
				switch (pos) {
					case NORTH:
						imageHolderClassBuffer.append("n");
						break;
					case EAST:
						imageHolderClassBuffer.append("e");
						break;
					case SOUTH:
						imageHolderClassBuffer.append("s");
						break;
					case WEST:
						imageHolderClassBuffer.append("w");
						break;
					default:
						throw new SystemException("Unknown image position: " + pos);
				}
				imageHolderClass = imageHolderClassBuffer.toString();
			}
			xml.appendAttribute("class", imageHolderClass);
			xml.appendClose();

			if (pos != null && text != null) {
				xml.appendTag("span");
				xml.appendEscaped(text);
				xml.appendEndTag("span");
			}

			xml.appendTagOpen("img");
			xml.appendUrlAttribute("src", imageUrl);
			String alternateText = pos == null ? text : "";
			xml.appendAttribute("alt", alternateText);
			xml.appendEnd();
			xml.appendEndTag("span");
		} else if (text != null) {
			xml.appendEscaped(text);
		}

		xml.appendEndTag(getTagName(button));

		if (button.isAjax()) {
			paintAjax(button, xml);
		}
	}
870125
@Override
	public void doRender(final WComponent component, final WebXmlRenderContext renderContext) {

		WTree tree = (WTree) component;
		XmlStringBuilder xml = renderContext.getWriter();

		// Check if rendering an open item request
		String openId = tree.getOpenRequestItemId();
		if (openId != null) {
			handleOpenItemRequest(tree, xml, openId);
			return;
		}

		// Check the tree has tree items (WCAG requirement)
		TreeItemModel model = tree.getTreeModel();
		if (model == null || model.getRowCount() <= 0) {
			LOG.warn("Tree not rendered as it has no items.");
			return;
		}

		xml.appendTagOpen("ui:tree");
		xml.appendAttribute("id", component.getId());
		xml.appendOptionalAttribute("class", component.getHtmlClass());
		xml.appendOptionalAttribute("track", component.isTracking(), "true");
		xml.appendOptionalAttribute("htree", WTree.Type.HORIZONTAL == tree.getType(), "true");
		xml.appendOptionalAttribute("multiple", WTree.SelectMode.MULTIPLE == tree.getSelectMode(), "true");
		xml.appendOptionalAttribute("disabled", tree.isDisabled(), "true");
		xml.appendOptionalAttribute("hidden", tree.isHidden(), "true");
		xml.appendOptionalAttribute("required", tree.isMandatory(), "true");

		switch (tree.getExpandMode()) {
			case CLIENT:
				xml.appendAttribute("mode", "client");
				break;

			case DYNAMIC:
				xml.appendAttribute("mode", "dynamic");
				break;

			case LAZY:
				xml.appendAttribute("mode", "lazy");
				break;

			default:
				throw new IllegalStateException("Invalid expand mode: " + tree.getType());
		}
		xml.appendClose();

		// Render margin
		MarginRendererUtil.renderMargin(tree, renderContext);

		if (tree.getCustomTree() == null) {
			handlePaintItems(tree, xml);
		} else {
			handlePaintCustom(tree, xml);
		}

		DiagnosticRenderUtil.renderDiagnostics(tree, renderContext);
		xml.appendEndTag("ui:tree");
	}