Reputation: 417
Hopefully not too much of an Orbeon-noob question. I have built a custom control for Orbeon (a simple slider for now) and was wanting to add the ability to change the min, max and step parameters for the range input in the Basic Settings dialog. I have had a look at the Dynamic Driven Dropdown and have added the control-details section in the control metadata, but I am stumped on how to get them to show up and how to use the value on the actual input element. Any help / example code would be hugely appreciated.
<xbl:xbl xmlns:xh="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xxi="http://orbeon.org/oxf/xml/xinclude"
xmlns:xxf="http://orbeon.org/oxf/xml/xforms"
xmlns:fr="http://orbeon.org/oxf/xml/form-runner"
xmlns:saxon="http://saxon.sf.net/"
xmlns:oxf="http://www.orbeon.com/oxf/processors"
xmlns:xbl="http://www.w3.org/ns/xbl"
xmlns:xxbl="http://orbeon.org/oxf/xml/xbl"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xbl:binding id="fr-slider" element="fr|slider"
xxf:external-events="fr-value-changed"
xxbl:mode="lhha binding value">
<metadata xmlns="http://orbeon.org/oxf/xml/form-builder">
<display-name lang="en">Slider</display-name>
<icon lang="en">
<small-icon>/forms/orbeon/builder/images/timeline_marker.png</small-icon>
<large-icon>/forms/orbeon/builder/images/timeline_marker.png</large-icon>
</icon>
<datatype>xf:number</datatype>
<template>
<fr:slider>
<xf:label ref=""/>
<xf:hint ref=""/>
<xf:help ref=""/>
<xf:alert ref=""/>
<xf:min ref=""/>
<xf:max ref=""/>
<xf:step ref=""/>
</fr:slider>
</template>
<control-details>
<xf:input ref="xf:min/@ref" type="number">
<xf:label>Minimum Value</xf:label>
<xf:hint />
</xf:input>
<xf:input ref="xf:max/@ref" type="number">
<xf:label>Maximum Value</xf:label>
<xf:hint />
</xf:input>
<xf:input ref="xf:step/@ref" type="number">
<xf:label>Step Size</xf:label>
<xf:hint>Smallest change in value the slider will allow</xf:hint>
</xf:input>
</control-details>
</metadata>
<xbl:resource>
<xbl:style>
input.fr-slider { width: 100% };
</xbl:style>
</xbl:resource>
<xbl:template xxbl:transform="oxf:unsafe-xslt">
<xsl:transform version="2.0">
<xsl:import href="oxf:/oxf/xslt/utils/xbl.xsl"/>
<xsl:template match="/*">
<xh:input type="range" min="0" max="10" step="1" class="fr-slider"/>
</xsl:template>
</xsl:transform>
</xbl:template>
</xbl:binding>
</xbl:xbl>
Upvotes: 0
Views: 246
Reputation: 417
To get the inputs in the <control-detail>
to show in the Basic Settings dialog, you need to add a lang parameter to the <xf:label>
and <xf:hint>
elements (thanks to @avernet for solving this).
Below is a full example of a custom control cust:betterinput that uses an xhtml input element for its input. It uses custom settings (and also the data type [set in Control Settings -> Validation and Alerts -> Data Type]) to configure parameters on the html element (note that the custom parameters have been changed to control parameters). Javascript is used to sync the value between the xhtml input and the xforms input (so that the value is captured by Orbeon). The example is based on number control included in Orbeon
xbl/cust/betterinput/betterinput.xbl
<xbl:xbl xmlns:xh="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xxi="http://orbeon.org/oxf/xml/xinclude"
xmlns:xxf="http://orbeon.org/oxf/xml/xforms"
xmlns:fr="http://orbeon.org/oxf/xml/form-runner"
xmlns:saxon="http://saxon.sf.net/"
xmlns:exf="http://www.exforms.org/exf/1-0"
xmlns:oxf="http://www.orbeon.com/oxf/processors"
xmlns:xbl="http://www.w3.org/ns/xbl"
xmlns:xxbl="http://orbeon.org/oxf/xml/xbl"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:cust="http://www.cust.com/">
<xbl:script src="/xbl/cust/betterinput/betterinput.js"/>
<xbl:binding
id="cust-betterinput"
element="cust|betterinput"
xxbl:mode="lhha binding value focus"
xxbl:label-for="html-input">
<metadata xmlns="http://orbeon.org/oxf/xml/form-builder">
<display-name lang="en">Better Input</display-name>
<icon lang="en">
<small-icon>/forms/orbeon/builder/images/input.png</small-icon>
<large-icon>/forms/orbeon/builder/images/input.png</large-icon>
</icon>
<description lang="en"/>
<templates>
<view>
<cust:betterinput type="" prefix="" suffix="">
<xf:label ref=""/>
<xf:hint ref=""/>
<xf:help ref=""/>
<xf:alert ref=""/>
</cust:betterinput>
</view>
</templates>
<control-details>
<xf:input ref="@type">
<xf:label lang="en">Input Type</xf:label>
<xf:hint lang="en">HTML5 Input Type</xf:hint>
</xf:input>
<xf:input ref="@prefix">
<xf:label lang="en">Input Prefix</xf:label>
<xf:hint/>
</xf:input>
<xf:input ref="@suffix">
<xf:label lang="en">Input Suffix</xf:label>
<xf:hint/>
</xf:input>
</control-details>
</metadata>
<xbl:resources>
<xbl:style src="/xbl/cust/betterinput/betterinput.css"/>
</xbl:resources>
<xbl:template xxbl:transform="oxf:unsafe-xslt">
<xsl:transform version="2.0">
<xsl:import href="oxf:/oxf/xslt/utils/xbl.xsl"/>
<xsl:template match="/*">
<xsl:variable
name="js-object"
as="xs:string"
select="'YAHOO.xbl.cust.BetterInput.instance(this)'"/>
<xf:group>
<xf:action type="javascript" ev:event="xforms-disabled" ev:target="#observer">
<xsl:value-of select="$js-object"/>.destroy();
</xf:action>
<xf:var name="binding" value="xxf:binding('cust-betterinput')"/>
<xf:var name="view"
value="exf:readonly($binding) and property('xxf:readonly-appearance') = 'static'"/>
<xf:action
ev:target="#observer"
ev:event="xforms-enabled xforms-value-changed">
<xxf:script>
<xsl:value-of select="$js-object"/>.updateWithServerValue();
</xxf:script>
</xf:action>
<xf:action
ev:target="#observer"
ev:event="DOMFocusOut">
<xxf:script>
<xsl:value-of select="$js-object"/>.updateWithServerValue();
</xxf:script>
</xf:action>
<xf:var name="htmlinputs" value="' email url color number datetime-local '" />
<xsl:copy-of select="xxbl:parameter(., 'type')"/>
<xsl:copy-of select="xxbl:parameter(., 'prefix')"/>
<xsl:copy-of select="xxbl:parameter(., 'suffix')"/>
<xf:group ref="$binding[not($view)]">
<xf:input ref="." class="betterinput-xform-input xforms-hidden">
<xf:action type="javascript" id="xf-ro" ev:event="xforms-readonly"><xsl:value-of select="$js-object"/>.readonly();</xf:action>
<xf:action type="javascript" id="xf-rw" ev:event="xforms-readwrite"><xsl:value-of select="$js-object"/>.readwrite();</xf:action>
</xf:input>
<xh:span class="{{(if ($prefix) then 'input-prepend' else (), if ($suffix) then 'input-append' else ())}}">
<xf:group class="add-on" ref=".[$prefix]"><xf:output value="$prefix"/></xf:group>
<xh:input id="html-input" class="betterinput-html-input" type="{{
if (not(type = '') and contains($htmlinputs, concat(' ', $type, ' '))) then
$type
else
if (contains(' decimal integer double ', concat(' ', xxf:type($binding), ' '))) then
'number'
else
if (contains($htmlinputs, concat(' ', xxf:type($binding), ' '))) then
xxf:type($binding)
else
'text'
}}"/>
<xf:group class="add-on" ref=".[$suffix]"><xf:output value="$suffix"/></xf:group>
</xh:span>
</xf:group>
<!-- Static readonly mode -->
<xf:group ref="$binding[$view]" class="{{(if ($prefix) then 'input-prepend' else (), if ($suffix) then 'input-append' else ())}}">
<xf:group class="add-on" ref=".[$prefix]"><xf:output value="$prefix"/></xf:group>
<xf:input ref="$binding[$view]" class="betterinput-html-input" />
<xf:group class="add-on" ref=".[$suffix]"><xf:output value="$suffix"/></xf:group>
</xf:group>
</xf:group>
</xsl:template>
</xsl:transform>
</xbl:template>
</xbl:binding>
</xbl:xbl>
xbl/cust/betterinput/betterinput.js
(function() {
var $ = ORBEON.jQuery;
var AS = ORBEON.xforms.server.AjaxServer;
var Document = ORBEON.xforms.Document;
YAHOO.namespace("xbl.cust");
YAHOO.xbl.cust.BetterInput = function() {};
ORBEON.xforms.XBL.declareClass(YAHOO.xbl.cust.BetterInput, "xbl-cust-betterinput");
YAHOO.xbl.cust.BetterInput.prototype = {
xformsInputElement: null,
visibleInputElement: null,
prefixElement: null,
prefix: null,
init: function() {
// Get information from the DOM
console.log('betterinput init');
this.xformsInputElement = YAHOO.util.Dom.getElementsByClassName("betterinput-xform-input", null, this.container)[0];
this.visibleInputElement = YAHOO.util.Dom.getElementsByClassName("betterinput-html-input", null, this.container)[0];
// Properties
// Find prefix based on class/control name, as this JS can be used with fr:number and fr:currency and properties use the control name
var controlClassPrefix = null;
var containerClasses = this.container.className.split(" ");
for (var classIndex = 0; classIndex < containerClasses.length; classIndex++) {
var currentClass = containerClasses[classIndex];
if (currentClass.indexOf("xbl-cust-") == 0) {
controlClassPrefix = currentClass;
break;
}
}
this.prefixElement = YAHOO.util.Dom.getElementsByClassName(controlClassPrefix + "-prefix", null, this.container)[0];
this.prefix = Document.getValue(this.prefixElement.id);
// Register listeners
// Restore input type, send the value to the server, and updates value after server response
$(this.visibleInputElement).on('change blur', _.bind(function(e) {
this.sendValueToServer();
var formId = $(this.container).parents('form').attr('id');
// Always update visible value with XForms value
// - relying just value change event from server is not enough
// - value change not dispatched if server value hasn't changed
// - if visible changed, but XForms hasn't, we still need to show XForms value
// - see: https://github.com/orbeon/orbeon-forms/issues/1026
AS.nextAjaxResponse(formId).then(_.bind(this.updateWithServerValue, this));
}, this));
$(this.visibleInputElement).on('keypress', _.bind(function(e) {
if (e.which == 13)
this.sendValueToServer();
}, this));
},
setFocus: function() {
this.visibleInputElement.focus();
},
sendValueToServer: function() {
var newValue = this.visibleInputElement.value;
Document.setValue(this.xformsInputElement.id, newValue);
},
updateWithServerValue: function() {
var value = Document.getValue(this.xformsInputElement.id);
this.visibleInputElement.value = value;
// Also update disabled because this might be called upon an iteration being moved, in which case all the control properties must be updated
this.visibleInputElement.disabled = YAHOO.util.Dom.hasClass(this.xformsInputElement, "xforms-readonly");
},
readonly: function() {
this.visibleInputElement.disabled = true;
},
readwrite: function() {
this.visibleInputElement.disabled = false;
},
};
})();
Upvotes: 1