Dynamic HTML Element Binding Using Kendo - javascript

Here is the scenario I'm currently in:
I have three HTML elements. A textbox called shipToAddress, another textbox called deliverToAddress and a checkbox called sameAsShipToAddress.
I then have a kendo view model behind the scenes that contains a variable called address which will hold a string of an address ex: "123 Main Street".
When the page first loads, the shipToAddress element data-binds to the kendo view model's address variable ex: "123 Main Street". The deliverToAddress has no data-bind what so ever when the page initially loads. However once the sameAsShipToAddress checked box gets checked, I want to add a data-bind attribute to the deliverToAddress so it too will look at the kendo view model's address variable.
Here is the HTML:
<input id="shipToAddress"
data-bind="value: address" />
<input type="checkbox"
id="deliverSameAsShipTo"
value="deliverSameAsShipTo"
data-bind="checked: sameAsShipToAddress,
events: { click: differentDeliveryAddress }" />
<input id="deliverToAddress" />
And here is the backend Kendo View Model:
var _vm = kendo.observable({
address: "",
sameAsShipToAddress: false,
differentDeliveryAddress() {
if (!this.sameAsShipToAddress)
$("#deliverToAddress").attr("data-bind", "value: address");
else
$("#deliverToAddress").removeAttr("data-bind");
}
});
Can this be done? I feel like I'm close with the following code but currently the deliverToAddress' value property is not getting set. Do I need to some how refresh the deliverToAddress element's attributes?

Your code works correctly and you are very close to the final solution. Instead of setting the value of the bound field, set the "binding definition" as the property value:
$("#deliverToAddress").attr("data-bind", "value: address");
The ViewModel is already bidirectionally bound, so rebind the model and the binding will work in both directions.
After adding the property (and thus binding to the address field of the ViewModel) you need to set also the value of the deliverToAddress field. After that the binding resp. unbinding (set the value to empty string here) works correctly - you can see using the browser DOM explorer, that the attribute is added - change the address text and click the test button - deliverToAddress changes too.
I have created an example in the Telerik Dojo.
The view model code looks like this (I have added a button to test the behaviour):
$(document).ready(function(){
var vm = kendo.observable({
address: "",
sameAsShipToAddress: false
});
kendo.bind(document.body, vm);
//
$("#setDeliverAddressButton").kendoButton({
click: function(e) {
console.log(vm.sameAsShipToAddress);
if (vm.sameAsShipToAddress) {
$("#deliverToAddress").attr("data-bind", "value: address");
$("#deliverToAddress").val(vm.address);
}
else {
$("#deliverToAddress").removeAttr("data-bind");
$("#deliverToAddress").val("");
}
}
});
$("#triggerChangeButton").kendoButton({
click: function(e) {
kendo.bind(document.body, vm);
}
});
});
The HTML:
<input id="shipToAddress" data-bind="value: address" />
<input type="checkbox"
id="deliverSameAsShipTo"
value="deliverSameAsShipTo"
data-bind="checked: sameAsShipToAddress" />
<input id="deliverToAddress" />
<button role="button" id="setDeliverAddressButton">set deliver address</button>
bidirectional update
Another option (or even you need to do this always) is to trigger the change programatically as described in this discussion: How to update the ViewModel after programatic changes.

Related

Trigger ng-change function without keyup or keypressed in AngularJS

