knockout binding handler - undefined bindingContext.$data on IE9 - javascript

I am trying to create a custom binding handler to apply a role based access to fields on page.
In custom handler it will check values of other observables from viewModel and based on condition it will enable or disable input control.
But I am not able to get current ViewModel through bindingContext.$parent , $root, $data or $rawData.
when I debug on IE it only displays {...}
The issue occurs only on IE, It works fine on google crome.
Can someone help me on how do I get current ViewModel through bindingContext.
var divForm = document.getElementById('divform');
function AuditFormViewModel() {
self = this;
self.Name = ko.observable("Kiran");
self.Email = ko.observable("kiranparab0#gmail.com");
self.Complete = ko.observable(true);
self.Send = ko.observable(false);
self.conditionArray = [
{ Field: "Name", condition: [{ Property: "Complete", Value: true }, { Property: "Send", Value: true }] },
{ Field: "Email", condition: [{ Property: "Complete", Value: false }] }
];
}
ko.bindingHandlers.hasAccess = {
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var field = valueAccessor();
var accessConditions = [];
accessConditions = bindingContext.$data.conditionArray;
var condition = accessConditions.filter(function (cnd) {
return cnd.Field === field
})[0].condition;
var value = true;
for (var i = 0; i < condition.length; i++) {
var cndProp = condition[i].Property;
var cndVal = bindingContext.$data[cndProp]();
value = value && (condition[i].Value === cndVal);
}
ko.bindingHandlers.enable.update(element, function () { return value });
}
};
auditFormViewModel = new AuditFormViewModel();
ko.applyBindings(auditFormViewModel, divForm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.1.0/knockout-min.js"></script>
<div id="divform">
Name : <input type="text" data-bind="value:Name, hasAccess:'Name'" />
<br />
Email : <input type="text" data-bind="value:Email, hasAccess:'Email'" />
</div>
Here is the fiddle

In my code I have not declared self as var,
its just change of line. 'var self = this' instead 'self = this'

Related

How can I add validation to an Input field? Binding JSON model doesn't work

I try to learn SAPUI5 with Samples frpm Demo kit Input - Checked. I get an error message: oInput.getBinding is not a function
I have a simple input field xml:
<Label text="Name" required="false" width="60%" visible="true"/>
<Input id="nameInput" type="Text" enabled="true" visible="true" valueHelpOnly="false" required="true" width="60%" valueStateText="Name must not be empty." maxLength="0" value="{previewModel>/name}" change= "onChange"/>
and my controller:
_validateInput: function(oInput) {
var oView = this.getView().byId("nameInput");
oView.setModel(this.getView().getModel("previewModel"));
var oBinding = oInput.getBinding("value");
var sValueState = "None";
var bValidationError = false;
try {
oBinding.getType().validateValue(oInput.getValue());
} catch (oException) {
sValueState = "Error";
bValidationError = true;
}
oInput.setValueState(sValueState);
return bValidationError;
},
/**
* Event handler for the continue button
*/
onContinue : function () {
// collect input controls
var that = this;
var oView = this.getView();
var aInputs =oView.byId("nameInput");
var bValidationError = false;
// check that inputs are not empty
// this does not happen during data binding as this is only triggered by changes
jQuery.each(aInputs, function (i, oInput) {
bValidationError = that._validateInput(oInput) || bValidationError;
});
// output result
if (!bValidationError) {
MessageToast.show("The input is validated. You could now continue to the next screen");
} else {
MessageBox.alert("A validation error has occured. Complete your input first");
}
},
// onChange update valueState of input
onChange: function(oEvent) {
var oInput = oEvent.getSource();
this._validateInput(oInput);
},
Can someone explain to me how I can set the Model?
Your model is fine and correctly binded.
The problem in your code is here, in the onContinue function
jQuery.each(aInputs, function (i, oInput) {
bValidationError = that._validateInput(oInput) || bValidationError;
});
aInput is not an array, so your code is not iterating on an array element.
To quickly fix this, you can put parentheses around the declaration like this:
var aInputs = [
oView.byId("nameInput")
];
Also, you could remove the first two lines of the _validateInput method since they are useless...
Usually, we set the model once the view is loaded, not when the value is changed. For example, if you would like to set a JSONModel with the name "previewModel", you can do as mentioned below.
Note that onInit is called when the controller is initialized. If you bind the model properly as follows, then the oEvent.getSource().getBinding("value") will return the expected value.
onInit: function(){
var oView = this.getView().byId("nameInput");
oView.setModel(new sap.ui.model.json.JSONModel({
name : "HELLO"
}), "previewModel");
},
onChange: function(oEvent) {
var oInput = oEvent.getSource();
this._validateInput(oInput);
},
...
Also, for validating the input text, you can do the following:
_validateInput: function(oInput) {
var oBinding = oInput.getBinding("value");
var sValueState = "None";
var sValueStateText = "";
var bValidationError = false;
if(oBinding.getValue().length === 0){
sValueState = "Error";
sValueStateText = "Custom Error"
}
oInput.setValueState(sValueState);
if(sValueState === "Error"){
oInput.setValueStateText(sValueStateText);
}
return bValidationError;
},
Please note that the code above is not high quality and production ready as it's a quick response to this post :)

Knockout observableArray not being populated by inherited datepicker value

I am having trouble populating an observableArray with a value inherited from a datepicker. I have a disabled textbox that displays the value of the datepicker as part of the data collection section. As it is disabled and not being typed in, it is not updating the observableArray.
I have created an example jsfiddle where I have stripped down and localised the problem.
Any help getting the value to appear in the observableArray would be great as I am really struggling to figure this one out!
HTML
<!--Date Load -->
<span><b>Select a date:</b></span>
<span><input id="theDate" data-bind="datepicker: viewModelWardStaff.dateMonthYear, datepickerOptions: { dateFormat: 'dd/mm/yy' } "></span>
<!--Input Form -->
<span><h4>Input New Entries</h4></span>
<div style="border: solid 1px;" data-bind="with: viewModelWardStaff">
<form class="grid-form" id="dataCollection">
<fieldset>
<div data-row-span="1">
<div data-field-span="1">
<label>Date</label>
<input id="cDate" class="autosend" data-bind="textInput: dateMonthYear, enable: false">
</div>
<div data-field-span="1">
<label>Status</label>
<input id="cStatus" maxlength="200" class="autosend" data-bind="textInput: wardstaff.Status" type="text">
</div>
</div>
</fieldset>
<div style="margin: 5px;">
<a style="margin-left: 300px;" id="addFileButton" class="button-link" data-bind="click: viewModelWardStaff.addEntry">Add</a>
</div>
</form>
</div>
<h4>View Model Ward Staff</h4>
<div data-bind="with: viewModelWardStaff">
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
</div>
KnockoutJS
moment.locale('en-gb');
function WardStaff(data) {
var self = this;
self.Date = ko.observable(data.Date());
self.Status = ko.observable(data.Status());
};
function oWardStaff() {
var self = this;
self.Date = ko.observable();
self.Status = ko.observable();
};
var viewModelWardStaff = function () {
var self = this;
self.wardstaff = new oWardStaff();
self.dateMonthYear = ko.observable();
self.entries = ko.observableArray([]);
self.addEntry = function () {
self.entries.push(new WardStaff(self.wardstaff));
}
self.removeEntry = function (entry) {
self.entries.remove(entry);
}
};
// dateString knockout
ko.bindingHandlers.dateString = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor(),
allBindings = allBindingsAccessor();
var valueUnwrapped = ko.utils.unwrapObservable(value);
var pattern = allBindings.datePattern || 'YYYY-MM-DD HH:mm:ss';
if (valueUnwrapped == undefined || valueUnwrapped == null) {
$(element).text("");
}
else {
var date = moment(valueUnwrapped, "YYYY-MM-DDTHH:mm:ss"); //new Date(Date.fromISO(valueUnwrapped));
$(element).text(moment(date).format(pattern));
}
}
}
//datepicker knockout
ko.bindingHandlers.datepicker = {
init: function (element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {};
$(element).datepicker(options);
//WORK
//handle the field changing
ko.utils.registerEventHandler(element, "change", function () {
var observable = valueAccessor();
if (moment($(element).datepicker("getDate")).local().format('YYYY-MM-DD') == 'Invalid date') {
observable(null);
}
else {
observable(moment($(element).datepicker("getDate")).local().format('YYYY-MM-DD'));
}
});
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).datepicker("destroy");
});
},
//update the control when the view model changes
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
current = $(element).datepicker("getDate");
if (moment(value).format('DD/MM/YYYY') == 'Invalid date') {
$(element).datepicker("setDate", null);
}
}
};
// Master View Model
var masterVM = function () {
var self = this;
self.viewModelWardStaff = new viewModelWardStaff();
};
// Activate Knockout
ko.applyBindings(masterVM);
I think the problem is that your observable date, dateMonthYear, lives in your master view model. The Date property of self.wardstaff is never set.
You could solve this by sharing the observable in your master view model with the one in the wardstaff property:
function oWardStaff(obsDate) {
var self = this;
self.Date = obsDate;
self.Status = ko.observable();
};
/* ... */
self.dateMonthYear = ko.observable();
self.wardstaff = new oWardStaff(self.dateMonthYear);
Now, whenever you pick a new date, it writes it to the observable referenced by both viewmodels.
This line suddenly becomes useful:
function WardStaff(data) {
var self = this;
self.Date = ko.observable(data.Date()); // <-- here
self.Status = ko.observable(data.Status());
};
since Date is now actually set.
Fiddle that I think now works correctly: https://jsfiddle.net/n0t91sra/
(let me know if I missed some other desired behavior)

