This is a bit of an odd issue I came across today. I have an application that is using Breeze and Knockout. On one of my pages I allow the user to edit and save project data. The save button is only enabled if a change has been made. To track changes I subscribe to the propertyChanged event. This page has quite a few dropdowns which are causing some problems. Here is an example of one of the dropdowns.
<div>
<label for="projQAManager">QA Manager</label>
<select id="projQAManager" data-bind="options: QAManagers,
optionsText: 'FullName',
optionsValue: 'USERNAME',
optionsCaption: 'None',
value: project().QAManager"></select>
</div>
The issue occurs when project().QAManager is "". The propertyChanged event gets fired as soon as the project is loaded and it shows the QAManager field being changed from "" to null. This is causing the entity to believe it has been modified even though nothing has really changed. If QAManager is already null everything works fine. I suppose I could go through and try and clean the DB and clear out any fields with "" and set them to null if I had to but I would rather not if it can be avoided.
The problem lies indeed with the fact that KnockoutJS assigns the value undefined to the caption of the list box, which you labelled "None".
What happens is that right after the listbox is populated, KnockoutJS checks if your selected value (project().QAManager) matches any of the options listed in the list box. If it does not match, it selects the option with the caption, and as such, the selected value of the listbox is modified, which triggers project().QAManager to get the undefined value.
Excerpt from the documentation of the options binding handler (emphasis is mine):
KO will prefix the list of items with one that displays the text
[caption text] and has the value undefined. So, if myChosenValue
holds the value undefined (which observables do by default), then the
dummy option will be selected. If the optionsCaption parameter is an
observable, then the text of the initial item will update as the
observable’s value changes.
I thought of the following workarounds ranging from the easiest to the hardest, but most "proper":
One of the workaround would be to add to your list of options (QAManagers) an entry which has the value undefined, before it is available as an observable array.
Write a custom binding handler for options that allows to set a given value to the caption item, instead of it being set to undefined. This should consist in copy/pasting 99% of KnockoutJS's implementation of "options", and just changing the code I wrote at option 3.
Change KnockoutJS's source so that a new "optionsCaptionValue" binding is taken into account, like this (I've modified the original code like you should do):
if (allBindings['optionsCaption']) {
var option = document.createElement("option");
ko.utils.setHtml(option, allBindings['optionsCaption']);
var captionsValue;
if (allBindings['optionsCaptionValue']) {
captionsValue = ko.utils.unwrapObservable(allBindings['optionsCaptionValue']);
}
ko.selectExtensions.writeValue(option, captionsValue ? captionsValue : undefined);
element.appendChild(option);
}
Related
I have a Kendo Grid which has an option to add a new record using the Popup Editor.
One field from the popup editor is a DropDownList. I have the first record from the dropdown list pre-selected when I open the Popup Editor. Since I pre-selected it, I would like it to be automatically created (bound) within the grid (when pressing "Update") without having to manually select it again.
I have the example script here
Working script: https://dojo.telerik.com/OFinidew/28
Here's a few things that are useful to know:
1. Defining schemas for your dataSources
A schema is a way to define what structure to expect from your data. When a schema is defined, your data will be "bound". As much as possible you'll want to bind your data, because as a last resort you'll end up having to use templates. Normally, Kendo UI will try to figure things out and get things bound automatically, but in special cases you'll have to give it a schema. This is one of those cases.
From the code sample, it seems like the approach of the workaround was to try change the "edit" event of the kendoGrid to immediately select the "Processing" status - Instead, you can define the "Processing" status (value "2") as the defaultValue of the "status" field in your model. But then, you'll need to make sure your custom editor template CAN be bound to, which leads us to..
2. Using the HTML property: data-bind="value:(nameOfYourField)"
When you're making your own editor templates for the kendo popup, it has no way of knowing what part of your HTML to bind to. See the statusDropdownEditorTemplate in the link provided as an example of how this is done.
3. What valuePrimitive means
Normally, a kendoDropDownList will return an object containing both the Text and Value of the selected choice. But this is not what we want in this case, because status is defined as "0", "1", "2" - so we just wanted the value. When you set valuePrimitive to true, you're instructing the kendoDropDownList to only return the value itself, not an object containing everything.
Before I begin, I can categorically state that I am not a web developer. I am currently attempting to fix the following bug in our small web app (used for configuring an embedded device), given that the web-fluent developers who wrote it are no longer around to ask for pointers. I'll try and explain as best I can what's going on.
This particular web page is set up to allow the user to configure and monitor values on an audio processing device. When the user changes a value on the page, the page sends an RPC request to the device to set the value there. When a value on the device is changed via some other means (eg. manually via the input screen), a JSON notification is sent to the web page and the relevant control is updated via JavaScript. The web page uses Bootstrap for the UI, and Knockout for ensuring that the relevant controls are updated whenever values (which we refer to as "parameters" on the device) are changed.
The issue I'm having is that comboboxes (ie. controls constructed via <select> and <option> tags in HTML) will sometimes not display the appropriate values when they are changed through JavaScript. If the user selects an item in the combobox, and the corresponding value is later changed on the device (and propagated back to the web page via JavaScript), the combobox will no longer be able to display the item that the user originally selected - instead, it will display the first item.
For example, with a combobox containing items A, B, C, D and E:
User selects: D.
When value is sent from device to web page:
Item A displays as A
Item B displays as B
Item C displays as C
Item D displays as A
Item E displays as E
This does not happen if the user hasn't ever interacted with the combobox - in this case, whatever value the device notifies the page with will be displayed correctly.
I don't have enough experience to know how all the gory details work behind the scenes, but I have investigated far enough to know the following:
When changing the value via JavaScript, the combobox responds to the Knockout observable changing. I'm not 100% sure how this all gets hooked up, but within the data-bind attribute that Knockout requires on the combobox, foreachis set to $data.widgetData.options, amongst other things. I don't know whether this is relevant or not.
The value set on the Knockout observable that corresponds to the combobox exactly matches the value attribute for the desired combobox option in all cases. I confirmed this by creating another manual subscription to the Knockout observable and logging out the value when it changed.
When the Knockout observable value is changed, the selected attribute is set on the correct combobox option, and is removed from whatever option was previously selected. This happens correctly in both cases (when the combobox displays the correct current value, and when it does not). This also occurs correctly when the user selects an item through the UI, leading me to believe it's not the cause of the issue.
In short: once the user has selected an item at least once, and further changes to the combobox are made programmatically, all the HTML attibute-related modifications appear to work exactly the same as before - except the combobox doesn't display the correct contents for the one item that the user selected.
When the combobox is present on the page, the HTML ends up looking something like this:
<select
class="form-control"
data-bind="
attr: {
'data-panelid': panelData.__hierarchyId,
'data-paramtrigger': widgetData.paramTrigger,
'data-widgetref': widgetData.ref,
'data-menuRef': widgetData.menuRef,
'data-widgetNotifyType': widgetData.notifyType
},
disable: ($data.widgetData.disabled ? $data.widgetData.disabled : false),
foreach: $data.widgetData.options
"
data-panelid="system-1-mpx-1"
data-widgetref="dspx.enum.8"
>
<option data-bind="
attr: {
'data-paramtrigger': $parents[0].widgetData.paramTrigger,
'value': $data.name,
'selected': ($root.remoteParameterObservables[$parents[0].widgetData.ref]() == $data.name ? 'selected' : null),
'data-notifyType': $parents[0].widgetData.notifyType
},
text: ($data.webName ? webName : ($data.displayName ? displayName : name))
"
value="none">none</option>
<!-- ... etc ... -->
</select>
(Whitespace/newlines added for readability.)
Again, I'm not 100% sure how much of this is specific to our setup and how much is part of what Knockout requires.
Is there any reason why a combobox might behave this way? It could well be that there's something in our Javascript code that's causing the issue, but since I'm not familiar with these kinds of web technologies I wanted to first see whether the cause might be something more obvious.
EDIT
For context, this is a trimmed-down version of the Javascript that handles notifications of value changes from the device:
// Called when a notification is received from the device.
var onNotify = function(data)
{
// If we have a KO observable for the parameter on the device with this name
// (eg. "system.param.1"):
if (remoteViewModel.remoteParameterObservables.hasOwnProperty(data.name))
{
try
{
// Set the value of the observable.
remoteViewModel.remoteParameterObservables[data.name](data.value);
}
catch (e)
{
console.debug('Param `' + param + '` on remoteViewModel.remoteParameterObservables is not a knockout observable');
}
}
}
// ...
// Elsewhere, when creating KO observables, I inserted this for debugging purposes.
// This function always prints out the value I expect, whenever the observable is changed.
if ( parameterName === "param.im.testing.with" )
{
observableForThisParam.subscribe(function(newValue)
{
console.log("Parameter was updated with a new value:", newValue);
});
}
I have an accordion that uses the datasource Competency, which stores a list of core competencies for their employees and the related metadata (such as a description). Inside the Accordion's detail, I have a panel that uses the Comment datasource. A user can then enter a comment that will get related to the Competency datasources with some unrelated logic.
Right now, I have an empty checkbox (check_box_outline_blank) in the Accordion Row. When a user enters text, I want the checkbox to have a check (i.e. change the value from check_box_outline_blank to check_box) The problem is that I'm not able to figure out a way to select the checkbox.
Selecting with widget.parent.parent.parent.parent.parent.parent.children.DetailAccordionRow.children.CompletionIcon.text = "check_box"; results in the error
Cannot read property 'children' of undefined
at CheckinSubmit.RootPanel.FormPanel.AccordionPanel.DetailAccordion.DetailAccordionDetail.AccordionDetialPanel.CommentFormPanel.CreateCommentForm.CreateCommentFormBody.Field.onValueEdit:1:78
Selecting with app.pages.CheckinSubmit.children.RootPanel.children.FormPanel.children.AccordionPanel.children.DetailAccordion.children.DetailAccordionRow.children.CompletionIcon.text = "check_box";
Throws the error:
Cannot read property 'children' of undefined
at CheckinSubmit.RootPanel.FormPanel.AccordionPanel.DetailAccordion.DetailAccordionDetail.AccordionDetialPanel.CommentFormPanel.CreateCommentForm.CreateCommentFormBody.Panel3.onValueEdit:1:140
Both commands were put into the Submit button's onValueEdit trigger.
I've been using AppMaker's autosugguestions to generate both of these commands. I'm not sure how else to select this item. Here's a screenshot with the UI and outline:
Did you try to use binding? It seems, that you are adding Comments to the Competency, so maybe this binding for the label's text will work?
#datasource.item.Comments.length > 0 ? 'check_box' : 'check_box_outline_blank'
when a user enters text, I want the checkbox to have a check
if you want exactly this behavior, and you have properly configured bindings, then you can bind label's text to something similar to this:
#datasource.relations.Comments.modes.create.item.Comment !== null ?
'check_box' : 'check_box_outline_blank'
Note that bindings will be reevaluated on focus loss of the Comment input (but there is a workaround for that).
I am trying to create a multiple select input element in Ember, where the content is coming from a hash, containing the label, value and group for the different options. This works fine, however I am not able to access the value binding.
Here is the JSBin; after submitting the form, it logs the selectedField variable to the console which is always 'undefined'.
I would like to implement the binding so that the initial contents of selectedField are preselected.
Update: I now see that the value method is unsupported for multiple selections, but then how do I pre-select and retrieve the selections?
Solved: I need to bind the Select via 'selection' (see JSBin).
When trying to deselect the currently selected item in chosen through a knockout binding it seems to get reset back to what it was before the reset:
Here's the example:
http://jsfiddle.net/WPpH2/7
Select jQuery from the drop down and click "clear" to see this behavior.
Here's how the data bind is being done:
data-bind="value: selected, chosen: {}">
Any thoughts on how I can make this actually reset?
The reason you're seeing this is because of how the control works. When you click clear, you set the observable to null. The plugin then tries to find an option that has the value null. Because it doesn't find it, it resets your selection to what was last selected.
In your jsfiddle, if you change this line:
myViewModel.selected(null);
to this line:
myViewModel.selected("");
then your example will work. The reason is that you have an option where the value is an empty string.
Also, if you want your plugin to update on the UI, you will need to use this as well:
$(".chosen").trigger("chosen:updated");