I have applied ng-model and ng-change directives on input type text, that shows color in hexa that can be changed from color panel as shown.
<input type="text" ng-model="colors.primary_color"
ng-change="colorChanged()" class="input-control"
placeholder="Primary Color"
color-picker
color-picker-model="primaryColorModel"
color-picker-position="{{colorPickerPosition}}">
<button class="btn-sidebar">
<svg width="20px" height="21px">
<path ng-style="{fill: primaryColorModel}" d="M10.000,3.429 C5.865,3.429 2.500,6.793 2.500,10.928 C2.500,15.063 5.865,18.427 10.000,18.427 C10.000,14.550 10.000,7.089 10.000,3.429 M10.000,0.929 C15.523,0.929 20.000,5.406 20.000,10.928 C20.000,16.450 15.523,20.927 10.000,20.927 C4.477,20.927 -0.000,16.450 -0.000,10.928 C-0.000,5.406 4.477,0.929 10.000,0.929 L10.000,0.929 Z"
/>
</svg>
</button>
Now when I open color panel and change colors, new hexa code shows up in input type text, but colorChanged function doesn't trigger. It only triggers when I click on text box and write some thing.
I want it to be triggerd when I remove any character or value is changed from color panel.
I have tried with $watch like so
$scope.colors = {
primary_color: "#008fff",
secondary_color:"#008fff",
text_color: "#008fff"
}
$scope.$watch('colors', function (nval, oval) {
console.log(nval);
});
$scope.colorChanged = function() {
$rootScope.$broadcast('color-change', {colors: $scope.colors});
}
But it doesn't trigger either.
If I use $apply, it says you are already in digestive cycle.
If you place the relevant markup inside a <form> element and give the <input> a name :
<form name="form">
<input name="primary_color" type="text" ng-model="colors.primary_color"
ng-change="colorChanged()" class="input-control"
placeholder="Primary Color"
color-picker color-picker-model="primaryColorModel"
color-picker-position="{{colorPickerPosition}}">
...
</form>
Then you can programmatically trigger the ng-change directive by using $setViewValue() with no params :
$scope.form.primary_color.$setViewValue()
Will trigger ng-change as the ng-model was changed without actually changing it.
demo -> http://plnkr.co/edit/PmthIfblkPkbKgRInLBc?p=preview
If you are using this plugin, then the variable you specify in color-picker-model attribute is treated as the binding variable for the color picker. So to detect changes to the color picked, you can watch that variable. In your case:
$scope.$watch('primaryColorModel', function(nval, oval) {
//your code here
});
I can't tell which plugin you used for, but regardless unless it was built with angular you'll likely have to wire up a callback that runs a $rootScope.$apply(). The reason being is that the piece of code is happening outside angulars eco-system and it needs to be notified to re-run. See my similar answer here: AngularJS: ng-repeat list is not updated when a model element is spliced from the model array

View only partially refreshed after $scope.$on()