createElement function not working with MAC Safari

I have below code to create Drop-down:
Below code for HTML to render Drop-down as:
<input class="form-control selectedTextBox mobile-space" list="rider" type="text" id="ridername" placeholder="FirstName LastName"
data-bind="trimedValue: item().Name, datalist: {
options: app.viewModel.riderProfiles(),
optionsValue: 'Id',
optionsText: 'Name',
value: app.viewModel.selectedRiderId
}" />
Below is Knockout.js handler:
ko.bindingHandlers.datalist = (function () {
function getVal(rawItem, prop) {
var item = ko.unwrap(rawItem);
return item && prop ? ko.unwrap(item[prop]) : item;
}
function findItem(options, prop, ref) {
return ko.utils.arrayFirst(options, function (item) {
return ref === getVal(item, prop);
});
}
return {
init: function (element, valueAccessor, allBindingsAccessor) {
var setup = valueAccessor(),
textProperty = ko.unwrap(setup.optionsText),
valueProperty = ko.unwrap(setup.optionsValue),
dataItems = ko.unwrap(setup.options),
myValue = setup.value,
koValue = allBindingsAccessor().value,
datalist = document.createElement("DATALIST");
// create an associated <datalist> element
datalist.id = element.getAttribute("list");
document.body.appendChild(datalist);
// when the value is changed, write to the associated myValue observable
function onNewValue(newVal) {
var dataItems = ko.unwrap(setup.options),
selectedItem = findItem(dataItems, textProperty, newVal),
newValue = selectedItem ? getVal(selectedItem, valueProperty) : void 0;
if (ko.isWriteableObservable(myValue)) {
myValue(newValue);
}
}
// listen for value changes
// - either via KO's value binding (preferred) or the change event
if (ko.isSubscribable(koValue)) {
koValue.subscribe(onNewValue);
} else {
ko.utils.registerEventHandler(element, "change", function () {
onNewValue(this.value);
});
}
// init the element's value
// - either via the myValue observable (preferred) or KO's value binding
if (ko.isObservable(myValue) && myValue()) {
element.value = getVal(findItem(dataItems, valueProperty, myValue()), textProperty);
} else if (ko.isObservable(koValue) && koValue()) {
onNewValue(koValue());
}
},
update: function (element, valueAccessor) {
var setup = valueAccessor(),
datalist = element.list,
dataItems = ko.unwrap(setup.options),
textProperty = ko.unwrap(setup.optionsText);
// rebuild list of options when an underlying observable changes
datalist.innerHTML = "";
ko.utils.arrayForEach(dataItems, function (item) {
var option = document.createElement("OPTION");
option.value = getVal(item, textProperty);
datalist.appendChild(option);
});
ko.utils.triggerEvent(element, "change");
}
};})();
And with only MAC(OS) & Safari (Web-Browser) below error message is shown as:
Message: Unable to process binding "datalist: function () {return { options:app.viewModel.riderProfiles(),optionsValue:'Id',optionsText:'Name',value:app.viewModel.selectedRiderId} }"
Message: undefined is not an object (evaluating 'u.innerHTML=""')
The HTML element contains a set of elements that represent the values available for other controls.
But "Safari" browser does not support it.

