Dijit.tree extension with radio buttons submitting the wrong value - dojo

I've written a class that extends dijit.Tree to include a radio button alongside each node. I'm using it in a form to show a folder tree from which the user can select a folder. Here is the code:
define("my/Tree/RadioButton",
['dojo/_base/declare', 'dijit/Tree', 'dijit/form/RadioButton', 'dojo/dom-construct', 'dojo/_base/connect', 'dojo/on', 'dojo/_base/lang'],
function (declare, Tree, RadioButton, domConstruct, connect, on, lang){
var TreeNode = declare(Tree._TreeNode, {
_radiobutton: null,
postCreate: function(){
this._createRadioButton();
this.inherited(arguments);
},
_createRadioButton: function(){
this._radiobutton = new RadioButton({
name: this.tree.name,
value: this.tree.model.store.getIdentity(this.item) + '',
checked: false
});
domConstruct.place(this._radiobutton.domNode, this.iconNode, 'before');
if (this.tree.model.store.hasAttribute(this.item, 'checked')) {
var attrValue = this.tree.model.store.getValue(this.item, 'checked');
if (attrValue === true) {
this._radiobutton.set('checked', true);
}
}
connect.connect(this._radiobutton, 'onClick', this, function(){
// set any checked items as unchecked in the store
this.tree.model.store.fetch({
query: {checked: true},
onItem: lang.hitch(this.tree.model.store, function(item){
console.log('found checked item ' + this.getValue(item, 'name'));
this.setValue(item, 'checked', false);
})
});
// check the one that was clicked on
var radioValue = this._radiobutton.get('value');
this.tree.model.store.setValue(this.item, 'checked', true);
});
}
});
return declare(Tree, {
_createTreeNode: function(/*Object*/ args){
return new TreeNode(args);
}
});
});
The issue is that when the form is submitted, the value that is submitted is always the value of the first radio button that was selected, even if other radio buttons are subsequently clicked on.
I can see by inspecting the dom that the value attribute for the checked radio button has the correct value. But what gets submitted is always the initially selected value.
I have a similar class that uses the checkbox widget instead and that one works fine.
Edit based on some feedback I created an even simpler version of this class that doesn't track the checked state using attribute in the store:
define("my/Tree/RadioButton",
['dojo/_base/declare', 'dijit/Tree', 'dijit/form/RadioButton', 'dojo/dom-construct'],
function (declare, Tree, RadioButton, domConstruct){
var TreeNode = declare(Tree._TreeNode, {
_radiobutton: null,
postCreate: function(){
this._createRadioButton();
this.inherited(arguments);
},
_createRadioButton: function(){
this._radiobutton = new RadioButton({
name: this.tree.name,
value: this.tree.model.store.getIdentity(this.item) + '',
checked: false
});
domConstruct.place(this._radiobutton.domNode, this.iconNode, 'before');
}
});
return declare(Tree, {
_createTreeNode: function(/*Object*/ args){
return new TreeNode(args);
}
});
});
but even this still has the same issue - whichever radio button the user clicks on first is the value that will be submitted, regardless of what other buttons are subsequently clicked.

I managed to workaround this issue by hooking on to the onchange event for the radio buttons. The hook explicitly sets checked to false on the unchecked radio button, which seems to fix the problem. I'm unsure why this is required though.

I have this exact same problem. It used to work in older Dojos. Specifically, ALL of the radioButtons incorrectly return true on "dijit.byId("whatever").checked" during the onClicked function. When checked manually after the onClicked function finishes using FireBug console, the above property returns the correct values. I think it is a bug, and I only worked around it by having a different onClicked function for each button, like so:
<form id="locateForm">
<label for="locate">Locate:</label><br />
<input type="radio" dojoType="dijit.form.RadioButton" name="locate" id="locateAddress" value="Address" checked="checked" onClick="enableLocate1();" />
<label for="locateAddress">Address</label>
<input type="radio" dojoType="dijit.form.RadioButton" name="locate" id="locatePlace" value="Place" onClick="enableLocate2();" />
<label for="locatePlace">Place</label>
</form>

Related

how to show or hide tab-content according to the form-wizard condition, vue-form-wizard?

