How to build a custom multifield component, having a text and a path field in each new entry?
To achieve the same, please refer to the steps below:
Within your app project, define a folder called “clientlib”. Create a text files called “js.txt” and specify the name of the js file which holds the custom xtype definition.
Example:
#base=js
CustomPathField.js
Define your js file which extends CQ.form.CompositeField and specify attributes required for the custom type. Here is an example for the same:
/**
* @class CQ.CustomPathFieldWidget
* @extends CQ.form.CompositeField
* This is a custom path field with link text and target
* @param {Object} config the config object
*/
/**
* @class Ejst.CustomWidget
* @extends CQ.form.CompositeField
* This is a custom widget based on {@link CQ.form.CompositeField}.
* @constructor
* Creates a new CustomWidget.
* @param {Object} config The config object
*/
CQ.CustomPathFieldWidget = CQ.Ext.extend(CQ.form.CompositeField, {
/**
* @private
* @type CQ.Ext.form.TextField
*/
hiddenField: null,
/**
* @private
* @type CQ.Ext.form.TextField
*/
linkText: null,
/**
* @private
* @type CQ.Ext.form.TextField
*/
linkURL: null,
/**
* @private
* @type CQ.Ext.form.CheckBox
*/
openInNewWindow: null,
/**
* @private
* @type CQ.Ext.form.FormPanel
*/
formPanel: null,
constructor: function (config) {
config = config || {};
var defaults = {
“border”: true,
“labelWidth”: 75,
“layout”: “form”
//”columns”:6
};
config = CQ.Util.applyDefaults(config, defaults);
CQ.CustomPathFieldWidget.superclass.constructor.call(this, config);
},
//overriding CQ.Ext.Component#initComponent
initComponent: function () {
CQ.CustomPathFieldWidget.superclass.initComponent.call(this);
// Hidden field
this.hiddenField = new CQ.Ext.form.Hidden({
name: this.name
});
this.add(this.hiddenField);
// Link text
this.add(new CQ.Ext.form.Label({
cls: “customwidget-label”,
text: “Link Text”
}));
this.linkText = new CQ.Ext.form.TextField({
cls: “customwidget-1″,
fieldLabel: “Link Text: “,
maxLength: 80,
maxLengthText: “A maximum of 80 characters is allowed for the Link Text.”,
allowBlank: true,
listeners: {
change: {
scope: this,
fn: this.updateHidden
}
}
});
this.add(this.linkText);
// Link URL
this.add(new CQ.Ext.form.Label({
cls: “customwidget-label”,
text: “Link URL”
}));
this.linkURL = new CQ.form.PathField({
cls: “customwidget-2″,
fieldLabel: “Link URL: “,
allowBlank: false,
width: 225,
listeners: {
change: {
scope: this,
fn: this.updateHidden
},
dialogclose: {
scope: this,
fn: this.updateHidden
}
}
});
this.add(this.linkURL);
// Link openInNewWindow
this.openInNewWindow = new CQ.Ext.form.Checkbox({
cls: “customwidget-3″,
boxLabel: “New window”,
listeners: {
change: {
scope: this,
fn: this.updateHidden
},
check: {
scope: this,
fn: this.updateHidden
}
}
});
this.add(this.openInNewWindow);
},
processInit: function (path, record) {
this.linkText.processInit(path, record);
this.linkURL.processInit(path, record);
this.openInNewWindow.processInit(path, record);
},
setValue: function (value) {
var link = JSON.parse(value);
this.linkText.setValue(link.text);
this.linkURL.setValue(link.url);
this.openInNewWindow.setValue(link.openInNewWindow);
this.hiddenField.setValue(value);
},
getValue: function () {
return this.getRawValue();
},
getRawValue: function () {
var link = {
“url”: this.linkURL.getValue(),
“text”: this.linkText.getValue(),
“openInNewWindow”: this.openInNewWindow.getValue()
};
return JSON.stringify(link);
},
updateHidden: function () {
this.hiddenField.setValue(this.getValue());
}
});
So, once we have defined that, it’s time to use in your component Here is snippet from dialog.xml from component:
<linkspanel
jcr:primaryType="cq:Panel"
border="false"
height=""
title="Links"
width="">
< items jcr:primaryType="cq:WidgetCollection">
< links
jcr:primaryType="cq:Widget"
fieldDescription="Press + to add more links"
fieldLabel="Links"
hideLabel="true"
name="./links"
width="1000"
xtype="multifield">
<fieldConfig
jcr:primaryType="cq:Widget"
xtype="mypathfield"/>
< listeners
jcr:primaryType="nt:unstructured" />
< /links>
< /items>
< /linkspanel>
Storing the objects as json strings in the multifield array works, but is not so nice, as reading the content (e.g. in the rendering JSP) requires parsing of JSON and searching for individual properties won't work.
In general, for cases of multiple objects (instead of a simple array of individual values, for which the MultiField and the JCR multi-value property is made for), I suggest to use parsys + individual components. See also my reply on the day-communique google group.
Cheers,
Alex
Hi, ive got the same requirement.
i've come up with following Dialog structure which works fine at the first look. The values are stored correctly ( path and label have the type String[] ) and my i am able to create link with my jsp.
The only Problem is that the values are not shown in the dialog when i want to edit them. The second Problem is that the move-up and move-down buttons doesn't work, although the delete-button works fine.
Does anybody does know a solution to that?
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="cq:Dialog"
title="TextLinkList"
xtype="tabpanel">
<items jcr:primaryType="cq:WidgetCollection">
<static
jcr:primaryType="cq:Widget"
title="Text link list"
xtype="panel">
<items jcr:primaryType="cq:WidgetCollection">
<list
jcr:primaryType="cq:Widget"
fieldDescription="Click the '+' to add a new textlink"
hideLabel="true"
xtype="multifield">
<fieldConfig
jcr:primaryType="cq:Widget"
xtype="compositefield">
<items jcr:primaryType="cq:WidgetCollection">
<label
jcr:primaryType="cq:Widget"
name="./label"
xtype="textfield"/>
<path
jcr:primaryType="cq:Widget"
name="./path"
xtype="pathfield"/>
</items>
</fieldConfig>
</list>
</items>
</static>
</items>
</jcr:root>
Please read the above comments carefully. The normal multifield is an array of *primitive* values (e.g. strings). It is not an array of *objects*.
The above CQ.CustomPathFieldWidget from Shelly puts objects into an array of strings by encoding the objects as json string. This can work, but I wouldn't really recommend it as I noted above.
@Shelly: I guess the problem you mention is what I meant: if you want to read those objects in the jsp, you also have to parse the json strings there.
Hi everyone.
I have also spent some time trying to built a custom ExtJS dialog that has a multifield with a textfield and a pathfield with the help of the "Using ExtJS Widgets" examples from the Package Share.
My problem is that the example saves the values in a Stringarray (String[]) with a seperator character and the user can mess up the input by typing that seperator character.
I have tried to alter my code to use JSON like Shelly suggested, but it didn't work and I got following error message in Firebug: "uncaught exception: cannot create Component: xtype 'ejstcustom' not found and no default supplied."
Is there some way to save the values in a child-node of the component node in separate properties instead of the String[]?
Here's my code:
Ejst.CustomWidget = CQ.Ext.extend(CQ.form.CompositeField, {
/**
* @private
* @type CQ.Ext.form.TextField
*/
hiddenField: null,
/**
* @private
* @type CQ.Ext.form.TextField
*/
nameField: null,
/**
* @private
* @type CQ.Ext.form.pathfield
*/
linkPathField: null,
constructor: function(config) {
config = config || { };
var defaults = {
"border": false,
"layout": "table",
"columns":2
};
config = CQ.Util.applyDefaults(config, defaults);
Ejst.CustomWidget.superclass.constructor.call(this, config);
},
// overriding CQ.Ext.Component#initComponent
initComponent: function() {
Ejst.CustomWidget.superclass.initComponent.call(this);
this.hiddenField = new CQ.Ext.form.Hidden({
name: this.name
});
this.add(this.hiddenField);
this.nameField= new CQ.Ext.form.TextField({
cls:"ejst-customwidget-2",
listeners: {
change: {
scope:this,
fn:this.updateHidden
}
}
});
this.add(this.nameField);
this.linkPathField= new CQ.form.PathField({
rootPath: "/content",
predicate: "nosystem",
showTitlesInTree: true,
allowBlank: false,
listeners: {
change: {
scope:this,
fn:this.updateHidden
},
dialogclose : {
scope:this,
fn:this.updateHidden
}
}
});
this.add(this.linkPathField);
},
processInit: function (path, record) {
this.nameField.processInit(path, record);
this.linkPathField.processInit(path, record);
},
// overriding CQ.form.CompositeField#setValue
setValue: function (value) {
var parts = value.split("|");
this.nameField.setValue(parts[0]);
this.linkPathField.setValue(parts[1]);
this.hiddenField.setValue(value);
},
// overriding CQ.form.CompositeField#getValue
getValue: function() {
return this.getRawValue();
},
getRawValue: function () {
return this.nameField.getValue() + "|" +
this.linkPathField.getValue();
},
// private
updateHidden: function() {
this.hiddenField.setValue(this.getValue());
}
});
// register xtype
CQ.Ext.reg('ejstcustom', Ejst.CustomWidget);
EDIT:
Here is my dialog.xml:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="cq:Dialog"
warnIfModified="{Boolean}false"
xtype="dialog">
<items
jcr:primaryType="cq:Widget"
xtype="tabpanel">
<items jcr:primaryType="cq:WidgetCollection">
<first
jcr:primaryType="cq:Widget"
title="Multi-Field"
xtype="panel">
<items jcr:primaryType="cq:WidgetCollection">
<multi
jcr:primaryType="cq:Widget"
fieldLabel="Multi-Field"
hideLabel="{Boolean}true"
name="./multi"
xtype="multifield">
<fieldConfig
jcr:primaryType="nt:unstructured"
xtype="ejstcustom"/>
</multi>
</items>
</first>
</items>
</items>
</jcr:root>
North America
Europe, Middle East and Africa
Asia Pacific