knockoutjs subscribe not firing

I have the following client-side code:
var ProfileManager = function () {
self.SelectedLanguage = ko.observable();
self.SelectedLanguage.subscribe(function (newValue) {
alert("The person's new name is " + newValue);
});
var bindUIwithViewModel = function (viewModel) {
ko.applyBindings(viewModel);
};
}
and I later do bindUIwithViewModel(self);.
and a HTML select bound to SelectedLanguage:
<select id="selectAvailableLanguages" class="form-control language-select" data-bind="options: AvailableLanguages, optionsText: 'Code', value : SelectedLanguage"></select>
The select gets populated successfully, the value inside SelectedLanguage observable changes, but the alert just won't show up. Any ideas?
Also, don't know if related but the observe array inside __ko.mapping_ is an array[0]..
Please check the fiddle. Note, "AvailableLanguages" and "SelectedLanguage" properties should exist in the same context, i.e. belong the binded view model.
I've modified your JS code:
var ProfileManager = function () {
self.SelectedLanguage = ko.observable();
self.SelectedLanguage.subscribe(function (newValue) {
alert("The person's new name is " + JSON.stringify(newValue));
});
self.AvailableLanguages = [ { Code: 'c++', Person: 'John' }, { Code: 'c#', Person: 'Mark' } ]
}
ko.applyBindings(new ProfileManager());

