2 way data binding in JavaScript - javascript

Two-way data binding refers to the ability to bind changes to an object’s properties to changes in the UI, and vice-versa.
Can we achieve 2-way data-binding with JavaScript?
Especially 2 Way Data Binding without Frameworks.

When an input is changed update the value, add a setter to the value which sets the inputs content. E.g this element:
<input id="age">
And some js:
var person = (function(el){
return {
set age(v){
el.value = v;
},
get age(){
return el.value;
}
};
})(document.getElementById("age"));
So you can do:
person.age = 15;
And the input will change. Changing the input changes person.age

Yes, we can achieve the two way data binding using pure javascript.
twoWay=function(event) {
var elem = document.getElementsByClassName(event.currentTarget.className);
for(var key in elem){
elem[key].value=event.currentTarget.value;
}
}
You can check the jsfiddle.

Simple and working approach to two-way binding, only using vanilla JS.
<!-- index.html -->
<form action="#" onsubmit="vm.onSubmit(event, this)">
<input onchange="vm.username=this.value" type="text" id="Username">
<input type="submit" id="Submit">
</form>
<script src="vm.js"></script>
// vm.js - vanialla JS
let vm = {
_username: "",
get username() {
return this._username;
},
set username(value) {
this._username = value;
},
onSubmit: function (event, element) {
console.log(this.username);
}
}
JS Getters and Setters are quite nice for this - especially when you look at the browser support.

Yes indeed.
There are frameworks like angular Js which provides full support for two way data binding.
And if you want to achieve the same in vanilla js you can bind value into view
Eg. document.getElementById('test').value="This is a Test"
And to bind view value to the controller you can trigger onchange event in html.
<Input type="text" id="test" onchange="Func()">

LemonadeJS is another micro-library (4K), with no dependencies worth looking at.
https://lemonadejs.net
https://github.com/lemonadejs/lemonadejs

Adding a little elaboration to Jonas Wilms answer, here's a sample without currying and also adds event binding for a full two way bind.
// Property binding
var person = {
set name(v) {
document.getElementById('name').value = v;
},
get name() {
return document.getElementById('name').value;
},
set age(v) {
document.getElementById('age').value = v;
},
get age() {
return document.getElementById('age').value;
}
};
// You can now set values as such
person.name = 'Cesar';
person.age = 12;
// Event binding completes the two way
function logToConsole(event) {
console.log(event.target.value);
}
// You can set person.name or person.age in console as well.
<label for="name">Name: </label><input id="name" onkeyup="logToConsole(event)">
<label for="age">Age: </label><input id="age" onkeyup="logToConsole(event)">

Would you mind if it would be a small component for databinding tasks that provides enough convenient databinding definition commands. I did it with databindjs. e.g.
// Lets assume that there is just simple form (target)
var simpleForm = {
input: $('.simple .input-value'),
output: $('.simple .output-value')
};
// And here is the simple model object (source)
var model = {
text: 'initial value'
};
// Lets set two directional binding between [input] <-> [text]
var simpleBinding = bindTo(simpleForm, () => model, {
'input.val': 'text', // bind to user input
'output.text': 'text' // simple region that will react on user input
});
// This command will sync values from source to target (from model to view)
updateLayout(simpleBinding);
subscribeToChange(simpleBinding, () => {
$('.simple .console').html(JSON.stringify(model));
});
// Just initialize console from default model state
$('.simple .console').html(JSON.stringify(model));
The full solution here.
You can check the full implementation of the databinding core on github

Related

AngularJS: Dynamic inputs with form validation