In this AngularJS controller for a pre-filled form, fetching the data is triggered by an event using $broadcast and $on. However, when the data updates, only some of the corresponding fields get updated in the view.
$scope.currentHouse = {};
$scope.$on('houseInfoLoad', function(event, data) {
$scope.currentHouse = data.houseDetail; //does not refresh the view
console.log($scope.currentHouse); //output is correct
//the next three calls refresh the corresponding fields in the view
$scope.changeGarageRadioValue($scope.currentHouse.hasGarage);
utils.setSelection($scope.houseKeywords, $scope.currentHouse.keyword.id);
utils.setSelection($scope.houseTypes, $scope.currentHouse.type.id);
});
changeGarageRadioValue() essentially does what it says, and utils.setSelection(list, id) adds a selected = true property to the element of the list that has the id. This has the effect of setting the value of a select field (using isteven's multi-select plugin).
The view has a few text fields bound to properties of $scope.currentHouse, as well as these radio buttons and select fields.
The result is that the text fields sometimes do not get updated, however the select and radio buttons do.
Here's what I tried unsuccessfully:
Wrapping everything in a $timeout();
Calling $scope.$apply() after setting $scope.currentHouse (throws an error saying that we are already inside an $apply())
Changing the initial definition of $scope.currentHouse from {} to an object with each of the fields set to null.
Can anyone see what I am missing, or how to force trigger the refresh?
EDIT : with extracts from the view :
A text field:
<label>ADDITIONAL DESCRIPTION</label>
<div>
<input type="text" ng-model="currentHouse.additionalDescription" class="input-mandatory" value=""/>
</div>
A multi-select:
<label>TYPE</label>
<div>
<div directive-id="houseType" multi-select input-model="houseTypes" output-model="currentHouse.type" button-label="text" item-label="text" selection-mode="single" default-label="Select" tick-property="selected" ></div>
</div>
The radio buttons :
<div><label>HAS GARAGE</label></div>
<div>
<div id="radiobuttonNo" ng-click="changeGarageRadioValue(false);">
NO
</div>
<div id="radiobuttonYes" ng-click="changeGarageRadioValue(true);" class="active">
YES
</div>
</div>
EDIT #2 as per Alok Singh's suggestion:
I tried putting data.houseDetail into $scope.currentHouse.house instead of $scope.currentHouse directly, and changing the view accordingly. Still no results.
$scope.currentHouse = data.houseDetail
You cannot get the updated value of currentHouse because its a model.
So change the above code i.e
$scope.currentHouse.house = data.houseDetail

Backbone bind model attribute to input

In Backbone is there a bind a model attribute to an input field so that when the input value changes the model attribute will be automatically set to the current value?
At the moment I have the following in my view
<input type="text" name="firstname" class="form-input" value="<%- model.firstname %>" />
Then in the view I listen to the following event and set the model attribute accordingly
events: {
"keydown .form-input": "setAttribute"
},
setAttribute: function() {
//Use model.set on the attribute that was changed
}
To me this seems like a bad way of doing it. Am I missing an easier way of doing it?
That's the right way to do it with vanilla Backbone. If you would like to set up automated data binding you'll need a plugin like Epoxy.
Using Epoxy, your example would look something like:
var BindingView = Backbone.Epoxy.View.extend({
bindings: {
"[name=firstname]": "value:firstName",
}
});
This binds the model's firstName attribute to the input with name="firstname".

Display error message for a textbox on blur

I'm brand new to javascript and knockout. I'm working on client side validation using knockout-validation and am having some trouble. I want textboxes that require some user input to show their error messages on blur (even if the user didn't enter anything). A problem I ran into is that I don't want the error messages to show up right away. I was able to get this working but was wondering if someone had a more elegant way to do this. The pseudo code for what I do is set a textbox's value as an observable and then subscribe that to hasfocus of the textbox. Here is the sample code of the view model and the fiddle to go with it:
self.firstName = ko.observable().extend({
required: true,
notify: 'always'
});
self.firstName.focused = ko.observable();
self.firstName.focused.subscribe(function(newVal) {
if(not the first time in the function and the value hasn't changed)
{
update the value to itself;
//if this is empty then it will trigger the "required" error message
}
});
http://jsfiddle.net/sderico/qAnxw/
I want to know if there's a nicer way to implement this functionality (or any other ways that aren't too convoluted). Thanks in advance!
You just need to specify the valueUpdate option to 'blur' on in your value binding. Then knockout will also update the value of firstName on the blur event which triggers the validation:
<input type="text" runat="server" ID="FirstName"
data-bind="value: firstName, valueUpdate: 'blur'"/>
Demo JSFiddle.

knockout, set property value changes view, which doesn't raise change event

In my view model I update an observable's property. The property is bind to an input element.
After I change the value (from JS), the view updates.
The thing is I have other elements on the page that subscribes the the input's change event, which doesn't publish when the value is updated.
Update (Code):
Model:
var viewModel = {
email: ko.observable()
}
Html:
<input class="form-input" data-bind="value: email" type="email" />
JS: (as a result of some click):
$('.form-input').change(function () {
// doesn't happen
});
viewModel.email('someemail#aaa.com');
Instead of using change, use the Knockout subscribe function. Observables Documentation
viewModel.email.subscribe(function(newValue) {
// Called whenever the value is updated
doSomethingWithNewValue(newValue);
});

Categories