value object not updating when item selected from a select list in knockoutjs

I'm having some problems with the value binding on a select tag in knockout.
I've got the following markup:
<select id="faultCode" data-bind="options: FaultCodes, optionsText: 'Description', value: FaultCode, optionsCaption: 'Choose a Fault Code'"></select>
<select id="causeCode" data-bind="options: CauseCodes, optionsText: 'Description', value: CauseCode, optionsCaption: 'Choose a Cause Code'"></select>
<select id="serviceAction" data-bind="options: ActionCodes, value: ActionCode, optionsText: 'Description', optionsCaption: 'Choose an Action Code'"></select>
<select id="plantClass" data-bind="options: PlantClasses, value: PlantClass, optionsText: 'Description', optionsCaption: 'Choose a Plant Class'"></select>
<select id="plantItem" data-bind="options: PlantItems, value: PlantItem, optionsText: 'Description', optionsCaption: 'Choose a Plant Item'"></select>
And my Javascript:
self.FaultCode = ko.observable();
self.ActionCode = ko.observable();
self.PlantClass = ko.observable();
self.PlantItem = ko.observable();
self.CauseCode = ko.observable();
self.FaultCodes = ko.observableArray();
self.ActionCodes = ko.observableArray();
self.PlantClasses = ko.observableArray();
self.PlantItems = ko.observableArray();
self.CauseCodes = ko.observableArray();
self.closeRequest = function () {
var fault = "";
var action = "";
var cause = "";
var pc = "";
var pi = "";
if (self.FaultCode() != undefined) {
fault = self.FaultCode();
}
if (self.ActionCode() != undefined) {
action = self.ActionCode();
}
if (self.CauseCode() != undefined) {
cause = self.CauseCode();
}
if (self.PlantClass() != undefined) {
pc = self.PlantClass();
}
if (self.PlantItem() != undefined) {
pi = self.PlantItem();
}
}
If the user chooses an option from all 5 of the select boxes and fires the closeRequest function, the FaultCode observable has a value of "" (Empty String) and the CauseCode observable has a value of undefined. The three other value observables all have the correct object as their value.
Are you sure that your FaultCode has a value when you are entering closeRequest or whatever?
console.log(self.FaultCode());
When you first get into that function. Also, you are saying if (self.FaultCode() != undefined) which is a loose comparison and not very good. If you are getting a value logged from the above console.log statement, this should fix it for you -
self.closeRequest = function () {
console.log(self.FaultCode());
var fault = "";
var action = "";
var cause = "";
var pc = "";
var pi = "";
if (self.FaultCode()) {
fault = self.FaultCode();
}
if (self.ActionCode()) {
action = self.ActionCode();
}
if (self.CauseCode()) {
cause = self.CauseCode();
}
if (self.PlantClass()) {
pc = self.PlantClass();
}
if (self.PlantItem()) {
pi = self.PlantItem();
}
}
As long as the observables aren't false, undefined, "", or null it will set fault equal to the observable.
When you use a double equal sign you are comparing the value loosely, use the triple equals for type comparison as well.

Categories

Resources