I have a table element where people can add rows to add data. Two of the fields are mutually exclusive: if you enter a value in one, the other should be disabled, and vice versa. My understanding is I need to do this in the postRender
callback using observables, but I can't seem to find the right path or string of IDs. And logically it doesn't make much sense how it would work. The table starts out empty.
So is there a callback to the "Add Row" button or something where I need to add this logic? Any guidance would be appreciated.
Schema:
var schema = {
"type": "object",
"properties": {
"cbnum": {
"type": "string",
"required": true,
"minLength": 5,
"maxLength": 5
},
"projects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"title": "Project name (required)",
"required": true
},
"tags": {
"type": "string",
"title": "Tags"
},
"hours": {
"type": "number",
"title": "Hours"
},
"percent": {
"type": "number",
"title": "Percent"
}
}
}
}
}
};
The following code does not work (again, not unsurprisingly when you think about it, but I don't know what else to try):
var postRenderCallback = function(control) {
var hours = control.childrenByPropertyId["projects"].childrenByPropertyId["hours"];
var percent = control.childrenByPropertyId["projects"].childrenByPropertyId["percent"];
hours.on("change", function() {
if (this.getValue().length > 0) {
percent.setValue("");
percent.options.disabled = true;
} else {
percent.options.disabled = false;
}
});
percent.on("change", function() {
if (this.getValue().length > 0) {
hours.setValue("");
hours.options.disabled = true;
} else {
hours.options.disabled = false;
}
});
};
The following postRenderCallback
fixed the persistent disabling problem:
var postRenderCallback = function(control) {
var table = control.childrenByPropertyId["projects"];
table.on("add", function() {
var lastrow = this.children[this.children.length-1];
var hours = lastrow.childrenByPropertyId["hours"];
var percent = lastrow.childrenByPropertyId["percent"];
hours.options.disabled = false;
percent.options.disabled = false;
hours.refresh();
percent.refresh();
});
};
But then I ran into a couple other problems that just made this whole solution problematic (the big one being that I cannot find a way to just set a number field to blank or undefined
; it keeps showing up as NaN
). So I went with the following solution, which is sufficient. Here's the relevant snippet of the options
object:
"projects": {
"type": "table",
"actionbarStyle": "bottom",
"items": {
"fields": {
"tags": {
"type": "tag"
},
"hours": {
"allowOptionalEmpty": true,
"validator": function (callback) {
var that = this.parent.childrenByPropertyId["percent"];
var thisValue = this.getValue();
var thatValue = that.getValue();
if ( (typeof thisValue !== "undefined" && !isNaN(thisValue)) && (typeof thatValue !== "undefined" && !isNaN(thatValue)) ) {
callback({
"status": false,
"message": "You can only enter a number into one of the fields, if any. You may not have numbers in both."
});
} else {
callback({
"status": true
});
}
}
},
"percent": {
"allowOptionalEmpty": true,
"validator": function (callback) {
var that = this.parent.childrenByPropertyId["hours"];
var thisValue = this.getValue();
var thatValue = that.getValue();
if ( (typeof thisValue !== "undefined" && !isNaN(thisValue)) && (typeof thatValue !== "undefined" && !isNaN(thatValue)) ) {
callback({
"status": false,
"message": "You can only enter a number into one of the fields, if any. You may not have numbers in both."
});
} else {
callback({
"status": true
});
}
}
}
}
}
}
Still not a perfect UX solution (I can't seem to make one field's validator execute another field's validator; I tried this.parent.validate(true)
), but it's functional enough for now.
Your schema is correct, and what you were thinking about is correct too using the postRender
always helps but in this case not too much because you have like nested fields/objects ( array > items > item 1 > hours ) this is why your code didn't work because you cannot assign change function to all hours of all created items! plus you're using getValue().length
on a number ( see your schema config ) and this will never work you should either use toString
or is isNaN
on number
fields.
So to achieve what you're looking for you should create an options config
and assign it to alpaca options field. And in this config you should use the change event
on percent and hours fields and put the same code you did already on postRender for each one of them.
Here's an example:
"hours": {
"events": {
"change": function() {
var percent = this.parent.childrenByPropertyId["percent"];
var hoursValue = this.getValue();
if (typeof hoursValue != 'undefined' && !isNaN(hoursValue)) {
percent.options.disabled = true;
} else {
percent.options.disabled = false;
}
percent.refresh();
}
}
}
In postRender
you have already a global control variable which you can use to get a field control object, but here we're on a child control so we can do it using the parent control like the following:
var percent = this.parent.childrenByPropertyId["percent"];
After you set whatever options to your hours or percent field you should call refresh()
function to tell alpaca that you updated some config and to re-render the field for us using that updated config.
percent.refresh();
Here's a working fiddle for this solution.
And to respond to your question about the "Add Row" button, yes there is a callback for that, but I don't think it will helps you, I tried though, with the array type field you have 2 different add buttons so for me it's not a good solution because you should implement code for both buttons! the first one when you have 0 items (the toolbar button) and the second when you start adding items (the action button).