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);
});
}
Related
I am continuing work on a large single page application for viewing results from our database. There are a total of 10 different select options to chose from before clicking the search button. It was built in codeigniter / angular so naturally it uses Ajax to fetch all the data.
Now my boss has decided he wants "sharable" urls added like the old system had (it used get parameters) and I was told angular-ui-router was the way to go to handle urls on larger single page web apps. I have some code written that currently replaces the url state in the address bar when a dropdown option is selected and it works but since its not using nested states it simply overrides it when another is selected. Before I go down the road of 10 deep nested states, would there be a solution to append the next part of the url onto the existing without overriding?
app.config(function($stateProvider) {
$stateProvider
.state('stateone', {
url: '/:key/:value',
});
});
$scope.changedValue=function(key, value){
console.log("Key:"+key+" Value:"+value);
$state.go('stateone', {
key: key,
value: value
});
}
<select ng-model="excess_state.value" ng-change="changedValue('excess', excess_state.value)">
<option ng-repeat="state in excess_state" value="{{state.value}}">
</option>
</select>
for clarification:
when a dropdown named "excess" has option of "yes" selected the url would be: index/excess/yes/
When the next dropdown named "inventory" has option of "3" selected the url should become:
index/excess/yes/inventory/3/
right now it just overrides to index/inventory/3/
since this code only needs to change the ng-selected option when its called from the url, the state itself doesn't serve much of a purpose beyond the standard get parameter.
I hope I have made myself clear enough, any suggestions would be greatly appreciated!
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.
I have a form which filters through different cars, and it's working perfect.
When a user selects a "Make" the correct sibling "Models" are populated into the next dropdown, so on and so forth.
The problem is that once a user has performed a search, if they click the browser's back button, the select values which are dynamically populated - are back to default!
I am not using ajax to dynamically populate the select fields, but only javascript where I am reading a JSON file and updating the models/series/etc like that.
I have looked at this post: Preserve dynamically changed HTML on back button
And I do not understand how this works, I have also heard about localstorage - what would be the best avenue for me to travel down? Thanks.
Using localStorage for this can be a bit unwieldy (when and how should I clear this value?) and there are security related considerations that may make it an infeasible solution.
Another option is to use a hidden textbox which makes use of the browser's default behaviour.
When a page is loaded after clicking the back button, browsers appear to populate textboxes based on the value contained in it when the user left the page (even if that value was dynamically changed). Note, this is different to how hidden inputs are handled, where dynamic changes are ignored, so you must use a textbox.
<select></select>
<input type="text" style="display:none" />
<script>
// store the dropdown's value in a hidden textbox which is persisted and
// used to populate the dropdown when the back button is used to get here
$('select').on('change', function() {
$('input').val($(this).val());
});
// example showing dynamic population of dropdown
$.ajax({
url: 'api/get-dropdown-options',
success: function(data) {
// dynamically populate the dropdown
$('select').html(data);
// update the dropdown's value based on the persistent value
// retained in the hidden textbox
$('select').val($('input').val());
}
});
</script>
Because the data is dynamically loaded, the browser will not be able to repopulate the previously selected entries when the user goes back to the previous page.
I suggest you make use of the browser's localStorage to store the latest selections and retrieve them when the user goes back. To accomplish that it's as simple as setting a new variable to the localStorage object and later retrieving it like so:
localStorage.make = "BMW";
alert(localStorage.make);
Also here's a more useful example:
select = document.getElementById("make");
if (localStorage.make) {
select.options[localStorage.make].selected = true;
}
I am customizing standard fiori application with Web IDE and in this application I have below requirement.
I want to add one check box and on selection of the checkbox, one of the existing input field should be shown or hidden.
Same field is there on the multiple screens. So, I have to add check box on the multiple screens. But, when it is selected on one screen, it should be reflected on another as well.
This is what I have done.
In the init method, I have written below javascript code to add the checkbox.
if(!this.oOtherDate)
{
var that = this;
this.oOtherDate = new sap.m.CheckBox("cOtherDelDate", {
text: "{i18n>OTHER_DELIVERY}",
selected: "{path : 'soc_cart>/showRddInput'}", // This carries the checkbox selection to other pages. It is JSON model.
select: function(oEvent) {
var checked = oEvent.getParameters().selected;
oModelList.getData().showRddList = !checked;
oModelList.getData().showRddInput = checked;
}
});
}
On above code, on the selection event of check box, I am setting the two JSON properties. One for the checkbox value and another to make one element hidden and vice versa.
Upto this point, everything works fine.
BUT, now, how can I bind the JSON property value "showRddList" to the element's visible property?
I have tried doing below but it is giving error:
this.byId("Field1").setVisible("{path : 'soc_cart>/showRddList'}");
setVisible() method expects boolean value and in above line of code, it considers as the string value.
FYI... Element which needs to be hidden is defined on the XML view and we can't extend or customize the view to specify the binding property in the view. So, I have to set it from the controller only.
Is there a possibility to set the visible property from controller to the existing element?
Thanks.
What you are looking for is the bindProperty method of the Input.
this.byId("Field1").bindProperty("visible", {
"soc_cart>/showRddList"
});
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);
}