Why subscribe event of dropdown is being called on load knockoutJs? - javascript

This may be a basic question but I am unable to get any solution.
I am trying to show a list of formats in a dropdown. I am using knockout binding to do so. I want to show a specific value initially.
When user selects some other value from the dropdown it should show the selected value (which is working fine).
On selecting any option I want to get the selected value so that I can send it to the server. I am using subscribefor that.
Below is my code:
var obj = this;
obj.formatArray = ko.observableArray([{'text' : "MM/DD/YYYY"},
{'text' : "MM-DD-YYYY"},
{'text' : "MM.DD.YYYY"},
{'text' : "DD/MM/YYYY"},
{'text' : "DD-MM-YYYY"},
{'text' : "DD.MM.YYYY"}]);
var format1 = 'DD-MM-YYYY';
obj.formatArrayDefault = ko.observable({text :format1});
obj.formatArrayDefault.subscribe(function(newValue){
alert("default value " + format1);
alert("new value in subscribe " + newValue.text);
});
ko.applyBindings(obj);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select class="" data-bind="options:formatArray(),optionsText:'text',value:formatArrayDefault"></select>
You can run the above snippet and check, alert inside subscribe is coming on loading and changing the value to be displayed to the first value in the array.
Why subscribe is called on load and why the value is changed to first value?
What mistake I am doing?
If the question is duplicate can someone give link to the solution (I searched here but could not get any solution).