I am using the following library:
https://binarcode.github.io/vue-form-wizard/#/
What I basically have to do is show or hide a tab-content according to the marking of a radio button, for that I am trying this way:
<tab-content v-if="form.company_referal == 'Yes'" title="Referal Form" icon="el-icon-info">
<referralForm :form="form.referal_form" :errors="errors" :isDisabled="isDisabled" ref="referralForm"></referralForm>
</tab-content>
form.company_referal == 'yes' to be able to display it, this company_referal property is used in a radio button with the values ​​'Yes' or 'No'
But I have the problem that it adds it to the end of all the tab content when in the radio button of company_referal it is 'Yes', but in reality it should go along with 'SnapShot'.
If form.company_referal is defined from created or mounted as 'Yes', it does appear alongside SnapShot, the problem is when making the change with the radio button.
This should look like this, even using the radio button:
The key here is to use a watch property & setTimeout. the setTimeout is important otherwise referral form will always appear at the end (which is the wrong order).
#TabContent
<TabContent v-if="form.company_referal == 'Yes'" title="Accounting">
<TabContent v-if="renderQuote" title="Quote"">
<TabContent v-if="renderBindConfirmation" title="Bind Confirmation">
#data
data() {
renderQuote: true,
renderBindConfirmation: true,
},
watch: {
'form.company_referal': async function(newVal, old) {
this.renderQuote = false
this.renderBindConfirmation= false
// fake delay before code execution
await new Promise((resolve)=>setTimeout(() => {
resolve();
}, 0));
this.renderQuote = true
this.renderBindConfirmation = true
},
deep: true
},

Best way to clear a dialogs form after submit or close of the dialog

Below I show the parent vue which contains a dialog+form for adding a new record, in this case a user. When the form is cancelled, I would like input fields to be cleared, which in this case I am using v-model to tie those fields to a user template in the data method.
I would like to control this from the parent, as this is where calls to API are taking place, and if there is an error on save, i would like to retain the dialog form and present the error message, otherwise I would just clear the form on the dialog for button clicks.
Have looked at quite a few examples, and all seem to be wonky. Seems this should be simple enough but I have yet to figure out.
Parent vue
...
<AddUser
:visible="isAddDialogVisible"
:error="error"
v-on:onConfirmed="onAddUserConfirmed"
v-on:onCancelled="onAddUserCancelled"
/>
...
onAddClick: function() {
this.isAddDialogVisible = true;
}
...
onAddUserCancelled () {
this.isAddDialogVisible = false;
}
Dialog component
data() {
return {
user: {}
}
},
props: {
error: {},
visible: {
type: Boolean,
default: false,
}
},
...
onCancel() {
this.$emit("onCancelled");
}
Maybe the best and shortest way would be to do it with v-if:
<AddUser
v-if="isAddDialogVisible"
:visible="isAddDialogVisible"
:error="error"
v-on:onConfirmed="onAddUserConfirmed"
v-on:onCancelled="onAddUserCancelled"
/>
It will completely destroy dialog when false and re-render it when true.

In Vue, how to get the content of a textarea?

I want to keep the value of a variable identical with the content of a textarea.
I don't want to use v-bind or v-model, because I have already bound the textarea with another value.
This is a notebook app, and the textarea is used to display the content of a note, so it has been bound using v-bind with a note object, like
<textarea cols="30" rows="3" v-bind:value="note"></textarea>
Now, I want to add the "edit note" functionality. So when the content of the textarea changes, I want to store its value into a variable, and when the "submit" button is clicked, I pass the value of the variable, which contains the new content of the note, to backend to update the note.
My question is, how to store the textarea's content into the variable after each time the content changes?
I think I cannot use v-model because this way the note will be changed right after the content of the textarea is modified (though not sent to backend), but this is not what I want. What I want is the note to be changed only after the "submit" button is clicked. Thus, I cannot use v-model
Should I use v-on:change? If so, how to get the content of the textarea?
Like,
<textarea v-on:change="updateTheVariable(I need to get the content of the textarea here)"> ... </textarea>
methods: {
updateTheVariable(content of the textarea) {
this.variable = content of the textarea
}
}
Thanks
I'm assuming this thing only shows up when you click some kind of edit button which is why you don't want to alter note so try something like this instead
<button type="button" v-if="!editMode" #click="editNote">Edit</button>
<form v-if="editMode" #submit="handleSubmit">
<fieldset :disabled="saving">
<textarea v-model="editingNote"></textarea>
<button type="submit">Edit</button>
</fieldset>
</form>
export default {
data: () => ({
note: 'whatever', // maybe it's a prop, maybe assigned later, doesn't matter
editMode: false,
editingNote: null, // this will be used to bind the edited value
saving: false
}),
methods: {
editNote () {
this.editingNote = this.note
this.editMode = true
this.saving = false
},
async handleSubmit () {
this.saving = true // disables form inputs and buttons
await axios.post('/notes/update', { note: this.editingNote}) // just an example
this.note = this.editingNote // or maybe use data from the response ¯\_(ツ)_/¯
// or if it's a prop, this.$emit('updated', this.editingNote)
this.editMode = false
}
}
}
As #Phil indicated in a deleted post, the right way to do it is
<textarea #input="updateTheVariable($event.target.value)"></textarea>
.
.
.
methods:{
updateTheVariable(value){
this.variable = value
}
}