I'm creating inputs inside a form dynamically. I created this directive for the purpose:
// Generate inputs on the fly using BE data.
.directive('inputGenerator', ['$compile', function ($compile) {
return {
restrict: 'E',
scope: {
id: '#',
type: '#',
name: '#',
attributes: '=',
ngModel: '=',
ngDisabled: '='
},
link: function (scope, iElement, iAttrs) {
// Get attributes
var id = iAttrs.id,
type = iAttrs.type,
name = iAttrs.name;
scope.ngModel[name] = {};
var extended_attributes = {
"type": type,
"id": id,
"data-ng-model": 'ngModel.' + name + '[\'value\']',
"name": name,
"data-ng-disabled": "ngDisabled"
};
if ( ! scope.attributes) {
scope.attributes = {};
}
// Append extra attributes to the object
angular.extend(scope.attributes, extended_attributes);
// Generate input
var input = '<input ';
angular.forEach(scope.attributes, function (value, key) {
input += key + '="' + value + '" ';
});
input += '/>';
// Compile input element using current scope (directive) variables
var compiledElement = $compile(input)(scope);
// Set the file selected name as the model value
compiledElement.on('change', function () {
if (this.files && this.files[0].name) {
var that = this;
scope.$apply(function () {
scope.ngModel[name] = {};
scope.ngModel[name]['value'] = that.files[0].name;
});
}
});
// Replace directive element with generated input
iElement.replaceWith(compiledElement);
}
};
}]);
This html line will trigger the directive:
<input-generator data-name="{{ item.name }}" data-ng-model="inputs.sources" data-attributes="item.attrs" data-type="{{ item.type }}" data-id="inputFile_{{ $index }}" data-ng-disabled="inputs.sources[item.name].selected" />
I'm running on Angular 1.4.3.
Problem
The model and pretty much everything works fine in the directive, but for some reason the form remains valid when the input added is invalid as you can see in this image.
I already tried:
Any of the Angular features of form validation works
I debugged Angular and seems to be that the input attached to the form is different from the input compiled inside the directive.
I already called formName.$setPristine() after each input was created, but it didn't work.
I couldn't access the form from the directive, but I think is not a good idea either.
I already wrapped the input with a ng-form tag, but nothing useful comes out of that.
I tried to use the directive compile method, but this is just triggered once when the app loads and I've a select input that loads different inputs on change.
Any help on this is much appreciated! :)
Thank you to everyone for contribute anyways!!
You should definitely take a look at my Angular-Validation Directive/Service. It as a ton of features and I also support dynamic inputs validation, you can also pass an isolated scope which helps if you want to not only have dynamic inputs but also have dynamic forms, also good to use inside a modal window.
For example, let's take this example being a dynamic form and inputs defined in the Controller:
$scope.items.item1.fields = [
{
name: 'firstName',
label:'Enter First Name',
validation:"required"
},
{
name: 'lastName',
label: 'Enter Last Name',
validation:"required"
}
];
$scope.items.item2 = {
heading:"Item2",
formName:"Form2"
};
$scope.items.item2.fields = [
{
name: 'email',
label:'Enter Email Id',
validation:"required"
},
{
name: 'phoneNo',
label: 'Enter Phone Number',
validation:"required"
}
];
It will bind the validation to the elements and if you want to easily check for the form validity directly from the Controller, simply use this
var myValidation = new validationService({ isolatedScope: $scope });
function saveData() {
if(myValidation.checkFormValidity($scope.Form1)) {
alert('all good');
}
}
You can also use interpolation like so
<input class="form-control" type="text" name="if1"
ng-model="vm.model.if1"
validation="{{vm.isRequired}}" />
Or using a radio/checkbox to enable/disable a field that you still want to validate when it becomes enable:
ON <input type="radio" ng-model="vm.disableInput4" value="on" ng-init="vm.disableInput4 = 'on'">
OFF <input type="radio" ng-model="vm.disableInput4" value="off">
<input type="text" name="input4"
ng-model="vm.input4"
validation="alpha_dash|min_len:2|required"
ng-disabled="vm.disableInput4 == 'on'" />
It really as a lot of features, and is available on both Bower and NuGet (under the tag name of angular-validation-ghiscoding). So please take a look at my library Angular-Validation and a live demo on PLUNKER.
It's loaded with features (custom Regex validators, AJAX remote validation, validation summary, alternate text errors, validation on the fly with the Service, etc...). So make sure to check the Wiki Documentation as well... and finally, it's fully tested with Protractor (over 1500 assertions), so don't be afraid of using in production.
Please note that I am the author of this library
I ran into this issue as well with Angular v1.5.9. The main issue here is that you are compiling the HTML template before it is in the DOM, so Angular doesn't know it's part of the form. If you add the HTML first, then compile, Angular will see your input as a form child, and it will be used in the form validation.
See similar answer to Form Validation and fields added with $compile
Don't do:
var myCompiledString = $compile(myHtmlString)(scope);
// ...
element.replaceWith(myCompiledString);
Do this instead:
var myElement = angular.element(myHtmlString)
element.replaceWith(myElement) // put in DOM
$compile(myElement)(scope) // $compile after myElement in DOM
Note: I swapped the more conventional element for OP's iElement, which is a jQLite reference of the directive's HTML element
you need to use ng-form directive as a wrapper for your input.
you can see an example of this here
but it works for me. You can pass the form reference to the directive and use it directly.
in the code below, the scope.form is used to know the general state of the form, and the scope.name to access the input field state.
< ng-form name="{{name}}" ng-class="{error: this[name][name].$invalid && form.$submitted}" >
i hope it helps
you need to set name of control dynamic and use this dynamic name for form validation. in the following e.g. you see the dynamic name and id of control and used for the validation of angular (using ng-massages)
more details see http://www.advancesharp.com/blog/1208/dynamic-angular-forms-validation-in-ngrepeat-with-ngmessage-and-live-demo
Field Num Is Required.

angular controller only picks up the input values changed