if you just want the value of the dropdown after any change.
just take a observable and set it as value it the drop down and initially it will be blank and in your subscribe function just add a line of comparision if the value of drop down is empty dont call the alert function.
var obj = this;
obj.formatArray = ko.observableArray([{'text' : "MM/DD/YYYY"},
{'text' : "MM-DD-YYYY"},
{'text' : "MM.DD.YYYY"},
{'text' : "DD/MM/YYYY"},
{'text' : "DD-MM-YYYY"},
{'text' : "DD.MM.YYYY"}]);
var format1 = 'DD-MM-YYYY';
obj.initialLoad = true;
obj.formatArrayDefault = ko.observable(format1);
obj.formatArrayDefault.subscribe(function(newValue){
if(obj.initialLoad) {
obj.initialLoad = false;
}
else {
alert("Hello");
}
});
ko.applyBindings(obj);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select class="projectUserAccess workflowSetOptions" data-bind="options:formatArray(),optionsText:'text',value:formatArrayDefault"></select>
===EDIT===
$(document).ready(function(){
var model = {
selectedType: 2 //default Type
};
var viewModel = {
selectedType: ko.observable(model.selectedType)
, availableTypes: ko.observableArray([
{ Label: 'Type1', Code: 1 }
, { Label: 'Type2', Code: 2 }
, { Label: 'Type3', Code: 3 }
])
};
ko.applyBindings(viewModel, $("#content")[0]);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="content">
<select data-bind="value: selectedType, options: availableTypes, optionsText: 'Label', optionsValue: 'Code', optionsCaption: 'Please select...'"></select><br/>
You've selected Type: <span data-bind="text: selectedType"></span>
</div>

Your array contains plain objets. In javascript, object comparison works like this:
var obj1 = { a: 1 };
var obj2 = { a: 1 };
console.log(obj1 === obj2); // false
console.log(obj1 === obj1); // true
It checks the actual object instance that is referenced by a variable, not the way it looks. Many libraries contain a "deepEquals" function that you can use to check if two objects actually look similar, but knockout doesn't use such a mechanism.
So when you set your initial selection, its value might be similar to the object at formatArray()[0], but knockout won't be able to mach the two together. I.e.:
obj.formatArrayDefault() === obj.formatArray()[1]; // false
Now, when applying bindings, knockout tries to solve the mismatch between selection and source data. It can't find an <option> element to match with the selection that is passed to the value binding.
It has to render a selected option, so it defaults to the first one. This is when your initial object gets replaced by formatArray()[0] and the subscription is triggered.
Here's a solution: we put an actual reference to the first object in our initial value!
var obj = this;
obj.formatArray = ko.observableArray([{'text' : "MM/DD/YYYY"},
{'text' : "MM-DD-YYYY"},
{'text' : "MM.DD.YYYY"},
{'text' : "DD/MM/YYYY"},
{'text' : "DD-MM-YYYY"},
{'text' : "DD.MM.YYYY"}]);
var format1 = 'DD-MM-YYYY';
obj.formatArrayDefault = ko.observable(obj.formatArray()[1]);
obj.formatArrayDefault.subscribe(function(newValue){
console.log("default value " + format1);
console.log("new value in subscribe " + newValue.text);
});
ko.applyBindings(obj);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select class="" data-bind="options:formatArray(),optionsText:'text',value:formatArrayDefault"></select>

Related

Filter Suggestion Items using Key in SAPUI5

I've implemented a suggestion items for an input field inside a table and it's working fine. Also, i've implemented the event "SuggestionItemSelected" to get the additional text of the suggested item to display the description of the code.
SuggestItemSelected event will trigger only if we select the suggested item from the list and then we could get the text of the code selected.
So how to get the text of the entered code if we enter the data directly in the input field without using suggestion items? I know we could get the value of the input field by reading "byId()" but not getting how to get the associated text.
I've tried to filter the suggestion items with the selected key value but it wouldn't work. Kindly help to filter the suggestion items on key value to get the associated text of it.
Suggestion Items Array
I played a bit around with the suggestions and it looks like if:
you type very fast and press enter before suggestion opens, onSuggestionItemSelected is not called.
leave (mouse click somewhere else) onSuggestionItemSelected is not called.
You could simply check getSelectedKey()and refuse this user input with a message like "use a suggestion".
Anyhow, there is a way to filter afterwards based on the input for a suggestion item key; see my example.
But keep in mind that getSuggestionItems() returns 0 items if the suggestions http-request is still in progress. Sadly ui5 has no promise api in place.
sap.ui.controller("view1.initial", {
onInit : function(oEvent) {
const oModel = new sap.ui.model.json.JSONModel();
oModel.setData({
rows : [
{ key: "1", col4 : "Value4" },
{ key: "2", col4 : "Value2" },
{ key: "3", col4 : "Value16" },
{ key: "4", col4 : "Value20" }
]
});
this.getView().setModel(oModel);
},
onChange: function(oEvent) {
const input = oEvent.getSource()
let selectedKey = input.getSelectedKey()
if(!selectedKey){
var dataObject =
selectedKey = input.getSuggestionItems()
.map( (suggestionItem)=> suggestionItem.getBindingContext().getObject() )
.find( (a)=> a.col4 === oEvent.getParameter("newValue") );
if(dataObject){
selectedKey = dataObject.key
console.log("found key:" + selectedKey)
}else{
console.warn("no matching key")
}
}else{
console.log("provided key:" + selectedKey)
}
},
onSuggestionItemSelected: function(oEvent) {
}
});
sap.ui.xmlview("main", {
viewContent: jQuery("#view1").html()
})
.placeAt("uiArea");
/* extra CSS classes here */
<script id="sap-ui-bootstrap"
src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-theme="sap_bluecrystal"
data-sap-ui-xx-bindingSyntax="complex"
data-sap-ui-libs="sap.m"></script>
<div id="uiArea"></div>
<script id="view1" type="ui5/xmlview">
<mvc:View controllerName="view1.initial" xmlns="sap.m" xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc">
<Input
showSuggestion="true"
change="onChange"
suggestionItemSelected="onSuggestionItemSelected"
suggestionItems="{/rows}">
<suggestionItems>
<core:Item text="{col4}" key="{key}" />
</suggestionItems>
</Input>
</mvc:View>
</script>

Overriding combo box for filtering items into the combobox, how to use this.filter item

I am using UI5 framework v1.38.39 and I want to do a custom combobox (no sap.m.Combobox but custom.comboboxsearch (which include sap.m.combobox and sap.m.comboboxbase)) where I can search what is inside (searching by contain) from key and text so I have something like follow in the custom control:
sap.ui.define([
"sap/m/ComboBox",
"sap/m/ComboBoxBase"
],
function (oComboBox, oComboBoxBase) {
"use strict";
/*some stuff*/
sValue = oEvent.target.value;
aVisibleItems1 = this.filterItems({
property: "text",
value: sValue
});
aVisibleItems2 = this.filterItems({
property: "key",
value: sValue
});
/*some stuff*/
My problem is that I don't have 2 filter, the second simply replace the first one then I don't have the items from the property text only from key, I would like to have both then I tried :
sValue = oEvent.target.value;
aVisibleItems1 = this.filterItems({
property: ["text","key"],
value: sValue
});
which give me the error :
Uncaught TypeError: p.charAt is not a function
at f.d.filterItems (ComboBox.js:6)
ComBox.js is a core file of UI5, then I tried :
sValue = oEvent.target.value;
aVisibleItems1 = this.filterItems({
property: "key" || "text",
value: sValue
});
Which also didn't work because take in consideration only key and no text, does someone has a solution ?
PS: As I use dynamic binding I suppose I cannot include items in view they are bind via another controller.
the view is :
<!--some stuff-->
<Label text="{i18n>MyText}" />
<Custom:ComboBoxSearch id="mycustombox" selectionChange='onChange'>
<core:Item key="{key}" text="{text}" />
</Custom:ComboBoxSearch>
<!--some stuff-->
items are added from the controller
mhm, i dont know if i understand your question right but i think it is this what you want.
<ComboBox
id="combobox1"
showSecondaryValues= "true"
items="{
path: '/yourdata',
sorter: { path: 'text' }
}">
<core:ListItem key="{key}" text="{text}" additionalText = "{key}"/>
</ComboBox>
and in your controller it should look like this:
this.getView().byId("combobox1").setFilterFunction(function(sTerm, oItem) {
return oItem.getText().match(new RegExp(sTerm, "i")) || oItem.getKey().match(new RegExp(sTerm, "i"));
});
so you can search for the key or the text it does not matter
sap.ui.ComboBox has a property called filterSecondaryValues which filters both the text and additionalText.
If you set 'additionalText' of the ListItem aggregation to the value of key (let showSecondaryValiues false if you dont wanna show them) and it should work?
<ComboBox
showSecondaryValues= "false"
filterSecondaryValues="true"
items="{
path: '/CountriesCollection'
}">
<core:ListItem key="{key}" text="{text}" additionalText="{key}"/>
</ComboBox>

Why does setting an optionsValue break Knockout updating?

I've been going through the Knockout tutorials, and I was playing around with one tutorial when something puzzled me. Here is my HTML:
<h2>Your seat reservations</h2>
<table>
<thead><tr>
<th>Passenger name</th><th>Meal</th><th>Surcharge</th>
</tr></thead>
<tbody data-bind="foreach: seats">
<tr>
<td><input data-bind="value: name" /></td>
<td><select data-bind="options: $root.availableMeals, optionsValue: 'mealVal', optionsText: 'mealName', value: meal"></select></td>
<td data-bind="text: formattedPrice"></td>
</tr>
</tbody>
</table>
<button data-bind="click: addSeat">Reserve another seat</button>
... and here is my JavaScript:
// Class to represent a row in the seat reservations grid
function SeatReservation(name, initialMeal) {
var self = this;
self.name = name;
self.meal = ko.observable(initialMeal);
self.formattedPrice = ko.computed(function() {
var price = self.meal().price;
return price ? "$" + price.toFixed(2) : "None";
});
}
// Overall viewmodel for this screen, along with initial state
function ReservationsViewModel() {
var self = this;
// Non-editable catalog data - would come from the server
self.availableMeals = [
{ mealVal: "STD", mealName: "Standard (sandwich)", price: 0 },
{ mealVal: "PRM", mealName: "Premium (lobster)", price: 34.95 },
{ mealVal: "ULT", mealName: "Ultimate (whole zebra)", price: 290 }
];
// Editable data
self.seats = ko.observableArray([
new SeatReservation("Steve", self.availableMeals[0]),
new SeatReservation("Bert", self.availableMeals[0])
]);
// Operations
self.addSeat = function() {
self.seats.push(new SeatReservation("", self.availableMeals[0]));
}
}
ko.applyBindings(new ReservationsViewModel());
When I run this example and select a different "Meal" from the dropdown menu for a passenger, the "Surcharge" value is not updated. The reason for this seems to be that I added optionsValue: 'mealVal' into the data-bind attribute for the select, and when I remove that, the "Surcharge" does indeed update when a new dropdown option is selected. But why does adding optionsValue break the updating? All that does is set the select list's option value attributes, which is quite useful for form submission - I don't see why it should prevent Knockout from auto-updating.
UPDATE: Upon further investigation, I've discovered that the formattedPrice fn is still getting called, but self.meal() is now resolving to the value string such as PRM instead of the whole meal object. But why is this? The documentation says that optionsValue sets the value attribute in the HTML, but doesn't say anything about changing the view model behaviour.
I think what's going on is that when you specify options: $root.availableMeals, but don't specify an optionsValue, Knockout magically determines which selection in the list you've made when the selection is changed and gives you access to the object from availableMeals instead of just the string value that was put into the value attribute. This does not appear to be well-documented.
I think you understand what's happening and why it breaks your code, but are still looking for an explanation on when you actually need to use optionsValue, and when not.
When to use the optionsValue binding
Let's say your meals can be sold out and you want to check with the server for updates in availableMeals:
const availableMeals = ko.observableArray([]);
const loadMeals = () => getMeals().then(availableMeals);
const selectedMeal = ko.observable(null);
loadMeals();
ko.applyBindings({ loadMeals, availableMeals, selectedMeal });
function getMeals() {
return {
then: function(cb) {
setTimeout(cb.bind(null, [{ mealVal: "STD", mealName: "Standard (sandwich)", price: 0 }, { mealVal: "PRM", mealName: "Premium (lobster)", price: 34.95 }, { mealVal: "ULT", mealName: "Ultimate (whole zebra)", price: 290 }]), 500);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<select data-bind="options: availableMeals,
value: selectedMeal,
optionsText: 'mealName'"></select>
<button data-bind="click: loadMeals">refresh meals</button>
<div data-bind="with: selectedMeal">
You've selected <em data-bind="text: mealName"></em>
</div>
<div data-bind="ifnot: selectedMeal">No selection</div>
<p>Make a selection, click on refresh and notice the selection is lost when new data arrives.</p>
What happens when you replace the objects in availableMeals:
Knockout re-renders the select box's options
Knockout checks the new values for selectedMeal() === mealObject
Knockout does not find the object in selectedMeal and defaults to the first option
Knockout writes the new object's reference to selectedMeal
Problem: you loose your UI selection because the object it points to is no longer in the available options.
optionsValue to the rescue!
The optionsValue allows us to solve this issue. Instead of storing a reference to an object that might be replaced at any time, we store a primitive value, the string inside mealVal, that allows us to check for equality in between different API calls! Knockout now does something like:
selection = newObjects.find(o => o["mealVal"] === selectedMeal());
Let's see this in action:
const availableMeals = ko.observableArray([]);
const loadMeals = () => getMeals().then(availableMeals);
const selectedMeal = ko.observable(null);
loadMeals();
ko.applyBindings({ loadMeals, availableMeals, selectedMeal });
function getMeals() {
return {
then: function(cb) {
setTimeout(cb.bind(null, [{ mealVal: "STD", mealName: "Standard (sandwich)", price: 0 }, { mealVal: "PRM", mealName: "Premium (lobster)", price: 34.95 }, { mealVal: "ULT", mealName: "Ultimate (whole zebra)", price: 290 }]), 500);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<select data-bind="options: availableMeals,
value: selectedMeal,
optionsText: 'mealName',
optionsValue: 'mealVal'"></select>
<button data-bind="click: loadMeals">refresh meals</button>
<div data-bind="if: selectedMeal">
You've selected <em data-bind="text: selectedMeal"></em>
</div>
<div data-bind="ifnot: selectedMeal">No selection</div>
<p>Make a selection, click on refresh and notice the selection is lost when new data arrives.</p>
The downsides of optionsValue
Notice how I had to rewrite the with binding? Suddenly, we only have one of meal's properties available in our viewmodel, which is quite limiting. Here's where you'll have to do some additional work if you want your app to be able to update its data. Your two options:
Store the string (hash) of your selection and the actual object independently, or
Have a repository of view models, when new server data arrives, map to the existing instances to ensure you keep selection states.
If it helps, I could add code snippets to explain those two approaches a bit better
OK, after looking through the Knockout code, I've figured out what's happening - and as of the time of writing this is not documented.
The value binding, when it reads the value of a select element, doesn't just look at the DOM value for the element; it calls var elementValue = ko.selectExtensions.readValue(element);
Now, what selectExtensions does, unsurprisingly, is implement special behaviour for select (and their child object) elements. This is where the magic happens, because as the comment in the code says:
// Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values
// are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values
// that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns.
So, when the value binding tries to read the select element via selectExtensions.readValue(...), it will come to this code:
case 'select':
return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
This basically says "OK, find the selected index and use this function again to read the option element at that index. So then it reads the option element and comes to this:
case 'option':
if (element[hasDomDataExpandoProperty] === true)
return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
return ko.utils.ieVersion <= 7
? (element.getAttributeNode('value') && element.getAttributeNode('value').specified ? element.value : element.text)
: element.value;
Aha! So it stores its own "has DOM data expando property" flag and if that is set it DOESN'T get the simple element.value, but it goes to its own JavaScript memory and gets the value. This is how it can return a complex JS object (like the meal object in my question's example) instead of just the value attribute string. However, if that flag is not set, it does indeed just return the value attribute string.
The writeValue extension, predictably, has the other side of this where it will write the complex data to JS memory if it's not a string, but otherwise it will just store it in the value attribute string for the option:
switch (ko.utils.tagNameLower(element)) {
case 'option':
if (typeof value === "string") {
ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
delete element[hasDomDataExpandoProperty];
}
element.value = value;
}
else {
// Store arbitrary object using DomData
ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
element[hasDomDataExpandoProperty] = true;
// Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
element.value = typeof value === "number" ? value : "";
}
break;
So yeah, as I suspected, Knockout is storing complex data behind-the-scenes but only when you ask it to store a complex JS object. This explains why, when you don't specify optionsValue: [someStringValue], your computed function received the complex meal object, whereas when you do specify it, you just get the basic string passed in - Knockout is just giving you the string from the option's value attribute.
Personally I think this should be CLEARLY documented because it is a bit unexpected and special behaviour that is potentially confusing, even if it's convenient. I'll be asking them to add it to the documentation.

how to display name of a particular code from a javascript object in angular js and html5

I have a angular controller that has the following array which has 3 values.
controller.medicalInstitues;
I want to take the 4th object in the array with code "AUS". Then further, I want to display the name of only 2 medical institutes with the code as "SYD" and "MEL" within that choosen bject from the parent array.
something like below:
var country = select controller.medicalInstitues.countryCode = "AUS"
var institues = select controller.medicalInstitues.name from country.code="SYD" and "MEL";
now I need to bind these in the UI (Angular).
<span class="code"> {{ controller.institutes.name }}</span>
Suppose you have got your list of values in medicalInstitues, then in the angularjs controller you can do
$scope.institues = medicalInstitues.filter(function(value){
return value.countryCode == "AUS" && (value.code == "SYD" || value.code == "MEL");
});
In HTML you can use ng-repeat:
<div controller="controller">
....
<span ng-repeat="institue in institues">{{institue.name}}</span>
....
</div>
In your controller, to bind the 4th object in your array you can set:
$scope.AUS = yourArray[3]
Then in the html you can show the object properties:
{{ AUS."object path to name" }}
Well, if I understood your question well, you can do the following:
// If you have an array that you already know that will come 4 items with the same country code, you can simply do:
$scope.medicalInstitues = array[3];
// Otherwise:
$scope.medicalInstitues = array.filter(function (value) {
return value.countryCode == "AUS";
})[3];
// To get all 'institues' with the countryCode equals to 'SYD' or 'MEL':
$scope.institues = array.filter(function (value) {
return value.countryCode == "SYD" || value.countryCode == "MEL";
});

Changing JavaScript variable dynamically

This is my program skeleton http://jsfiddle.net/zhwzY/
$(document).ready(function(){
$("#btnSend").click(function(){
var option = {
'delay' : false,
'helpSpanDisplay' : 'help-block',
'disableSubmitBtn' : false
};
if($('#agree').is(':checked')){
form_input = 'input:not("#reason")';
}else{
form_input = 'input';
}
var metric = [
[ form_input, 'presence', 'Please fill this textbox!']
];
$("#frm").nod(metric, option);
});
});
From the snippet, I try to make variable "form_input" to switch to a new value depend on the checkbox. With this method, I can prevent NOD (validation plugins) from validating unecessary input. But the variable "form_input" is not changing to the new value upon updating.
Please help.
P/s : No external reference to the NOD library in jsFiddle.

Categories