Dojo dijit tree with checkbox is not keyboard accessible

I have created a dijit.Tree object where every node is a checkbox. When you select/deselect the parent node, the child nodes get selected/deselected;
when one of the children is deselected, the parent gets deselected; when all the children are selected, the parent gets selected. It works perfectly fine.
However I need it to be keyboard accessible. When I navigate to the tree nodes and press spacebar or Enter, nothing happens.
I tried adding tabindex and aria-role to the checkbox (programmatically), but it did not work.
Here is the fiddle - http://jsfiddle.net/pdabade/pyz9Lcpv/65/
require([
"dojo/_base/window", "dojo/store/Memory",
"dijit/tree/ObjectStoreModel",
"dijit/Tree", "dijit/form/CheckBox", "dojo/dom",
"dojo/domReady!"
], function(win, Memory, ObjectStoreModel, Tree, checkBox, dom) {
// Create test store, adding the getChildren() method required by ObjectStoreModel
var myStore = new Memory({
data: [{
id: 'allDocuments',
name: 'All Documents'
}, {
id: 'inboxDocuments',
name: 'Inbox Documents',
parent: 'allDocuments'
}, {
id: 'outboxDocuments',
name: 'Outbox Documents',
parent: 'allDocuments'
}, {
id: 'draftDocuments',
name: 'Draft Documents',
parent: 'allDocuments'
}, {
id: 'finalDocuments',
name: 'Final Documents',
parent: 'allDocuments'
}],
getChildren: function(object) {
return this.query({
parent: object.id
});
}
});
// Create the model
var myModel = new ObjectStoreModel({
store: myStore,
query: {
id: 'allDocuments'
}
});
// Create the Tree.
var tree = new Tree({
model: myModel,
autoExpand: true,
getIconClass: function(item, opened) {
// console.log('tree getIconClass', item, opened);
// console.log('tree item type', item.id);
},
onClick: function(item, node, event) {
//node._iconClass= "dijitFolderClosed";
//node.iconNode.className = "dijitFolderClosed";
var _this = this;
console.log(item.id);
var id = node.domNode.id,
isNodeSelected = node.checkBox.get('checked');
dojo.query('#' + id + ' .dijitCheckBox').forEach(function(node) {
dijit.getEnclosingWidget(node).set('checked', isNodeSelected);
});
if (item.id != 'allComments') {
if (!isNodeSelected) {
var parent = node.tree.rootNode; // parent node id
//console.log(node);
parent.checkBox.set('checked', false);
} else {
var parent = node.tree.rootNode;
var selected = true;
var i = 0;
dojo.query('#' + parent.id + '.dijitCheckBox').forEach(function(node) {
if (i > 0) {
var isSet = dijit.getEnclosingWidget(node).get('checked');
console.log(isSet);
if (isSet == false) {
selected = false;
}
}
i++;
});
if (selected) {
parent.checkBox.set('checked', true);
}
}
}
//console.log(node.id);
},
_createTreeNode: function(args) {
var tnode = new dijit._TreeNode(args);
tnode.labelNode.innerHTML = args.label;
console.log(args);
var cb = new dijit.form.CheckBox({
"aria-checked": "false",
"aria-describedby": args.label
});
cb.placeAt(tnode.labelNode, "first");
tnode.checkBox = cb;
return tnode;
}
});
tree.placeAt(contentHere);
tree.startup();
tree.checkedItems();
//tree.expandAll();
});
}
Any ideas as to how to make it keyboard accessible?
Thanks!
Looking into the dijit/Tree source I see that it sets the function _onNodePress() as an event handler for keyboard events. You can override it (or add an aspect after it) and handle the key presses you want manually. It takes as argument the tree node and an event object that you can use to check specifically for the space and the enter key.
I forked your jsfiddle with an example: https://jsfiddle.net/pgianna/jjore5sm/1/
_onNodePress: function(/*TreeNode*/ nodeWidget, /*Event*/ e){
// This is the original implementation of _onNodePress:
this.focusNode(nodeWidget);
// This requires "dojo/keys"
if (e.keyCode == keys.ENTER || e.keyCode == keys.SPACE)
{
var cb = nodeWidget.checkBox;
cb.set('checked', !cb.get('checked'));
}
}
Do not add role, aria-checked, nor tabindex to the checkbox. Those are already built into the control, so you are adding risk of breaking it down the road. You can probably also get rid of every role="presentation" as those are on <div>s and <span>s which are presentational by nature. Finally, you need <label> on each block of text that is associated with a checkbox if you want this to be accessible. The aria-describedby is incorrect and is the less good option anyway.
I am getting the error: Uncaught TypeError: tree.checkedItems is not a function (line 159)
You also have a big focus management problem. Put the following in your CSS and you will see that it takes two presses of the Tab key for each single control (if starting at a focused checkbox): :focus {outline:2px solid #f00;}
It looks like you have the containing elements stealing any clicks, meaning the correct element never gets selected. The <span> with classes dijit dijitReset dijitInline dijitCheckBox keeps stealing focus as it toggles its tabindex from -1 to 0, taking itself in and out of the tab order. That may be a factor.
I suggest addressing the script error and then looking at focus management.
With dijit, there's all kinds of stuff going on in the background that might be out of your control. As aardrian said, there's lots of role=presentation and all the aria tags on the <input type='checkbox> are superfluous. dijit is probably (incorrectly) setting all that. An <input type='checkbox> already handles selections and it's role is inherently a checkbox. Those aria properties are for when you're making a custom checkbox out of div/span tags.
There is a native checkbox buried down in the code but it has opacity set to 0 so you can't see it. dijit is probably using it for the checkbox events.
The native checkbox also has data-dojo-attach-event="ondijitclick:_onClick". I'm not sure what that means but anytime I see "click" in an event name, I get suspicious that it might not work with a keyboard.
I tried the example on https://dojotoolkit.org/reference-guide/1.10/dijit/form/CheckBox.html and it works with the keyboard. Hit space will check and uncheck the box. Whether you can see the focus on the checkbox is another issue.
As a side note, it might be nice if your checkbox tree used aria-checked="mixed" for the parent branch. Anytime you have child checkboxes where some are selected and some are not, you can use "mixed" for the parent checkbox to indicate a mixture of selections. Not sure if dijit supports that.

onfocus event not firing on dojo dijit FilteringSelect

I have a page with 7 dijit FilteringSelect widgets that I want to connect to stores fetched from the server if the user clicks on the widget or the widget otherwise gains focus. I want the event to fire one time.
If I add onfocus="loadDropDown(this)" to my markup, it executes every time the widget gains focus, as you would expect.
I'm trying to use dojo to fire the event one time using on.once(). The function to use dojo event handling is running but the event handler function never gets called when a widget gains focus.
Any pointers?
This is my markup
<select data-dojo-type="dijit.form.FilteringSelect"
type="text" intermediateChanges="false"
data-dojo-props="required:false, pageSize:20, placeholder: '---'"
scrollOnFocus="true" name="CJ1lxA" style="width: 40em;"
id="searchAgency">
</select>
This is to regester the events
function registerDDLoad(){
require(["dojo/on", "dijit", "dojo/ready"], function(on, dijit, ready){
ready(function(){
var dropDown = dijit.byId("searchAgency");
on.once(dropDown, "onfocus", function() {
loadDropDown(dropDown);
});
dropDown = dijit.byId("searchLocation");
on.once(dropDown, "onfocus", function() {
loadDropDown(dropDown);
});
dropDown = dijit.byId("searchCounty");
on.once(dropDown, "onfocus", function() {
loadDropDown(dropDown);
});
dropDown = dijit.byId("searchRep");
on.once(dropDown, "onfocus", function() {
loadDropDown(dropDown);
});
dropDown = dijit.byId("searchSenate");
on.once(dropDown, "onfocus", function() {
loadDropDown(dropDown);
});
dropDown = dijit.byId("searchStatus");
on.once(dropDown, "onfocus", function() {
loadDropDown(dropDown);
});
dropDown = dijit.byId("searchAE");
on.once(dropDown, "onfocus", function() {
loadDropDown(dropDown);
});
});
});
}
registerDDLoad();
The dojo event class, dojo/on expects events to be specified without the 'on':
onFocus = focus
onClick = click
onMouseOut = mouseout
...
I think changing that should fix your problem. I've copied your code into test area on jsFiddle, so you can play around with it.
NB: Since you are re-using the dropdown variable, it will always equal the last filteringSelect (id=searchAE) and never the earlier ones.