I've got a fairly simple angular controller method :
$scope.addComment = function () {
if ($scope.newComment.message) {
$scope.can_add_comments = false;
new Comments({ comment: $scope.newComment }).$save(function (comment) {
$scope.comments.unshift(comment);
return $scope.can_add_comments = true;
});
return $scope.newComment = {};
}
};
And in my form I have a textarea that holds the value of comment :
<textarea class="required" cols="40" id="new_comment_message" maxlength="2500" ng-disabled="!can_add_comments" ng-model="newComment.message" required="required" rows="20"></textarea>
Works great so far, however I do want to send some data, hidden data with the comment as well. So I added something to hold that value :
<input id="hash_id" name="hash_id" ng-init="__1515604539_122642" ng-model="newComment.hash_id" type="hidden" value="__1515604539_122642">
However when I inspect the $scope.newComment it always comes back as an object with only message as it's property, which is the value from the text area, and I don't get the property on the object hash_id.
When I make this input non hidden and I manually type in the value into the input field and submit a form, I do get a object with hash_id property. What am I doing wrong here, not setting it right?
As far as I know, ng-model doesn't use the value property as a "default" (i.e. it won't copy it back into your model). If you want a default, it should be placed wherever the model is defined:
$scope.newComment = { hash_id: "__1515604539_122642", /* Other params */ };
Alternatively, changing the ng-init to an assignment should work (though I would recommend the above solution instead):
<input id="hash_id" name="hash_id" ng-init="newComment.hash_id = '__1515604539_122642'" ng-model="newComment.hash_id" type="hidden">

Enable binding with multiple boolean observable flags

Trying to bind enable state using data-bind based on two flags. We need to enable a input box if flagA is true and also flagB is false.
var viewModel = function () {
var self = this;
self.flagA = ko.observable(true);
self.flagB = ko.observable(false);
self.changeState = function () {
self.flagA(false);
}
}
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type='text' data-bind='enable: flagA && !flagB' />
<button data-bind='click:changeState'>changeState</button>
Can any one help me find out why it is not working?
I've tried using a function like enable:function(){flagA && !flagB} to make this work. But it's not working: it does not observe when I change the state using a button.
Because flagA and flagB are observables (which are functions) you need to call them without any argument to get there values if you are using them in an expression:
<input type='text' data-bind='enable: flagA() && !flagB()' />
Demo JSFiddle.
Try to avoid putting logic in your views, it's a bad practice. In order to do this add computed variable
self.isEnabled = ko.computed(function() {
return this.flagA() && !this.flagB()
}, this);
and bind it as usual:
<input type='text' data-bind='enable: isEnabled' />
See fiddle

Can Knockout.js change variable which is not in the viewModel

This is my sample code
HTML:
<input type="text" data-bind="value: a"/>
And in JavaScript I want something like.
var a = 5;
a = ko.observable(a);
But i want to keep a number. And when i change a the input to change and when I change the input a to change.
ViewModel->Scope is possible, but Scope->ViewModel is not possible:
var nonObservable = 5,
observable = ko.observable(nonObservable);
// ViewModel->Scope
observable.subscribe(function(newValue) {
nonObservable = newValue;
});
// Scope->ViewModel
observable("new value");
// This however, will not work
nonObservable = "not in any way connected";
The reason for this is that there is no way for knockout to detect what name you are binding it to (ko.observable just knows that it has been given the value 5, not that it has been given the name nonObservable) ... and even if it could detect this, you would probably not want to do this by default.

Angularjs form reset

I have a reset function in angular to clear all the fields in a form. If I do something like:
reset
$scope.resetForm = function() {
$scope.someForm = {};
}
Everything works fine. But I want to use this function for multiple forms on the site. If I pass the form object in like:
reset
$scope.resetForm = function(form) {
$scope.form = {};
}
Then it won't work. Can someone explain to me why this would be happening?
You have 2 problems:
You're not accessing the passed in variable, still access the someForm of current scope.
When you pass parameter to the function, it's passed by reference. Even when you use form = {}, it does not work because it only changes the reference of the parameter, not the reference of the passed in someForm.
Try:
$scope.resetForm = function(form) {
//Even when you use form = {} it does not work
form.fieldA = null;
form.fieldB = null;
///more fields
}
Or
$scope.resetForm = function(form) {
//Even when you use form = {} it does not work
angular.copy({},form);
}
instead of:
$scope.resetForm = function(form) {
$scope.form = {};
}
In your plunk, I see that you're not separating the view from the model. You should do it for separation of concerns and to avoid problems that might happen when you clear all the fields (including DOM form object's fields).
<form name="form2" ng-controller="SecondController">
<label for="first_field">First Field</label>
<input ng-model="form2Model.first_field" />
<br />
<label for="second_field">Second Field</label>
<input ng-model="form2Model.second_field" />
<br />
Reset the form
</form>
http://plnkr.co/edit/x4JAeXra1bP4cQjIBld0?p=preview
You can also do:
form.fieldA = undefined;
It works great for radio buttons and checkboxes.
you can try this :
Deploy your function inside form button reset , in this way ...
<input type ="button" ng-click="Object.field1 = null; ObjectN.fieldN = null;" value="Reset" />

Categories

Resources